<commit_before>package libkb

import (
	"errors"
	"fmt"
	"strings"

	keybase1 "github.com/keybase/client/go/protocol/keybase1"
)

func GetKeybasePassphrase(g *GlobalContext, ui SecretUI, username, retryMsg string) (keybase1.GetPassphraseRes, error) {
	arg := DefaultPassphraseArg(g)
	arg.WindowTitle = "Keybase passphrase"
	arg.Type = keybase1.PassphraseType_PASS_PHRASE
	arg.Username = username
	arg.Prompt = fmt.Sprintf("Please enter the Keybase passphrase for %s (%d+ characters)", username, MinPassphraseLength)
	arg.RetryLabel = retryMsg
	res, err := GetPassphraseUntilCheckWithChecker(g, arg, newUIPrompter(ui), &CheckPassphraseSimple)
	if err != nil {
		return res, err
	}
	res.StoreSecret = true
	return res, nil
}

func GetSecret(g *GlobalContext, ui SecretUI, title, prompt, retryMsg string, allowSecretStore bool) (keybase1.GetPassphraseRes, error) {
	arg := DefaultPassphraseArg(g)
	arg.WindowTitle = title
	arg.Type = keybase1.PassphraseType_PASS_PHRASE
	arg.Prompt = prompt
	arg.RetryLabel = retryMsg
	res, err := GetPassphraseUntilCheckWithChecker(g, arg, newUIPrompter(ui), &CheckPassphraseSimple)
	if err != nil {
		return res, err
	}
	res.StoreSecret = allowSecretStore
	return res, nil
}

func GetPaperKeyPassphrase(g *GlobalContext, ui SecretUI, username string, lastErr error) (string, error) {
	arg := DefaultPassphraseArg(g)
	arg.WindowTitle = "Paper Key"
	arg.Type = keybase1.PassphraseType_PAPER_KEY
	if len(username) == 0 {
		username = "your account"
	}
	arg.Prompt = fmt.Sprintf("Please enter a paper key for %s", username)
	arg.Username = username
	arg.Features.ShowTyping.Allow = true
	arg.Features.ShowTyping.DefaultValue = true
	if lastErr != nil {
		arg.RetryLabel = lastErr.Error()
	}
	res, err := GetPassphraseUntilCheck(g, arg, newUIPrompter(ui), &PaperChecker{})
	if err != nil {
		return "", err
	}
	return res.Passphrase, nil
}

func GetPaperKeyForCryptoPassphrase(g *GlobalContext, ui SecretUI, reason string, devices []*Device) (string, error) {
	if len(devices) == 0 {
		return "", errors.New("empty device list")
	}
	arg := DefaultPassphraseArg(g)
	arg.WindowTitle = "Paper Key"
	arg.Type = keybase1.PassphraseType_PAPER_KEY
	arg.Features.ShowTyping.Allow = true
	arg.Features.ShowTyping.DefaultValue = true
	if len(devices) == 1 {
		arg.Prompt = fmt.Sprintf("%s: please enter the paper key '%s...'", reason, *devices[0].Description)
	} else {
		descs := make([]string, len(devices))
		for i, dev := range devices {
			descs[i] = fmt.Sprintf("'%s...'", *dev.Description)
		}
		paperOpts := strings.Join(descs, " or ")
		arg.Prompt = fmt.Sprintf("%s: please enter one of the following paper keys %s", reason, paperOpts)
	}

	res, err := GetPassphraseUntilCheck(g, arg, newUIPrompter(ui), &PaperChecker{})
	if err != nil {
		return "", err
	}
	return res.Passphrase, nil
}

type PassphrasePrompter interface {
	Prompt(keybase1.GUIEntryArg) (keybase1.GetPassphraseRes, error)
}

type uiPrompter struct {
	ui SecretUI
}

var _ PassphrasePrompter = &uiPrompter{}

func newUIPrompter(ui SecretUI) *uiPrompter {
	return &uiPrompter{ui: ui}
}

func (u *uiPrompter) Prompt(arg keybase1.GUIEntryArg) (keybase1.GetPassphraseRes, error) {
	return u.ui.GetPassphrase(arg, nil)
}

func GetPassphraseUntilCheckWithChecker(g *GlobalContext, arg keybase1.GUIEntryArg, prompter PassphrasePrompter, checker *Checker) (keybase1.GetPassphraseRes, error) {
	if checker == nil {
		return keybase1.GetPassphraseRes{}, errors.New("nil passphrase checker")
	}
	w := &CheckerWrapper{checker: *checker}
	return GetPassphraseUntilCheck(g, arg, prompter, w)
}

func GetPassphraseUntilCheck(g *GlobalContext, arg keybase1.GUIEntryArg, prompter PassphrasePrompter, checker PassphraseChecker) (keybase1.GetPassphraseRes, error) {
	for i := 0; i < 10; i++ {
		res, err := prompter.Prompt(arg)
		if err != nil {
			return keybase1.GetPassphraseRes{}, err
		}
		if checker == nil {
			return res, nil
		}
		err = checker.Check(g, res.Passphrase)
		if err == nil {
			return res, nil
		}
		arg.RetryLabel = err.Error()
	}

	return keybase1.GetPassphraseRes{}, RetryExhaustedError{}
}

func DefaultPassphraseArg(g *GlobalContext) keybase1.GUIEntryArg {
	arg := keybase1.GUIEntryArg{
		SubmitLabel: "Submit",
		CancelLabel: "Cancel",
		Features: keybase1.GUIEntryFeatures{
			ShowTyping: keybase1.Feature{
				Allow:        true,
				DefaultValue: false,
				Readonly:     true,
				Label:        "Show typing",
			},
		},
	}

	return arg
}

// PassphraseChecker is an interface for checking the format of a
// passphrase. Returns nil if the format is ok, or a descriptive
// hint otherwise.
type PassphraseChecker interface {
	Check(*GlobalContext, string) error
}

// CheckerWrapper wraps a Checker type to make it conform to the
// PassphraseChecker interface.
type CheckerWrapper struct {
	checker Checker
}

// Check s using checker, respond with checker.Hint if check
// fails.
func (w *CheckerWrapper) Check(_ *GlobalContext, s string) error {
	if w.checker.F(s) {
		return nil
	}
	return errors.New(w.checker.Hint)
}

// PaperChecker implements PassphraseChecker for paper keys.
type PaperChecker struct{}

// Check a paper key format.  Will return a detailed error message
// specific to the problems found in s.
func (p *PaperChecker) Check(g *GlobalContext, s string) error {
	phrase := NewPaperKeyPhrase(s)

	// check for empty
	if len(phrase.String()) == 0 {
		g.Log.Debug("paper phrase is empty")
		return PassphraseError{Msg: "Empty paper key. Please try again."}
	}

	// check for at least PaperKeyWordCountMin words
	if phrase.NumWords() < PaperKeyWordCountMin {
		return PassphraseError{Msg: "Not enough words entered for a paper key.  Please try again."}
	}

	// check for invalid words
	invalids := phrase.InvalidWords()
	if len(invalids) > 0 {
		g.Log.Debug("paper phrase has invalid word(s) in it")
		var perr PassphraseError
		if len(invalids) > 1 {
			perr.Msg = fmt.Sprintf("Please try again. These words are invalid: %s", strings.Join(invalids, ", "))
		} else {
			perr.Msg = fmt.Sprintf("Please try again. This word is invalid: %s", invalids[0])
		}
		return perr
	}

	// check version
	version, err := phrase.Version()
	if err != nil {
		g.Log.Debug("error getting paper key version: %s", err)
		// despite the error, just tell the user there was a typo:
		return PassphraseError{Msg: "It looks like there was a typo in the paper key. Please try again."}
	}
	if version != PaperKeyVersion {
		g.Log.Debug("paper key version mismatch: generated version = %d, libkb version = %d", version, PaperKeyVersion)
		return PassphraseError{Msg: "It looks like there was a typo. The paper key you entered had an invalid version. Please try again."}
	}

	return nil
}
<commit_msg>Add 3m timeout in GetPassphrase<commit_after>package libkb

import (
	"errors"
	"fmt"
	"strings"
	"time"

	keybase1 "github.com/keybase/client/go/protocol/keybase1"
)

func GetKeybasePassphrase(g *GlobalContext, ui SecretUI, username, retryMsg string) (keybase1.GetPassphraseRes, error) {
	resCh := make(chan keybase1.GetPassphraseRes)
	errCh := make(chan error)
	go func() {
		arg := DefaultPassphraseArg(g)
		arg.WindowTitle = "Keybase passphrase"
		arg.Type = keybase1.PassphraseType_PASS_PHRASE
		arg.Username = username
		arg.Prompt = fmt.Sprintf("Please enter the Keybase passphrase for %s (%d+ characters)", username, MinPassphraseLength)
		arg.RetryLabel = retryMsg
		res, err := GetPassphraseUntilCheckWithChecker(g, arg, newUIPrompter(ui), &CheckPassphraseSimple)
		if err != nil {
			errCh <- err
			return
		}
		res.StoreSecret = true
		resCh <- res
	}()

	select {
	case res := <-resCh:
		return res, nil
	case err := <-errCh:
		return keybase1.GetPassphraseRes{}, err
	case <-time.After(3 * time.Minute):
		return keybase1.GetPassphraseRes{}, TimeoutError{}
	}
}

func GetSecret(g *GlobalContext, ui SecretUI, title, prompt, retryMsg string, allowSecretStore bool) (keybase1.GetPassphraseRes, error) {
	arg := DefaultPassphraseArg(g)
	arg.WindowTitle = title
	arg.Type = keybase1.PassphraseType_PASS_PHRASE
	arg.Prompt = prompt
	arg.RetryLabel = retryMsg
	res, err := GetPassphraseUntilCheckWithChecker(g, arg, newUIPrompter(ui), &CheckPassphraseSimple)
	if err != nil {
		return res, err
	}
	res.StoreSecret = allowSecretStore
	return res, nil
}

func GetPaperKeyPassphrase(g *GlobalContext, ui SecretUI, username string, lastErr error) (string, error) {
	arg := DefaultPassphraseArg(g)
	arg.WindowTitle = "Paper Key"
	arg.Type = keybase1.PassphraseType_PAPER_KEY
	if len(username) == 0 {
		username = "your account"
	}
	arg.Prompt = fmt.Sprintf("Please enter a paper key for %s", username)
	arg.Username = username
	arg.Features.ShowTyping.Allow = true
	arg.Features.ShowTyping.DefaultValue = true
	if lastErr != nil {
		arg.RetryLabel = lastErr.Error()
	}
	res, err := GetPassphraseUntilCheck(g, arg, newUIPrompter(ui), &PaperChecker{})
	if err != nil {
		return "", err
	}
	return res.Passphrase, nil
}

func GetPaperKeyForCryptoPassphrase(g *GlobalContext, ui SecretUI, reason string, devices []*Device) (string, error) {
	if len(devices) == 0 {
		return "", errors.New("empty device list")
	}
	arg := DefaultPassphraseArg(g)
	arg.WindowTitle = "Paper Key"
	arg.Type = keybase1.PassphraseType_PAPER_KEY
	arg.Features.ShowTyping.Allow = true
	arg.Features.ShowTyping.DefaultValue = true
	if len(devices) == 1 {
		arg.Prompt = fmt.Sprintf("%s: please enter the paper key '%s...'", reason, *devices[0].Description)
	} else {
		descs := make([]string, len(devices))
		for i, dev := range devices {
			descs[i] = fmt.Sprintf("'%s...'", *dev.Description)
		}
		paperOpts := strings.Join(descs, " or ")
		arg.Prompt = fmt.Sprintf("%s: please enter one of the following paper keys %s", reason, paperOpts)
	}

	res, err := GetPassphraseUntilCheck(g, arg, newUIPrompter(ui), &PaperChecker{})
	if err != nil {
		return "", err
	}
	return res.Passphrase, nil
}

type PassphrasePrompter interface {
	Prompt(keybase1.GUIEntryArg) (keybase1.GetPassphraseRes, error)
}

type uiPrompter struct {
	ui SecretUI
}

var _ PassphrasePrompter = &uiPrompter{}

func newUIPrompter(ui SecretUI) *uiPrompter {
	return &uiPrompter{ui: ui}
}

func (u *uiPrompter) Prompt(arg keybase1.GUIEntryArg) (keybase1.GetPassphraseRes, error) {
	return u.ui.GetPassphrase(arg, nil)
}

