<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package testutils

import (
	"encoding/json"
	"fmt"
	"runtime"
	"strings"
	"testing"
	"time"

	"github.com/uber/tchannel-go"
	"github.com/uber/tchannel-go/atomic"
	"github.com/uber/tchannel-go/raw"
	"github.com/uber/tchannel-go/testutils/goroutines"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"golang.org/x/net/context"
)

// Has a previous test already leaked a goroutine?
var _leakedGoroutine = atomic.NewInt32(0)

// A TestServer encapsulates a TChannel server, a client factory, and functions
// to ensure that we're not leaking resources.
type TestServer struct {
	testing.TB

	// relayIdx is the index of the relay channel, if any, in the channels slice.
	relayIdx int

	// relayHosts is the relayer's SimpleRelayHosts (if any).
	relayHosts *SimpleRelayHosts

	// channels is the list of channels created for this TestServer. The first
	// element is always the initial server.
	channels []*tchannel.Channel

	// channelState the initial runtime state for all channels created
	// as part of the TestServer (including the server).
	channelStates map[*tchannel.Channel]*tchannel.RuntimeState

	introspectOpts *tchannel.IntrospectionOptions
	verifyOpts     *goroutines.VerifyOpts
	postFns        []func()
}

// NewTestServer constructs a TestServer.
func NewTestServer(t testing.TB, opts *ChannelOpts) *TestServer {
	ts := &TestServer{
		TB:            t,
		channelStates: make(map[*tchannel.Channel]*tchannel.RuntimeState),
		introspectOpts: &tchannel.IntrospectionOptions{
			IncludeExchanges:  true,
			IncludeTombstones: true,
		},
	}

	ts.NewServer(opts)
	if opts != nil && opts.IncludeRelay {
		ts.addRelay(opts.LogVerification)
	}

	return ts
}

// WithTestServer creates a new TestServer, runs the passed function, and then
// verifies that no resources were leaked.
func WithTestServer(t testing.TB, chanOpts *ChannelOpts, f func(*TestServer)) {
	chanOpts = chanOpts.Copy()
	runCount := chanOpts.RunCount
	if runCount < 1 {
		runCount = 1
	}

	for i := 0; i < runCount; i++ {
		if t.Failed() {
			return
		}

		if !chanOpts.OnlyRelay {
			noRelayOpts := chanOpts.Copy()
			noRelayOpts.IncludeRelay = false
			withServer(t, noRelayOpts, f)
		}

		if chanOpts.IncludeRelay {
			withServer(t, chanOpts.Copy(), f)
		}
	}
}

// SetVerifyOpts specifies the options we'll use during teardown to verify that
// no goroutines were leaked.
func (ts *TestServer) SetVerifyOpts(opts *goroutines.VerifyOpts) {
	ts.verifyOpts = opts
}

// Server returns the underlying TChannel for the server (i.e., the channel on
// which we're registering handlers).
//
// To support test cases with relays interposed between clients and servers,
// callers should use the Client(), HostPort(), ServiceName(), and Register()
// methods instead of accessing the server channel explicitly.
func (ts *TestServer) Server() *tchannel.Channel {
	return ts.channels[0]
}

// Relay returns the relay channel, if one is present.
func (ts *TestServer) Relay() *tchannel.Channel {
	if ts.HasRelay() {
		return ts.channels[ts.relayIdx]
	}
	return nil
}

// HostPort returns the host:port for clients to connect to. Note that this may
// not be the same as the host:port of the server channel.
func (ts *TestServer) HostPort() string {
	if ts.HasRelay() {
		return ts.Relay().PeerInfo().HostPort
	}
	return ts.Server().PeerInfo().HostPort
}

// ServiceName returns the service name of the server channel.
func (ts *TestServer) ServiceName() string {
	return ts.Server().PeerInfo().ServiceName
}

// Register registers a handler on the server channel.
func (ts *TestServer) Register(h tchannel.Handler, methodName string) {
	ts.Server().Register(h, methodName)
}

// RegisterFunc registers a function as a handler for the given method name.
//
// TODO: Delete testutils.RegisterFunc in favor of this test server.
func (ts *TestServer) RegisterFunc(name string, f func(context.Context, *raw.Args) (*raw.Res, error)) {
	ts.Register(raw.Wrap(rawFuncHandler{ts.Server(), f}), name)
}

// CloseAndVerify closes all channels verifying each channel as it is closed.
// It then verifies that no goroutines were leaked.
func (ts *TestServer) CloseAndVerify() {
	for i := len(ts.channels) - 1; i >= 0; i-- {
		ch := ts.channels[i]
		ch.Logger().Debugf("TEST: TestServer is closing and verifying channel")
		ts.close(ch)
		ts.verify(ch)
	}
}

// NewClient returns a client that with log verification.
// TODO: Verify message exchanges and leaks for client channels as well.
func (ts *TestServer) NewClient(opts *ChannelOpts) *tchannel.Channel {
	return ts.addChannel(newClient, opts.Copy())
}

// NewServer returns a server with log and channel state verification.
func (ts *TestServer) NewServer(opts *ChannelOpts) *tchannel.Channel {
	ch := ts.addChannel(newServer, opts.Copy())
	if ts.HasRelay() {
		ts.relayHosts.Add(ch.ServiceName(), ch.PeerInfo().HostPort)
	}
	return ch
}

// addRelay adds a relay in front of the test server, altering public methods as
// necessary to route traffic through the relay.
func (ts *TestServer) addRelay(logOpts LogVerification) {
	ts.relayHosts = NewSimpleRelayHosts(map[string][]string{
		ts.Server().ServiceName(): []string{ts.Server().PeerInfo().HostPort},
	})
	opts := &ChannelOpts{
		ServiceName:     "relay",
		ChannelOptions:  tchannel.ChannelOptions{RelayHosts: ts.relayHosts},
		LogVerification: logOpts,
	}
	ts.addChannel(NewServer, opts)
	ts.relayIdx = len(ts.channels) - 1
}

// HasRelay indicates whether this TestServer has a relay interposed between the
// server and clients.
func (ts *TestServer) HasRelay() bool {
	return ts.relayIdx > 0
}

func (ts *TestServer) addChannel(createChannel func(t testing.TB, opts *ChannelOpts) *tchannel.Channel, opts *ChannelOpts) *tchannel.Channel {
	ch := createChannel(ts, opts)
	ts.postFns = append(ts.postFns, opts.postFns...)
	ts.channels = append(ts.channels, ch)
	ts.channelStates[ch] = comparableState(ch, ts.introspectOpts)
	return ch
}

// close closes all channels in most-recently-created order.
// it waits for the channels to close.
func (ts *TestServer) close(ch *tchannel.Channel) {
	ch.Close()
	ts.waitForChannelClose(ch)
}

func (ts *TestServer) verify(ch *tchannel.Channel) {
	// For the main server channel, we want to ensure there's no goroutine leaks
	// which will wait for all runnable goroutines. We cannot verify goroutines
	// for all channels, as it would detect goroutines in the outer channels.
	if ch == ts.channels[0] {
		ts.verifyNoGoroutinesLeaked()
	}

	ts.verifyRelaysEmpty(ch)
	ts.verifyExchangesCleared(ch)
}

func (ts *TestServer) post() {
	if !ts.Failed() {
		for _, ch := range ts.channels {
			ts.verifyNoStateLeak(ch)
		}
	}
	for _, fn := range ts.postFns {
		fn()
	}
}

func (ts *TestServer) waitForChannelClose(ch *tchannel.Channel) {
	if ts.Failed() {
		return
	}
	started := time.Now()

	var state tchannel.ChannelState
	for i := 0; i < 50; i++ {
		if state = ch.State(); state == tchannel.ChannelClosed {
			return
		}

		runtime.Gosched()
		if i < 5 {
			continue
		}

		sleepFor := time.Duration(i) * 100 * time.Microsecond
		time.Sleep(Timeout(sleepFor))
	}

	// Channel is not closing, fail the test.
	sinceStart := time.Since(started)
	ts.Errorf("Channel did not close after %v, last state: %v", sinceStart, state)
}

func (ts *TestServer) verifyNoStateLeak(ch *tchannel.Channel) {
	initial := ts.channelStates[ch]
	final := comparableState(ch, ts.introspectOpts)
	assert.Equal(ts.TB, initial, final, "Runtime state has leaks")
}

func (ts *TestServer) verifyExchangesCleared(ch *tchannel.Channel) {
	if ts.Failed() {
		return
	}
	// Ensure that all the message exchanges are empty.
	serverState := ch.IntrospectState(ts.introspectOpts)
	if exchangesLeft := describeLeakedExchanges(serverState); exchangesLeft != "" {
		ts.Errorf("Found uncleared message exchanges on server:\n%v", exchangesLeft)
	}
}

func (ts *TestServer) verifyRelaysEmpty(ch *tchannel.Channel) {
	if ts.Failed() {
		return
	}
	var foundErrors bool
	state := ch.IntrospectState(ts.introspectOpts)
	for _, peerState := range state.RootPeers {
		var connStates []tchannel.ConnectionRuntimeState
		connStates = append(connStates, peerState.InboundConnections...)
		connStates = append(connStates, peerState.OutboundConnections...)
		for _, connState := range connStates {
			n := connState.Relayer.Count
			if assert.Equal(ts, 0, n, "Found %v left-over items in relayer for %v.", n, connState.LocalHostPort) {
				continue
			}
			foundErrors = true
		}
	}

	if !foundErrors {
		return
	}

	marshalled, err := json.MarshalIndent(state, "", "  ")
	require.NoError(ts, err, "Failed to marshal relayer state")
	// Print out all the exchanges we found.
	ts.Logf("Relayer state:\n%s", marshalled)
}

func (ts *TestServer) verifyNoGoroutinesLeaked() {
	if _leakedGoroutine.Load() == 1 {
		ts.Log("Skipping check for leaked goroutines because of a previous leak.")
		return
	}
	err := goroutines.IdentifyLeaks(ts.verifyOpts)
	if err == nil {
		// No leaks, nothing to do.
		return
	}
	if isFirstLeak := _leakedGoroutine.CAS(0, 1); !isFirstLeak {
		ts.Log("Skipping check for leaked goroutines because of a previous leak.")
		return
	}
	if ts.Failed() {
		// If we've already failed this test, don't pollute the test output with
		// more failures.
		return
	}
	ts.Error(err.Error())
}

func comparableState(ch *tchannel.Channel, opts *tchannel.IntrospectionOptions) *tchannel.RuntimeState {
	s := ch.IntrospectState(opts)
	s.OtherChannels = nil
	s.SubChannels = nil
	s.Peers = nil
	return s
}

func describeLeakedExchanges(rs *tchannel.RuntimeState) string {
	var connections []*tchannel.ConnectionRuntimeState
	for _, peer := range rs.RootPeers {
		for _, conn := range peer.InboundConnections {
			connections = append(connections, &conn)
		}
		for _, conn := range peer.OutboundConnections {
			connections = append(connections, &conn)
		}
	}
	return describeLeakedExchangesConns(connections)
}

func describeLeakedExchangesConns(connections []*tchannel.ConnectionRuntimeState) string {
	var exchanges []string
	for _, c := range connections {
		if exch := describeLeakedExchangesSingleConn(c); exch != "" {
			exchanges = append(exchanges, exch)
		}
	}
	return strings.Join(exchanges, "\n")
}

func describeLeakedExchangesSingleConn(cs *tchannel.ConnectionRuntimeState) string {
	var exchanges []string
	checkExchange := func(e tchannel.ExchangeSetRuntimeState) {
		if e.Count > 0 {
			exchanges = append(exchanges, fmt.Sprintf(" %v leftover %v exchanges", e.Name, e.Count))
			for _, v := range e.Exchanges {
				exchanges = append(exchanges, fmt.Sprintf("  exchanges: %+v", v))
			}
		}
	}
	checkExchange(cs.InboundExchange)
	checkExchange(cs.OutboundExchange)
	if len(exchanges) == 0 {
		return ""
	}

	return fmt.Sprintf("Connection %d has leftover exchanges:\n\t%v", cs.ID, strings.Join(exchanges, "\n\t"))
}

func withServer(t testing.TB, chanOpts *ChannelOpts, f func(*TestServer)) {
	ts := NewTestServer(t, chanOpts)
	// Note: We use defer, as we want the postFns to run even if the test
	// goroutine exits (e.g. user calls t.Fatalf).
	defer ts.post()

	f(ts)
	ts.Server().Logger().Debugf("TEST: Test function complete")
	ts.CloseAndVerify()
}
<commit_msg>testutils: Fix for relay logs not showing up<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package testutils

import (
	"encoding/json"
	"fmt"
	"runtime"
	"strings"
	"testing"
	"time"

	"github.com/uber/tchannel-go"
	"github.com/uber/tchannel-go/atomic"
	"github.com/uber/tchannel-go/raw"
	"github.com/uber/tchannel-go/testutils/goroutines"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"golang.org/x/net/context"
)

// Has a previous test already leaked a goroutine?
var _leakedGoroutine = atomic.NewInt32(0)

// A TestServer encapsulates a TChannel server, a client factory, and functions
// to ensure that we're not leaking resources.
type TestServer struct {
	testing.TB

	// relayIdx is the index of the relay channel, if any, in the channels slice.
	relayIdx int

	// relayHosts is the relayer's SimpleRelayHosts (if any).
	relayHosts *SimpleRelayHosts

	// channels is the list of channels created for this TestServer. The first
	// element is always the initial server.
	channels []*tchannel.Channel

	// channelState the initial runtime state for all channels created
	// as part of the TestServer (including the server).
	channelStates map[*tchannel.Channel]*tchannel.RuntimeState

	introspectOpts *tchannel.IntrospectionOptions
	verifyOpts     *goroutines.VerifyOpts
	postFns        []func()
}

// NewTestServer constructs a TestServer.
func NewTestServer(t testing.TB, opts *ChannelOpts) *TestServer {
	ts := &TestServer{
		TB:            t,
		channelStates: make(map[*tchannel.Channel]*tchannel.RuntimeState),
		introspectOpts: &tchannel.IntrospectionOptions{
			IncludeExchanges:  true,
			IncludeTombstones: true,
		},
	}

	ts.NewServer(opts)
	if opts != nil && opts.IncludeRelay {
		ts.addRelay(opts.LogVerification)
	}

	return ts
}

// WithTestServer creates a new TestServer, runs the passed function, and then
// verifies that no resources were leaked.
func WithTestServer(t testing.TB, chanOpts *ChannelOpts, f func(*TestServer)) {
	chanOpts = chanOpts.Copy()
	runCount := chanOpts.RunCount
	if runCount < 1 {
		runCount = 1
	}

	for i := 0; i < runCount; i++ {
		if t.Failed() {
			return
		}

		if !chanOpts.OnlyRelay {
			noRelayOpts := chanOpts.Copy()
			noRelayOpts.IncludeRelay = false
			withServer(t, noRelayOpts, f)
		}

		if chanOpts.IncludeRelay {
			withServer(t, chanOpts.Copy(), f)
		}
	}
}

// SetVerifyOpts specifies the options we'll use during teardown to verify that
// no goroutines were leaked.
func (ts *TestServer) SetVerifyOpts(opts *goroutines.VerifyOpts) {
	ts.verifyOpts = opts
}

// Server returns the underlying TChannel for the server (i.e., the channel on
// which we're registering handlers).
//
// To support test cases with relays interposed between clients and servers,
// callers should use the Client(), HostPort(), ServiceName(), and Register()
// methods instead of accessing the server channel explicitly.
func (ts *TestServer) Server() *tchannel.Channel {
	return ts.channels[0]
}

// Relay returns the relay channel, if one is present.
func (ts *TestServer) Relay() *tchannel.Channel {
	if ts.HasRelay() {
		return ts.channels[ts.relayIdx]
	}
	return nil
}

// HostPort returns the host:port for clients to connect to. Note that this may
// not be the same as the host:port of the server channel.
func (ts *TestServer) HostPort() string {
	if ts.HasRelay() {
		return ts.Relay().PeerInfo().HostPort
	}
	return ts.Server().PeerInfo().HostPort
}

// ServiceName returns the service name of the server channel.
func (ts *TestServer) ServiceName() string {
	return ts.Server().PeerInfo().ServiceName
}

// Register registers a handler on the server channel.
func (ts *TestServer) Register(h tchannel.Handler, methodName string) {
	ts.Server().Register(h, methodName)
}

// RegisterFunc registers a function as a handler for the given method name.
//
// TODO: Delete testutils.RegisterFunc in favor of this test server.
func (ts *TestServer) RegisterFunc(name string, f func(context.Context, *raw.Args) (*raw.Res, error)) {
	ts.Register(raw.Wrap(rawFuncHandler{ts.Server(), f}), name)
}

// CloseAndVerify closes all channels verifying each channel as it is closed.
// It then verifies that no goroutines were leaked.
func (ts *TestServer) CloseAndVerify() {
	for i := len(ts.channels) - 1; i >= 0; i-- {
		ch := ts.channels[i]
		ch.Logger().Debugf("TEST: TestServer is closing and verifying channel")
		ts.close(ch)
		ts.verify(ch)
	}
}

// NewClient returns a client that with log verification.
// TODO: Verify message exchanges and leaks for client channels as well.
func (ts *TestServer) NewClient(opts *ChannelOpts) *tchannel.Channel {
	return ts.addChannel(newClient, opts.Copy())
}

// NewServer returns a server with log and channel state verification.
func (ts *TestServer) NewServer(opts *ChannelOpts) *tchannel.Channel {
	ch := ts.addChannel(newServer, opts.Copy())
	if ts.HasRelay() {
		ts.relayHosts.Add(ch.ServiceName(), ch.PeerInfo().HostPort)
	}
	return ch
}

// addRelay adds a relay in front of the test server, altering public methods as
// necessary to route traffic through the relay.
func (ts *TestServer) addRelay(logOpts LogVerification) {
	ts.relayHosts = NewSimpleRelayHosts(map[string][]string{
		ts.Server().ServiceName(): []string{ts.Server().PeerInfo().HostPort},
	})
	opts := &ChannelOpts{
		ServiceName:     "relay",
		ChannelOptions:  tchannel.ChannelOptions{RelayHosts: ts.relayHosts},
		LogVerification: logOpts,
	}
	ts.addChannel(newServer, opts)
	ts.relayIdx = len(ts.channels) - 1
}

// HasRelay indicates whether this TestServer has a relay interposed between the
// server and clients.
func (ts *TestServer) HasRelay() bool {
	return ts.relayIdx > 0
}

func (ts *TestServer) addChannel(createChannel func(t testing.TB, opts *ChannelOpts) *tchannel.Channel, opts *ChannelOpts) *tchannel.Channel {
	ch := createChannel(ts, opts)
	ts.postFns = append(ts.postFns, opts.postFns...)
	ts.channels = append(ts.channels, ch)
	ts.channelStates[ch] = comparableState(ch, ts.introspectOpts)
	return ch
}

// close closes all channels in most-recently-created order.
// it waits for the channels to close.
func (ts *TestServer) close(ch *tchannel.Channel) {
	ch.Close()
	ts.waitForChannelClose(ch)
}

func (ts *TestServer) verify(ch *tchannel.Channel) {
	// For the main server channel, we want to ensure there's no goroutine leaks
	// which will wait for all runnable goroutines. We cannot verify goroutines
	// for all channels, as it would detect goroutines in the outer channels.
	if ch == ts.channels[0] {
		ts.verifyNoGoroutinesLeaked()
	}

	ts.verifyRelaysEmpty(ch)
	ts.verifyExchangesCleared(ch)
}

func (ts *TestServer) post() {
	if !ts.Failed() {
		for _, ch := range ts.channels {
			ts.verifyNoStateLeak(ch)
		}
	}
	for _, fn := range ts.postFns {
		fn()
	}
}

func (ts *TestServer) waitForChannelClose(ch *tchannel.Channel) {
	if ts.Failed() {
		return
	}
	started := time.Now()

	var state tchannel.ChannelState
	for i := 0; i < 50; i++ {
		if state = ch.State(); state == tchannel.ChannelClosed {
			return
		}

		runtime.Gosched()
		if i < 5 {
			continue
		}

		sleepFor := time.Duration(i) * 100 * time.Microsecond
		time.Sleep(Timeout(sleepFor))
	}

	// Channel is not closing, fail the test.
	sinceStart := time.Since(started)
	ts.Errorf("Channel %p did not close after %v, last state: %v", ch, sinceStart, state)
}

func (ts *TestServer) verifyNoStateLeak(ch *tchannel.Channel) {
	initial := ts.channelStates[ch]
	final := comparableState(ch, ts.introspectOpts)
	assert.Equal(ts.TB, initial, final, "Runtime state has leaks")
}

func (ts *TestServer) verifyExchangesCleared(ch *tchannel.Channel) {
	if ts.Failed() {
		return
	}
	// Ensure that all the message exchanges are empty.
	serverState := ch.IntrospectState(ts.introspectOpts)
	if exchangesLeft := describeLeakedExchanges(serverState); exchangesLeft != "" {
		ts.Errorf("Found uncleared message exchanges on server:\n%v", exchangesLeft)
	}
}

func (ts *TestServer) verifyRelaysEmpty(ch *tchannel.Channel) {
	if ts.Failed() {
		return
	}
	var foundErrors bool
	state := ch.IntrospectState(ts.introspectOpts)
	for _, peerState := range state.RootPeers {
		var connStates []tchannel.ConnectionRuntimeState
		connStates = append(connStates, peerState.InboundConnections...)
		connStates = append(connStates, peerState.OutboundConnections...)
		for _, connState := range connStates {
			n := connState.Relayer.Count
			if assert.Equal(ts, 0, n, "Found %v left-over items in relayer for %v.", n, connState.LocalHostPort) {
				continue
			}
			foundErrors = true
		}
	}

	if !foundErrors {
		return
	}

	marshalled, err := json.MarshalIndent(state, "", "  ")
	require.NoError(ts, err, "Failed to marshal relayer state")
	// Print out all the exchanges we found.
	ts.Logf("Relayer state:\n%s", marshalled)
}

func (ts *TestServer) verifyNoGoroutinesLeaked() {
	if _leakedGoroutine.Load() == 1 {
		ts.Log("Skipping check for leaked goroutines because of a previous leak.")
		return
	}
	err := goroutines.IdentifyLeaks(ts.verifyOpts)
	if err == nil {
		// No leaks, nothing to do.
		return
	}
	if isFirstLeak := _leakedGoroutine.CAS(0, 1); !isFirstLeak {
		ts.Log("Skipping check for leaked goroutines because of a previous leak.")
		return
	}
	if ts.Failed() {
		// If we've already failed this test, don't pollute the test output with
		// more failures.
		return
	}
	ts.Error(err.Error())
}

func comparableState(ch *tchannel.Channel, opts *tchannel.IntrospectionOptions) *tchannel.RuntimeState {
	s := ch.IntrospectState(opts)
	s.OtherChannels = nil
	s.SubChannels = nil
	s.Peers = nil
	return s
}

func describeLeakedExchanges(rs *tchannel.RuntimeState) string {
	var connections []*tchannel.ConnectionRuntimeState
	for _, peer := range rs.RootPeers {
		for _, conn := range peer.InboundConnections {
			connections = append(connections, &conn)
		}
		for _, conn := range peer.OutboundConnections {
			connections = append(connections, &conn)
		}
	}
	return describeLeakedExchangesConns(connections)
}

func describeLeakedExchangesConns(connections []*tchannel.ConnectionRuntimeState) string {
	var exchanges []string
	for _, c := range connections {
		if exch := describeLeakedExchangesSingleConn(c); exch != "" {
			exchanges = append(exchanges, exch)
		}
	}
	return strings.Join(exchanges, "\n")
}

func describeLeakedExchangesSingleConn(cs *tchannel.ConnectionRuntimeState) string {
	var exchanges []string
	checkExchange := func(e tchannel.ExchangeSetRuntimeState) {
		if e.Count > 0 {
			exchanges = append(exchanges, fmt.Sprintf(" %v leftover %v exchanges", e.Name, e.Count))
			for _, v := range e.Exchanges {
				exchanges = append(exchanges, fmt.Sprintf("  exchanges: %+v", v))
			}
		}
	}
	checkExchange(cs.InboundExchange)
	checkExchange(cs.OutboundExchange)
	if len(exchanges) == 0 {
		return ""
	}

	return fmt.Sprintf("Connection %d has leftover exchanges:\n\t%v", cs.ID, strings.Join(exchanges, "\n\t"))
}

func withServer(t testing.TB, chanOpts *ChannelOpts, f func(*TestServer)) {
	ts := NewTestServer(t, chanOpts)
	// Note: We use defer, as we want the postFns to run even if the test
	// goroutine exits (e.g. user calls t.Fatalf).
	defer ts.post()

	f(ts)
	ts.Server().Logger().Debugf("TEST: Test function complete")
	ts.CloseAndVerify()
}
<|endoftext|>


<commit_before>package victorops

// Schedule current schedule
type Schedule struct {
	Oncall         string     `json:"oncall"`
	OverrideOncall string     `json:"overrideoncall"`
	PolicyType     string     `json:"policyType"`
	RotationName   string     `json:"rotationName"`
	ShiftName      string     `json:"shiftName"`
	ShiftRoll      int        `json:"shiftRoll"`
	Rolls          []Roll     `json:"rolls"`
	Overrides      []Override `json:"overrides"`
}

// Roll is shcedule roll
type Roll struct {
	Change int    `json:"change"`
	Until  int    `json:"until"`
	Oncall string `json:"oncall"`
	IsRoll bool   `json:"isroll"`
}

// Override describes oncall overrides
type Override struct {
	Orig  string `json:"orig"`
	Over  string `json:"over"`
	Start int    `json:"start"`
	End   int    `json:"end"`
}

// TeamSchedule is api response struct
type TeamSchedule struct {
	Team      string     `json:"team"`
	Schedules []Schedule `json:"schedule"`
}

// ResponseResult is result of an api request
type ResponseResult struct {
	Result string `json:"result"`
}

//UserOncallSchedule returns the on-call schedule for a user for all teams, including on-call overrides
func (client *Client) UserOncallSchedule(nick string) ([]TeamSchedule, error) {
	var schd []TeamSchedule
	return schd, nil
}