func GetPassphraseUntilCheckWithChecker(g *GlobalContext, arg keybase1.GUIEntryArg, prompter PassphrasePrompter, checker *Checker) (keybase1.GetPassphraseRes, error) {
	if checker == nil {
		return keybase1.GetPassphraseRes{}, errors.New("nil passphrase checker")
	}
	w := &CheckerWrapper{checker: *checker}
	return GetPassphraseUntilCheck(g, arg, prompter, w)
}

func GetPassphraseUntilCheck(g *GlobalContext, arg keybase1.GUIEntryArg, prompter PassphrasePrompter, checker PassphraseChecker) (keybase1.GetPassphraseRes, error) {
	for i := 0; i < 10; i++ {
		res, err := prompter.Prompt(arg)
		if err != nil {
			return keybase1.GetPassphraseRes{}, err
		}
		if checker == nil {
			return res, nil
		}
		err = checker.Check(g, res.Passphrase)
		if err == nil {
			return res, nil
		}
		arg.RetryLabel = err.Error()
	}

	return keybase1.GetPassphraseRes{}, RetryExhaustedError{}
}

func DefaultPassphraseArg(g *GlobalContext) keybase1.GUIEntryArg {
	arg := keybase1.GUIEntryArg{
		SubmitLabel: "Submit",
		CancelLabel: "Cancel",
		Features: keybase1.GUIEntryFeatures{
			ShowTyping: keybase1.Feature{
				Allow:        true,
				DefaultValue: false,
				Readonly:     true,
				Label:        "Show typing",
			},
		},
	}

	return arg
}

// PassphraseChecker is an interface for checking the format of a
// passphrase. Returns nil if the format is ok, or a descriptive
// hint otherwise.
type PassphraseChecker interface {
	Check(*GlobalContext, string) error
}

// CheckerWrapper wraps a Checker type to make it conform to the
// PassphraseChecker interface.
type CheckerWrapper struct {
	checker Checker
}

// Check s using checker, respond with checker.Hint if check
// fails.
func (w *CheckerWrapper) Check(_ *GlobalContext, s string) error {
	if w.checker.F(s) {
		return nil
	}
	return errors.New(w.checker.Hint)
}

// PaperChecker implements PassphraseChecker for paper keys.
type PaperChecker struct{}

// Check a paper key format.  Will return a detailed error message
// specific to the problems found in s.
func (p *PaperChecker) Check(g *GlobalContext, s string) error {
	phrase := NewPaperKeyPhrase(s)

	// check for empty
	if len(phrase.String()) == 0 {
		g.Log.Debug("paper phrase is empty")
		return PassphraseError{Msg: "Empty paper key. Please try again."}
	}

	// check for at least PaperKeyWordCountMin words
	if phrase.NumWords() < PaperKeyWordCountMin {
		return PassphraseError{Msg: "Not enough words entered for a paper key.  Please try again."}
	}

	// check for invalid words
	invalids := phrase.InvalidWords()
	if len(invalids) > 0 {
		g.Log.Debug("paper phrase has invalid word(s) in it")
		var perr PassphraseError
		if len(invalids) > 1 {
			perr.Msg = fmt.Sprintf("Please try again. These words are invalid: %s", strings.Join(invalids, ", "))
		} else {
			perr.Msg = fmt.Sprintf("Please try again. This word is invalid: %s", invalids[0])
		}
		return perr
	}

	// check version
	version, err := phrase.Version()
	if err != nil {
		g.Log.Debug("error getting paper key version: %s", err)
		// despite the error, just tell the user there was a typo:
		return PassphraseError{Msg: "It looks like there was a typo in the paper key. Please try again."}
	}
	if version != PaperKeyVersion {
		g.Log.Debug("paper key version mismatch: generated version = %d, libkb version = %d", version, PaperKeyVersion)
		return PassphraseError{Msg: "It looks like there was a typo. The paper key you entered had an invalid version. Please try again."}
	}

	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Vitess 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 mysql

import (
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"os/signal"
	"strings"
	"sync"
	"syscall"
	"time"

	vaultapi "github.com/aquarapid/vaultlib"

	"vitess.io/vitess/go/vt/log"
)

var (
	vaultAddr             = flag.String("mysql_auth_vault_addr", "", "URL to Vault server")
	vaultTimeout          = flag.Duration("mysql_auth_vault_timeout", 10*time.Second, "Timeout for vault API operations")
	vaultCACert           = flag.String("mysql_auth_vault_tls_ca", "", "Path to CA PEM for validating Vault server certificate")
	vaultPath             = flag.String("mysql_auth_vault_path", "", "Vault path to vtgate credentials JSON blob, e.g.: secret/data/prod/vtgatecreds")
	vaultCacheTTL         = flag.Duration("mysql_auth_vault_ttl", 30*time.Minute, "How long to cache vtgate credentials from the Vault server")
	vaultTokenFile        = flag.String("mysql_auth_vault_tokenfile", "", "Path to file containing Vault auth token; token can also be passed using VAULT_TOKEN environment variable")
	vaultRoleID           = flag.String("mysql_auth_vault_roleid", "", "Vault AppRole id; can also be passed using VAULT_ROLEID environment variable")
	vaultRoleSecretIDFile = flag.String("mysql_auth_vault_role_secretidfile", "", "Path to file containing Vault AppRole secret_id; can also be passed using VAULT_SECRETID environment variable")
	vaultRoleMountPoint   = flag.String("mysql_auth_vault_role_mountpoint", "approle", "Vault AppRole mountpoint; can also be passed using VAULT_MOUNTPOINT environment variable")
)

// AuthServerVault implements AuthServer with a config loaded from Vault.
type AuthServerVault struct {
	mu sync.Mutex
	// method can be set to:
	// - MysqlNativePassword
	// - MysqlClearPassword
	// - MysqlDialog
	// It defaults to MysqlNativePassword.
	method string
	// users, passwords and user data
	//   We use the same JSON format as for -mysql_auth_server_static
	//   Acts as a cache for the in-Vault data
	entries                map[string][]*AuthServerStaticEntry
	vaultCacheExpireTicker *time.Ticker
	vaultClient            *vaultapi.Client
	vaultPath              string
	vaultTTL               time.Duration

	sigChan chan os.Signal
}

// InitAuthServerVault - entrypoint for initialization of Vault AuthServer implementation
func InitAuthServerVault() {
	// Check critical parameters.
	if *vaultAddr == "" {
		log.Infof("Not configuring AuthServerVault, as -mysql_auth_vault_addr is empty.")
		return
	}
	if *vaultPath == "" {
		log.Exitf("If using Vault auth server, -mysql_auth_vault_path is required.")
	}

	registerAuthServerVault(*vaultAddr, *vaultTimeout, *vaultCACert, *vaultPath, *vaultCacheTTL, *vaultTokenFile, *vaultRoleID, *vaultRoleSecretIDFile, *vaultRoleMountPoint)
}

func registerAuthServerVault(addr string, timeout time.Duration, caCertPath string, path string, ttl time.Duration, tokenFilePath string, roleID string, secretIDPath string, roleMountPoint string) {
	authServerVault, err := newAuthServerVault(addr, timeout, caCertPath, path, ttl, tokenFilePath, roleID, secretIDPath, roleMountPoint)
	if err != nil {
		log.Exitf("%s", err)
	}
	RegisterAuthServerImpl("vault", authServerVault)
}

func newAuthServerVault(addr string, timeout time.Duration, caCertPath string, path string, ttl time.Duration, tokenFilePath string, roleID string, secretIDPath string, roleMountPoint string) (*AuthServerVault, error) {
	// Validate more parameters
	token, err := readFromFile(tokenFilePath)
	if err != nil {
		return nil, fmt.Errorf("No Vault token in provided filename for -mysql_auth_vault_tokenfile")
	}
	secretID, err := readFromFile(secretIDPath)
	if err != nil {
		return nil, fmt.Errorf("No Vault secret_id in provided filename for -mysql_auth_vault_role_secretidfile")
	}

	config := vaultapi.NewConfig()

	// All these can be overriden by environment
	//   so we need to check if they have been set by NewConfig
	if config.Address == "" {
		config.Address = addr
	}
	if config.Timeout == (0 * time.Second) {
		config.Timeout = timeout
	}
	if config.CACert == "" {
		config.CACert = caCertPath
	}
	if config.Token == "" {
		config.Token = token
	}
	if config.AppRoleCredentials.RoleID == "" {
		config.AppRoleCredentials.RoleID = roleID
	}
	if config.AppRoleCredentials.SecretID == "" {
		config.AppRoleCredentials.SecretID = secretID
	}
	if config.AppRoleCredentials.MountPoint == "" {
		config.AppRoleCredentials.MountPoint = roleMountPoint
	}

	if config.CACert != "" {
		// If we provide a CA, ensure we actually use it
		config.InsecureSSL = false
	}

	client, err := vaultapi.NewClient(config)
	if err != nil || client == nil {
		log.Errorf("Error in vault client initialization, will retry: %v", err)
	}

	a := &AuthServerVault{
		vaultClient: client,
		vaultPath:   path,
		vaultTTL:    ttl,
		method:      MysqlNativePassword,
		entries:     make(map[string][]*AuthServerStaticEntry),
	}

	a.reloadVault()
	a.installSignalHandlers()
	return a, nil
}

// Caller must hold the a.mu mutex.
func (a *AuthServerVault) setTTLTicker(ttl time.Duration) {
	if a.vaultCacheExpireTicker != nil {
		a.vaultCacheExpireTicker.Stop()
	}
	a.vaultCacheExpireTicker = time.NewTicker(ttl)
	go func() {
		for range a.vaultCacheExpireTicker.C {
			a.sigChan <- syscall.SIGHUP
		}
	}()
}

// Reload JSON auth key from Vault. Return true if successful, false if not
func (a *AuthServerVault) reloadVault() error {
	a.mu.Lock()
	defer a.mu.Unlock()
	secret, err := a.vaultClient.GetSecret(a.vaultPath)
	a.setTTLTicker(10 * time.Second) // Reload frequently on error

	if err != nil {
		return fmt.Errorf("Error in vtgate Vault auth server params: %v", err)
	}

	if secret.JSONSecret == nil {
		return fmt.Errorf("Empty vtgate credentials retrieved from Vault server")
	}

	entries := make(map[string][]*AuthServerStaticEntry)
	if err := parseConfig(secret.JSONSecret, &entries); err != nil {
		return fmt.Errorf("Error parsing vtgate Vault auth server config: %v", err)
	}
	if len(entries) == 0 {
		return fmt.Errorf("vtgate credentials from Vault empty! Not updating previously cached values")
	}

	log.Infof("reloadVault(): success. Client status: %s", a.vaultClient.GetStatus())
	a.entries = entries
	a.setTTLTicker(a.vaultTTL)
	return nil
}

func (a *AuthServerVault) installSignalHandlers() {
	a.mu.Lock()
	defer a.mu.Unlock()

	a.sigChan = make(chan os.Signal, 1)
	signal.Notify(a.sigChan, syscall.SIGHUP)
	go func() {
		for range a.sigChan {
			err := a.reloadVault()
			if err != nil {
				log.Errorf("%s", err)
			}

		}
	}()
}

func (a *AuthServerVault) close() {
	log.Warningf("Closing AuthServerVault instance.")
	a.mu.Lock()
	defer a.mu.Unlock()
	if a.vaultCacheExpireTicker != nil {
		a.vaultCacheExpireTicker.Stop()
	}
	if a.sigChan != nil {
		signal.Stop(a.sigChan)
	}
}

// AuthMethod is part of the AuthServer interface.
func (a *AuthServerVault) AuthMethod(user string) (string, error) {
	return a.method, nil
}

// Salt is part of the AuthServer interface.
func (a *AuthServerVault) Salt() ([]byte, error) {
	return NewSalt()
}

// ValidateHash is part of the AuthServer interface.
func (a *AuthServerVault) ValidateHash(salt []byte, user string, authResponse []byte, remoteAddr net.Addr) (Getter, error) {
	a.mu.Lock()
	userEntries, ok := a.entries[user]
	a.mu.Unlock()

	if !ok {
		return &StaticUserData{}, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user)
	}

	for _, entry := range userEntries {
		if entry.MysqlNativePassword != "" {
			isPass := isPassScrambleMysqlNativePassword(authResponse, salt, entry.MysqlNativePassword)
			if matchSourceHost(remoteAddr, entry.SourceHost) && isPass {
				return &StaticUserData{entry.UserData, entry.Groups}, nil
			}
		} else {
			computedAuthResponse := ScramblePassword(salt, []byte(entry.Password))
			// Validate the password.
			if matchSourceHost(remoteAddr, entry.SourceHost) && bytes.Equal(authResponse, computedAuthResponse) {
				return &StaticUserData{entry.UserData, entry.Groups}, nil
			}
		}
	}
	return &StaticUserData{}, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user)
}