// TeamOncallSchedule Get the on-call schedule for a user for all teams, including on-call overrides
func (client *Client) TeamOncallSchedule(team string) (TeamSchedule, error) {
	return TeamSchedule{}, nil
}

// CreateOncallOverride replaces a currently on-call user on the team with another
func (client *Client) CreateOncallOverride(fromUser, toUser, team string) (ResponseResult, error) {
	return ResponseResult{}, nil
}
<commit_msg>implement UserOncallSchedule method<commit_after>package victorops

// Schedule current schedule
type Schedule struct {
	Oncall         string `json:"oncall,omitempty"`
	OverrideOncall string `json:"overrideoncall,omitempty"`
	PolicyType     string `json:"policyType,omitempty"`
	RotationName   string `json:"rotationName,omitempty"`
	ShiftName      string `json:"shiftName,omitempty"`
	ShiftRoll      int    `json:"shiftRoll,omitempty"`
	Rolls          []Roll `json:"rolls,omitempty"`
}

// Roll is shcedule roll
type Roll struct {
	Change int    `json:"change,omitempty"`
	Until  int    `json:"until,omitempty"`
	Oncall string `json:"oncall,omitempty"`
	IsRoll bool   `json:"isroll,omitempty"`
}

// Override describes oncall overrides
type Override struct {
	Orig  string `json:"orig,omitempty"`
	Over  string `json:"over,omitempty"`
	Start int    `json:"start,omitempty"`
	End   int    `json:"end,omitempty"`
}

// TeamSchedule is api response struct
type TeamSchedule struct {
	Team      string     `json:"team"`
	Schedules []Schedule `json:"schedule"`
	Overrides []Override `json:"overrides"`
}

// ResponseResult is result of an api request
type ResponseResult struct {
	Result string `json:"result"`
}

//UserOncallSchedule returns the on-call schedule for a user for all teams, including on-call overrides
func (client *Client) UserOncallSchedule(nick string) ([]TeamSchedule, error) {
	var schd []TeamSchedule
	//response := &schd
	err := client.sendRequest("GET", "api-public/v1/user/"+nick+"/oncall/schedule", nil, &schd)
	if err != nil {
		return schd, err
	}
	return schd, nil
}

// TeamOncallSchedule Get the on-call schedule for a user for all teams, including on-call overrides
func (client *Client) TeamOncallSchedule(team string) (TeamSchedule, error) {
	return TeamSchedule{}, nil
}

// CreateOncallOverride replaces a currently on-call user on the team with another
func (client *Client) CreateOncallOverride(fromUser, toUser, team string) (ResponseResult, error) {
	return ResponseResult{}, nil
}
<|endoftext|>


<commit_before>// Package sanitize provides functions for sanitizing text.
package sanitize

import (
	"bytes"
	"html"
	"html/template"
	"io"
	"path"
	"regexp"
	"strings"

	parser "golang.org/x/net/html"
)

// Sanitize utf8 html, allowing some tags
// Usage: sanitize.HTMLAllowing("my html",[]string{"b"},[]string{"id"})
func HTMLAllowing(s string, args ...[]string) (string, error) {
	var IGNORE_TAGS = []string{"title", "script", "style", "iframe", "frame", "frameset", "noframes", "noembed", "embed", "applet", "object", "base"}
	var DEFAULT_TAGS = []string{"h1", "h2", "h3", "h4", "h5", "h6", "div", "span", "hr", "p", "br", "b", "i", "ol", "ul", "li", "a", "img"}
	var DEFAULT_ATTR = []string{"id", "class", "src", "href", "title", "alt", "name", "rel"}

	allowedTags := DEFAULT_TAGS
	if len(args) > 0 {
		allowedTags = args[0]
	}
	allowedAttributes := DEFAULT_ATTR
	if len(args) > 1 {
		allowedAttributes = args[1]
	}

	// Parse the html
	tokenizer := parser.NewTokenizer(strings.NewReader(s))

	buffer := bytes.NewBufferString("")
	ignore := ""

	for {
		tokenType := tokenizer.Next()
		token := tokenizer.Token()

		switch tokenType {

		case parser.ErrorToken:
			err := tokenizer.Err()
			if err == io.EOF {
				return buffer.String(), nil
			} else {
				return "", err
			}
		case parser.StartTagToken:

			if len(ignore) == 0 && includes(allowedTags, token.Data) {
				token.Attr = cleanAttributes(token.Attr, allowedAttributes)
				buffer.WriteString(token.String())
			} else if includes(IGNORE_TAGS, token.Data) {
				ignore = token.Data
			}

		case parser.SelfClosingTagToken:

			if len(ignore) == 0 && includes(allowedTags, token.Data) {
				token.Attr = cleanAttributes(token.Attr, allowedAttributes)
				buffer.WriteString(token.String())
			} else if token.Data == ignore {
				ignore = ""
			}

		case parser.EndTagToken:
			if len(ignore) == 0 && includes(allowedTags, token.Data) {
				token.Attr = []parser.Attribute{}
				buffer.WriteString(token.String())
			} else if token.Data == ignore {
				ignore = ""
			}

		case parser.TextToken:
			// We allow text content through, unless ignoring this entire tag and its contents (including other tags)
			if ignore == "" {
				buffer.WriteString(token.String())
			}
		case parser.CommentToken:
			// We ignore comments by default
		case parser.DoctypeToken:
			// We ignore doctypes by default - html5 does not require them and this is intended for sanitizing snippets of text
		default:
			// We ignore unknown token types by default

		}

	}

}

// Strip html tags, replace common entities, and escape <>&;'" in the result.
// Note the returned text may contain entities as it is escaped by HTMLEscapeString, and most entities are not translated.
func HTML(s string) (output string) {

	output = ""

	// Shortcut strings with no tags in them
	if !strings.ContainsAny(s, "<>") {
		output = s
	} else {

		// First remove line breaks etc as these have no meaning outside html tags (except pre)
		// this means pre sections will lose formatting... but will result in less uninentional paras.
		s = strings.Replace(s, "\n", "", -1)

		// Then replace line breaks with newlines, to preserve that formatting
		s = strings.Replace(s, "
", "\n", -1)
		s = strings.Replace(s, "
", "\n", -1)
		s = strings.Replace(s, "
", "\n", -1)
		s = strings.Replace(s, "
", "\n", -1)

		// Walk through the string removing all tags
		b := bytes.NewBufferString("")
		inTag := false
		for _, r := range s {
			switch r {
			case '<':
				inTag = true
			case '>':
				inTag = false
			default:
				if !inTag {
					b.WriteRune(r)
				}
			}
		}
		output = b.String()
	}

	// Remove a few common harmless entities, to arrive at something more like plain text
	output = strings.Replace(output, "‘", "'", -1)
	output = strings.Replace(output, "’", "'", -1)
	output = strings.Replace(output, "“", "\"", -1)
	output = strings.Replace(output, "”", "\"", -1)
	output = strings.Replace(output, " ", " ", -1)
	output = strings.Replace(output, """, "\"", -1)
	output = strings.Replace(output, "'", "'", -1)

	// Translate some entities into their plain text equivalent (for example accents, if encoded as entities)
	output = html.UnescapeString(output)

	// In case we have missed any tags above, escape the text - removes <, >, &, ' and ".
	output = template.HTMLEscapeString(output)

	// After processing, remove some harmless entities &, ' and " which are encoded by HTMLEscapeString
	output = strings.Replace(output, """, "\"", -1)
	output = strings.Replace(output, "'", "'", -1)
	output = strings.Replace(output, "& ", "& ", -1)     // NB space after
	output = strings.Replace(output, "&amp; ", "& ", -1) // NB space after

	return output
}

// Makes a string safe to use as an url path, cleaned of .. and unsuitable characters
func Path(text string) string {
	// Start with lowercase string
	fileName := strings.ToLower(text)
	fileName = strings.Replace(fileName, "..", "", -1)
	fileName = path.Clean(fileName)
	fileName = strings.Trim(fileName, " ")

	// Replace certain joining characters with a dash
	seps, err := regexp.Compile(`[ &_=+:]`)
	if err == nil {
		fileName = seps.ReplaceAllString(fileName, "-")
	}

	// Flatten accents first
	fileName = Accents(fileName)

	// Remove all other unrecognised characters
	// we are very restrictive as this is intended for ascii url slugs
	legal, err := regexp.Compile(`[^\w\_\~\-\./]`)
	if err == nil {
		fileName = legal.ReplaceAllString(fileName, "")
	}

	// Remove any double dashes caused by existing - in name
	fileName = strings.Replace(fileName, "--", "-", -1)

	// NB this may be of length 0, caller must check
	return fileName
}

// Makes a string safe to use in a file name (e.g. for saving file atttachments)
func Name(text string) string {
	// Start with lowercase string
	fileName := strings.ToLower(text)
	fileName = path.Clean(path.Base(fileName))
	fileName = strings.Trim(fileName, " ")

	// Replace certain joining characters with a dash
	seps, err := regexp.Compile(`[ &_=+:]`)
	if err == nil {
		fileName = seps.ReplaceAllString(fileName, "-")
	}

	// Remove all other unrecognised characters - NB we do allow any printable characters
	legal, err := regexp.Compile(`[^[:alnum:]-.]`)
	if err == nil {
		fileName = legal.ReplaceAllString(fileName, "")
	}

	// Remove any double dashes caused by existing - in name
	fileName = strings.Replace(fileName, "--", "-", -1)

	// NB this may be of length 0, caller must check
	return fileName
}

// Replace a set of accented characters with ascii equivalents.
func Accents(text string) string {
	// Replace some common accent characters
	b := bytes.NewBufferString("")
	for _, c := range text {
		// Check transliterations first
		if val, ok := transliterations[c]; ok {
			b.WriteString(val)
		} else {
			b.WriteRune(c)
		}
	}
	return b.String()
}

// A very limited list of transliterations to catch common european names translated to urls.
// This set could be expanded with at least caps and many more characters.
var transliterations = map[rune]string{
	'À': "A",
	'Á': "A",
	'Â': "A",
	'Ã': "A",
	'Ä': "A",
	'Å': "AA",
	'Æ': "AE",
	'Ç': "C",
	'È': "E",
	'É': "E",
	'Ê': "E",
	'Ë': "E",
	'Ì': "I",
	'Í': "I",
	'Î': "I",
	'Ï': "I",
	'Ð': "D",
	'Ł': "L",
	'Ñ': "N",
	'Ò': "O",
	'Ó': "O",
	'Ô': "O",
	'Õ': "O",
	'Ö': "O",
	'Ø': "OE",
	'Ù': "U",
	'Ú': "U",
	'Ü': "U",
	'Û': "U",
	'Ý': "Y",
	'Þ': "Th",
	'ß': "ss",
	'à': "a",
	'á': "a",
	'â': "a",
	'ã': "a",
	'ä': "a",
	'å': "aa",
	'æ': "ae",
	'ç': "c",
	'è': "e",
	'é': "e",
	'ê': "e",
	'ë': "e",
	'ì': "i",
	'í': "i",
	'î': "i",
	'ï': "i",
	'ð': "d",
	'ł': "l",
	'ñ': "n",
	'ń': "n",
	'ò': "o",
	'ó': "o",
	'ô': "o",
	'õ': "o",
	'ō': "o",
	'ö': "o",
	'ø': "oe",
	'ś': "s",
	'ù': "u",
	'ú': "u",
	'û': "u",
	'ū': "u",
	'ü': "u",
	'ý': "y",
	'þ': "th",
	'ÿ': "y",
	'ż': "z",
	'Œ': "OE",
	'œ': "oe",
}

func includes(a []string, s string) bool {
	for _, as := range a {
		if as == s {
			return true
		}
	}
	return false
}

func cleanAttributes(a []parser.Attribute, allowed []string) []parser.Attribute {
	if len(a) == 0 {
		return a
	}

	cleaned := make([]parser.Attribute, 0)
	for _, attr := range a {
		if includes(allowed, attr.Key) {

			// If the attribute contains data: or javascript: anywhere, ignore it
			// we don't allow this in attributes as it is so frequently used for xss
			// NB we allow spaces in the value, and lowercase
			re := regexp.MustCompile(`(d\s*a\s*t\s*a|j\s*a\s*v\s*a\s*s\s*c\s*r\s*i\s*p\s*t\s*)\s*:`)
			val := strings.ToLower(attr.Val)
			if re.FindString(val) != "" {
				attr.Val = ""
			}

			// We are far more restrictive with href attributes
			// The url may start with /, mailto://, http:// or https://
			if attr.Key == "href" {
				urlre := regexp.MustCompile(`\A/[^/\\]?|mailto://|http://|https://`)
				if urlre.FindString(strings.ToLower(attr.Val)) == "" {
					attr.Val = ""
				}
			}

			if attr.Val != "" {
				cleaned = append(cleaned, attr)
			}
		}
	}
	return cleaned
}
<commit_msg>making code and comments confirm to GoLang's RFC spec (linted it to match)<commit_after>// Package sanitize provides functions for sanitizing text.
package sanitize

import (
	"bytes"
	"html"
	"html/template"
	"io"
	"path"
	"regexp"
	"strings"

	parser "golang.org/x/net/html"
)

// HTMLAllowing sanitizes utf8 html, allowing some tags
// Usage: sanitize.HTMLAllowing("my html",[]string{"b"},[]string{"id"})
func HTMLAllowing(s string, args ...[]string) (string, error) {
	var IGNORE_TAGS = []string{"title", "script", "style", "iframe", "frame", "frameset", "noframes", "noembed", "embed", "applet", "object", "base"}
	var DEFAULT_TAGS = []string{"h1", "h2", "h3", "h4", "h5", "h6", "div", "span", "hr", "p", "br", "b", "i", "ol", "ul", "li", "a", "img"}
	var DEFAULT_ATTR = []string{"id", "class", "src", "href", "title", "alt", "name", "rel"}

	allowedTags := DEFAULT_TAGS
	if len(args) > 0 {
		allowedTags = args[0]
	}
	allowedAttributes := DEFAULT_ATTR
	if len(args) > 1 {
		allowedAttributes = args[1]
	}

	// Parse the html
	tokenizer := parser.NewTokenizer(strings.NewReader(s))

	buffer := bytes.NewBufferString("")
	ignore := ""

	for {
		tokenType := tokenizer.Next()
		token := tokenizer.Token()

		switch tokenType {

		case parser.ErrorToken:
			err := tokenizer.Err()
			if err == io.EOF {
				return buffer.String(), nil
			} else {
				return "", err
			}
		case parser.StartTagToken:

			if len(ignore) == 0 && includes(allowedTags, token.Data) {
				token.Attr = cleanAttributes(token.Attr, allowedAttributes)
				buffer.WriteString(token.String())
			} else if includes(IGNORE_TAGS, token.Data) {
				ignore = token.Data
			}

		case parser.SelfClosingTagToken:

			if len(ignore) == 0 && includes(allowedTags, token.Data) {
				token.Attr = cleanAttributes(token.Attr, allowedAttributes)
				buffer.WriteString(token.String())
			} else if token.Data == ignore {
				ignore = ""
			}

		case parser.EndTagToken:
			if len(ignore) == 0 && includes(allowedTags, token.Data) {
				token.Attr = []parser.Attribute{}
				buffer.WriteString(token.String())
			} else if token.Data == ignore {
				ignore = ""
			}

		case parser.TextToken:
			// We allow text content through, unless ignoring this entire tag and its contents (including other tags)
			if ignore == "" {
				buffer.WriteString(token.String())
			}
		case parser.CommentToken:
			// We ignore comments by default
		case parser.DoctypeToken:
			// We ignore doctypes by default - html5 does not require them and this is intended for sanitizing snippets of text
		default:
			// We ignore unknown token types by default

		}

	}

}

// HTML strips html tags, replace common entities, and escape <>&;'" in the result.
// Note the returned text may contain entities as it is escaped by HTMLEscapeString, and most entities are not translated.
func HTML(s string) (output string) {

	output = ""

	// Shortcut strings with no tags in them
	if !strings.ContainsAny(s, "<>") {
		output = s
	} else {

		// First remove line breaks etc as these have no meaning outside html tags (except pre)
		// this means pre sections will lose formatting... but will result in less uninentional paras.
		s = strings.Replace(s, "\n", "", -1)

		// Then replace line breaks with newlines, to preserve that formatting
		s = strings.Replace(s, "
", "\n", -1)
		s = strings.Replace(s, "
", "\n", -1)
		s = strings.Replace(s, "
", "\n", -1)
		s = strings.Replace(s, "
", "\n", -1)

		// Walk through the string removing all tags
		b := bytes.NewBufferString("")
		inTag := false
		for _, r := range s {
			switch r {
			case '<':
				inTag = true
			case '>':
				inTag = false
			default:
				if !inTag {
					b.WriteRune(r)
				}
			}
		}
		output = b.String()
	}

	// Remove a few common harmless entities, to arrive at something more like plain text
	output = strings.Replace(output, "‘", "'", -1)
	output = strings.Replace(output, "’", "'", -1)
	output = strings.Replace(output, "“", "\"", -1)
	output = strings.Replace(output, "”", "\"", -1)
	output = strings.Replace(output, " ", " ", -1)
	output = strings.Replace(output, """, "\"", -1)
	output = strings.Replace(output, "'", "'", -1)

	// Translate some entities into their plain text equivalent (for example accents, if encoded as entities)
	output = html.UnescapeString(output)

	// In case we have missed any tags above, escape the text - removes <, >, &, ' and ".
	output = template.HTMLEscapeString(output)

	// After processing, remove some harmless entities &, ' and " which are encoded by HTMLEscapeString
	output = strings.Replace(output, """, "\"", -1)
	output = strings.Replace(output, "'", "'", -1)
	output = strings.Replace(output, "& ", "& ", -1)     // NB space after
	output = strings.Replace(output, "&amp; ", "& ", -1) // NB space after

	return output
}

// Path makes a string safe to use as an url path, cleaned of .. and unsuitable characters
func Path(text string) string {
	// Start with lowercase string
	fileName := strings.ToLower(text)
	fileName = strings.Replace(fileName, "..", "", -1)
	fileName = path.Clean(fileName)
	fileName = strings.Trim(fileName, " ")

	// Replace certain joining characters with a dash
	seps, err := regexp.Compile(`[ &_=+:]`)
	if err == nil {
		fileName = seps.ReplaceAllString(fileName, "-")
	}

	// Flatten accents first
	fileName = Accents(fileName)

	// Remove all other unrecognised characters
	// we are very restrictive as this is intended for ascii url slugs
	legal, err := regexp.Compile(`[^\w\_\~\-\./]`)
	if err == nil {
		fileName = legal.ReplaceAllString(fileName, "")
	}

	// Remove any double dashes caused by existing - in name
	fileName = strings.Replace(fileName, "--", "-", -1)

	// NB this may be of length 0, caller must check
	return fileName
}

// Name makes a string safe to use in a file name (e.g. for saving file atttachments)
func Name(text string) string {
	// Start with lowercase string
	fileName := strings.ToLower(text)
	fileName = path.Clean(path.Base(fileName))
	fileName = strings.Trim(fileName, " ")

	// Replace certain joining characters with a dash
	seps, err := regexp.Compile(`[ &_=+:]`)
	if err == nil {
		fileName = seps.ReplaceAllString(fileName, "-")
	}

	// Remove all other unrecognised characters - NB we do allow any printable characters
	legal, err := regexp.Compile(`[^[:alnum:]-.]`)
	if err == nil {
		fileName = legal.ReplaceAllString(fileName, "")
	}

	// Remove any double dashes caused by existing - in name
	fileName = strings.Replace(fileName, "--", "-", -1)

	// NB this may be of length 0, caller must check
	return fileName
}

// Accents replace a set of accented characters with ascii equivalents.
func Accents(text string) string {
	// Replace some common accent characters
	b := bytes.NewBufferString("")
	for _, c := range text {
		// Check transliterations first
		if val, ok := transliterations[c]; ok {
			b.WriteString(val)
		} else {
			b.WriteRune(c)
		}
	}
	return b.String()
}

// A very limited list of transliterations to catch common european names translated to urls.
// This set could be expanded with at least caps and many more characters.
var transliterations = map[rune]string{
	'À': "A",
	'Á': "A",
	'Â': "A",
	'Ã': "A",
	'Ä': "A",
	'Å': "AA",
	'Æ': "AE",
	'Ç': "C",
	'È': "E",
	'É': "E",
	'Ê': "E",
	'Ë': "E",
	'Ì': "I",
	'Í': "I",
	'Î': "I",
	'Ï': "I",
	'Ð': "D",
	'Ł': "L",
	'Ñ': "N",
	'Ò': "O",
	'Ó': "O",
	'Ô': "O",
	'Õ': "O",
	'Ö': "O",
	'Ø': "OE",
	'Ù': "U",
	'Ú': "U",
	'Ü': "U",
	'Û': "U",
	'Ý': "Y",
	'Þ': "Th",
	'ß': "ss",
	'à': "a",
	'á': "a",
	'â': "a",
	'ã': "a",
	'ä': "a",
	'å': "aa",
	'æ': "ae",
	'ç': "c",
	'è': "e",
	'é': "e",
	'ê': "e",
	'ë': "e",
	'ì': "i",
	'í': "i",
	'î': "i",
	'ï': "i",
	'ð': "d",
	'ł': "l",
	'ñ': "n",
	'ń': "n",
	'ò': "o",
	'ó': "o",
	'ô': "o",
	'õ': "o",
	'ō': "o",
	'ö': "o",
	'ø': "oe",
	'ś': "s",
	'ù': "u",
	'ú': "u",
	'û': "u",
	'ū': "u",
	'ü': "u",
	'ý': "y",
	'þ': "th",
	'ÿ': "y",
	'ż': "z",
	'Œ': "OE",
	'œ': "oe",
}

func includes(a []string, s string) bool {
	for _, as := range a {
		if as == s {
			return true
		}
	}
	return false
}

func cleanAttributes(a []parser.Attribute, allowed []string) []parser.Attribute {
	if len(a) == 0 {
		return a
	}

	cleaned := make([]parser.Attribute, 0)
	for _, attr := range a {
		if includes(allowed, attr.Key) {

			// If the attribute contains data: or javascript: anywhere, ignore it
			// we don't allow this in attributes as it is so frequently used for xss
			// NB we allow spaces in the value, and lowercase
			re := regexp.MustCompile(`(d\s*a\s*t\s*a|j\s*a\s*v\s*a\s*s\s*c\s*r\s*i\s*p\s*t\s*)\s*:`)
			val := strings.ToLower(attr.Val)
			if re.FindString(val) != "" {
				attr.Val = ""
			}

			// We are far more restrictive with href attributes
			// The url may start with /, mailto://, http:// or https://
			if attr.Key == "href" {
				urlre := regexp.MustCompile(`\A/[^/\\]?|mailto://|http://|https://`)
				if urlre.FindString(strings.ToLower(attr.Val)) == "" {
					attr.Val = ""
				}
			}

			if attr.Val != "" {
				cleaned = append(cleaned, attr)
			}
		}
	}
	return cleaned
}
<|endoftext|>


<commit_before>package main

import (
	twodee "../libs/twodee"
	"fmt"
	"github.com/go-gl/gl"
)

const GLOW_FRAGMENT = `#version 150
precision mediump float;

uniform sampler2D u_TextureUnit;
in vec2 v_TextureCoordinates;
uniform int Orientation;
uniform int BlurAmount;
uniform float BlurScale;
uniform float BlurStrength;
uniform vec2 BufferDimensions;
out vec4 v_FragData;
//const int BlurAmount = 4;
//const float BlurScale = 1.0;
//const float BlurStrength = 0.4;
vec2 TexelSize = vec2(1.0 / BufferDimensions.x, 1.0 / BufferDimensions.y);

float Gaussian (float x, float deviation)
{
  return (1.0 / sqrt(2.0 * 3.141592 * deviation)) * exp(-((x * x) / (2.0 * deviation)));
}

void main()
{
  // Locals
  float halfBlur = float(BlurAmount) * 0.5;
  vec4 colour = vec4(0.0);
  vec4 texColour = vec4(0.0);

  // Gaussian deviation
  float deviation = halfBlur * 0.35;
  deviation *= deviation;
  float strength = 1.0 - BlurStrength;

  if ( Orientation == 0 ) {
    // Horizontal blur
    for (int i = 0; i < 10; ++i) {
      if ( i >= BlurAmount ) {
        break;
      }
      float offset = float(i) - halfBlur;
      texColour = texture(
        u_TextureUnit,
        v_TextureCoordinates + vec2(offset * TexelSize.x * BlurScale, 0.0)) * Gaussian(offset * strength, deviation);
      colour += texColour;
    }
  } else {
    // Vertical blur
    for (int i = 0; i < 10; ++i) {
      if ( i >= BlurAmount ) {
        break;
      }
      float offset = float(i) - halfBlur;
      texColour = texture(
        u_TextureUnit,
        v_TextureCoordinates + vec2(0.0, offset * TexelSize.y * BlurScale)) * Gaussian(offset * strength, deviation);
      colour += texColour;
    }
  }
  // Apply colour
  v_FragData = clamp(colour, 0.0, 1.0);
  v_FragData.w = 1.0;
}`

const GLOW_VERTEX = `#version 150

in vec4 a_Position;
in vec2 a_TextureCoordinates;

out vec2 v_TextureCoordinates;

void main()
{
    v_TextureCoordinates = a_TextureCoordinates;
    gl_Position = a_Position;
}`

type GlowRenderer struct {
	GlowFb              gl.Framebuffer
	GlowTex             gl.Texture
	BlurFb              gl.Framebuffer
	BlurTex             gl.Texture
	shader              gl.Program
	positionLoc         gl.AttribLocation
	textureLoc          gl.AttribLocation
	orientationLoc      gl.UniformLocation
	blurAmountLoc       gl.UniformLocation
	blurScaleLoc        gl.UniformLocation
	blurStrengthLoc     gl.UniformLocation
	bufferDimensionsLoc gl.UniformLocation
	textureUnitLoc      gl.UniformLocation
	coords              gl.Buffer
	width               int
	height              int
	oldwidth            int
	oldheight           int
}

func NewGlowRenderer(w, h int) (r *GlowRenderer, err error) {
	r = &GlowRenderer{
		width:  w,
		height: h,
	}
	_, _, r.oldwidth, r.oldheight = GetInteger4(gl.VIEWPORT)
	if r.shader, err = twodee.BuildProgram(GLOW_VERTEX, GLOW_FRAGMENT); err != nil {
		return
	}
	r.orientationLoc = r.shader.GetUniformLocation("Orientation")
	r.blurAmountLoc = r.shader.GetUniformLocation("BlurAmount")
	r.blurScaleLoc = r.shader.GetUniformLocation("BlurScale")
	r.blurStrengthLoc = r.shader.GetUniformLocation("BlurStrength")
	r.bufferDimensionsLoc = r.shader.GetUniformLocation("BufferDimensions")
	r.positionLoc = r.shader.GetAttribLocation("a_Position")
	r.textureLoc = r.shader.GetAttribLocation("a_TextureCoordinates")
	r.textureUnitLoc = r.shader.GetUniformLocation("u_TextureUnit")
	r.shader.BindFragDataLocation(0, "v_FragData")
	var size float32 = 1.0
	var rect = []float32{
		-size, -size, 0.0, 0, 0,
		-size, size, 0.0, 0, 1,
		size, -size, 0.0, 1, 0,
		size, size, 0.0, 1, 1,
	}
	if r.coords, err = twodee.CreateVBO(len(rect)*4, rect, gl.STATIC_DRAW); err != nil {
		return
	}

	if r.GlowFb, r.GlowTex, err = r.initFramebuffer(w, h); err != nil {
		return
	}
	if r.BlurFb, r.BlurTex, err = r.initFramebuffer(w, h); err != nil {
		return
	}
	return
}

func (r *GlowRenderer) initFramebuffer(w, h int) (fb gl.Framebuffer, tex gl.Texture, err error) {
	fb = gl.GenFramebuffer()
	fb.Bind()

	tex = gl.GenTexture()
	tex.Bind(gl.TEXTURE_2D)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
	gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, nil)

	gl.FramebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0)
	if err = r.GetError(); err != nil {
		return
	}
	gl.DrawBuffers(1, []gl.GLenum{gl.COLOR_ATTACHMENT0})

	rb := gl.GenRenderbuffer()
	rb.Bind()
	gl.RenderbufferStorage(gl.RENDERBUFFER, gl.STENCIL_INDEX8, w, h)
	rb.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER)

	tex.Unbind(gl.TEXTURE_2D)
	fb.Unbind()
	rb.Unbind()
	return
}

func (r *GlowRenderer) GetError() error {
	if e := gl.GetError(); e != 0 {
		return fmt.Errorf("OpenGL error: %X", e)
	}
	var status = gl.CheckFramebufferStatus(gl.DRAW_FRAMEBUFFER)
	switch status {
	case gl.FRAMEBUFFER_COMPLETE:
		return nil
	case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
		return fmt.Errorf("Attachment point unconnected")
	case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
		return fmt.Errorf("Missing attachment")
	case gl.FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
		return fmt.Errorf("Draw buffer")
	case gl.FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
		return fmt.Errorf("Read buffer")
	case gl.FRAMEBUFFER_UNSUPPORTED:
		return fmt.Errorf("Unsupported config")
	default:
		return fmt.Errorf("Unknown framebuffer error: %X", status)
	}
}

func (r *GlowRenderer) Delete() error {
	r.GlowFb.Delete()
	r.BlurFb.Delete()
	return r.GetError()
}

func (r *GlowRenderer) Bind() error {
	r.GlowFb.Bind()
	gl.Enable(gl.STENCIL_TEST)
	gl.Viewport(0, 0, r.width, r.height)
	gl.ClearStencil(0)
	gl.ClearColor(0.0, 0.0, 0.0, 0.0)
	gl.StencilMask(0xFF) // Write to buffer
	gl.Clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
	gl.StencilMask(0x00) // Don't write to buffer

	return nil
}

func (r *GlowRenderer) Draw() (err error) {
	r.shader.Use()
	r.textureUnitLoc.Uniform1i(0)
	r.coords.Bind(gl.ARRAY_BUFFER)
	r.positionLoc.AttribPointer(3, gl.FLOAT, false, 5*4, uintptr(0))
	r.textureLoc.AttribPointer(2, gl.FLOAT, false, 5*4, uintptr(3*4))
	r.positionLoc.EnableArray()
	r.textureLoc.EnableArray()
	r.blurAmountLoc.Uniform1i(6)
	r.blurScaleLoc.Uniform1f(1.0)
	r.blurStrengthLoc.Uniform1f(0.2)
	r.bufferDimensionsLoc.Uniform2f(float32(r.width), float32(r.height))

	r.BlurFb.Bind()
	gl.Viewport(0, 0, r.width, r.height)
	gl.ClearColor(0.0, 0.0, 0.0, 0.0)
	gl.Clear(gl.COLOR_BUFFER_BIT)
	gl.ActiveTexture(gl.TEXTURE0)
	r.GlowTex.Bind(gl.TEXTURE_2D)
	r.orientationLoc.Uniform1i(0)
	gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
	r.BlurFb.Unbind()
	gl.Viewport(0, 0, r.oldwidth, r.oldheight)

	gl.BlendFunc(gl.ONE, gl.ONE)
	gl.ActiveTexture(gl.TEXTURE0)
	r.BlurTex.Bind(gl.TEXTURE_2D)
	r.orientationLoc.Uniform1i(1)
	gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)

	r.coords.Unbind(gl.ARRAY_BUFFER)
	gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
	return nil
}

func (r *GlowRenderer) Unbind() error {
	r.GlowFb.Unbind()
	gl.Viewport(0, 0, r.oldwidth, r.oldheight)
	gl.Disable(gl.STENCIL_TEST)
	return r.GetError()
}

func (r *GlowRenderer) DisableOutput() {
	gl.ColorMask(false, false, false, false)
	gl.DepthMask(false)
	gl.StencilFunc(gl.NEVER, 1, 0xFF)                // Never pass
	gl.StencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE) // Replace to ref=1
	gl.StencilMask(0xFF)                             // Write to buffer
	//gl.Enable(gl.ALPHA_TEST)
	//gl.AlphaFunc(gl.GREATER, 0)
}

func (r *GlowRenderer) EnableOutput() {
	gl.ColorMask(true, true, true, true)
	gl.DepthMask(true)
	gl.StencilMask(0x00)                // No more writing
	gl.StencilFunc(gl.EQUAL, 0, 0xFF) // Only pass where stencil is 0
	//gl.Disable(gl.ALPHA_TEST)
}

// Convenience function for glGetIntegerv
func GetInteger4(pname gl.GLenum) (v0, v1, v2, v3 int) {
	var values = []int32{0, 0, 0, 0}
	gl.GetIntegerv(pname, values)
	v0 = int(values[0])
	v1 = int(values[1])
	v2 = int(values[2])
	v3 = int(values[3])
	return
}
<commit_msg>Speed up the glow renderer.<commit_after>package main

import (
	twodee "../libs/twodee"
	"fmt"
	"github.com/go-gl/gl"
)

const GLOW_FRAGMENT = `#version 150
precision mediump float;

uniform sampler2D u_TextureUnit;
in vec2 v_TextureCoordinates;
uniform int Orientation;
uniform int BlurAmount;
uniform float BlurScale;
uniform float BlurStrength;
uniform vec2 BufferDimensions;
out vec4 v_FragData;
vec2 TexelSize = vec2(1.0 / BufferDimensions.x, 1.0 / BufferDimensions.y);

float Gaussian (float x, float deviation)
{
  return (1.0 / sqrt(2.0 * 3.141592 * deviation)) * exp(-((x * x) / (2.0 * deviation)));
}

void main()
{
  // Locals
  float halfBlur = float(BlurAmount) * 0.5;
  vec4 colour = vec4(0.0);
  vec4 texColour = vec4(0.0);

  // Gaussian deviation
  float deviation = halfBlur * 0.35;
  deviation *= deviation;
  float strength = 1.0 - BlurStrength;

  if ( Orientation == 0 ) {
    // Horizontal blur
    for (int i = 0; i < 10; ++i) {
      if ( i >= BlurAmount ) {
        break;
      }
      float offset = float(i) - halfBlur;
      texColour = texture(
        u_TextureUnit,
        v_TextureCoordinates + vec2(offset * TexelSize.x * BlurScale, 0.0)) * Gaussian(offset * strength, deviation);
      colour += texColour;
    }
  } else {
    // Vertical blur
    for (int i = 0; i < 10; ++i) {
      if ( i >= BlurAmount ) {
        break;
      }
      float offset = float(i) - halfBlur;
      texColour = texture(
        u_TextureUnit,
        v_TextureCoordinates + vec2(0.0, offset * TexelSize.y * BlurScale)) * Gaussian(offset * strength, deviation);
      colour += texColour;
    }
  }
  // Apply colour
  v_FragData = clamp(colour, 0.0, 1.0);
  v_FragData.w = 1.0;
}`

const GLOW_VERTEX = `#version 150

in vec4 a_Position;
in vec2 a_TextureCoordinates;

out vec2 v_TextureCoordinates;

void main()
{
    v_TextureCoordinates = a_TextureCoordinates;
    gl_Position = a_Position;
}`

type GlowRenderer struct {
	GlowFb              gl.Framebuffer
	GlowTex             gl.Texture
	BlurFb              gl.Framebuffer
	BlurTex             gl.Texture
	shader              gl.Program
	positionLoc         gl.AttribLocation
	textureLoc          gl.AttribLocation
	orientationLoc      gl.UniformLocation
	blurAmountLoc       gl.UniformLocation
	blurScaleLoc        gl.UniformLocation
	blurStrengthLoc     gl.UniformLocation
	bufferDimensionsLoc gl.UniformLocation
	textureUnitLoc      gl.UniformLocation
	coords              gl.Buffer
	width               int
	height              int
	oldwidth            int
	oldheight           int
}

func NewGlowRenderer(w, h int) (r *GlowRenderer, err error) {
	r = &GlowRenderer{
		width:  w,
		height: h,
	}
	_, _, r.oldwidth, r.oldheight = GetInteger4(gl.VIEWPORT)
	if r.shader, err = twodee.BuildProgram(GLOW_VERTEX, GLOW_FRAGMENT); err != nil {
		return
	}
	r.orientationLoc = r.shader.GetUniformLocation("Orientation")
	r.blurAmountLoc = r.shader.GetUniformLocation("BlurAmount")
	r.blurScaleLoc = r.shader.GetUniformLocation("BlurScale")
	r.blurStrengthLoc = r.shader.GetUniformLocation("BlurStrength")
	r.bufferDimensionsLoc = r.shader.GetUniformLocation("BufferDimensions")
	r.positionLoc = r.shader.GetAttribLocation("a_Position")
	r.textureLoc = r.shader.GetAttribLocation("a_TextureCoordinates")
	r.textureUnitLoc = r.shader.GetUniformLocation("u_TextureUnit")
	r.shader.BindFragDataLocation(0, "v_FragData")
	var size float32 = 1.0
	var rect = []float32{
		-size, -size, 0.0, 0, 0,
		-size, size, 0.0, 0, 1,
		size, -size, 0.0, 1, 0,
		size, size, 0.0, 1, 1,
	}
	if r.coords, err = twodee.CreateVBO(len(rect)*4, rect, gl.STATIC_DRAW); err != nil {
		return
	}

	if r.GlowFb, r.GlowTex, err = r.initFramebuffer(w, h); err != nil {
		return
	}
	if r.BlurFb, r.BlurTex, err = r.initFramebuffer(w, h); err != nil {
		return
	}
	return
}

func (r *GlowRenderer) initFramebuffer(w, h int) (fb gl.Framebuffer, tex gl.Texture, err error) {
	fb = gl.GenFramebuffer()
	fb.Bind()

	tex = gl.GenTexture()
	tex.Bind(gl.TEXTURE_2D)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
	gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, nil)

	gl.FramebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0)
	if err = r.GetError(); err != nil {
		return
	}
	gl.DrawBuffers(1, []gl.GLenum{gl.COLOR_ATTACHMENT0})

	rb := gl.GenRenderbuffer()
	rb.Bind()
	gl.RenderbufferStorage(gl.RENDERBUFFER, gl.STENCIL_INDEX8, w, h)
	rb.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER)

	tex.Unbind(gl.TEXTURE_2D)
	fb.Unbind()
	rb.Unbind()
	return
}