// Negotiate is part of the AuthServer interface.
// It will be called if method is anything else than MysqlNativePassword.
// We only recognize MysqlClearPassword and MysqlDialog here.
func (a *AuthServerVault) Negotiate(c *Conn, user string, remoteAddr net.Addr) (Getter, error) {
	// Finish the negotiation.
	password, err := AuthServerNegotiateClearOrDialog(c, a.method)
	if err != nil {
		return nil, err
	}

	a.mu.Lock()
	userEntries, ok := a.entries[user]
	a.mu.Unlock()

	if !ok {
		return &StaticUserData{}, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user)
	}
	for _, entry := range userEntries {
		// Validate the password.
		if matchSourceHost(remoteAddr, entry.SourceHost) && entry.Password == password {
			return &StaticUserData{entry.UserData, entry.Groups}, nil
		}
	}
	return &StaticUserData{}, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user)
}

// We ignore most errors here, to allow us to retry cleanly
//   or ignore the cases where the input is not passed by file, but via env
func readFromFile(filePath string) (string, error) {
	if filePath == "" {
		return "", nil
	}
	fileBytes, err := ioutil.ReadFile(filePath)
	if err != nil {
		log.Errorf("Could not read file: %s", filePath)
		return "", err
	}
	return strings.TrimSpace(string(fileBytes)), nil
}
<commit_msg>Rework mutex handling to avoid data races.<commit_after>/*
Copyright 2020 The Vitess 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 mysql

import (
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"os/signal"
	"strings"
	"sync"
	"syscall"
	"time"

	vaultapi "github.com/aquarapid/vaultlib"

	"vitess.io/vitess/go/vt/log"
)

var (
	vaultAddr             = flag.String("mysql_auth_vault_addr", "", "URL to Vault server")
	vaultTimeout          = flag.Duration("mysql_auth_vault_timeout", 10*time.Second, "Timeout for vault API operations")
	vaultCACert           = flag.String("mysql_auth_vault_tls_ca", "", "Path to CA PEM for validating Vault server certificate")
	vaultPath             = flag.String("mysql_auth_vault_path", "", "Vault path to vtgate credentials JSON blob, e.g.: secret/data/prod/vtgatecreds")
	vaultCacheTTL         = flag.Duration("mysql_auth_vault_ttl", 30*time.Minute, "How long to cache vtgate credentials from the Vault server")
	vaultTokenFile        = flag.String("mysql_auth_vault_tokenfile", "", "Path to file containing Vault auth token; token can also be passed using VAULT_TOKEN environment variable")
	vaultRoleID           = flag.String("mysql_auth_vault_roleid", "", "Vault AppRole id; can also be passed using VAULT_ROLEID environment variable")
	vaultRoleSecretIDFile = flag.String("mysql_auth_vault_role_secretidfile", "", "Path to file containing Vault AppRole secret_id; can also be passed using VAULT_SECRETID environment variable")
	vaultRoleMountPoint   = flag.String("mysql_auth_vault_role_mountpoint", "approle", "Vault AppRole mountpoint; can also be passed using VAULT_MOUNTPOINT environment variable")
)

// AuthServerVault implements AuthServer with a config loaded from Vault.
type AuthServerVault struct {
	mu sync.Mutex
	// method can be set to:
	// - MysqlNativePassword
	// - MysqlClearPassword
	// - MysqlDialog
	// It defaults to MysqlNativePassword.
	method string
	// users, passwords and user data
	//   We use the same JSON format as for -mysql_auth_server_static
	//   Acts as a cache for the in-Vault data
	entries                map[string][]*AuthServerStaticEntry
	vaultCacheExpireTicker *time.Ticker
	vaultClient            *vaultapi.Client
	vaultPath              string
	vaultTTL               time.Duration

	sigChan chan os.Signal
}

// InitAuthServerVault - entrypoint for initialization of Vault AuthServer implementation
func InitAuthServerVault() {
	// Check critical parameters.
	if *vaultAddr == "" {
		log.Infof("Not configuring AuthServerVault, as -mysql_auth_vault_addr is empty.")
		return
	}
	if *vaultPath == "" {
		log.Exitf("If using Vault auth server, -mysql_auth_vault_path is required.")
	}

	registerAuthServerVault(*vaultAddr, *vaultTimeout, *vaultCACert, *vaultPath, *vaultCacheTTL, *vaultTokenFile, *vaultRoleID, *vaultRoleSecretIDFile, *vaultRoleMountPoint)
}

func registerAuthServerVault(addr string, timeout time.Duration, caCertPath string, path string, ttl time.Duration, tokenFilePath string, roleID string, secretIDPath string, roleMountPoint string) {
	authServerVault, err := newAuthServerVault(addr, timeout, caCertPath, path, ttl, tokenFilePath, roleID, secretIDPath, roleMountPoint)
	if err != nil {
		log.Exitf("%s", err)
	}
	RegisterAuthServerImpl("vault", authServerVault)
}

func newAuthServerVault(addr string, timeout time.Duration, caCertPath string, path string, ttl time.Duration, tokenFilePath string, roleID string, secretIDPath string, roleMountPoint string) (*AuthServerVault, error) {
	// Validate more parameters
	token, err := readFromFile(tokenFilePath)
	if err != nil {
		return nil, fmt.Errorf("No Vault token in provided filename for -mysql_auth_vault_tokenfile")
	}
	secretID, err := readFromFile(secretIDPath)
	if err != nil {
		return nil, fmt.Errorf("No Vault secret_id in provided filename for -mysql_auth_vault_role_secretidfile")
	}

	config := vaultapi.NewConfig()

	// All these can be overriden by environment
	//   so we need to check if they have been set by NewConfig
	if config.Address == "" {
		config.Address = addr
	}
	if config.Timeout == (0 * time.Second) {
		config.Timeout = timeout
	}
	if config.CACert == "" {
		config.CACert = caCertPath
	}
	if config.Token == "" {
		config.Token = token
	}
	if config.AppRoleCredentials.RoleID == "" {
		config.AppRoleCredentials.RoleID = roleID
	}
	if config.AppRoleCredentials.SecretID == "" {
		config.AppRoleCredentials.SecretID = secretID
	}
	if config.AppRoleCredentials.MountPoint == "" {
		config.AppRoleCredentials.MountPoint = roleMountPoint
	}

	if config.CACert != "" {
		// If we provide a CA, ensure we actually use it
		config.InsecureSSL = false
	}

	client, err := vaultapi.NewClient(config)
	if err != nil || client == nil {
		log.Errorf("Error in vault client initialization, will retry: %v", err)
	}

	a := &AuthServerVault{
		vaultClient: client,
		vaultPath:   path,
		vaultTTL:    ttl,
		method:      MysqlNativePassword,
		entries:     make(map[string][]*AuthServerStaticEntry),
	}

	a.reloadVault()
	a.installSignalHandlers()
	return a, nil
}

func (a *AuthServerVault) setTTLTicker(ttl time.Duration) {
	a.mu.Lock()
	defer a.mu.Unlock()
	if a.vaultCacheExpireTicker == nil {
		a.vaultCacheExpireTicker = time.NewTicker(ttl)
		go func() {
			for range a.vaultCacheExpireTicker.C {
				a.sigChan <- syscall.SIGHUP
			}
		}()
	} else {
		a.vaultCacheExpireTicker.Reset(ttl)
	}
}

// Reload JSON auth key from Vault. Return true if successful, false if not
func (a *AuthServerVault) reloadVault() error {
	a.mu.Lock()
	secret, err := a.vaultClient.GetSecret(a.vaultPath)
	a.mu.Unlock()
	a.setTTLTicker(10 * time.Second) // Reload frequently on error

	if err != nil {
		return fmt.Errorf("Error in vtgate Vault auth server params: %v", err)
	}

	if secret.JSONSecret == nil {
		return fmt.Errorf("Empty vtgate credentials retrieved from Vault server")
	}

	entries := make(map[string][]*AuthServerStaticEntry)
	if err := parseConfig(secret.JSONSecret, &entries); err != nil {
		return fmt.Errorf("Error parsing vtgate Vault auth server config: %v", err)
	}
	if len(entries) == 0 {
		return fmt.Errorf("vtgate credentials from Vault empty! Not updating previously cached values")
	}

	log.Infof("reloadVault(): success. Client status: %s", a.vaultClient.GetStatus())
	a.mu.Lock()
	a.entries = entries
	a.mu.Unlock()
	a.setTTLTicker(a.vaultTTL)
	return nil
}

func (a *AuthServerVault) installSignalHandlers() {
	a.mu.Lock()
	defer a.mu.Unlock()

	a.sigChan = make(chan os.Signal, 1)
	signal.Notify(a.sigChan, syscall.SIGHUP)
	go func() {
		for range a.sigChan {
			err := a.reloadVault()
			if err != nil {
				log.Errorf("%s", err)
			}

		}
	}()
}

func (a *AuthServerVault) close() {
	log.Warningf("Closing AuthServerVault instance.")
	a.mu.Lock()
	defer a.mu.Unlock()
	if a.vaultCacheExpireTicker != nil {
		a.vaultCacheExpireTicker.Stop()
	}
	if a.sigChan != nil {
		signal.Stop(a.sigChan)
	}
}

// AuthMethod is part of the AuthServer interface.
func (a *AuthServerVault) AuthMethod(user string) (string, error) {
	return a.method, nil
}

// Salt is part of the AuthServer interface.
func (a *AuthServerVault) Salt() ([]byte, error) {
	return NewSalt()
}

// ValidateHash is part of the AuthServer interface.
func (a *AuthServerVault) ValidateHash(salt []byte, user string, authResponse []byte, remoteAddr net.Addr) (Getter, error) {
	a.mu.Lock()
	userEntries, ok := a.entries[user]
	a.mu.Unlock()

	if !ok {
		return &StaticUserData{}, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user)
	}

	for _, entry := range userEntries {
		if entry.MysqlNativePassword != "" {
			isPass := isPassScrambleMysqlNativePassword(authResponse, salt, entry.MysqlNativePassword)
			if matchSourceHost(remoteAddr, entry.SourceHost) && isPass {
				return &StaticUserData{entry.UserData, entry.Groups}, nil
			}
		} else {
			computedAuthResponse := ScramblePassword(salt, []byte(entry.Password))
			// Validate the password.
			if matchSourceHost(remoteAddr, entry.SourceHost) && bytes.Equal(authResponse, computedAuthResponse) {
				return &StaticUserData{entry.UserData, entry.Groups}, nil
			}
		}
	}
	return &StaticUserData{}, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user)
}

// Negotiate is part of the AuthServer interface.
// It will be called if method is anything else than MysqlNativePassword.
// We only recognize MysqlClearPassword and MysqlDialog here.
func (a *AuthServerVault) Negotiate(c *Conn, user string, remoteAddr net.Addr) (Getter, error) {
	// Finish the negotiation.
	password, err := AuthServerNegotiateClearOrDialog(c, a.method)
	if err != nil {
		return nil, err
	}

	a.mu.Lock()
	userEntries, ok := a.entries[user]
	a.mu.Unlock()

	if !ok {
		return &StaticUserData{}, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user)
	}
	for _, entry := range userEntries {
		// Validate the password.
		if matchSourceHost(remoteAddr, entry.SourceHost) && entry.Password == password {
			return &StaticUserData{entry.UserData, entry.Groups}, nil
		}
	}
	return &StaticUserData{}, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user)
}

// We ignore most errors here, to allow us to retry cleanly
//   or ignore the cases where the input is not passed by file, but via env
func readFromFile(filePath string) (string, error) {
	if filePath == "" {
		return "", nil
	}
	fileBytes, err := ioutil.ReadFile(filePath)
	if err != nil {
		log.Errorf("Could not read file: %s", filePath)
		return "", err
	}
	return strings.TrimSpace(string(fileBytes)), nil
}
<|endoftext|>


<commit_before>// Extremely simple console tool.
// Takes Long URL as a first argument.
// Returns Bitly short URL to console.
// That's It.
package main

import (
      "os"
      "log"
      "fmt"
      "io/ioutil"
      "regexp"
      "net/http"
      "encoding/base64"
      "net/url"
)

const(
      API_BASEURL = "https://api-ssl.bitly.com"
      TOKEN_DIR   = ".hobbit"
      TOKEN_FILE  = "token"

      API_AUTH    = "/oauth/access_token"
      API_SHORT   = "/v3/shorten"
)

var(
      HOME        = os.Getenv("HOME")
)

// Config file will be created under given path with given filename
// Returns *os.File object and error
func InitConfig(path string, filename string) (*os.File, error) {
  if len(HOME) == 0 {
    log.Println("HOME environment variable is empty. I'll use current folder")
    config_file, err := os.Create(TOKEN_FILE)
    if err != nil {
      return nil, err
    } else {
      return config_file, nil
    }
  }

  // Define absolute path to config
  config_dir       := HOME + "/" + TOKEN_DIR
  config_file_path := config_dir + "/" + TOKEN_FILE

  // Create config directory if not exists
  if _, err := os.Stat(config_dir); os.IsNotExist(err) {
    err := os.Mkdir(config_dir, 0755)
    if err != nil {
      log.Println(err)
      log.Fatal("can't create directory: ", config_dir ," Check free space and permissions")
    }
  }

  // Create config file if not exists
  if _, err := os.Stat(config_file_path); os.IsNotExist(err) {
    config_file, err := os.Create(config_file_path)
    if err != nil {
      log.Println(err)
      log.Fatal("can't create config file: ", config_file_path, " Check free space and permissions")
    }
    return config_file, err
  }

  return os.Open(config_file_path)
}

// Save Token on a filesystem
func SaveToken(tk string, f *os.File) (int, error) {
  return f.WriteString(tk)
}

// Get access_token
func Auth() string {
  username := ""
  password := ""

  // Get username and password from user
  fmt.Println("Looks like access_token is not set. Let's do It!")
  fmt.Printf("%s: ","username")
  fmt.Scanln(&username)
  fmt.Printf("%s: ", "password")
  fmt.Scanln(&password)

  // Create request
  client := &http.Client{}
  urlStr := API_BASEURL + API_AUTH
  r, err := http.NewRequest("POST", urlStr, nil)
  if err != nil {
    log.Fatal(err)
  }
  // Set auth header
  msg := username + ":" + password
  auth_header := "Basic " + base64.StdEncoding.EncodeToString([]byte(msg))
  r.Header.Add("Authorization", auth_header)

  // Get response
  resp, err := client.Do(r)
  if err != nil {
    log.Fatal(err)
  }
  defer resp.Body.Close()

  // Get token
  body, err := ioutil.ReadAll(resp.Body)
  if err != nil {
    log.Fatal(err)
  }

  // Check errors and return access_token
  response_string := string(body)
  _, err = regexp.MatchString("[a-zA-Z0-9]+", response_string)
  if err != nil {
    log.Fatal(response_string)
  }
  return response_string
}

// Shorten long URL
func Shorten(tk string ,longurl string) string {
  urlStr := API_BASEURL + API_SHORT

  Url, err := url.Parse(urlStr)
  if err != nil {
    log.Fatal(err)
  }

  // Set parameters
  parameters := url.Values{}
  parameters.Add("access_token", tk)
  parameters.Add("longUrl", longurl)
  parameters.Add("format", "txt")
  Url.RawQuery = parameters.Encode()

  // Call API endpoint
  resp, err := http.Get(Url.String())
  if err != nil {
    log.Fatal(err)
  }
  defer resp.Body.Close()

  body, err := ioutil.ReadAll(resp.Body)
  if err != nil {
    log.Fatal(err)
  }

  return string(body)
}

func main() {
  var token, longUrl string

  f, _ := InitConfig(TOKEN_DIR, TOKEN_FILE)
  defer f.Close()

  // Read existing token from file or get a new one
  stat, _ := f.Stat()
  if stat.Size() > 0 {
    bs := make([]byte, stat.Size())
    f.Read(bs)
    token = string(bs)
  } else {
    token = Auth()
    SaveToken(token, f)
  }

  // Check if argument has been passed to the script
  if len(os.Args) < 2 {
    fmt.Println("USAGE:", os.Args[0], "http://example.com")
    fmt.Println("NOTE:  'http/https' part is obligatory")
    os.Exit(1)
  }

  longUrl = os.Args[1]
  fmt.Printf(Shorten(token, longUrl))
}
<commit_msg>Formatting<commit_after>// Extremely simple console tool.
// Takes Long URL as a first argument.
// Returns Bitly short URL to console.
// That's It.
package main

import (
      "os"
      "log"
      "fmt"
      "regexp"
      "io/ioutil"
      "net/http"
      "net/url"
      "encoding/base64"
)

const(
      API_BASEURL = "https://api-ssl.bitly.com"
      TOKEN_DIR   = ".hobbit"
      TOKEN_FILE  = "token"

      API_AUTH    = "/oauth/access_token"
      API_SHORT   = "/v3/shorten"
)

var(
    HOME = os.Getenv("HOME")
)

// Config file will be created under given path with given filename
// Returns *os.File object and error
func InitConfig(path string, filename string) (*os.File, error) {
  if len(HOME) == 0 {
    log.Println("HOME environment variable is empty. I'll use current folder")
    config_file, err := os.Create(TOKEN_FILE)
    if err != nil {
      return nil, err
    } else {
      return config_file, nil
    }
  }

  // Define absolute path to config
  config_dir       := HOME + "/" + TOKEN_DIR
  config_file_path := config_dir + "/" + TOKEN_FILE

  // Create config directory if not exists
  if _, err := os.Stat(config_dir); os.IsNotExist(err) {
    err := os.Mkdir(config_dir, 0755)
    if err != nil {
      log.Println(err)
      log.Fatal("can't create directory: ", config_dir ," Check free space and permissions")
    }
  }

  // Create config file if not exists
  if _, err := os.Stat(config_file_path); os.IsNotExist(err) {
    config_file, err := os.Create(config_file_path)
    if err != nil {
      log.Println(err)
      log.Fatal("can't create config file: ", config_file_path, " Check free space and permissions")
    }
    return config_file, err
  }

  return os.Open(config_file_path)
}

// Save Token on a filesystem
func SaveToken(tk string, f *os.File) (int, error) {
  return f.WriteString(tk)
}

// Get access_token
func Auth() string {
  username := ""
  password := ""

  // Get username and password from user
  fmt.Println("Looks like access_token is not set. Let's do It!")
  fmt.Printf("%s: ","username")
  fmt.Scanln(&username)
  fmt.Printf("%s: ", "password")
  fmt.Scanln(&password)

  // Create request
  client := &http.Client{}
  urlStr := API_BASEURL + API_AUTH
  r, err := http.NewRequest("POST", urlStr, nil)
  if err != nil {
    log.Fatal(err)
  }
  // Set auth header
  msg := username + ":" + password
  auth_header := "Basic " + base64.StdEncoding.EncodeToString([]byte(msg))
  r.Header.Add("Authorization", auth_header)

  // Get response
  resp, err := client.Do(r)
  if err != nil {
    log.Fatal(err)
  }
  defer resp.Body.Close()

  // Get token
  body, err := ioutil.ReadAll(resp.Body)
  if err != nil {
    log.Fatal(err)
  }

  // Check errors and return access_token
  response_string := string(body)
  _, err = regexp.MatchString("[a-zA-Z0-9]+", response_string)
  if err != nil {
    log.Fatal(response_string)
  }
  return response_string
}

// Shorten long URL
func Shorten(tk string ,longurl string) string {
  urlStr := API_BASEURL + API_SHORT

  Url, err := url.Parse(urlStr)
  if err != nil {
    log.Fatal(err)
  }

  // Set parameters
  parameters := url.Values{}
  parameters.Add("access_token", tk)
  parameters.Add("longUrl", longurl)
  parameters.Add("format", "txt")
  Url.RawQuery = parameters.Encode()

  // Call API endpoint
  resp, err := http.Get(Url.String())
  if err != nil {
    log.Fatal(err)
  }
  defer resp.Body.Close()

  body, err := ioutil.ReadAll(resp.Body)
  if err != nil {
    log.Fatal(err)
  }

  return string(body)
}

func main() {
  var token, longUrl string

  f, _ := InitConfig(TOKEN_DIR, TOKEN_FILE)
  defer f.Close()

  // Read existing token from file or get a new one
  stat, _ := f.Stat()
  if stat.Size() > 0 {
    bs := make([]byte, stat.Size())
    f.Read(bs)
    token = string(bs)
  } else {
    token = Auth()
    SaveToken(token, f)
  }

  // Check if argument has been passed to the script
  if len(os.Args) < 2 {
    fmt.Println("USAGE:", os.Args[0], "http://example.com")
    fmt.Println("NOTE:  'http/https' part is obligatory")
    os.Exit(1)
  }

  longUrl = os.Args[1]
  fmt.Printf(Shorten(token, longUrl))
}
<|endoftext|>


<commit_before>package expr

import (
	"github.com/antonmedv/expr/checker"
	"github.com/antonmedv/expr/compiler"
	"github.com/antonmedv/expr/parser"
	"github.com/antonmedv/expr/vm"
	"reflect"
)

func Eval(input string, env interface{}) (interface{}, error) {
	node, err := parser.Parse(input)
	if err != nil {
		return nil, err
	}

	program, err := compiler.Compile(node)
	if err != nil {
		return nil, err
	}

	output, err := vm.Run(program, env)
	if err != nil {
		return nil, err
	}

	return output, nil
}

type config struct {
	mapEnv bool
	types  checker.TypesTable
}

// OptionFn for configuring expr.
type OptionFn func(c *config)

// Env specifies expected input of env for type checks.
// If struct is passed, all fields will be treated as variables,
// as well as all fields of embedded structs and struct itself.
// If map is passed, all items will be treated as variables.
// Methods defined on this type will be available as functions.
func Env(i interface{}) OptionFn {
	return func(c *config) {
		if _, ok := i.(map[string]interface{}); ok {
			c.mapEnv = true
		}
		c.types = checker.CreateTypesTable(i)
	}
}

func CompileType(input string, ops ...OptionFn) (*vm.Program, reflect.Type, error) {
	c := &config{}

	for _, op := range ops {
		op(c)
	}

	node, err := parser.Parse(input)
	if err != nil {
		return nil, nil, err
	}

	var t reflect.Type
	if c.types != nil {
		t, err = checker.Check(node, c.types)
		if err != nil {
			return nil, nil, err
		}
	}

	compilerOps := make([]compiler.OptionFn, 0)
	if c.mapEnv {
		compilerOps = append(compilerOps, compiler.MapEnv())
	}

	program, err := compiler.Compile(node, compilerOps...)
	if err != nil {
		return nil, nil, err
	}

	return program, t, nil
}

func Compile(input string, ops ...OptionFn) (*vm.Program, error) {
	program, _, err := CompileType(input, ops...)
	return program, err
}

func Run(program *vm.Program, env interface{}) (interface{}, error) {
	return vm.Run(program, env)
}
<commit_msg>add comments<commit_after>package expr

import (
	"github.com/antonmedv/expr/checker"
	"github.com/antonmedv/expr/compiler"
	"github.com/antonmedv/expr/parser"
	"github.com/antonmedv/expr/vm"
	"reflect"
)

// Eval parses, compiles and runs given input.
func Eval(input string, env interface{}) (interface{}, error) {
	node, err := parser.Parse(input)
	if err != nil {
		return nil, err
	}

	program, err := compiler.Compile(node)
	if err != nil {
		return nil, err
	}

	output, err := vm.Run(program, env)
	if err != nil {
		return nil, err
	}

	return output, nil
}

type config struct {
	mapEnv bool
	types  checker.TypesTable
}

// OptionFn for configuring expr.
type OptionFn func(c *config)

// Env specifies expected input of env for type checks.
// If struct is passed, all fields will be treated as variables,
// as well as all fields of embedded structs and struct itself.
// If map is passed, all items will be treated as variables.
// Methods defined on this type will be available as functions.
func Env(i interface{}) OptionFn {
	return func(c *config) {
		if _, ok := i.(map[string]interface{}); ok {
			c.mapEnv = true
		}
		c.types = checker.CreateTypesTable(i)
	}
}

// CompileType compiles input and returns program with output type, if Env was specified.
func CompileType(input string, ops ...OptionFn) (*vm.Program, reflect.Type, error) {
	c := &config{}

	for _, op := range ops {
		op(c)
	}

	node, err := parser.Parse(input)
	if err != nil {
		return nil, nil, err
	}

	var t reflect.Type
	if c.types != nil {
		t, err = checker.Check(node, c.types)
		if err != nil {
			return nil, nil, err
		}
	}

	compilerOps := make([]compiler.OptionFn, 0)
	if c.mapEnv {
		compilerOps = append(compilerOps, compiler.MapEnv())
	}

	program, err := compiler.Compile(node, compilerOps...)
	if err != nil {
		return nil, nil, err
	}

	return program, t, nil
}

// Compile parses and compiles given input expression to bytecode program.
func Compile(input string, ops ...OptionFn) (*vm.Program, error) {
	program, _, err := CompileType(input, ops...)
	return program, err
}

// Run evaluates given bytecode program.
func Run(program *vm.Program, env interface{}) (interface{}, error) {
	return vm.Run(program, env)
}
<|endoftext|>


<commit_before>package main

import (
	"net/url"
	"os"
	"os/exec"
	"strconv"
	"strings"
)

func main() {
	_args := os.Args
	var args1, args2 string = "", ""
	var _url, _line string = "", ""

	for v, i := range _args {
		fmt.Print("i ::::" + i)
		fmt.Println("::: v ::::" + strconv.Itoa(v))
	}

	if len(_args) >= 3 {
		args1 = _args[1]
		args2 = _args[2]
	}
	thisUrl, err := url.Parse(args2)

	if err == nil {
		values := thisUrl.Query()

		_url = strings.Replace(values.Get("url"), "file://", "", -1)
		_line = values.Get("line")

		args2 = _url
		if _line != "" {
			args2 = args2 + ":" + _line
		}

		cmd := exec.Command(args1, args2)
		cmd.Run()
	}
}
<commit_msg>no message<commit_after>package main

import (
	"net/url"
	"os"
	"os/exec"
	"strconv"
	"strings"
)

func main() {
	_args := os.Args
	var args1, args2 string = "", ""
	var _url, _line string = "", ""

	if len(_args) >= 3 {
		args1 = _args[1]
		args2 = _args[2]
	}
	thisUrl, err := url.Parse(args2)

	if err == nil {
		values := thisUrl.Query()

		_url = strings.Replace(values.Get("url"), "file://", "", -1)
		_line = values.Get("line")

		args2 = _url
		if _line != "" {
			args2 = args2 + ":" + _line
		}

		cmd := exec.Command(args1, args2)
		cmd.Run()
	}
}
<|endoftext|>


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

package main

import (
	"bufio"
	"bytes"
	"crypto/sha256"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"

	"github.com/u-root/u-root/pkg/cpio"
	"github.com/u-root/u-root/pkg/golang"
	"github.com/u-root/u-root/pkg/testutil"
	"github.com/u-root/u-root/pkg/uio"
	itest "github.com/u-root/u-root/pkg/uroot/initramfs/test"
)

var twocmds = []string{
	"github.com/u-root/u-root/cmds/core/ls",
	"github.com/u-root/u-root/cmds/core/init",
}

var srcmds = []string{
	"github.com/u-root/u-root/cmds/core/ls",
	"github.com/u-root/u-root/cmds/core/init",
	"github.com/u-root/u-root/cmds/core/installcommand",
}

type buildSourceValidator struct {
	gopath string
	goroot string
	env    []string
}

func (b buildSourceValidator) Validate(a *cpio.Archive) error {
	dir, err := ioutil.TempDir("", "u-root-source-")
	if err != nil {
		return err
	}
	defer os.RemoveAll(dir)

	if err := os.Mkdir(filepath.Join(dir, "tmp"), 0755); err != nil {
		return err
	}

	// Unpack into dir.
	err = cpio.ForEachRecord(a.Reader(), func(r cpio.Record) error {
		return cpio.CreateFileInRoot(r, dir, false)
	})
	if err != nil {
		return err
	}

	goroot := filepath.Join(dir, b.goroot)
	gopath := filepath.Join(dir, b.gopath)
	// go build ./src/...
	c := exec.Command(filepath.Join(goroot, "bin/go"), "build", filepath.Join(gopath, "src/..."))
	c.Env = append(b.env,
		fmt.Sprintf("GOPATH=%s", gopath),
		fmt.Sprintf("GOCACHE=%s", filepath.Join(dir, "tmp")),
		fmt.Sprintf("GOROOT=%s", goroot),
		"GO111MODULE=off",
		"CGO_ENABLED=0")
	out, err := c.CombinedOutput()
	if err != nil {
		return fmt.Errorf("could not build go source %v; output\n%s", err, out)
	}
	return nil
}

func xTestDCE(t *testing.T) {
	delFiles := false
	f, _ := buildIt(
		t,
		[]string{
			"-build=bb", "-no-strip",
			"./cmds/*/*",
			"-cmds/core/installcommand", "-cmds/exp/builtin", "-cmds/exp/run",
			"pkg/uroot/test/foo",
		},
		nil,
		nil)
	defer func() {
		if delFiles {
			os.RemoveAll(f.Name())
		}
	}()
	st, _ := f.Stat()
	t.Logf("Built %s, size %d", f.Name(), st.Size())
	cmd := golang.Default().GoCmd("tool", "nm", f.Name())
	nmOutput, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("failed to run nm: %s %s", err, nmOutput)
	}
	symScanner := bufio.NewScanner(bytes.NewBuffer(nmOutput))
	syms := map[string]bool{}
	for symScanner.Scan() {
		line := symScanner.Text()
		parts := strings.Split(line, " ")
		if len(parts) == 0 {
			continue
		}
		sym := parts[len(parts)-1]
		syms[sym] = true
		t.Logf("%s", sym)
	}
}