func (r *GlowRenderer) GetError() error {
	if e := gl.GetError(); e != 0 {
		return fmt.Errorf("OpenGL error: %X", e)
	}
	var status = gl.CheckFramebufferStatus(gl.DRAW_FRAMEBUFFER)
	switch status {
	case gl.FRAMEBUFFER_COMPLETE:
		return nil
	case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
		return fmt.Errorf("Attachment point unconnected")
	case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
		return fmt.Errorf("Missing attachment")
	case gl.FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
		return fmt.Errorf("Draw buffer")
	case gl.FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
		return fmt.Errorf("Read buffer")
	case gl.FRAMEBUFFER_UNSUPPORTED:
		return fmt.Errorf("Unsupported config")
	default:
		return fmt.Errorf("Unknown framebuffer error: %X", status)
	}
}

func (r *GlowRenderer) Delete() error {
	r.GlowFb.Delete()
	r.BlurFb.Delete()
	return r.GetError()
}

func (r *GlowRenderer) Bind() error {
	r.GlowFb.Bind()
	gl.Enable(gl.STENCIL_TEST)
	gl.Viewport(0, 0, r.width, r.height)
	gl.ClearStencil(0)
	gl.ClearColor(0.0, 0.0, 0.0, 0.0)
	gl.StencilMask(0xFF) // Write to buffer
	gl.Clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
	gl.StencilMask(0x00) // Don't write to buffer

	return nil
}

func (r *GlowRenderer) Draw() (err error) {
	r.shader.Use()
	r.textureUnitLoc.Uniform1i(0)
	r.coords.Bind(gl.ARRAY_BUFFER)
	r.positionLoc.AttribPointer(3, gl.FLOAT, false, 5*4, uintptr(0))
	r.textureLoc.AttribPointer(2, gl.FLOAT, false, 5*4, uintptr(3*4))
	r.positionLoc.EnableArray()
	r.textureLoc.EnableArray()
	r.blurAmountLoc.Uniform1i(4)
	r.blurScaleLoc.Uniform1f(1.0)
	r.blurStrengthLoc.Uniform1f(0.2)
	r.bufferDimensionsLoc.Uniform2f(float32(r.width), float32(r.height))

	r.BlurFb.Bind()
	gl.Viewport(0, 0, r.width, r.height)
	gl.ClearColor(0.0, 0.0, 0.0, 0.0)
	gl.Clear(gl.COLOR_BUFFER_BIT)
	gl.ActiveTexture(gl.TEXTURE0)
	r.GlowTex.Bind(gl.TEXTURE_2D)
	r.orientationLoc.Uniform1i(0)
	gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
	r.BlurFb.Unbind()
	gl.Viewport(0, 0, r.oldwidth, r.oldheight)

	gl.BlendFunc(gl.ONE, gl.ONE)
	gl.ActiveTexture(gl.TEXTURE0)
	r.BlurTex.Bind(gl.TEXTURE_2D)
	r.orientationLoc.Uniform1i(1)
	gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)

	r.coords.Unbind(gl.ARRAY_BUFFER)
	gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
	return nil
}

func (r *GlowRenderer) Unbind() error {
	r.GlowFb.Unbind()
	gl.Viewport(0, 0, r.oldwidth, r.oldheight)
	gl.Disable(gl.STENCIL_TEST)
	return r.GetError()
}

func (r *GlowRenderer) DisableOutput() {
	gl.ColorMask(false, false, false, false)
	gl.DepthMask(false)
	gl.StencilFunc(gl.NEVER, 1, 0xFF)                // Never pass
	gl.StencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE) // Replace to ref=1
	gl.StencilMask(0xFF)                             // Write to buffer
	//gl.Enable(gl.ALPHA_TEST)
	//gl.AlphaFunc(gl.GREATER, 0)
}

func (r *GlowRenderer) EnableOutput() {
	gl.ColorMask(true, true, true, true)
	gl.DepthMask(true)
	gl.StencilMask(0x00)                // No more writing
	gl.StencilFunc(gl.EQUAL, 0, 0xFF) // Only pass where stencil is 0
	//gl.Disable(gl.ALPHA_TEST)
}

// Convenience function for glGetIntegerv
func GetInteger4(pname gl.GLenum) (v0, v1, v2, v3 int) {
	var values = []int32{0, 0, 0, 0}
	gl.GetIntegerv(pname, values)
	v0 = int(values[0])
	v1 = int(values[1])
	v2 = int(values[2])
	v3 = int(values[3])
	return
}
<|endoftext|>


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

// Parse input AST and prepare Prog structure.

package main

import (
	"fmt"
	"go/ast"
	"go/doc"
	"go/parser"
	"go/scanner"
	"os"
)

// A Cref refers to an expression of the form C.xxx in the AST.
type Cref struct {
	Name     string
	Expr     *ast.Expr
	Context  string // "type", "expr", or "call"
	TypeName bool   // whether xxx is a C type name
	Type     *Type  // the type of xxx
	FuncType *FuncType
}

// A Prog collects information about a cgo program.
type Prog struct {
	AST         *ast.File // parsed AST
	Preamble    string    // C preamble (doc comment on import "C")
	PackagePath string
	Package     string
	Crefs       []*Cref
	Typedef     map[string]ast.Expr
	Vardef      map[string]*Type
	Funcdef     map[string]*FuncType
	Enumdef     map[string]int64
	PtrSize     int64
	GccOptions  []string
	OutDefs     map[string]bool
}

// A Type collects information about a type in both the C and Go worlds.
type Type struct {
	Size       int64
	Align      int64
	C          string
	Go         ast.Expr
	EnumValues map[string]int64
}

// A FuncType collects information about a function type in both the C and Go worlds.
type FuncType struct {
	Params []*Type
	Result *Type
	Go     *ast.FuncType
}

func openProg(name string, p *Prog) {
	var err os.Error
	p.AST, err = parser.ParsePkgFile("", name, parser.ParseComments)
	if err != nil {
		if list, ok := err.(scanner.ErrorList); ok {
			// If err is a scanner.ErrorList, its String will print just
			// the first error and then (+n more errors).
			// Instead, turn it into a new Error that will return
			// details for all the errors.
			for _, e := range list {
				fmt.Fprintln(os.Stderr, e)
			}
			os.Exit(2)
		}
		fatal("parsing %s: %s", name, err)
	}
	p.Package = p.AST.Name.Value

	// Find the import "C" line and get any extra C preamble.
	// Delete the import "C" line along the way.
	sawC := false
	w := 0
	for _, decl := range p.AST.Decls {
		d, ok := decl.(*ast.GenDecl)
		if !ok {
			p.AST.Decls[w] = decl
			w++
			continue
		}
		ws := 0
		for _, spec := range d.Specs {
			s, ok := spec.(*ast.ImportSpec)
			if !ok || len(s.Path) != 1 || string(s.Path[0].Value) != `"C"` {
				d.Specs[ws] = spec
				ws++
				continue
			}
			sawC = true
			if s.Name != nil {
				error(s.Path[0].Pos(), `cannot rename import "C"`)
			}
			if s.Doc != nil {
				p.Preamble += doc.CommentText(s.Doc) + "\n"
			} else if len(d.Specs) == 1 && d.Doc != nil {
				p.Preamble += doc.CommentText(d.Doc) + "\n"
			}
		}
		if ws == 0 {
			continue
		}
		d.Specs = d.Specs[0:ws]
		p.AST.Decls[w] = d
		w++
	}
	p.AST.Decls = p.AST.Decls[0:w]

	if !sawC {
		error(noPos, `cannot find import "C"`)
	}

	// Accumulate pointers to uses of C.x.
	p.Crefs = make([]*Cref, 0, 8)
	walk(p.AST, p, "prog")
}

func walk(x interface{}, p *Prog, context string) {
	switch n := x.(type) {
	case *ast.Expr:
		if sel, ok := (*n).(*ast.SelectorExpr); ok {
			// For now, assume that the only instance of capital C is
			// when used as the imported package identifier.
			// The parser should take care of scoping in the future,
			// so that we will be able to distinguish a "top-level C"
			// from a local C.
			if l, ok := sel.X.(*ast.Ident); ok && l.Value == "C" {
				i := len(p.Crefs)
				if i >= cap(p.Crefs) {
					new := make([]*Cref, 2*i)
					for j, v := range p.Crefs {
						new[j] = v
					}
					p.Crefs = new
				}
				p.Crefs = p.Crefs[0 : i+1]
				p.Crefs[i] = &Cref{
					Name: sel.Sel.Value,
					Expr: n,
					Context: context,
				}
				break
			}
		}
		walk(*n, p, context)

	// everything else just recurs
	default:
		error(noPos, "unexpected type %T in walk", x)
		panic()

	case nil:

	// These are ordered and grouped to match ../../pkg/go/ast/ast.go
	case *ast.Field:
		walk(&n.Type, p, "type")
	case *ast.BadExpr:
	case *ast.Ident:
	case *ast.Ellipsis:
	case *ast.BasicLit:
	case *ast.StringList:
	case *ast.FuncLit:
		walk(n.Type, p, "type")
		walk(n.Body, p, "stmt")
	case *ast.CompositeLit:
		walk(&n.Type, p, "type")
		walk(n.Elts, p, "expr")
	case *ast.ParenExpr:
		walk(&n.X, p, context)
	case *ast.SelectorExpr:
		walk(&n.X, p, "selector")
	case *ast.IndexExpr:
		walk(&n.X, p, "expr")
		walk(&n.Index, p, "expr")
	case *ast.SliceExpr:
		walk(&n.X, p, "expr")
		walk(&n.Index, p, "expr")
		if n.End != nil {
			walk(&n.End, p, "expr")
		}
	case *ast.TypeAssertExpr:
		walk(&n.X, p, "expr")
		walk(&n.Type, p, "type")
	case *ast.CallExpr:
		walk(&n.Fun, p, "call")
		walk(n.Args, p, "expr")
	case *ast.StarExpr:
		walk(&n.X, p, context)
	case *ast.UnaryExpr:
		walk(&n.X, p, "expr")
	case *ast.BinaryExpr:
		walk(&n.X, p, "expr")
		walk(&n.Y, p, "expr")
	case *ast.KeyValueExpr:
		walk(&n.Key, p, "expr")
		walk(&n.Value, p, "expr")

	case *ast.ArrayType:
		walk(&n.Len, p, "expr")
		walk(&n.Elt, p, "type")
	case *ast.StructType:
		walk(n.Fields, p, "field")
	case *ast.FuncType:
		walk(n.Params, p, "field")
		walk(n.Results, p, "field")
	case *ast.InterfaceType:
		walk(n.Methods, p, "field")
	case *ast.MapType:
		walk(&n.Key, p, "type")
		walk(&n.Value, p, "type")
	case *ast.ChanType:
		walk(&n.Value, p, "type")

	case *ast.BadStmt:
	case *ast.DeclStmt:
		walk(n.Decl, p, "decl")
	case *ast.EmptyStmt:
	case *ast.LabeledStmt:
		walk(n.Stmt, p, "stmt")
	case *ast.ExprStmt:
		walk(&n.X, p, "expr")
	case *ast.IncDecStmt:
		walk(&n.X, p, "expr")
	case *ast.AssignStmt:
		walk(n.Lhs, p, "expr")
		walk(n.Rhs, p, "expr")
	case *ast.GoStmt:
		walk(n.Call, p, "expr")
	case *ast.DeferStmt:
		walk(n.Call, p, "expr")
	case *ast.ReturnStmt:
		walk(n.Results, p, "expr")
	case *ast.BranchStmt:
	case *ast.BlockStmt:
		walk(n.List, p, "stmt")
	case *ast.IfStmt:
		walk(n.Init, p, "stmt")
		walk(&n.Cond, p, "expr")
		walk(n.Body, p, "stmt")
		walk(n.Else, p, "stmt")
	case *ast.CaseClause:
		walk(n.Values, p, "expr")
		walk(n.Body, p, "stmt")
	case *ast.SwitchStmt:
		walk(n.Init, p, "stmt")
		walk(&n.Tag, p, "expr")
		walk(n.Body, p, "stmt")
	case *ast.TypeCaseClause:
		walk(n.Types, p, "type")
		walk(n.Body, p, "stmt")
	case *ast.TypeSwitchStmt:
		walk(n.Init, p, "stmt")
		walk(n.Assign, p, "stmt")
		walk(n.Body, p, "stmt")
	case *ast.CommClause:
		walk(n.Lhs, p, "expr")
		walk(n.Rhs, p, "expr")
		walk(n.Body, p, "stmt")
	case *ast.SelectStmt:
		walk(n.Body, p, "stmt")
	case *ast.ForStmt:
		walk(n.Init, p, "stmt")
		walk(&n.Cond, p, "expr")
		walk(n.Post, p, "stmt")
		walk(n.Body, p, "stmt")
	case *ast.RangeStmt:
		walk(&n.Key, p, "expr")
		walk(&n.Value, p, "expr")
		walk(&n.X, p, "expr")
		walk(n.Body, p, "stmt")

	case *ast.ImportSpec:
	case *ast.ValueSpec:
		walk(&n.Type, p, "type")
		walk(n.Values, p, "expr")
	case *ast.TypeSpec:
		walk(&n.Type, p, "type")

	case *ast.BadDecl:
	case *ast.GenDecl:
		walk(n.Specs, p, "spec")
	case *ast.FuncDecl:
		if n.Recv != nil {
			walk(n.Recv, p, "field")
		}
		walk(n.Type, p, "type")
		if n.Body != nil {
			walk(n.Body, p, "stmt")
		}

	case *ast.File:
		walk(n.Decls, p, "decl")

	case *ast.Package:
		for _, f := range n.Files {
			walk(f, p, "file")
		}

	case []ast.Decl:
		for _, d := range n {
			walk(d, p, context)
		}
	case []ast.Expr:
		for i := range n {
			walk(&n[i], p, context)
		}
	case []*ast.Field:
		for _, f := range n {
			walk(f, p, context)
		}
	case []ast.Stmt:
		for _, s := range n {
			walk(s, p, context)
		}
	case []ast.Spec:
		for _, s := range n {
			walk(s, p, context)
		}
	}
}
<commit_msg>cgo: don't overwrite p.Crefs<commit_after>// Copyright 2009 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Parse input AST and prepare Prog structure.

package main

import (
	"fmt"
	"go/ast"
	"go/doc"
	"go/parser"
	"go/scanner"
	"os"
)