type noDeadCode struct {
	Path string
}

func (v noDeadCode) Validate(a *cpio.Archive) error {
	// 1. Extract BB binary into a temporary file.
	delFiles := true
	bbRecord, ok := a.Get(v.Path)
	if !ok {
		return fmt.Errorf("archive does not contain %s, but should", v.Path)
	}
	tf, err := ioutil.TempFile("", "u-root-temp-bb-")
	if err != nil {
		return err
	}
	bbData, _ := uio.ReadAll(bbRecord)
	tf.Write(bbData)
	tf.Close()
	defer func() {
		if delFiles {
			os.RemoveAll(tf.Name())
		}
	}()
	// 2. Run "go nm" on it and build symbol table.
	cmd := golang.Default().GoCmd("tool", "nm", tf.Name())
	nmOutput, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to run nm: %s %s", err, nmOutput)
	}
	symScanner := bufio.NewScanner(bytes.NewBuffer(nmOutput))
	syms := map[string]bool{}
	for symScanner.Scan() {
		line := symScanner.Text()
		parts := strings.Split(line, " ")
		if len(parts) == 0 {
			continue
		}
		sym := parts[len(parts)-1]
		syms[sym] = true
	}
	// 3. Check for presence and absence of particular symbols.
	if !syms["github.com/u-root/u-root/pkg/uroot/test/bar.Bar.UsedInterfaceMethod"] {
		// Sanity check of the test itself: this method must be in the binary.
		return fmt.Errorf("expected symbol not found, something is wrong with the build")
	}
	if syms["github.com/u-root/u-root/pkg/uroot/test/bar.Bar.UnusedNonInterfaceMethod"] {
		// Sanity check of the test itself: this method must be in the binary.
		delFiles = false
		return fmt.Errorf(
			"Unused non-interface method has not been eliminated, dead code elimination is not working properly.\n"+
				"The most likely reason is use of reflect.Value.Method or .MethodByName somewhere "+
				"(could be a command or vendor dependency, apologies for not being more precise here).\n"+
				"See https://golang.org/src/cmd/link/internal/ld/deadcode.go for explanation.\n"+
				"%s contains the resulting binary.\n", tf.Name())
	}
	return nil
}