// A Cref refers to an expression of the form C.xxx in the AST.
type Cref struct {
	Name     string
	Expr     *ast.Expr
	Context  string // "type", "expr", or "call"
	TypeName bool   // whether xxx is a C type name
	Type     *Type  // the type of xxx
	FuncType *FuncType
}

// A Prog collects information about a cgo program.
type Prog struct {
	AST         *ast.File // parsed AST
	Preamble    string    // C preamble (doc comment on import "C")
	PackagePath string
	Package     string
	Crefs       []*Cref
	Typedef     map[string]ast.Expr
	Vardef      map[string]*Type
	Funcdef     map[string]*FuncType
	Enumdef     map[string]int64
	PtrSize     int64
	GccOptions  []string
	OutDefs     map[string]bool
}

// A Type collects information about a type in both the C and Go worlds.
type Type struct {
	Size       int64
	Align      int64
	C          string
	Go         ast.Expr
	EnumValues map[string]int64
}

// A FuncType collects information about a function type in both the C and Go worlds.
type FuncType struct {
	Params []*Type
	Result *Type
	Go     *ast.FuncType
}

func openProg(name string, p *Prog) {
	var err os.Error
	p.AST, err = parser.ParsePkgFile("", name, parser.ParseComments)
	if err != nil {
		if list, ok := err.(scanner.ErrorList); ok {
			// If err is a scanner.ErrorList, its String will print just
			// the first error and then (+n more errors).
			// Instead, turn it into a new Error that will return
			// details for all the errors.
			for _, e := range list {
				fmt.Fprintln(os.Stderr, e)
			}
			os.Exit(2)
		}
		fatal("parsing %s: %s", name, err)
	}
	p.Package = p.AST.Name.Value

	// Find the import "C" line and get any extra C preamble.
	// Delete the import "C" line along the way.
	sawC := false
	w := 0
	for _, decl := range p.AST.Decls {
		d, ok := decl.(*ast.GenDecl)
		if !ok {
			p.AST.Decls[w] = decl
			w++
			continue
		}
		ws := 0
		for _, spec := range d.Specs {
			s, ok := spec.(*ast.ImportSpec)
			if !ok || len(s.Path) != 1 || string(s.Path[0].Value) != `"C"` {
				d.Specs[ws] = spec
				ws++
				continue
			}
			sawC = true
			if s.Name != nil {
				error(s.Path[0].Pos(), `cannot rename import "C"`)
			}
			if s.Doc != nil {
				p.Preamble += doc.CommentText(s.Doc) + "\n"
			} else if len(d.Specs) == 1 && d.Doc != nil {
				p.Preamble += doc.CommentText(d.Doc) + "\n"
			}
		}
		if ws == 0 {
			continue
		}
		d.Specs = d.Specs[0:ws]
		p.AST.Decls[w] = d
		w++
	}
	p.AST.Decls = p.AST.Decls[0:w]

	if !sawC {
		error(noPos, `cannot find import "C"`)
	}

	// Accumulate pointers to uses of C.x.
	if p.Crefs == nil {
		p.Crefs = make([]*Cref, 0, 8)
	}
	walk(p.AST, p, "prog")
}