func TestUrootCmdline(t *testing.T) {
	samplef, err := ioutil.TempFile("", "u-root-test-")
	if err != nil {
		t.Fatal(err)
	}
	samplef.Close()
	defer os.RemoveAll(samplef.Name())
	sampledir, err := ioutil.TempDir("", "u-root-test-dir-")
	if err != nil {
		t.Fatal(err)
	}
	if err = ioutil.WriteFile(filepath.Join(sampledir, "foo"), nil, 0644); err != nil {
		t.Fatal(err)
	}
	if err = ioutil.WriteFile(filepath.Join(sampledir, "bar"), nil, 0644); err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(sampledir)

	for _, tt := range []struct {
		name       string
		env        []string
		args       []string
		err        error
		validators []itest.ArchiveValidator
	}{
		{
			name: "include one extra file",
			args: []string{"-nocmd", "-files=/bin/bash"},
			err:  nil,
			validators: []itest.ArchiveValidator{
				itest.HasFile{"bin/bash"},
			},
		},
		{
			name: "uinitcmd",
			args: []string{"-build=bb", "-uinitcmd=echo foobar fuzz", "-defaultsh=", "./cmds/core/init", "./cmds/core/echo"},
			err:  nil,
			validators: []itest.ArchiveValidator{
				itest.HasRecord{cpio.Symlink("bin/uinit", "../bbin/echo")},
				itest.HasContent{
					Path:    "etc/uinit.flags",
					Content: "\"foobar\"\n\"fuzz\"",
				},
			},
		},
		{
			name: "fix usage of an absolute path",
			args: []string{"-nocmd", fmt.Sprintf("-files=%s:/bin", sampledir)},
			err:  nil,
			validators: []itest.ArchiveValidator{
				itest.HasFile{"/bin/foo"},
				itest.HasFile{"/bin/bar"},
			},
		},
		{
			name: "include multiple extra files",
			args: []string{"-nocmd", "-files=/bin/bash", "-files=/bin/ls", fmt.Sprintf("-files=%s", samplef.Name())},
			validators: []itest.ArchiveValidator{
				itest.HasFile{"bin/bash"},
				itest.HasFile{"bin/ls"},
				itest.HasFile{samplef.Name()},
			},
		},
		{
			name: "include one extra file with rename",
			args: []string{"-nocmd", "-files=/bin/bash:bin/bush"},
			validators: []itest.ArchiveValidator{
				itest.HasFile{"bin/bush"},
			},
		},
		{
			name: "make sure dead code gets eliminated",
			args: []string{
				// Build the world + test symbols, unstripped.
				"-build=bb", "-no-strip", "world", "pkg/uroot/test/foo",
				// These are known to disable DCE and need to be exluded.
				// The reason is https://github.com/golang/go/issues/36021 and is fixed in Go 1.15,
				// so these can be removed once we no longer support Go < 1.15.
				"-cmds/core/installcommand", "-cmds/exp/builtin", "-cmds/exp/run",
			},
			err: nil,
			validators: []itest.ArchiveValidator{
				noDeadCode{Path: "bbin/bb"},
			},
		},
		{
			name: "hosted source mode",
			args: append([]string{"-build=source", "-base=/dev/null", "-defaultsh=", "-initcmd="}, srcmds...),
		},
		{
			name: "hosted bb mode",
			args: append([]string{"-build=bb", "-base=/dev/null", "-defaultsh=", "-initcmd="}, twocmds...),
		},
		{
			name: "AMD64 bb build",
			env:  []string{"GOARCH=amd64"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "AMD64 source build",
			env:  []string{"GOARCH=amd64"},
			args: []string{"-build=source", "all"},
			validators: []itest.ArchiveValidator{
				buildSourceValidator{
					goroot: "/go",
					gopath: ".",
					env:    []string{"GOARCH=amd64"},
				},
			},
		},
		{
			name: "MIPS bb build",
			env:  []string{"GOARCH=mips"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "MIPSLE bb build",
			env:  []string{"GOARCH=mipsle"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "MIPS64 bb build",
			env:  []string{"GOARCH=mips64"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "MIPS64LE bb build",
			env:  []string{"GOARCH=mips64le"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "ARM7 bb build",
			env:  []string{"GOARCH=arm", "GOARM=7"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "ARM64 bb build",
			env:  []string{"GOARCH=arm64"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "386 (32 bit) bb build",
			env:  []string{"GOARCH=386"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "Power 64bit bb build",
			env:  []string{"GOARCH=ppc64le"},
			args: []string{"-build=bb", "all"},
		},
	} {
		t.Run(tt.name, func(t *testing.T) {
			delFiles := true
			f, sum1 := buildIt(t, tt.args, tt.env, tt.err)
			defer func() {
				if delFiles {
					os.RemoveAll(f.Name())
				}
			}()

			a, err := itest.ReadArchive(f.Name())
			if err != nil {
				t.Fatal(err)
			}

			for _, v := range tt.validators {
				if err := v.Validate(a); err != nil {
					t.Errorf("validator failed: %v / archive:\n%s", err, a)
				}
			}

			f2, sum2 := buildIt(t, tt.args, tt.env, tt.err)
			defer func() {
				if delFiles {
					os.RemoveAll(f2.Name())
				}
			}()
			if !bytes.Equal(sum1, sum2) {
				delFiles = false
				t.Errorf("not reproducible, hashes don't match")
				t.Errorf("env: %v args: %v", tt.env, tt.args)
				t.Errorf("file1: %v file2: %v", f.Name(), f2.Name())
			}
		})
	}
}

func buildIt(t *testing.T, args, env []string, want error) (*os.File, []byte) {
	f, err := ioutil.TempFile("", "u-root-")
	if err != nil {
		t.Fatal(err)
	}

	arg := append([]string{"-o", f.Name()}, args...)
	c := testutil.Command(t, arg...)
	t.Logf("Commandline: %v", arg)
	c.Env = append(c.Env, env...)
	if out, err := c.CombinedOutput(); err != want {
		t.Fatalf("Error: %v\nOutput:\n%s", err, out)
	} else if err != nil {
		h1 := sha256.New()
		if _, err := io.Copy(h1, f); err != nil {
			t.Fatal()
		}
		return f, h1.Sum(nil)
	}
	return f, nil
}

func TestMain(m *testing.M) {
	testutil.Run(m, main)
}
<commit_msg>test: test u-root command outside of GOPATH<commit_after>// Copyright 2015-2018 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bufio"
	"bytes"
	"crypto/sha256"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"

	"github.com/u-root/u-root/pkg/cpio"
	"github.com/u-root/u-root/pkg/golang"
	"github.com/u-root/u-root/pkg/testutil"
	"github.com/u-root/u-root/pkg/uio"
	itest "github.com/u-root/u-root/pkg/uroot/initramfs/test"
)

var twocmds = []string{
	"github.com/u-root/u-root/cmds/core/ls",
	"github.com/u-root/u-root/cmds/core/init",
}

var srcmds = []string{
	"github.com/u-root/u-root/cmds/core/ls",
	"github.com/u-root/u-root/cmds/core/init",
	"github.com/u-root/u-root/cmds/core/installcommand",
}

type buildSourceValidator struct {
	gopath string
	goroot string
	env    []string
}

func (b buildSourceValidator) Validate(a *cpio.Archive) error {
	dir, err := ioutil.TempDir("", "u-root-source-")
	if err != nil {
		return err
	}
	defer os.RemoveAll(dir)

	if err := os.Mkdir(filepath.Join(dir, "tmp"), 0755); err != nil {
		return err
	}

	// Unpack into dir.
	err = cpio.ForEachRecord(a.Reader(), func(r cpio.Record) error {
		return cpio.CreateFileInRoot(r, dir, false)
	})
	if err != nil {
		return err
	}

	goroot := filepath.Join(dir, b.goroot)
	gopath := filepath.Join(dir, b.gopath)
	// go build ./src/...
	c := exec.Command(filepath.Join(goroot, "bin/go"), "build", filepath.Join(gopath, "src/..."))
	c.Env = append(b.env,
		fmt.Sprintf("GOPATH=%s", gopath),
		fmt.Sprintf("GOCACHE=%s", filepath.Join(dir, "tmp")),
		fmt.Sprintf("GOROOT=%s", goroot),
		"GO111MODULE=off",
		"CGO_ENABLED=0")
	out, err := c.CombinedOutput()
	if err != nil {
		return fmt.Errorf("could not build go source %v; output\n%s", err, out)
	}
	return nil
}

func xTestDCE(t *testing.T) {
	delFiles := false
	f, _ := buildIt(
		t,
		[]string{
			"-build=bb", "-no-strip",
			"world",
			"-github.com/u-root/u-root/cmds/core/installcommand",
			"-github.com/u-root/u-root/cmds/exp/builtin",
			"-github.com/u-root/u-root/cmds/exp/run",
			"github.com/u-root/u-root/pkg/uroot/test/foo",
		},
		nil,
		nil)
	defer func() {
		if delFiles {
			os.RemoveAll(f.Name())
		}
	}()
	st, _ := f.Stat()
	t.Logf("Built %s, size %d", f.Name(), st.Size())
	cmd := golang.Default().GoCmd("tool", "nm", f.Name())
	nmOutput, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("failed to run nm: %s %s", err, nmOutput)
	}
	symScanner := bufio.NewScanner(bytes.NewBuffer(nmOutput))
	syms := map[string]bool{}
	for symScanner.Scan() {
		line := symScanner.Text()
		parts := strings.Split(line, " ")
		if len(parts) == 0 {
			continue
		}
		sym := parts[len(parts)-1]
		syms[sym] = true
		t.Logf("%s", sym)
	}
}

type noDeadCode struct {
	Path string
}

func (v noDeadCode) Validate(a *cpio.Archive) error {
	// 1. Extract BB binary into a temporary file.
	delFiles := true
	bbRecord, ok := a.Get(v.Path)
	if !ok {
		return fmt.Errorf("archive does not contain %s, but should", v.Path)
	}
	tf, err := ioutil.TempFile("", "u-root-temp-bb-")
	if err != nil {
		return err
	}
	bbData, _ := uio.ReadAll(bbRecord)
	tf.Write(bbData)
	tf.Close()
	defer func() {
		if delFiles {
			os.RemoveAll(tf.Name())
		}
	}()
	// 2. Run "go nm" on it and build symbol table.
	cmd := golang.Default().GoCmd("tool", "nm", tf.Name())
	nmOutput, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to run nm: %s %s", err, nmOutput)
	}
	symScanner := bufio.NewScanner(bytes.NewBuffer(nmOutput))
	syms := map[string]bool{}
	for symScanner.Scan() {
		line := symScanner.Text()
		parts := strings.Split(line, " ")
		if len(parts) == 0 {
			continue
		}
		sym := parts[len(parts)-1]
		syms[sym] = true
	}
	// 3. Check for presence and absence of particular symbols.
	if !syms["github.com/u-root/u-root/pkg/uroot/test/bar.Bar.UsedInterfaceMethod"] {
		// Sanity check of the test itself: this method must be in the binary.
		return fmt.Errorf("expected symbol not found, something is wrong with the build")
	}
	if syms["github.com/u-root/u-root/pkg/uroot/test/bar.Bar.UnusedNonInterfaceMethod"] {
		// Sanity check of the test itself: this method must be in the binary.
		delFiles = false
		return fmt.Errorf(
			"Unused non-interface method has not been eliminated, dead code elimination is not working properly.\n"+
				"The most likely reason is use of reflect.Value.Method or .MethodByName somewhere "+
				"(could be a command or vendor dependency, apologies for not being more precise here).\n"+
				"See https://golang.org/src/cmd/link/internal/ld/deadcode.go for explanation.\n"+
				"%s contains the resulting binary.\n", tf.Name())
	}
	return nil
}

func TestUrootCmdline(t *testing.T) {
	samplef, err := ioutil.TempFile("", "u-root-test-")
	if err != nil {
		t.Fatal(err)
	}
	samplef.Close()
	defer os.RemoveAll(samplef.Name())
	sampledir, err := ioutil.TempDir("", "u-root-test-dir-")
	if err != nil {
		t.Fatal(err)
	}
	if err = ioutil.WriteFile(filepath.Join(sampledir, "foo"), nil, 0644); err != nil {
		t.Fatal(err)
	}
	if err = ioutil.WriteFile(filepath.Join(sampledir, "bar"), nil, 0644); err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(sampledir)

	for _, tt := range []struct {
		name       string
		env        []string
		args       []string
		err        error
		validators []itest.ArchiveValidator
	}{
		{
			name: "include one extra file",
			args: []string{"-nocmd", "-files=/bin/bash"},
			err:  nil,
			validators: []itest.ArchiveValidator{
				itest.HasFile{"bin/bash"},
			},
		},
		{
			name: "uinitcmd",
			args: []string{"-build=bb", "-uinitcmd=echo foobar fuzz", "-defaultsh=", "github.com/u-root/u-root/cmds/core/init", "github.com/u-root/u-root/cmds/core/echo"},
			err:  nil,
			validators: []itest.ArchiveValidator{
				itest.HasRecord{cpio.Symlink("bin/uinit", "../bbin/echo")},
				itest.HasContent{
					Path:    "etc/uinit.flags",
					Content: "\"foobar\"\n\"fuzz\"",
				},
			},
		},
		{
			name: "fix usage of an absolute path",
			args: []string{"-nocmd", fmt.Sprintf("-files=%s:/bin", sampledir)},
			err:  nil,
			validators: []itest.ArchiveValidator{
				itest.HasFile{"/bin/foo"},
				itest.HasFile{"/bin/bar"},
			},
		},
		{
			name: "include multiple extra files",
			args: []string{"-nocmd", "-files=/bin/bash", "-files=/bin/ls", fmt.Sprintf("-files=%s", samplef.Name())},
			validators: []itest.ArchiveValidator{
				itest.HasFile{"bin/bash"},
				itest.HasFile{"bin/ls"},
				itest.HasFile{samplef.Name()},
			},
		},
		{
			name: "include one extra file with rename",
			args: []string{"-nocmd", "-files=/bin/bash:bin/bush"},
			validators: []itest.ArchiveValidator{
				itest.HasFile{"bin/bush"},
			},
		},
		{
			name: "make sure dead code gets eliminated",
			args: []string{
				// Build the world + test symbols, unstripped.
				"-build=bb", "-no-strip", "world", "github.com/u-root/u-root/pkg/uroot/test/foo",
				// These are known to disable DCE and need to be exluded.
				// The reason is https://github.com/golang/go/issues/36021 and is fixed in Go 1.15,
				// so these can be removed once we no longer support Go < 1.15.
				"-github.com/u-root/u-root/cmds/core/installcommand",
				"-github.com/u-root/u-root/cmds/exp/builtin",
				"-github.com/u-root/u-root/cmds/exp/run",
			},
			err: nil,
			validators: []itest.ArchiveValidator{
				noDeadCode{Path: "bbin/bb"},
			},
		},
		{
			name: "hosted source mode",
			args: append([]string{"-build=source", "-base=/dev/null", "-defaultsh=", "-initcmd="}, srcmds...),
		},
		{
			name: "hosted bb mode",
			args: append([]string{"-build=bb", "-base=/dev/null", "-defaultsh=", "-initcmd="}, twocmds...),
		},
		{
			name: "AMD64 bb build",
			env:  []string{"GOARCH=amd64"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "AMD64 source build",
			env:  []string{"GOARCH=amd64"},
			args: []string{"-build=source", "all"},
			validators: []itest.ArchiveValidator{
				buildSourceValidator{
					goroot: "/go",
					gopath: ".",
					env:    []string{"GOARCH=amd64"},
				},
			},
		},
		{
			name: "MIPS bb build",
			env:  []string{"GOARCH=mips"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "MIPSLE bb build",
			env:  []string{"GOARCH=mipsle"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "MIPS64 bb build",
			env:  []string{"GOARCH=mips64"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "MIPS64LE bb build",
			env:  []string{"GOARCH=mips64le"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "ARM7 bb build",
			env:  []string{"GOARCH=arm", "GOARM=7"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "ARM64 bb build",
			env:  []string{"GOARCH=arm64"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "386 (32 bit) bb build",
			env:  []string{"GOARCH=386"},
			args: []string{"-build=bb", "all"},
		},
		{
			name: "Power 64bit bb build",
			env:  []string{"GOARCH=ppc64le"},
			args: []string{"-build=bb", "all"},
		},
	} {
		t.Run(tt.name, func(t *testing.T) {
			delFiles := true
			f, sum1 := buildIt(t, tt.args, tt.env, tt.err)
			defer func() {
				if delFiles {
					os.RemoveAll(f.Name())
				}
			}()

			a, err := itest.ReadArchive(f.Name())
			if err != nil {
				t.Fatal(err)
			}

			for _, v := range tt.validators {
				if err := v.Validate(a); err != nil {
					t.Errorf("validator failed: %v / archive:\n%s", err, a)
				}
			}

			f2, sum2 := buildIt(t, tt.args, tt.env, tt.err)
			defer func() {
				if delFiles {
					os.RemoveAll(f2.Name())
				}
			}()
			if !bytes.Equal(sum1, sum2) {
				delFiles = false
				t.Errorf("not reproducible, hashes don't match")
				t.Errorf("env: %v args: %v", tt.env, tt.args)
				t.Errorf("file1: %v file2: %v", f.Name(), f2.Name())
			}
		})
	}
}

func buildIt(t *testing.T, args, env []string, want error) (*os.File, []byte) {
	f, err := ioutil.TempFile("", "u-root-")
	if err != nil {
		t.Fatal(err)
	}
	// Use the u-root command outside of the $GOPATH tree to make sure it
	// still works.
	dir, err := ioutil.TempDir("", "build-")
	if err != nil {
		t.Fatal(err)
	}

	arg := append([]string{"-o", f.Name()}, args...)
	c := testutil.Command(t, arg...)
	t.Logf("Commandline: %v", arg)
	c.Env = append(c.Env, env...)
	c.Dir = dir
	if out, err := c.CombinedOutput(); err != want {
		t.Fatalf("Error: %v\nOutput:\n%s", err, out)
	} else if err != nil {
		h1 := sha256.New()
		if _, err := io.Copy(h1, f); err != nil {
			t.Fatal()
		}
		return f, h1.Sum(nil)
	}
	return f, nil
}

func TestMain(m *testing.M) {
	testutil.Run(m, main)
}
<|endoftext|>


<commit_before>package siteengines

// TODO: Check for the visual likeness of two usernames when checking for availability! Generate images and compare pixels.
// TODO: Logging in should work case sensitively, or at least without concern for the case of the first letter
// TODO: Consider using "0" and "1" instead of "true" or "false" when setting values, while still understanding "true" or "false"
// TODO: The password should be set at confirmation time instead of registration-time in order to make the process clearer and pave the way for invite-only?

import (
	"crypto/sha256"
	"io"
	"math/rand"
	"strings"
	"time"

	"github.com/hoisie/web"
	. "github.com/xyproto/browserspeak"
	. "github.com/xyproto/genericsite"
	"github.com/xyproto/instapage"
)

// An Engine is a specific piece of a website
// This part handles the login/logout/registration/confirmation pages

const (
	MINIMUM_CONFIRMATION_CODE_LENGTH = 20
	USERNAME_ALLOWED_LETTERS         = "abcdefghijklmnopqrstuvwxyzæøåABCDEFGHIJKLMNOPQRSTUVWXYZÆØÅ_0123456789"
)

type UserEngine struct {
	state *UserState
}

func NewUserEngine(userState *UserState) *UserEngine {
	// For the secure cookies
	// This must happen before the random seeding, or
	// else people will have to log in again after every server restart
	web.Config.CookieSecret = RandomCookieFriendlyString(30)

	rand.Seed(time.Now().UnixNano())

	return &UserEngine{userState}
}

func (ue *UserEngine) GetState() *UserState {
	return ue.state
}

// Create a user by adding the username to the list of usernames
func GenerateConfirmUser(state *UserState) WebHandle {
	return func(ctx *web.Context, val string) string {
		confirmationCode := val

		unconfirmedUsernames, err := state.GetAllUnconfirmedUsernames()
		if err != nil {
			return instapage.MessageOKurl("Confirmation", "All users are confirmed already.", "/register")
		}

		// Find the username by looking up the confirmationCode on unconfirmed users
		username := ""
		for _, aUsername := range unconfirmedUsernames {
			aConfirmationCode, err := state.GetConfirmationCode(aUsername)
			if err != nil {
				// If the confirmation code can not be found, just skip this one
				continue
			}
			if confirmationCode == aConfirmationCode {
				// Found the right user
				username = aUsername
				break
			}
		}

		// Check that the user is there
		if username == "" {
			// Say "no longer" because we don't care about people that just try random confirmation links
			return instapage.MessageOKurl("Confirmation", "The confirmation link is no longer valid.", "/register")
		}
		hasUser := state.HasUser(username)
		if !hasUser {
			return instapage.MessageOKurl("Confirmation", "The user you wish to confirm does not exist anymore.", "/register")
		}

		// Remove from the list of unconfirmed usernames
		state.RemoveUnconfirmed(username)

		// Mark user as confirmed
		state.MarkConfirmed(username)

		return instapage.MessageOKurl("Confirmation", "Thank you "+username+", you can now log in.", "/login")
	}
}

// Log in a user by changing the loggedin value
func GenerateLoginUser(state *UserState) WebHandle {
	return func(ctx *web.Context, val string) string {
		// Fetch password from ctx
		password, found := ctx.Params["password"]
		if !found {
			return instapage.MessageOKback("Login", "Can't log in without a password.")
		}
		username := val
		if username == "" {
			return instapage.MessageOKback("Login", "Can't log in with a blank username.")
		}
		if !state.HasUser(username) {
			return instapage.MessageOKback("Login", "User "+username+" does not exist, could not log in.")
		}
		if !state.IsConfirmed(username) {
			return instapage.MessageOKback("Login", "The email for "+username+" has not been confirmed, check your email and follow the link.")
		}
		if !CorrectPassword(state, username, password) {
			return instapage.MessageOKback("Login", "Wrong password.")
		}

		// Log in the user by changing the database and setting a secure cookie
		state.SetLoggedIn(username)

		state.SetBrowserUsername(ctx, username)

		// TODO: Use a welcoming messageOK where the user can see when he/she last logged in and from which host

		if username == "admin" {
			ctx.SetHeader("Refresh", "0; url=/admin", true)
		} else {
			// TODO: Redirect to the page the user was at before logging in
			ctx.SetHeader("Refresh", "0; url=/", true)
		}

		return ""
	}
}

// TODO: Forgot username? Enter email, send username.
// TODO: Lost confirmation link? Enter mail, Receive confirmation link.
// TODO: Forgot password? Enter mail, receive reset-password link.
// TODO: Make sure not two usernames can register at once before confirming
// TODO: Only one username per email address? (meh? can use more than one address?=
// TODO: Maximum 1 confirmation email per email adress
// TODO: Maximum 1 forgot username per email adress per day
// TODO: Maximum 1 forgot password per email adress per day
// TODO: Maximum 1 lost confirmation link per email adress per day
// TODO: Link for "Did you not request this email? Click here" i alle eposter som sendes.
// TODO: Rate limiting, maximum rate per minute or day

// Register a new user, site is ie. "archlinux.no"
func GenerateRegisterUser(state *UserState, site string) WebHandle {
	return func(ctx *web.Context, val string) string {

		// Password checks
		password1, found := ctx.Params["password1"]
		if password1 == "" || !found {
			return instapage.MessageOKback("Register", "Can't register without a password.")
		}
		password2, found := ctx.Params["password2"]
		if password2 == "" || !found {
			return instapage.MessageOKback("Register", "Please confirm the password by typing it in twice.")
		}
		if password1 != password2 {
			return instapage.MessageOKback("Register", "The password and confirmation password must be equal.")
		}

		// Email checks
		email, found := ctx.Params["email"]
		if !found {
			return instapage.MessageOKback("Register", "Can't register without an email address.")
		}
		// must have @ and ., but no " "
		if !strings.Contains(email, "@") || !strings.Contains(email, ".") || strings.Contains(email, " ") {
			return instapage.MessageOKback("Register", "Please use a valid email address.")
		}
		if email != CleanUserInput(email) {
			return instapage.MessageOKback("Register", "The sanitized email differs from the given email.")
		}

		// Username checks
		username := val
		if username == "" {
			return instapage.MessageOKback("Register", "Can't register without a username.")
		}
		if state.HasUser(username) {
			return instapage.MessageOKback("Register", "That user already exists, try another username.")
		}

		// Only some letters are allowed in the username
	NEXT:
		for _, letter := range username {
			for _, allowedLetter := range USERNAME_ALLOWED_LETTERS {
				if letter == allowedLetter {
					continue NEXT
				}
			}
			return instapage.MessageOKback("Register", "Only a-å, A-Å, 0-9 and _ are allowed in usernames.")
		}
		if username == password1 {
			return instapage.MessageOKback("Register", "Username and password must be different, try another password.")
		}

		adminuser := false
		// A special case
		if username == "admin" {
			// The first user to register with the username "admin" becomes the administrator
			adminuser = true
		}

		// Register the user
		state.AddUser(username, password1, email)

		// Mark user as administrator if that is the case
		if adminuser {
			// Set admin status
			state.SetAdminStatus(username)
		}

		// The confirmation code must be a minimum of 8 letters long
		length := MINIMUM_CONFIRMATION_CODE_LENGTH
		confirmationCode := RandomHumanFriendlyString(length)
		for AlreadyHasConfirmationCode(state, confirmationCode) {
			// Increase the length of the confirmationCode random string every time there is a collision
			length++
			confirmationCode = RandomHumanFriendlyString(length)
			if length > 100 {
				// This should never happen
				panic("ERROR: Too many generated confirmation codes are not unique, something is wrong")
			}
		}

		// Send confirmation email
		ConfirmationEmail(site, "https://"+site+"/confirm/"+confirmationCode, username, email)

		// Register the need to be confirmed
		state.AddUnconfirmed(username, confirmationCode)

		// Redirect
		return instapage.MessageOKurl("Registration complete", "Thanks for registering, the confirmation e-mail has been sent.", "/login")
	}
}

// Log out a user by changing the loggedin value
func GenerateLogoutCurrentUser(state *UserState) SimpleContextHandle {
	return func(ctx *web.Context) string {
		username := GetBrowserUsername(ctx)
		if username == "" {
			return instapage.MessageOKback("Logout", "No user to log out")
		}
		if !state.HasUser(username) {
			return instapage.MessageOKback("Logout", "user "+username+" does not exist, could not log out")
		}

		// Log out the user by changing the database, the cookie can stay
		state.SetLoggedOut(username)

		// Redirect
		//ctx.SetHeader("Refresh", "0; url=/login", true)
		return instapage.MessageOKurl("Logout", username+" is now logged out. Hope to see you soon!", "/login")
	}
}

func GenerateNoJavascriptMessage() SimpleContextHandle {
	return func(ctx *web.Context) string {
		return instapage.MessageOKback("JavaScript error", "Cookies and Javascriåt must be enabled.<br />Older browsers might be supported in the future.")
	}
}

func LoginCP(basecp BaseCP, state *UserState, url string) *ContentPage {
	cp := basecp(state)
	cp.ContentTitle = "Login"
	cp.ContentHTML = instapage.LoginForm()
	cp.ContentJS += OnClick("#loginButton", "$('#loginForm').get(0).setAttribute('action', '/login/' + $('#username').val());")
	//cp.ExtraCSSurls = append(cp.ExtraCSSurls, "/css/login.css")
	cp.Url = url

	// Hide the Login menu if we're on the Login page
	//cp.HeaderJS = strings.Replace(cp.HeaderJS, "menuLogin", "menuNop", 1)
	//cp.ContentJS += Hide("#menuLogin")

	return cp
}

func RegisterCP(basecp BaseCP, state *UserState, url string) *ContentPage {
	cp := basecp(state)
	cp.ContentTitle = "Register"
	cp.ContentHTML = instapage.RegisterForm()
	cp.ContentJS += OnClick("#registerButton", "$('#registerForm').get(0).setAttribute('action', '/register/' + $('#username').val());")
	//cp.ExtraCSSurls = append(cp.ExtraCSSurls, "/css/register.css")
	cp.Url = url

	// Hide the Register menu if we're on the Register page
	//cp.HeaderJS = strings.Replace(cp.HeaderJS, "menuRegister", "menuNop", 1)
	//cp.ContentJS += Hide("#menuRegister")

	return cp
}

// Site is ie. "archlinux.no" and used for sending confirmation emails
func (ue *UserEngine) ServePages(site string) {
	state := ue.state
	web.Post("/register/(.*)", GenerateRegisterUser(state, site))
	web.Post("/login/(.*)", GenerateLoginUser(state))
	web.Post("/login", GenerateNoJavascriptMessage())
	web.Get("/logout", GenerateLogoutCurrentUser(state))
	web.Get("/confirm/(.*)", GenerateConfirmUser(state))
}
<commit_msg>Fixed imports<commit_after>package siteengines

// TODO: Check for the visual likeness of two usernames when checking for availability! Generate images and compare pixels.
// TODO: Logging in should work case sensitively, or at least without concern for the case of the first letter
// TODO: Consider using "0" and "1" instead of "true" or "false" when setting values, while still understanding "true" or "false"
// TODO: The password should be set at confirmation time instead of registration-time in order to make the process clearer and pave the way for invite-only?

import (
	"math/rand"
	"strings"
	"time"

	"github.com/hoisie/web"
	. "github.com/xyproto/browserspeak"
	. "github.com/xyproto/genericsite"
	"github.com/xyproto/instapage"
)

// An Engine is a specific piece of a website
// This part handles the login/logout/registration/confirmation pages

const (
	MINIMUM_CONFIRMATION_CODE_LENGTH = 20
	USERNAME_ALLOWED_LETTERS         = "abcdefghijklmnopqrstuvwxyzæøåABCDEFGHIJKLMNOPQRSTUVWXYZÆØÅ_0123456789"
)

type UserEngine struct {
	state *UserState
}

func NewUserEngine(userState *UserState) *UserEngine {
	// For the secure cookies
	// This must happen before the random seeding, or
	// else people will have to log in again after every server restart
	web.Config.CookieSecret = RandomCookieFriendlyString(30)

	rand.Seed(time.Now().UnixNano())

	return &UserEngine{userState}
}

func (ue *UserEngine) GetState() *UserState {
	return ue.state
}

// Create a user by adding the username to the list of usernames
func GenerateConfirmUser(state *UserState) WebHandle {
	return func(ctx *web.Context, val string) string {
		confirmationCode := val

		unconfirmedUsernames, err := state.GetAllUnconfirmedUsernames()
		if err != nil {
			return instapage.MessageOKurl("Confirmation", "All users are confirmed already.", "/register")
		}

		// Find the username by looking up the confirmationCode on unconfirmed users
		username := ""
		for _, aUsername := range unconfirmedUsernames {
			aConfirmationCode, err := state.GetConfirmationCode(aUsername)
			if err != nil {
				// If the confirmation code can not be found, just skip this one
				continue
			}
			if confirmationCode == aConfirmationCode {
				// Found the right user
				username = aUsername
				break
			}
		}

		// Check that the user is there
		if username == "" {
			// Say "no longer" because we don't care about people that just try random confirmation links
			return instapage.MessageOKurl("Confirmation", "The confirmation link is no longer valid.", "/register")
		}
		hasUser := state.HasUser(username)
		if !hasUser {
			return instapage.MessageOKurl("Confirmation", "The user you wish to confirm does not exist anymore.", "/register")
		}

		// Remove from the list of unconfirmed usernames
		state.RemoveUnconfirmed(username)

		// Mark user as confirmed
		state.MarkConfirmed(username)

		return instapage.MessageOKurl("Confirmation", "Thank you "+username+", you can now log in.", "/login")
	}
}

// Log in a user by changing the loggedin value
func GenerateLoginUser(state *UserState) WebHandle {
	return func(ctx *web.Context, val string) string {
		// Fetch password from ctx
		password, found := ctx.Params["password"]
		if !found {
			return instapage.MessageOKback("Login", "Can't log in without a password.")
		}
		username := val
		if username == "" {
			return instapage.MessageOKback("Login", "Can't log in with a blank username.")
		}
		if !state.HasUser(username) {
			return instapage.MessageOKback("Login", "User "+username+" does not exist, could not log in.")
		}
		if !state.IsConfirmed(username) {
			return instapage.MessageOKback("Login", "The email for "+username+" has not been confirmed, check your email and follow the link.")
		}
		if !CorrectPassword(state, username, password) {
			return instapage.MessageOKback("Login", "Wrong password.")
		}

		// Log in the user by changing the database and setting a secure cookie
		state.SetLoggedIn(username)

		state.SetBrowserUsername(ctx, username)

		// TODO: Use a welcoming messageOK where the user can see when he/she last logged in and from which host

		if username == "admin" {
			ctx.SetHeader("Refresh", "0; url=/admin", true)
		} else {
			// TODO: Redirect to the page the user was at before logging in
			ctx.SetHeader("Refresh", "0; url=/", true)
		}

		return ""
	}
}

// TODO: Forgot username? Enter email, send username.
// TODO: Lost confirmation link? Enter mail, Receive confirmation link.
// TODO: Forgot password? Enter mail, receive reset-password link.
// TODO: Make sure not two usernames can register at once before confirming
// TODO: Only one username per email address? (meh? can use more than one address?=
// TODO: Maximum 1 confirmation email per email adress
// TODO: Maximum 1 forgot username per email adress per day
// TODO: Maximum 1 forgot password per email adress per day
// TODO: Maximum 1 lost confirmation link per email adress per day
// TODO: Link for "Did you not request this email? Click here" i alle eposter som sendes.
// TODO: Rate limiting, maximum rate per minute or day

// Register a new user, site is ie. "archlinux.no"
func GenerateRegisterUser(state *UserState, site string) WebHandle {
	return func(ctx *web.Context, val string) string {

		// Password checks
		password1, found := ctx.Params["password1"]
		if password1 == "" || !found {
			return instapage.MessageOKback("Register", "Can't register without a password.")
		}
		password2, found := ctx.Params["password2"]
		if password2 == "" || !found {
			return instapage.MessageOKback("Register", "Please confirm the password by typing it in twice.")
		}
		if password1 != password2 {
			return instapage.MessageOKback("Register", "The password and confirmation password must be equal.")
		}

		// Email checks
		email, found := ctx.Params["email"]
		if !found {
			return instapage.MessageOKback("Register", "Can't register without an email address.")
		}
		// must have @ and ., but no " "
		if !strings.Contains(email, "@") || !strings.Contains(email, ".") || strings.Contains(email, " ") {
			return instapage.MessageOKback("Register", "Please use a valid email address.")
		}
		if email != CleanUserInput(email) {
			return instapage.MessageOKback("Register", "The sanitized email differs from the given email.")
		}

		// Username checks
		username := val
		if username == "" {
			return instapage.MessageOKback("Register", "Can't register without a username.")
		}
		if state.HasUser(username) {
			return instapage.MessageOKback("Register", "That user already exists, try another username.")
		}

		// Only some letters are allowed in the username
	NEXT:
		for _, letter := range username {
			for _, allowedLetter := range USERNAME_ALLOWED_LETTERS {
				if letter == allowedLetter {
					continue NEXT
				}
			}
			return instapage.MessageOKback("Register", "Only a-å, A-Å, 0-9 and _ are allowed in usernames.")
		}
		if username == password1 {
			return instapage.MessageOKback("Register", "Username and password must be different, try another password.")
		}

		adminuser := false
		// A special case
		if username == "admin" {
			// The first user to register with the username "admin" becomes the administrator
			adminuser = true
		}

		// Register the user
		state.AddUser(username, password1, email)

		// Mark user as administrator if that is the case
		if adminuser {
			// Set admin status
			state.SetAdminStatus(username)
		}

		// The confirmation code must be a minimum of 8 letters long
		length := MINIMUM_CONFIRMATION_CODE_LENGTH
		confirmationCode := RandomHumanFriendlyString(length)
		for AlreadyHasConfirmationCode(state, confirmationCode) {
			// Increase the length of the confirmationCode random string every time there is a collision
			length++
			confirmationCode = RandomHumanFriendlyString(length)
			if length > 100 {
				// This should never happen
				panic("ERROR: Too many generated confirmation codes are not unique, something is wrong")
			}
		}

		// Send confirmation email
		ConfirmationEmail(site, "https://"+site+"/confirm/"+confirmationCode, username, email)

		// Register the need to be confirmed
		state.AddUnconfirmed(username, confirmationCode)

		// Redirect
		return instapage.MessageOKurl("Registration complete", "Thanks for registering, the confirmation e-mail has been sent.", "/login")
	}
}

// Log out a user by changing the loggedin value
func GenerateLogoutCurrentUser(state *UserState) SimpleContextHandle {
	return func(ctx *web.Context) string {
		username := GetBrowserUsername(ctx)
		if username == "" {
			return instapage.MessageOKback("Logout", "No user to log out")
		}
		if !state.HasUser(username) {
			return instapage.MessageOKback("Logout", "user "+username+" does not exist, could not log out")
		}

		// Log out the user by changing the database, the cookie can stay
		state.SetLoggedOut(username)

		// Redirect
		//ctx.SetHeader("Refresh", "0; url=/login", true)
		return instapage.MessageOKurl("Logout", username+" is now logged out. Hope to see you soon!", "/login")
	}
}

func GenerateNoJavascriptMessage() SimpleContextHandle {
	return func(ctx *web.Context) string {
		return instapage.MessageOKback("JavaScript error", "Cookies and Javascriåt must be enabled.<br />Older browsers might be supported in the future.")
	}
}

func LoginCP(basecp BaseCP, state *UserState, url string) *ContentPage {
	cp := basecp(state)
	cp.ContentTitle = "Login"
	cp.ContentHTML = instapage.LoginForm()
	cp.ContentJS += OnClick("#loginButton", "$('#loginForm').get(0).setAttribute('action', '/login/' + $('#username').val());")
	//cp.ExtraCSSurls = append(cp.ExtraCSSurls, "/css/login.css")
	cp.Url = url

	// Hide the Login menu if we're on the Login page
	//cp.HeaderJS = strings.Replace(cp.HeaderJS, "menuLogin", "menuNop", 1)
	//cp.ContentJS += Hide("#menuLogin")

	return cp
}

func RegisterCP(basecp BaseCP, state *UserState, url string) *ContentPage {
	cp := basecp(state)
	cp.ContentTitle = "Register"
	cp.ContentHTML = instapage.RegisterForm()
	cp.ContentJS += OnClick("#registerButton", "$('#registerForm').get(0).setAttribute('action', '/register/' + $('#username').val());")
	//cp.ExtraCSSurls = append(cp.ExtraCSSurls, "/css/register.css")
	cp.Url = url

	// Hide the Register menu if we're on the Register page
	//cp.HeaderJS = strings.Replace(cp.HeaderJS, "menuRegister", "menuNop", 1)
	//cp.ContentJS += Hide("#menuRegister")

	return cp
}

// Site is ie. "archlinux.no" and used for sending confirmation emails
func (ue *UserEngine) ServePages(site string) {
	state := ue.state
	web.Post("/register/(.*)", GenerateRegisterUser(state, site))
	web.Post("/login/(.*)", GenerateLoginUser(state))
	web.Post("/login", GenerateNoJavascriptMessage())
	web.Get("/logout", GenerateLogoutCurrentUser(state))
	web.Get("/confirm/(.*)", GenerateConfirmUser(state))
}
<|endoftext|>


<commit_before>package main

// ZSH is a singleton instance of ZSH_T
type zsh struct{}

var ZSH Shell = zsh{}

const ZSH_HOOK = `
_direnv_hook() {
  eval "$("{{.SelfPath}}" export zsh)";
}
typeset -ag precmd_functions;
if [[ -z ${precmd_functions[(r)_direnv_hook]} ]]; then
  precmd_functions+=_direnv_hook;
fi
`

func (sh zsh) Hook() (string, error) {
	return ZSH_HOOK, nil
}

func (sh zsh) Export(e ShellExport) (out string) {
	for key, value := range e {
		if value == nil {
			out += sh.unset(key)
		} else {
			out += sh.export(key, *value)
		}
	}
	return out
}

func (sh zsh) Dump(env Env) (out string) {
	for key, value := range env {
		out += sh.export(key, value)
	}
	return out
}

func (sh zsh) export(key, value string) string {
	return "export " + sh.escape(key) + "=" + sh.escape(value) + ";"
}

func (sh zsh) unset(key string) string {
	return "unset " + sh.escape(key) + ";"
}

func (sh zsh) escape(str string) string {
	return BashEscape(str)
}
<commit_msg>shell: improve zsh hook (#514)<commit_after>package main

// ZSH is a singleton instance of ZSH_T
type zsh struct{}

var ZSH Shell = zsh{}

const ZSH_HOOK = `
_direnv_hook() {
  eval "$("{{.SelfPath}}" export zsh)";
}
typeset -ag precmd_functions;
if [[ -z ${precmd_functions[(r)_direnv_hook]} ]]; then
  precmd_functions=( _direnv_hook ${precmd_functions[@]} )
fi
typeset -ag chpwd_functions;
if [[ -z ${chpwd_functions[(r)_direnv_hook]} ]]; then
  chpwd_functions=( _direnv_hook ${chpwd_functions[@]} )
fi
`

func (sh zsh) Hook() (string, error) {
	return ZSH_HOOK, nil
}

func (sh zsh) Export(e ShellExport) (out string) {
	for key, value := range e {
		if value == nil {
			out += sh.unset(key)
		} else {
			out += sh.export(key, *value)
		}
	}
	return out
}

func (sh zsh) Dump(env Env) (out string) {
	for key, value := range env {
		out += sh.export(key, value)
	}
	return out
}

func (sh zsh) export(key, value string) string {
	return "export " + sh.escape(key) + "=" + sh.escape(value) + ";"
}

func (sh zsh) unset(key string) string {
	return "unset " + sh.escape(key) + ";"
}

func (sh zsh) escape(str string) string {
	return BashEscape(str)
}
<|endoftext|>