func walk(x interface{}, p *Prog, context string) {
	switch n := x.(type) {
	case *ast.Expr:
		if sel, ok := (*n).(*ast.SelectorExpr); ok {
			// For now, assume that the only instance of capital C is
			// when used as the imported package identifier.
			// The parser should take care of scoping in the future,
			// so that we will be able to distinguish a "top-level C"
			// from a local C.
			if l, ok := sel.X.(*ast.Ident); ok && l.Value == "C" {
				i := len(p.Crefs)
				if i >= cap(p.Crefs) {
					new := make([]*Cref, 2*i)
					for j, v := range p.Crefs {
						new[j] = v
					}
					p.Crefs = new
				}
				p.Crefs = p.Crefs[0 : i+1]
				p.Crefs[i] = &Cref{
					Name: sel.Sel.Value,
					Expr: n,
					Context: context,
				}
				break
			}
		}
		walk(*n, p, context)

	// everything else just recurs
	default:
		error(noPos, "unexpected type %T in walk", x)
		panic()

	case nil:

	// These are ordered and grouped to match ../../pkg/go/ast/ast.go
	case *ast.Field:
		walk(&n.Type, p, "type")
	case *ast.BadExpr:
	case *ast.Ident:
	case *ast.Ellipsis:
	case *ast.BasicLit:
	case *ast.StringList:
	case *ast.FuncLit:
		walk(n.Type, p, "type")
		walk(n.Body, p, "stmt")
	case *ast.CompositeLit:
		walk(&n.Type, p, "type")
		walk(n.Elts, p, "expr")
	case *ast.ParenExpr:
		walk(&n.X, p, context)
	case *ast.SelectorExpr:
		walk(&n.X, p, "selector")
	case *ast.IndexExpr:
		walk(&n.X, p, "expr")
		walk(&n.Index, p, "expr")
	case *ast.SliceExpr:
		walk(&n.X, p, "expr")
		walk(&n.Index, p, "expr")
		if n.End != nil {
			walk(&n.End, p, "expr")
		}
	case *ast.TypeAssertExpr:
		walk(&n.X, p, "expr")
		walk(&n.Type, p, "type")
	case *ast.CallExpr:
		walk(&n.Fun, p, "call")
		walk(n.Args, p, "expr")
	case *ast.StarExpr:
		walk(&n.X, p, context)
	case *ast.UnaryExpr:
		walk(&n.X, p, "expr")
	case *ast.BinaryExpr:
		walk(&n.X, p, "expr")
		walk(&n.Y, p, "expr")
	case *ast.KeyValueExpr:
		walk(&n.Key, p, "expr")
		walk(&n.Value, p, "expr")

	case *ast.ArrayType:
		walk(&n.Len, p, "expr")
		walk(&n.Elt, p, "type")
	case *ast.StructType:
		walk(n.Fields, p, "field")
	case *ast.FuncType:
		walk(n.Params, p, "field")
		walk(n.Results, p, "field")
	case *ast.InterfaceType:
		walk(n.Methods, p, "field")
	case *ast.MapType:
		walk(&n.Key, p, "type")
		walk(&n.Value, p, "type")
	case *ast.ChanType:
		walk(&n.Value, p, "type")

	case *ast.BadStmt:
	case *ast.DeclStmt:
		walk(n.Decl, p, "decl")
	case *ast.EmptyStmt:
	case *ast.LabeledStmt:
		walk(n.Stmt, p, "stmt")
	case *ast.ExprStmt:
		walk(&n.X, p, "expr")
	case *ast.IncDecStmt:
		walk(&n.X, p, "expr")
	case *ast.AssignStmt:
		walk(n.Lhs, p, "expr")
		walk(n.Rhs, p, "expr")
	case *ast.GoStmt:
		walk(n.Call, p, "expr")
	case *ast.DeferStmt:
		walk(n.Call, p, "expr")
	case *ast.ReturnStmt:
		walk(n.Results, p, "expr")
	case *ast.BranchStmt:
	case *ast.BlockStmt:
		walk(n.List, p, "stmt")
	case *ast.IfStmt:
		walk(n.Init, p, "stmt")
		walk(&n.Cond, p, "expr")
		walk(n.Body, p, "stmt")
		walk(n.Else, p, "stmt")
	case *ast.CaseClause:
		walk(n.Values, p, "expr")
		walk(n.Body, p, "stmt")
	case *ast.SwitchStmt:
		walk(n.Init, p, "stmt")
		walk(&n.Tag, p, "expr")
		walk(n.Body, p, "stmt")
	case *ast.TypeCaseClause:
		walk(n.Types, p, "type")
		walk(n.Body, p, "stmt")
	case *ast.TypeSwitchStmt:
		walk(n.Init, p, "stmt")
		walk(n.Assign, p, "stmt")
		walk(n.Body, p, "stmt")
	case *ast.CommClause:
		walk(n.Lhs, p, "expr")
		walk(n.Rhs, p, "expr")
		walk(n.Body, p, "stmt")
	case *ast.SelectStmt:
		walk(n.Body, p, "stmt")
	case *ast.ForStmt:
		walk(n.Init, p, "stmt")
		walk(&n.Cond, p, "expr")
		walk(n.Post, p, "stmt")
		walk(n.Body, p, "stmt")
	case *ast.RangeStmt:
		walk(&n.Key, p, "expr")
		walk(&n.Value, p, "expr")
		walk(&n.X, p, "expr")
		walk(n.Body, p, "stmt")

	case *ast.ImportSpec:
	case *ast.ValueSpec:
		walk(&n.Type, p, "type")
		walk(n.Values, p, "expr")
	case *ast.TypeSpec:
		walk(&n.Type, p, "type")

	case *ast.BadDecl:
	case *ast.GenDecl:
		walk(n.Specs, p, "spec")
	case *ast.FuncDecl:
		if n.Recv != nil {
			walk(n.Recv, p, "field")
		}
		walk(n.Type, p, "type")
		if n.Body != nil {
			walk(n.Body, p, "stmt")
		}

	case *ast.File:
		walk(n.Decls, p, "decl")

	case *ast.Package:
		for _, f := range n.Files {
			walk(f, p, "file")
		}

	case []ast.Decl:
		for _, d := range n {
			walk(d, p, context)
		}
	case []ast.Expr:
		for i := range n {
			walk(&n[i], p, context)
		}
	case []*ast.Field:
		for _, f := range n {
			walk(f, p, context)
		}
	case []ast.Stmt:
		for _, s := range n {
			walk(s, p, context)
		}
	case []ast.Spec:
		for _, s := range n {
			walk(s, p, context)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
    zmq "github.com/alecthomas/gozmq"
    "log"
    "github.com/howeyc/fsnotify"
    "os"
)

func watchFile(fileName string, fileChannel chan string, logChannel chan []byte) {
    log_file, err := os.Open(fileName)

    stat, _ := os.Stat(fileName)
    size := stat.Size()
    log_file.Seek(0, 2)

    watcher, err := fsnotify.NewWatcher()

    if err != nil {
        log.Fatal(err)
    }

    go func() {
        for {
            ev := <-watcher.Event

            if (ev != nil && ev.IsModify()) {
                // Create a buffer for reading the new data
                stat, err = os.Stat(fileName)

                if err != nil {
                    continue
                }

                new_size := stat.Size()
                bytes := make([]byte, new_size - size)

                if (new_size - size > 0) {
                    _, err := log_file.Read(bytes)

                    if err != nil {
                        log.Fatal(err)
                    }

                    fileChannel <- fileName
                    logChannel <- bytes

                    if err != nil {
                       log.Fatal(err)
                    }
                }
                size = new_size
            }
        }
    }()

    err = watcher.Watch(fileName)

    if err != nil {
        log.Fatal(err)
    }
}

func main() {
    files := []string {
        "/var/log/system.log",
        "/Users/ashish/server_log",
    }

    // Create and bind socket
    context, _ := zmq.NewContext()
    socket, _ := context.NewSocket(zmq.PUB)
    defer context.Close()
    defer socket.Close()
    socket.Bind("tcp://*:5556")

    fileChannel := make(chan string)
    logChannel := make(chan []byte)

    println("Watching file for changes")

    for _, v := range files {
        watchFile(v, fileChannel, logChannel)
    }

    for {
        fName := <-fileChannel
        logData := <-logChannel
        outData := append([]byte(fName + ":"), logData...)
        _ = socket.Send(outData, 0)
    }
}
<commit_msg>take list of files to watch from file<commit_after>package main

import (
    zmq "github.com/alecthomas/gozmq"
    "log"
    "github.com/howeyc/fsnotify"
    "os"
    "io/ioutil"
    "strings"
)

func watchFile(fileName string, fileChannel chan string, logChannel chan []byte) {
    log_file, err := os.Open(fileName)

    stat, _ := os.Stat(fileName)
    size := stat.Size()
    log_file.Seek(0, 2)

    watcher, err := fsnotify.NewWatcher()

    if err != nil {
        log.Fatal(err)
    }

    go func() {
        for {
            ev := <-watcher.Event

            if (ev != nil && ev.IsModify()) {
                // Create a buffer for reading the new data
                stat, err = os.Stat(fileName)

                if err != nil {
                    continue
                }

                new_size := stat.Size()
                bytes := make([]byte, new_size - size)

                if (new_size - size > 0) {
                    _, err := log_file.Read(bytes)

                    if err != nil {
                        log.Fatal(err)
                    }

                    fileChannel <- fileName
                    logChannel <- bytes

                    if err != nil {
                       log.Fatal(err)
                    }
                }
                size = new_size
            }
        }
    }()

    err = watcher.Watch(fileName)

    if err != nil {
        log.Fatal(err)
    }
}

func main() {
    dat, err := ioutil.ReadFile("files.lst")
    if err != nil {
        println("Cannot open file `files.lst` containing files to watch.")
        return
    }

    files := strings.Split(string(dat), "\n")

    // Create and bind socket
    context, _ := zmq.NewContext()
    socket, _ := context.NewSocket(zmq.PUB)
    defer context.Close()
    defer socket.Close()
    socket.Bind("tcp://*:5556")

    fileChannel := make(chan string)
    logChannel := make(chan []byte)

    println("Watching file for changes")

    for _, v := range files {
        if len(v) > 0 {
            watchFile(v, fileChannel, logChannel)
        }
    }

    for {
        fName := <-fileChannel
        logData := <-logChannel
        outData := append([]byte(fName + ":"), logData...)
        _ = socket.Send(outData, 0)
    }
}
<|endoftext|>


<commit_before>package lib

import (
	"errors"
	auth3 "github.com/BytemarkHosting/auth-client"
)

// EndpointURLs are the URLs stored by the client for the various API endpoints the client touches.
// The key endpoints that you may wish to alter are Auth and Brain. When using an auth server and brain
// that doesn't have a matching bmbilling API, Billing should be set to ""
type EndpointURLs struct {
	API     string
	Auth    string
	Billing string
	Brain   string
	SPP     string
}

// DefaultURLs returns an EndpointURLs for the usual customer-facing Bytemark APIs.
func DefaultURLs() EndpointURLs {
	return EndpointURLs{
		API:     "https://api.bytemark.co.uk",
		Auth:    "https://auth.bytemark.co.uk",
		Billing: "https://bmbilling.bytemark.co.uk",
		Brain:   "https://uk0.bigv.io",
		SPP:     "https://spp-submissions.bytemark.co.uk",
	}
}

// Endpoint is an enum-style type to avoid people using endpoints like ints
type Endpoint int

const (
	// AuthEndpoint means "make the connection to auth!"
	AuthEndpoint Endpoint = iota
	// BrainEndpoint means "make the connection to the brain!"
	BrainEndpoint
	// BillingEndpoint means "make the connection to bmbilling!"
	BillingEndpoint
	// SPPEndpoint means "make the connection to SPP!"
	SPPEndpoint
	// APIEndpoint means "make the connection to the general API endpoint!" (api.bytemark.co.uk - atm only used for domains?)
	APIEndpoint
)

// bytemarkClient is the main type in the Bytemark API client library
type bytemarkClient struct {
	allowInsecure bool
	auth          *auth3.Client
	authSession   *auth3.SessionData
	debugLevel    int
	urls          EndpointURLs
}

// NewSimple creates a new Bytemark API client using the default bytemark endpoints.
// This function will be renamed to New in 3.0
func NewSimple() (Client, error) {
	return NewWithURLs(DefaultURLs())
}

// NewWithURLs creates a new Bytemark API client using the given endpoints.
func NewWithURLs(urls EndpointURLs) (c Client, err error) {
	if urls.Auth == "" {
		urls.Auth = "https://auth.bytemark.co.uk"
	}
	auth, err := auth3.New(urls.Auth)
	if err != nil {
		return nil, err
	}
	client := bytemarkClient{
		urls:       urls,
		auth:       auth,
		debugLevel: 0,
	}
	return &client, nil
}

// New creates a new Bytemark API client using the given Bytemark API endpoint and the default Bytemark auth endpoint, and fills the rest in with defaults.
// This function will be replaced with NewSimple in 3.0
func New(brainEndpoint, billingEndpoint, sppEndpoint string) (c Client, err error) {
	auth, err := auth3.New("https://auth.bytemark.co.uk")
	if err != nil {
		return nil, err
	}
	return NewWithAuth(brainEndpoint, billingEndpoint, sppEndpoint, auth), nil
}

// NewWithAuth creates a new Bytemark API client using the given Bytemark API endpoint and github.com/BytemarkHosting/auth-client Client
// This function is deprecated and will be removed in 3.0
func NewWithAuth(brainEndpoint, billingEndpoint, sppEndpoint string, auth *auth3.Client) Client {
	urls := DefaultURLs()
	urls.Brain = brainEndpoint
	urls.Billing = billingEndpoint
	urls.SPP = sppEndpoint
	client := bytemarkClient{
		urls:       urls,
		auth:       auth,
		debugLevel: 0,
	}
	return &client
}

// AuthWithCredentials attempts to authenticate with the given credentials. Returns nil on success or an error otherwise.
func (c *bytemarkClient) AuthWithCredentials(credentials auth3.Credentials) error {
	session, err := c.auth.CreateSession(credentials)
	if err == nil {
		c.authSession = session
	}
	return err
}

// AuthWithToken attempts to read sessiondata from auth for the given token. Returns nil on success or an error otherwise.
func (c *bytemarkClient) AuthWithToken(token string) error {
	if token == "" {
		return errors.New("No token provided")
	}

	session, err := c.auth.ReadSession(token)
	if err == nil {
		c.authSession = session
	}
	return err

}

// GetEndpoint returns the Bytemark API endpoint currently in use.
func (c *bytemarkClient) GetEndpoint() string {
	return c.urls.Brain
}

// GetBillingEndpoint returns the Bytemark Billing API endpoint in use.
// This function is deprecated and will be removed in a point release.
// DO NOT DEPEND ON IT
func (c *bytemarkClient) GetBillingEndpoint() string {
	return c.urls.Billing
}

// SetDebugLevel sets the debug level / verbosity of the Bytemark API client. 0 (default) is silent.
func (c *bytemarkClient) SetDebugLevel(debugLevel int) {
	c.debugLevel = debugLevel
}

// GetSessionFactors returns the factors provided when the current auth session was set up
func (c *bytemarkClient) GetSessionFactors() []string {
	if c.authSession == nil {
		return []string{}
	}
	return c.authSession.Factors
}

// GetSessionToken returns the token for the current auth session
func (c *bytemarkClient) GetSessionToken() string {
	if c.authSession == nil {
		return ""
	}
	return c.authSession.Token
}

func (c *bytemarkClient) GetSessionUser() string {
	if c.authSession == nil {
		return ""
	}
	return c.authSession.Username
}

func (c *bytemarkClient) AllowInsecureRequests() {
	c.allowInsecure = true
}

func (c *bytemarkClient) validateVirtualMachineName(vm *VirtualMachineName) error {
	if vm.Account == "" {
		if err := c.validateAccountName(&vm.Account); err != nil {
			return err
		}
	}
	if vm.Group == "" {
		vm.Group = DefaultGroup
	}

	if vm.VirtualMachine == "" {
		return BadNameError{Type: "virtual machine", ProblemField: "name", ProblemValue: vm.VirtualMachine}
	}
	return nil
}

func (c *bytemarkClient) validateGroupName(group *GroupName) error {
	if group.Account == "" {
		if err := c.validateAccountName(&group.Account); err != nil {
			return err
		}
	}
	if group.Group == "" {
		group.Group = DefaultGroup
	}
	return nil
}

func (c *bytemarkClient) validateAccountName(account *string) error {
	if *account == "" && c.authSession != nil {
		billAcc, err := c.getDefaultBillingAccount()
		if err == nil && billAcc != nil {
			*account = billAcc.Name
		} else {
			return err
		}
	}
	if *account == "" {
		return NoDefaultAccountError{}
	}
	return nil
}
<commit_msg>fix validateAccoutName returning early with a blank account<commit_after>package lib

import (
	"errors"
	auth3 "github.com/BytemarkHosting/auth-client"
)

// EndpointURLs are the URLs stored by the client for the various API endpoints the client touches.
// The key endpoints that you may wish to alter are Auth and Brain. When using an auth server and brain
// that doesn't have a matching bmbilling API, Billing should be set to ""
type EndpointURLs struct {
	API     string
	Auth    string
	Billing string
	Brain   string
	SPP     string
}

// DefaultURLs returns an EndpointURLs for the usual customer-facing Bytemark APIs.
func DefaultURLs() EndpointURLs {
	return EndpointURLs{
		API:     "https://api.bytemark.co.uk",
		Auth:    "https://auth.bytemark.co.uk",
		Billing: "https://bmbilling.bytemark.co.uk",
		Brain:   "https://uk0.bigv.io",
		SPP:     "https://spp-submissions.bytemark.co.uk",
	}
}

// Endpoint is an enum-style type to avoid people using endpoints like ints
type Endpoint int

const (
	// AuthEndpoint means "make the connection to auth!"
	AuthEndpoint Endpoint = iota
	// BrainEndpoint means "make the connection to the brain!"
	BrainEndpoint
	// BillingEndpoint means "make the connection to bmbilling!"
	BillingEndpoint
	// SPPEndpoint means "make the connection to SPP!"
	SPPEndpoint
	// APIEndpoint means "make the connection to the general API endpoint!" (api.bytemark.co.uk - atm only used for domains?)
	APIEndpoint
)

// bytemarkClient is the main type in the Bytemark API client library
type bytemarkClient struct {
	allowInsecure bool
	auth          *auth3.Client
	authSession   *auth3.SessionData
	debugLevel    int
	urls          EndpointURLs
}

// NewSimple creates a new Bytemark API client using the default bytemark endpoints.
// This function will be renamed to New in 3.0
func NewSimple() (Client, error) {
	return NewWithURLs(DefaultURLs())
}

// NewWithURLs creates a new Bytemark API client using the given endpoints.
func NewWithURLs(urls EndpointURLs) (c Client, err error) {
	if urls.Auth == "" {
		urls.Auth = "https://auth.bytemark.co.uk"
	}
	auth, err := auth3.New(urls.Auth)
	if err != nil {
		return nil, err
	}
	client := bytemarkClient{
		urls:       urls,
		auth:       auth,
		debugLevel: 0,
	}
	return &client, nil
}

// New creates a new Bytemark API client using the given Bytemark API endpoint and the default Bytemark auth endpoint, and fills the rest in with defaults.
// This function will be replaced with NewSimple in 3.0
func New(brainEndpoint, billingEndpoint, sppEndpoint string) (c Client, err error) {
	auth, err := auth3.New("https://auth.bytemark.co.uk")
	if err != nil {
		return nil, err
	}
	return NewWithAuth(brainEndpoint, billingEndpoint, sppEndpoint, auth), nil
}

// NewWithAuth creates a new Bytemark API client using the given Bytemark API endpoint and github.com/BytemarkHosting/auth-client Client
// This function is deprecated and will be removed in 3.0
func NewWithAuth(brainEndpoint, billingEndpoint, sppEndpoint string, auth *auth3.Client) Client {
	urls := DefaultURLs()
	urls.Brain = brainEndpoint
	urls.Billing = billingEndpoint
	urls.SPP = sppEndpoint
	client := bytemarkClient{
		urls:       urls,
		auth:       auth,
		debugLevel: 0,
	}
	return &client
}

// AuthWithCredentials attempts to authenticate with the given credentials. Returns nil on success or an error otherwise.
func (c *bytemarkClient) AuthWithCredentials(credentials auth3.Credentials) error {
	session, err := c.auth.CreateSession(credentials)
	if err == nil {
		c.authSession = session
	}
	return err
}

// AuthWithToken attempts to read sessiondata from auth for the given token. Returns nil on success or an error otherwise.
func (c *bytemarkClient) AuthWithToken(token string) error {
	if token == "" {
		return errors.New("No token provided")
	}

	session, err := c.auth.ReadSession(token)
	if err == nil {
		c.authSession = session
	}
	return err

}

// GetEndpoint returns the Bytemark API endpoint currently in use.
func (c *bytemarkClient) GetEndpoint() string {
	return c.urls.Brain
}

// GetBillingEndpoint returns the Bytemark Billing API endpoint in use.
// This function is deprecated and will be removed in a point release.
// DO NOT DEPEND ON IT
func (c *bytemarkClient) GetBillingEndpoint() string {
	return c.urls.Billing
}

// SetDebugLevel sets the debug level / verbosity of the Bytemark API client. 0 (default) is silent.
func (c *bytemarkClient) SetDebugLevel(debugLevel int) {
	c.debugLevel = debugLevel
}

// GetSessionFactors returns the factors provided when the current auth session was set up
func (c *bytemarkClient) GetSessionFactors() []string {
	if c.authSession == nil {
		return []string{}
	}
	return c.authSession.Factors
}

// GetSessionToken returns the token for the current auth session
func (c *bytemarkClient) GetSessionToken() string {
	if c.authSession == nil {
		return ""
	}
	return c.authSession.Token
}

func (c *bytemarkClient) GetSessionUser() string {
	if c.authSession == nil {
		return ""
	}
	return c.authSession.Username
}

func (c *bytemarkClient) AllowInsecureRequests() {
	c.allowInsecure = true
}

func (c *bytemarkClient) validateVirtualMachineName(vm *VirtualMachineName) error {
	if vm.Account == "" {
		if err := c.validateAccountName(&vm.Account); err != nil {
			return err
		}
	}
	if vm.Group == "" {
		vm.Group = DefaultGroup
	}

	if vm.VirtualMachine == "" {
		return BadNameError{Type: "virtual machine", ProblemField: "name", ProblemValue: vm.VirtualMachine}
	}
	return nil
}

func (c *bytemarkClient) validateGroupName(group *GroupName) error {
	if group.Account == "" {
		if err := c.validateAccountName(&group.Account); err != nil {
			return err
		}
	}
	if group.Group == "" {
		group.Group = DefaultGroup
	}
	return nil
}

func (c *bytemarkClient) validateAccountName(account *string) error {
	if *account == "" && c.authSession != nil {
		billAcc, err := c.getDefaultBillingAccount()
		if err == nil && billAcc != nil {
			*account = billAcc.Name
		} else if err != nil {
			return err
		}
	}
	if *account == "" {
		return NoDefaultAccountError{}
	}
	return nil
}
<|endoftext|>