<commit_before>package filterlist

import (
	"io"
	"bufio"
	"strings"
	"log"
)

type ABPFilterParser struct { }

func NewABPFilterParser() (*ABPFilterParser) {
	return &ABPFilterParser{}
}

func (parser *ABPFilterParser) Parse(reader io.Reader) (whitelist []Filter, blacklist []Filter, err error) {
	scanner := bufio.NewScanner(reader)

	for scanner.Scan() {
		rule, isException := parser.checkAndCleanIfExceptionRule(scanner.Text())
		if rule, domainRule := parser.checkAndCleanIfSimpleDomainRule(rule); domainRule {
			filter, err := NewABPFilter(rule)

			if err {
				log.Fatalf("Could not create ABPFilter on rule %s. Error %t", rule, err)
				break
			}

			if isException {
				whitelist = append(whitelist, *filter)
			} else {
				blacklist = append(blacklist, *filter)
			}
		}
	}

	return whitelist, blacklist, err
}

const (
	exceptionPrefix = "@@"
	domainPrefix = "||"
	domainSuffix = "^"
)

func (parser *ABPFilterParser) checkAndCleanIfSimpleDomainRule(rule string) (string, bool) {
	if cleanedRule, matchesPrefix := parser.checkRulePrefixAndRemove(rule, domainPrefix); matchesPrefix {
		return parser.checkRuleSuffixAndRemove(cleanedRule, domainSuffix)
	}
	return rule, false
}

func (parser *ABPFilterParser) checkAndCleanIfExceptionRule(rule string) (string, bool) {

	return parser.checkRulePrefixAndRemove(rule, exceptionPrefix)
}

func (*ABPFilterParser) checkRulePrefixAndRemove(rule string, prefix string) (string, bool) {
	if strings.HasPrefix(rule, prefix) {
		return strings.TrimPrefix(rule, prefix), true
	}
	return rule, false
}

func (*ABPFilterParser) checkRuleSuffixAndRemove(rule string, suffix string) (string, bool) {
	if strings.HasSuffix(rule, suffix) {
		return strings.TrimSuffix(rule, suffix), true
	}
	return rule, false
}<commit_msg>Remove unnecessary break after fatal log<commit_after>package filterlist

import (
	"io"
	"bufio"
	"strings"
	"log"
)

type ABPFilterParser struct { }

func NewABPFilterParser() (*ABPFilterParser) {
	return &ABPFilterParser{}
}

func (parser *ABPFilterParser) Parse(reader io.Reader) (whitelist []Filter, blacklist []Filter, err error) {
	scanner := bufio.NewScanner(reader)

	for scanner.Scan() {
		rule, isException := parser.checkAndCleanIfExceptionRule(scanner.Text())
		if rule, domainRule := parser.checkAndCleanIfSimpleDomainRule(rule); domainRule {
			filter, err := NewABPFilter(rule)

			if err {
				log.Fatalf("Could not create ABPFilter on rule %s. Error %t", rule, err)
			}

			if isException {
				whitelist = append(whitelist, *filter)
			} else {
				blacklist = append(blacklist, *filter)
			}
		}
	}

	return whitelist, blacklist, err
}

const (
	exceptionPrefix = "@@"
	domainPrefix = "||"
	domainSuffix = "^"
)

func (parser *ABPFilterParser) checkAndCleanIfSimpleDomainRule(rule string) (string, bool) {
	if cleanedRule, matchesPrefix := parser.checkRulePrefixAndRemove(rule, domainPrefix); matchesPrefix {
		return parser.checkRuleSuffixAndRemove(cleanedRule, domainSuffix)
	}
	return rule, false
}

func (parser *ABPFilterParser) checkAndCleanIfExceptionRule(rule string) (string, bool) {

	return parser.checkRulePrefixAndRemove(rule, exceptionPrefix)
}

func (*ABPFilterParser) checkRulePrefixAndRemove(rule string, prefix string) (string, bool) {
	if strings.HasPrefix(rule, prefix) {
		return strings.TrimPrefix(rule, prefix), true
	}
	return rule, false
}

func (*ABPFilterParser) checkRuleSuffixAndRemove(rule string, suffix string) (string, bool) {
	if strings.HasSuffix(rule, suffix) {
		return strings.TrimSuffix(rule, suffix), true
	}
	return rule, false
}<|endoftext|>


<commit_before>// Copyright (c) 2012, Jorge Acereda Maciá. All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can
// be found in the LICENSE file.

// Package ffvorbis provides a wrapper around the vorbis codec in ffmpeg.
package ffvorbis

// #cgo LDFLAGS: -lavcodec
/*
#include "libavcodec/avcodec.h"
#if LIBAVCODEC_VERSION_MAJOR == 53
#define AV_CODEC_ID_VORBIS CODEC_ID_VORBIS
#endif
#include <string.h>

static void convertS16(void * vd, const void * vs, int n) {
 const int16_t * s = (const int16_t*)vs;
 float * d = (float*)vd;
 float scale = 1 / 65536.0; // 32768.0f;
 int i;
 for (i = 0; i < n; i++) d[i] = scale * s[i];
}
*/
import "C"

import (
	"log"
	"unsafe"
)

func init() {
	C.avcodec_register_all()
}

type Decoder struct {
	c  *C.AVCodec
	cc *C.AVCodecContext
}

func NewDecoder(data []byte, channels, rate int) *Decoder {
	var d Decoder
	d.c = C.avcodec_find_decoder(C.AV_CODEC_ID_VORBIS)
	d.cc = C.avcodec_alloc_context3(d.c)
	d.cc.codec_type = C.AVMEDIA_TYPE_AUDIO
	d.cc.sample_rate = C.int(rate)
	d.cc.channels = C.int(channels)
	d.cc.extradata = (*C.uint8_t)(&data[0])
	d.cc.extradata_size = C.int(len(data))
	d.cc.channels = 2
	C.avcodec_open2(d.cc, d.c, nil)
	return &d
}

func (d *Decoder) Decode(data []byte) []float32 {
	var pkt C.AVPacket
	var fr C.AVFrame
	var got C.int
	C.avcodec_get_frame_defaults(&fr)
	C.av_init_packet(&pkt)
	pkt.data = (*C.uint8_t)(&data[0])
	pkt.size = C.int(len(data))
	dec := C.avcodec_decode_audio4(d.cc, &fr, &got, &pkt)
	if dec < 0 {
		log.Println("Unable to decode")
		return nil
	}
	if dec != pkt.size {
		log.Println("Partial decode")
	}
	if got == 0 {
		return nil
	}
	nvals := d.cc.channels * fr.nb_samples
	buf := make([]float32, nvals)
	dst := unsafe.Pointer(&buf[0])
	src := unsafe.Pointer(fr.data[0])
	switch d.cc.sample_fmt {
	case C.AV_SAMPLE_FMT_FLT:
		C.memcpy(dst, src, C.size_t(nvals*4))
	case C.AV_SAMPLE_FMT_S16:
		C.convertS16(dst, src, nvals)
	default:
		log.Panic("Unsupported format")
	}
	if pkt.data != nil {
		C.av_free_packet(&pkt)
	}
	return buf
}
<commit_msg>Updated to latest libavcodec API<commit_after>// Copyright (c) 2012, Jorge Acereda Maciá. All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can
// be found in the LICENSE file.

// Package ffvorbis provides a wrapper around the vorbis codec in ffmpeg.
package ffvorbis

// #cgo LDFLAGS: -lavcodec -lavutil
/*
#include "libavcodec/avcodec.h"
#include "libavutil/frame.h"
#if LIBAVCODEC_VERSION_MAJOR == 53
#define AV_CODEC_ID_VORBIS CODEC_ID_VORBIS
#endif
#include <string.h>

static void convertS16(void * vd, const void * vs, int n) {
 const int16_t * s = (const int16_t*)vs;
 float * d = (float*)vd;
 float scale = 1 / 65536.0; // 32768.0f;
 int i;
 for (i = 0; i < n; i++) d[i] = scale * s[i];
}

static void convertFLTP(void * vd, const void * vs, int n, int nch) {
 const float ** s = (const float**)vs;
 float * d = (float*)vd;
 int i, ch;
 for (ch = 0; ch < nch; ch++)
  for (i = 0; i < n; i++) d[i*nch+ch] = s[ch][i];
}

*/
import "C"

import (
	"log"
	"unsafe"
)

func init() {
	C.avcodec_register_all()
}

type Decoder struct {
	c  *C.AVCodec
	cc *C.AVCodecContext
}

func NewDecoder(data []byte, channels, rate int) *Decoder {
	var d Decoder
	d.c = C.avcodec_find_decoder(C.AV_CODEC_ID_VORBIS)
	d.cc = C.avcodec_alloc_context3(d.c)
	d.cc.codec_type = C.AVMEDIA_TYPE_AUDIO
	d.cc.sample_rate = C.int(rate)
	d.cc.channels = C.int(channels)
	log.Println("channels ", d.cc.channels, "rate ", d.cc.sample_rate)
	d.cc.extradata = (*C.uint8_t)(&data[0])
	d.cc.extradata_size = C.int(len(data))
	C.avcodec_open2(d.cc, d.c, nil)
	return &d
}

func (d *Decoder) Decode(data []byte) []float32 {
	var pkt C.AVPacket
	var fr *C.AVFrame
	var got C.int
	fr = C.av_frame_alloc()
	defer C.av_frame_free(&fr)
	C.av_init_packet(&pkt)
	defer C.av_packet_unref(&pkt)
	pkt.data = (*C.uint8_t)(&data[0])
	pkt.size = C.int(len(data))
	dec := C.avcodec_decode_audio4(d.cc, fr, &got, &pkt)
	if dec < 0 {
		log.Println("Unable to decode")
		return nil
	}
	if dec != pkt.size {
		log.Println("Partial decode")
	}
	if got == 0 {
		return nil
	}
	nvals := d.cc.channels * fr.nb_samples
	buf := make([]float32, nvals)
	dst := unsafe.Pointer(&buf[0])
	src := unsafe.Pointer(fr.data[0])
	switch d.cc.sample_fmt {
	case C.AV_SAMPLE_FMT_FLT:
		C.memcpy(dst, src, C.size_t(nvals*4))
	case C.AV_SAMPLE_FMT_S16:
		C.convertS16(dst, src, nvals)
	case C.AV_SAMPLE_FMT_FLTP:
		C.convertFLTP(dst, unsafe.Pointer(&fr.data[0]), nvals, d.cc.channels)
	default:
		log.Panic("Unsupported format ", d.cc.sample_fmt)
	}
	return buf
}
<|endoftext|>


<commit_before>package packet

import (
	"fmt"
	"net"

	"github.com/jsimonetti/go-artnet/packet/code"
)

var _ ArtNetPacket = &ArtPollReplyPacket{}

// ArtPollReplyPacket contains an ArtPollReply Packet.
type ArtPollReplyPacket struct {
	// Inherit the Header header
	Header

	// IPAddress is the Node’s IPv4 address. When binding is implemented, bound nodes may
	// share the root node’s IP Address and the BindIndex is used to differentiate the nodes.
	IPAddress net.IP

	// Port is always 0x1936 Transmitted low byte first.
	Port uint16

	// VersionInfo contains the Node’s firmware revision number. The Controller should only
	// use this field to decide if a firmware update should proceed. The convention is that
	// a higher number is a more recent release of firmware.
	VersionInfo uint16

	// NetSwitch contains Bits 14-8 of the 15 bit Port-Address are encoded into the bottom 7
	// bits of this field. This is used in combination with SubSwitch and SwIn[] or SwOut[]
	// to produce the full universe address.
	NetSwitch uint8

	// SubSwitch contains Bits 7-4 of the 15 bit Port-Address are encoded into the bottom 4
	// bits of this field. This is used in combination with NetSwitch and SwIn[] or SwOut[]
	// to produce the full universe address.
	SubSwitch uint8

	// Oem word describes the equipment vendor and the feature set available.
	Oem uint16

	// UBEAVersion contains the firmware version of the User Bios Extension Area (UBEA).
	// If the UBEA is not programmed, this field contains zero.
	UBEAVersion uint8

	// Status1 indicates General Status register containing bit fields as follows.
	Status1 code.Status1

	// ESTAmanufacturer contains a code used to represent equipment manufacturer.
	// They are assigned by ESTA. This field can be interpreted as two ASCII bytes
	// representing the manufacturer initials.
	ESTAmanufacturer string

	// ShortName for the Node. The Controller uses the ArtAddress packet to program this
	// string. Max length is 17 characters. This is a fixed length field, although the string
	// it contains can be shorter than the field.
	ShortName string

	// LongName for the Node. The Controller uses the ArtAddress packet to program this string.
	// Max length is 63. This is a fixed length field, although the string it contains can be
	// shorter than the field.
	LongName string

	// NodeReport is a textual report of the Node’s operating status or operational errors.
	// It is primarily intended for ‘engineering’ data.
	NodeReport [64]code.NodeReportCode

	// NumPorts describes the number of input or output ports. If number of inputs is not
	// equal to number of outputs, the largest value is taken. Zero is a legal value if no
	// input or output ports are implemented. The maximum value is 4. Nodes can ignore this
	// field as the information is implicit in PortTypes.
	NumPorts uint16

	// PortTypes defines the operation and protocol of each channel
	PortTypes [4]code.PortType

	// GoodInput defines input status of the node
	GoodInput [4]code.GoodInput

	// GoodOutput defines output status of the node
	GoodOutput [4]code.GoodOutput

	// TODO (4x uint8, array)
	SwIn [4]uint8

	// TODO (4x uint8, array)
	SwOut [4]uint8

	// SwVideo is set to 00 when video display is showing local data. Set to 01 when video
	// is showing ethernet data. The field is now deprecated
	SwVideo uint8

	// SwMacro shows if the Node supports macro key inputs, this byte represents the trigger values.
	SwMacro uint8

	// SwRemote show if the Node supports remote trigger inputs, this byte represents the trigger values.
	SwRemote uint8

	// spare bytes
	spare [3]byte

	// Style code defines the equipment style of the device.
	Style code.StyleCode

	// Macaddress of the Node. Set to zero if node cannot supply this information.
	Macaddress net.HardwareAddr

	// BindIP is the IP of the root device if this unit is part of a larger or modular product.
	BindIP net.IP

	// BindIndex represents the order of bound devices. A lower number means closer to root device.
	// A value of 1 means root device.
	BindIndex uint8

	// Status2 indicates Product capabilities
	Status2 code.Status2

	// filler bytes. Transmit as zero. For future expansion.
	filler [26]byte
}

// NewArtPollReplyPacket returns a new ArtPollReply Packet
func NewArtPollReplyPacket() *ArtPollReplyPacket {
	return &ArtPollReplyPacket{
		Header: Header{
			OpCode: code.OpPoll,
			id:     ArtNet,
		},
	}
}

// MarshalBinary marshals an ArtPollReplyPacket into a byte slice.
// TODO
func (p *ArtPollReplyPacket) MarshalBinary() ([]byte, error) {
	return nil, p.validate()
}

// UnmarshalBinary unmarshals the contents of a byte slice into an ArtPollReplyPacket.
func (p *ArtPollReplyPacket) UnmarshalBinary(b []byte) error {
	if err := p.Header.unmarshal(b[:10]); err != nil {
		return err
	}
	if len(b) != 238 {
		return fmt.Errorf("invalid packet length received. want: 238, got: %d", len(b))
	}

	p.IPAddress = b[10:14]
	p.Port = uint16(b[14]) | uint16(b[15])<<8
	p.VersionInfo = uint16(b[16]) | uint16(b[17])<<8

	p.NetSwitch = b[18]
	p.SubSwitch = b[19]
	p.Oem = uint16(b[20]) | uint16(b[21])<<8

	p.UBEAVersion = b[22]
	p.Status1 = code.Status1(b[23])

	man := []byte{uint8(b[24] & 0xff), uint8(uint16(b[25]) << 8)}
	for _, c := range man {
		if c == 0x00 {
			break
		}
		p.ESTAmanufacturer += string(c)
	}

	for _, c := range b[26:44] {
		if c == 0x00 {
			break
		}
		p.ShortName += string(c)
	}

	for _, c := range b[44:108] {
		if c == 0x00 {
			break
		}
		p.LongName += string(c)
	}

	for i, r := range b[108:172] {
		p.NodeReport[i] = code.NodeReportCode(r)
	}

	p.NumPorts = uint16(b[172]) | uint16(b[173])<<8
	for i, r := range b[174:178] {
		p.PortTypes[i] = code.PortType(r)
	}

	for i, r := range b[178:182] {
		p.GoodInput[i] = code.GoodInput(r)
	}

	for i, r := range b[182:186] {
		p.GoodOutput[i] = code.GoodOutput(r)
	}

	for i, r := range b[186:190] {
		p.SwIn[i] = r
	}

	for i, r := range b[190:194] {
		p.SwOut[i] = r
	}
	p.SwVideo = b[194]
	p.SwMacro = b[195]
	p.SwRemote = b[196]
	p.spare = [3]byte{b[197], b[198], b[199]}
	p.Style = code.StyleCode(b[200])

	p.Macaddress = b[200:206]
	p.BindIP = b[206:210]
	p.BindIndex = b[210]
	p.Status2 = code.Status2(b[211])

	for i := 0; i < 26; i++ {
		p.filler[i] = b[212+i]
	}

	return p.validate()
}

// artPacket is an empty method to sattisfy the ArtNetPacket interface.
func (p *ArtPollReplyPacket) validate() error {
	if p.OpCode != code.OpPollReply {
		return errInvalidOpCode
	}
	if !code.ValidStyle(p.Style) {
		return errInvalidStyle
	}
	return nil
}
<commit_msg>Fix OpCode for PollReply packet<commit_after>package packet

import (
	"fmt"
	"net"

	"github.com/jsimonetti/go-artnet/packet/code"
)

var _ ArtNetPacket = &ArtPollReplyPacket{}

// ArtPollReplyPacket contains an ArtPollReply Packet.
type ArtPollReplyPacket struct {
	// Inherit the Header header
	Header

	// IPAddress is the Node’s IPv4 address. When binding is implemented, bound nodes may
	// share the root node’s IP Address and the BindIndex is used to differentiate the nodes.
	IPAddress net.IP

	// Port is always 0x1936 Transmitted low byte first.
	Port uint16

	// VersionInfo contains the Node’s firmware revision number. The Controller should only
	// use this field to decide if a firmware update should proceed. The convention is that
	// a higher number is a more recent release of firmware.
	VersionInfo uint16

	// NetSwitch contains Bits 14-8 of the 15 bit Port-Address are encoded into the bottom 7
	// bits of this field. This is used in combination with SubSwitch and SwIn[] or SwOut[]
	// to produce the full universe address.
	NetSwitch uint8

	// SubSwitch contains Bits 7-4 of the 15 bit Port-Address are encoded into the bottom 4
	// bits of this field. This is used in combination with NetSwitch and SwIn[] or SwOut[]
	// to produce the full universe address.
	SubSwitch uint8

	// Oem word describes the equipment vendor and the feature set available.
	Oem uint16

	// UBEAVersion contains the firmware version of the User Bios Extension Area (UBEA).
	// If the UBEA is not programmed, this field contains zero.
	UBEAVersion uint8

	// Status1 indicates General Status register containing bit fields as follows.
	Status1 code.Status1

	// ESTAmanufacturer contains a code used to represent equipment manufacturer.
	// They are assigned by ESTA. This field can be interpreted as two ASCII bytes
	// representing the manufacturer initials.
	ESTAmanufacturer string

	// ShortName for the Node. The Controller uses the ArtAddress packet to program this
	// string. Max length is 17 characters. This is a fixed length field, although the string
	// it contains can be shorter than the field.
	ShortName string

	// LongName for the Node. The Controller uses the ArtAddress packet to program this string.
	// Max length is 63. This is a fixed length field, although the string it contains can be
	// shorter than the field.
	LongName string

	// NodeReport is a textual report of the Node’s operating status or operational errors.
	// It is primarily intended for ‘engineering’ data.
	NodeReport [64]code.NodeReportCode

	// NumPorts describes the number of input or output ports. If number of inputs is not
	// equal to number of outputs, the largest value is taken. Zero is a legal value if no
	// input or output ports are implemented. The maximum value is 4. Nodes can ignore this
	// field as the information is implicit in PortTypes.
	NumPorts uint16

	// PortTypes defines the operation and protocol of each channel
	PortTypes [4]code.PortType

	// GoodInput defines input status of the node
	GoodInput [4]code.GoodInput

	// GoodOutput defines output status of the node
	GoodOutput [4]code.GoodOutput

	// TODO (4x uint8, array)
	SwIn [4]uint8

	// TODO (4x uint8, array)
	SwOut [4]uint8

	// SwVideo is set to 00 when video display is showing local data. Set to 01 when video
	// is showing ethernet data. The field is now deprecated
	SwVideo uint8

	// SwMacro shows if the Node supports macro key inputs, this byte represents the trigger values.
	SwMacro uint8

	// SwRemote show if the Node supports remote trigger inputs, this byte represents the trigger values.
	SwRemote uint8

	// spare bytes
	spare [3]byte

	// Style code defines the equipment style of the device.
	Style code.StyleCode

	// Macaddress of the Node. Set to zero if node cannot supply this information.
	Macaddress net.HardwareAddr

	// BindIP is the IP of the root device if this unit is part of a larger or modular product.
	BindIP net.IP

	// BindIndex represents the order of bound devices. A lower number means closer to root device.
	// A value of 1 means root device.
	BindIndex uint8

	// Status2 indicates Product capabilities
	Status2 code.Status2

	// filler bytes. Transmit as zero. For future expansion.
	filler [26]byte
}

// NewArtPollReplyPacket returns a new ArtPollReply Packet
func NewArtPollReplyPacket() *ArtPollReplyPacket {
	return &ArtPollReplyPacket{
		Header: Header{
			OpCode: code.OpPollReply,
			id:     ArtNet,
		},
	}
}

// MarshalBinary marshals an ArtPollReplyPacket into a byte slice.
// TODO
func (p *ArtPollReplyPacket) MarshalBinary() ([]byte, error) {
	return nil, p.validate()
}

// UnmarshalBinary unmarshals the contents of a byte slice into an ArtPollReplyPacket.
func (p *ArtPollReplyPacket) UnmarshalBinary(b []byte) error {
	if err := p.Header.unmarshal(b[:10]); err != nil {
		return err
	}
	if len(b) != 238 {
		return fmt.Errorf("invalid packet length received. want: 238, got: %d", len(b))
	}

	p.IPAddress = b[10:14]
	p.Port = uint16(b[14]) | uint16(b[15])<<8
	p.VersionInfo = uint16(b[16]) | uint16(b[17])<<8

	p.NetSwitch = b[18]
	p.SubSwitch = b[19]
	p.Oem = uint16(b[20]) | uint16(b[21])<<8

	p.UBEAVersion = b[22]
	p.Status1 = code.Status1(b[23])

	man := []byte{uint8(b[24] & 0xff), uint8(uint16(b[25]) << 8)}
	for _, c := range man {
		if c == 0x00 {
			break
		}
		p.ESTAmanufacturer += string(c)
	}

	for _, c := range b[26:44] {
		if c == 0x00 {
			break
		}
		p.ShortName += string(c)
	}

	for _, c := range b[44:108] {
		if c == 0x00 {
			break
		}
		p.LongName += string(c)
	}

	for i, r := range b[108:172] {
		p.NodeReport[i] = code.NodeReportCode(r)
	}

	p.NumPorts = uint16(b[172]) | uint16(b[173])<<8
	for i, r := range b[174:178] {
		p.PortTypes[i] = code.PortType(r)
	}

	for i, r := range b[178:182] {
		p.GoodInput[i] = code.GoodInput(r)
	}

	for i, r := range b[182:186] {
		p.GoodOutput[i] = code.GoodOutput(r)
	}

	for i, r := range b[186:190] {
		p.SwIn[i] = r
	}

	for i, r := range b[190:194] {
		p.SwOut[i] = r
	}
	p.SwVideo = b[194]
	p.SwMacro = b[195]
	p.SwRemote = b[196]
	p.spare = [3]byte{b[197], b[198], b[199]}
	p.Style = code.StyleCode(b[200])

	p.Macaddress = b[200:206]
	p.BindIP = b[206:210]
	p.BindIndex = b[210]
	p.Status2 = code.Status2(b[211])

	for i := 0; i < 26; i++ {
		p.filler[i] = b[212+i]
	}

	return p.validate()
}

// artPacket is an empty method to sattisfy the ArtNetPacket interface.
func (p *ArtPollReplyPacket) validate() error {
	if p.OpCode != code.OpPollReply {
		return errInvalidOpCode
	}
	if !code.ValidStyle(p.Style) {
		return errInvalidStyle
	}
	return nil
}
<|endoftext|>


<commit_before>package gutscript

import (
	"fmt"
	"unicode"
)

var LexKeywords = map[string]int{
	"if":      T_IF,
	"class":   T_CLASS,
	"for":     T_FOR,
	"foreach": T_FOREACH,
	"else":    T_ELSE,
	"elseif":  T_ELSEIF,
	"echo":    T_ECHO,
	"is":      T_IS,
	"return":  T_RETURN,
	"extends": T_EXTENDS,
	"does":    T_DOES,
}

func (l *GutsLex) emitIfKeywordMatches() bool {
	l.remember()
	for keyword, typ := range LexKeywords {
		var match bool = true
	next_keyword:
		for _, sc := range keyword {
			c := l.next()
			if c == eof {
				match = false
				break next_keyword
			}
			if sc != c {
				match = false
				break next_keyword
			}
		}
		if match {
			c := l.next()
			if c == '\n' || c == eof || c == ' ' || c == '\t' || unicode.IsSymbol(c) {
				l.backup()
				l.emit(TokenType(typ))
				return true
			}
		}
		l.rollback()
	}
	return false
}

func lexStartLine(l *GutsLex) stateFn {
	// default start state.
	return lexStart
}

func lexStart(l *GutsLex) stateFn {
	var c rune = l.peek()
	if unicode.IsDigit(c) {
		return lexNumber
	} else if l.emitIfMatch("->", T_FUNCTION_GLYPH) {
		return lexStart
	} else if l.emitIfMatch("::", T_FUNCTION_PROTOTYPE) {
		return lexStart
	} else if l.consumeIfMatch("//") {
		return lexOnelineComment
	} else if l.consumeIfMatch("/*") {
		return lexComment
	} else if c == '-' {
		l.next()
		l.emit(TokenType(c))
		return lexStart
	} else if c == ' ' || c == '\t' {
		// return lexSpaces
		return lexIgnoreSpaces
	} else if c == '\n' || c == '\r' {
		// if there is a new line, check the next line is indent or outdent,
		// if there is no spaces/indent in the next line, then it should be outdent.
		l.Line++
		c = l.next() // take the the line break char

		// skip multiple newline at one time
		// sometimes we wrote:
		// --->a = 3$
		// $
		// --->b = 10$
		// and this should be in the same block.
		for c == '\n' {
			c = l.next()
		}
		l.backup()

		// emit a newline (can be the end of block)
		// l.emit(T_NEWLINE)

		// c = l.peek()
		if c == eof {
			// if we're in an indent block, and it's the end of file.
			// we should treat the newline as a block end.
			if l.space > 0 {
				l.emit(T_OUTDENT)
			} else {
				l.ignore()
			}
			return nil
		}

		// reset space info
		l.lastSpace = l.space
		l.space = 0

		// calculate spaces
		c = l.next()
		for c == ' ' || c == '\t' {
			l.space++
			c = l.next()
		}
		l.backup()
		if l.space == l.lastSpace {
			l.emit(T_NEWLINE)
		} else if l.space < l.lastSpace {
			l.emit(T_OUTDENT)
			l.IndentLevel--
			l.emit(T_NEWLINE) // means end of statement
		} else if l.space > l.lastSpace {
			l.IndentLevel++
			l.emit(T_INDENT)
		}
		return lexStart
	} else if l.emitIfMatch("&&", T_BOOLEAN_AND) || l.emitIfMatch("||", T_BOOLEAN_OR) {
		return lexStart
	} else if l.emitIfMatch("==", T_EQUAL) || l.emitIfMatch(">=", T_GT_EQUAL) || l.emitIfMatch("<=", T_LT_EQUAL) {
		return lexStart
	} else if c == '=' && l.peekMore(2) != '=' {
		l.next()
		l.emit(TokenType(c))
		return lexStart
	} else if l.accept("+-*|&[]{}()<>,") {
		l.emit(TokenType(c))
		return lexStart
	} else if l.lastTokenType == T_NUMBER && l.emitIfMatch("..", T_RANGE_OPERATOR) {
		return lexStart
	} else if c == '"' || c == '\'' {
		return lexString
	} else if l.emitIfKeywordMatches() {
		return lexStart
	} else if unicode.IsLetter(c) {
		return lexIdentifier
	} else if c == eof {
		if l.IndentLevel > 0 {
			for i := 0; i < l.IndentLevel; i++ {
				l.emit(T_OUTDENT)
				l.emit(T_NEWLINE)
			}
		}
		// l.emit(T_EOF)
		return nil
	} else {
		panic(fmt.Errorf("unknown token %c\n", c))
		return nil
	}
	return nil
}

// Lex double quote string
func lexString(l *GutsLex) stateFn {
	var q rune = l.next() // the quote char

	l.ignore()

	var c rune
	for {
		c = l.next()
		if c == eof {
			panic("unexpected end of string.")
			break
		}
		// un-escaped string quote
		if c == q {
			if l.last() != '\\' {
				break
			}
		}
	}
	l.backup()
	l.emit(T_STRING)
	l.next()
	l.ignore() // ignore the quote rune
	return lexStart

}

func lexComment(l *GutsLex) stateFn {
	var c rune
	for {
		c = l.next()
		if c == eof {
			break
		}
		if c == '*' && l.peek() == '/' {
			break
		}
	}
	// consume the "/" char
	l.next()
	l.emit(T_COMMENT)
	return lexStart
}

func lexOnelineComment(l *GutsLex) stateFn {
	var c rune
	for {
		c = l.next()
		if c == '\n' || c == eof {
			break
		}
	}
	l.backup()
	l.emit(T_ONELINE_COMMENT)
	return lexStartLine
}

func lexIdentifier(l *GutsLex) stateFn {
	for {
		c := l.next()
		if unicode.IsLetter(c) || unicode.IsDigit(c) {
		} else {
			break
		}
	}
	l.backup()
	l.emit(T_IDENTIFIER)
	return lexStart
}

func lexIgnoreSpaces(l *GutsLex) stateFn {
	var c rune
	for {
		c = l.next()
		if c == eof {
			l.ignore()
			return nil
		}
		if c != ' ' {
			break
		}
	}
	l.backup()
	l.ignore()
	return lexStart
}

func lexIndentSpaces(l *GutsLex) stateFn {
	l.space = 0
	var c rune = l.next()
	// if there are one or more space in the next line
	// consume the spaces and guess it's
	// indent/outdent/newline
	for c == ' ' || c == '\t' {
		l.space++
		c = l.next()
	}
	l.backup()
	if l.space == l.lastSpace {
		l.emit(T_NEWLINE)
	} else if l.space < l.lastSpace {
		l.emit(T_OUTDENT)
		l.emit(T_NEWLINE)
		l.IndentLevel--
	} else if l.space > l.lastSpace {
		l.emit(T_INDENT)
		l.IndentLevel++
	}
	return lexStart
}

func lexSpaces(l *GutsLex) stateFn {
	for c := l.next(); c == ' ' || c == '\t'; {
	}
	l.backup()
	l.emit(T_SPACE)
	return lexStart
}

func lexFloating(l *GutsLex) stateFn {
	for {
		c := l.next()
		if !unicode.IsDigit(c) {
			break
		}
	}
	l.backup()
	l.emit(T_FLOATING)
	return lexStart
}

func lexNumber(l *GutsLex) stateFn {
	var c rune
	for c = l.next(); true; c = l.next() {
		if unicode.IsDigit(c) {
			continue
		} else if c == '.' && unicode.IsDigit(l.peek()) {
			return lexFloating
		} else {
			break
		}
	}
	l.backup()
	l.emit(T_NUMBER)
	return lexStart
}
<commit_msg>fix lexer for eof<commit_after>package gutscript

import (
	"fmt"
	"unicode"
)

var LexKeywords = map[string]int{
	"if":      T_IF,
	"class":   T_CLASS,
	"for":     T_FOR,
	"foreach": T_FOREACH,
	"else":    T_ELSE,
	"elseif":  T_ELSEIF,
	"echo":    T_ECHO,
	"is":      T_IS,
	"return":  T_RETURN,
	"extends": T_EXTENDS,
	"does":    T_DOES,
}

func (l *GutsLex) emitIfKeywordMatches() bool {
	l.remember()
	for keyword, typ := range LexKeywords {
		var match bool = true
	next_keyword:
		for _, sc := range keyword {
			c := l.next()
			if c == eof {
				match = false
				break next_keyword
			}
			if sc != c {
				match = false
				break next_keyword
			}
		}
		if match {
			c := l.next()
			if c == '\n' || c == eof || c == ' ' || c == '\t' || unicode.IsSymbol(c) {
				l.backup()
				l.emit(TokenType(typ))
				return true
			}
		}
		l.rollback()
	}
	return false
}

func lexStartLine(l *GutsLex) stateFn {
	// default start state.
	return lexStart
}

func lexStart(l *GutsLex) stateFn {
	var c rune = l.peek()
	if unicode.IsDigit(c) {
		return lexNumber
	} else if l.emitIfMatch("->", T_FUNCTION_GLYPH) {
		return lexStart
	} else if l.emitIfMatch("::", T_FUNCTION_PROTOTYPE) {
		return lexStart
	} else if l.consumeIfMatch("//") {
		return lexOnelineComment
	} else if l.consumeIfMatch("/*") {
		return lexComment
	} else if c == '-' {
		l.next()
		l.emit(TokenType(c))
		return lexStart
	} else if c == ' ' || c == '\t' {
		// return lexSpaces
		return lexIgnoreSpaces
	} else if c == '\n' || c == '\r' {
		// if there is a new line, check the next line is indent or outdent,
		// if there is no spaces/indent in the next line, then it should be outdent.
		l.Line++
		c = l.next() // take the the line break char

		// skip multiple newline at one time
		// sometimes we wrote:
		// --->a = 3$
		// $
		// --->b = 10$
		// and this should be in the same block.
		for c == '\n' {
			c = l.next()
		}
		l.backup()

		// emit a newline (can be the end of block)
		// l.emit(T_NEWLINE)

		// c = l.peek()
		if c == eof {
			// if we're in an indent block, and it's the end of file.
			// we should treat the newline as a block end.
			if l.space > 0 {
				l.emit(T_OUTDENT)
				l.IndentLevel--
			} else {
				l.ignore()
			}
			return lexStart
		}

		// reset space info
		l.lastSpace = l.space
		l.space = 0

		// calculate spaces
		c = l.next()
		for c == ' ' || c == '\t' {
			l.space++
			c = l.next()
		}
		l.backup()
		if l.space == l.lastSpace {
			l.emit(T_NEWLINE)
		} else if l.space < l.lastSpace {
			l.emit(T_OUTDENT)
			l.IndentLevel--
			l.emit(T_NEWLINE) // means end of statement
		} else if l.space > l.lastSpace {
			l.IndentLevel++
			l.emit(T_INDENT)
		}
		return lexStart
	} else if l.emitIfMatch("&&", T_BOOLEAN_AND) || l.emitIfMatch("||", T_BOOLEAN_OR) {
		return lexStart
	} else if l.emitIfMatch("==", T_EQUAL) || l.emitIfMatch(">=", T_GT_EQUAL) || l.emitIfMatch("<=", T_LT_EQUAL) {
		return lexStart
	} else if c == '=' && l.peekMore(2) != '=' {
		l.next()
		l.emit(TokenType(c))
		return lexStart
	} else if l.accept("+-*|&[]{}()<>,") {
		l.emit(TokenType(c))
		return lexStart
	} else if l.lastTokenType == T_NUMBER && l.emitIfMatch("..", T_RANGE_OPERATOR) {
		return lexStart
	} else if c == '"' || c == '\'' {
		return lexString
	} else if l.emitIfKeywordMatches() {
		return lexStart
	} else if unicode.IsLetter(c) {
		return lexIdentifier
	} else if c == eof {
		if l.IndentLevel > 0 {
			for i := 0; i < l.IndentLevel; i++ {
				l.emit(T_OUTDENT)
				l.emit(T_NEWLINE)
			}
		}
		// l.emit(T_EOF)
		return nil
	} else {
		panic(fmt.Errorf("unknown token %c\n", c))
		return nil
	}
	return nil
}

// Lex double quote string
func lexString(l *GutsLex) stateFn {
	var q rune = l.next() // the quote char

	l.ignore()

	var c rune
	for {
		c = l.next()
		if c == eof {
			panic("unexpected end of string.")
			break
		}
		// un-escaped string quote
		if c == q {
			if l.last() != '\\' {
				break
			}
		}
	}
	l.backup()
	l.emit(T_STRING)
	l.next()
	l.ignore() // ignore the quote rune
	return lexStart

}

func lexComment(l *GutsLex) stateFn {
	var c rune
	for {
		c = l.next()
		if c == eof {
			break
		}
		if c == '*' && l.peek() == '/' {
			break
		}
	}
	// consume the "/" char
	l.next()
	l.emit(T_COMMENT)
	return lexStart
}

func lexOnelineComment(l *GutsLex) stateFn {
	var c rune
	for {
		c = l.next()
		if c == '\n' || c == eof {
			break
		}
	}
	l.backup()
	l.emit(T_ONELINE_COMMENT)
	return lexStartLine
}

func lexIdentifier(l *GutsLex) stateFn {
	for {
		c := l.next()
		if unicode.IsLetter(c) || unicode.IsDigit(c) {
		} else {
			break
		}
	}
	l.backup()
	l.emit(T_IDENTIFIER)
	return lexStart
}

func lexIgnoreSpaces(l *GutsLex) stateFn {
	var c rune
	for {
		c = l.next()
		if c == eof {
			l.ignore()
			return lexStart
		}
		if c != ' ' {
			break
		}
	}
	l.backup()
	l.ignore()
	return lexStart
}

func lexIndentSpaces(l *GutsLex) stateFn {
	l.space = 0
	var c rune = l.next()
	// if there are one or more space in the next line
	// consume the spaces and guess it's
	// indent/outdent/newline
	for c == ' ' || c == '\t' {
		l.space++
		c = l.next()
	}
	l.backup()
	if l.space == l.lastSpace {
		l.emit(T_NEWLINE)
	} else if l.space < l.lastSpace {
		l.emit(T_OUTDENT)
		l.emit(T_NEWLINE)
		l.IndentLevel--
	} else if l.space > l.lastSpace {
		l.emit(T_INDENT)
		l.IndentLevel++
	}
	return lexStart
}

func lexSpaces(l *GutsLex) stateFn {
	for c := l.next(); c == ' ' || c == '\t'; {
	}
	l.backup()
	l.emit(T_SPACE)
	return lexStart
}

func lexFloating(l *GutsLex) stateFn {
	for {
		c := l.next()
		if !unicode.IsDigit(c) {
			break
		}
	}
	l.backup()
	l.emit(T_FLOATING)
	return lexStart
}

func lexNumber(l *GutsLex) stateFn {
	var c rune
	for c = l.next(); true; c = l.next() {
		if unicode.IsDigit(c) {
			continue
		} else if c == '.' && unicode.IsDigit(l.peek()) {
			return lexFloating
		} else {
			break
		}
	}
	l.backup()
	l.emit(T_NUMBER)
	return lexStart
}
<|endoftext|>


<commit_before>package pingdom

import (
	"fmt"
	"sort"
	"strconv"
)

// HttpCheck represents a Pingdom http check.
type HttpCheck struct {
	Name                     string            `json:"name"`
	Hostname                 string            `json:"hostname,omitempty"`
	Resolution               int               `json:"resolution,omitempty"`
	Paused                   bool              `json:"paused,omitempty"`
	SendNotificationWhenDown int               `json:"sendnotificationwhendown,omitempty"`
	NotifyAgainEvery         int               `json:"notifyagainevery,omitempty"`
	NotifyWhenBackup         bool              `json:"notifywhenbackup,omitempty"`
	Url                      string            `json:"url,omitempty"`
	Encryption               bool              `json:"encryption,omitempty"`
	Port                     int               `json:"port,omitempty"`
	Username                 string            `json:"username,omitempty"`
	Password                 string            `json:"password,omitempty"`
	ShouldContain            string            `json:"shouldcontain,omitempty"`
	ShouldNotContain         string            `json:"shouldnotcontain,omitempty"`
	PostData                 string            `json:"postdata,omitempty"`
	RequestHeaders           map[string]string `json:"requestheaders,omitempty"`
	IntegrationIds           []int             `json:"integrationids,omitempty"`
	Tags                     string            `json:"tags,omitempty"`
	ProbeFilters             string            `json:"probe_filters,omitempty"`
	UserIds                  []int             `json:"userids,omitempty"`
	TeamIds                  []int             `json:"teamids,omitempty"`
}

// PingCheck represents a Pingdom ping check
type PingCheck struct {
	Name                     string `json:"name"`
	Hostname                 string `json:"hostname,omitempty"`
	Resolution               int    `json:"resolution,omitempty"`
	Paused                   bool   `json:"paused,omitempty"`
	SendNotificationWhenDown int    `json:"sendnotificationwhendown,omitempty"`
	NotifyAgainEvery         int    `json:"notifyagainevery,omitempty"`
	NotifyWhenBackup         bool   `json:"notifywhenbackup,omitempty"`
	IntegrationIds           []int  `json:"integrationids,omitempty"`
	ProbeFilters             string `json:"probe_filters,omitempty"`
	UserIds                  []int  `json:"userids,omitempty"`
	TeamIds                  []int  `json:"teamids,omitempty"`
}

// TCPCheck represents a Pingdom TCP check
type TCPCheck struct {
	Name                     string `json:"name"`
	Hostname                 string `json:"hostname,omitempty"`
	Resolution               int    `json:"resolution,omitempty"`
	Paused                   bool   `json:"paused,omitempty"`
	SendNotificationWhenDown int    `json:"sendnotificationwhendown,omitempty"`
	NotifyAgainEvery         int    `json:"notifyagainevery,omitempty"`
	NotifyWhenBackup         bool   `json:"notifywhenbackup,omitempty"`
	IntegrationIds           []int  `json:"integrationids,omitempty"`
	ProbeFilters             string `json:"probe_filters,omitempty"`
	UserIds                  []int  `json:"userids,omitempty"`
	TeamIds                  []int  `json:"teamids,omitempty"`
	Port                     int    `json:"port"`
	StringToSend             string `json:"stringtosend,omitempty"`
	StringToExpect           string `json:"stringtoexpect,omitempty"`
}

// Params returns a map of parameters for an HttpCheck that can be sent along
// with an HTTP PUT request
func (ck *HttpCheck) PutParams() map[string]string {
	m := map[string]string{
		"name":             ck.Name,
		"host":             ck.Hostname,
		"resolution":       strconv.Itoa(ck.Resolution),
		"paused":           strconv.FormatBool(ck.Paused),
		"notifyagainevery": strconv.Itoa(ck.NotifyAgainEvery),
		"notifywhenbackup": strconv.FormatBool(ck.NotifyWhenBackup),
		"url":              ck.Url,
		"encryption":       strconv.FormatBool(ck.Encryption),
		"postdata":         ck.PostData,
		"integrationids":   intListToCDString(ck.IntegrationIds),
		"tags":             ck.Tags,
		"probe_filters":    ck.ProbeFilters,
		"userids":          intListToCDString(ck.UserIds),
		"teamids":          intListToCDString(ck.TeamIds),
	}

	// Ignore zero values
	if ck.Port != 0 {
		m["port"] = strconv.Itoa(ck.Port)
	}

	if ck.SendNotificationWhenDown != 0 {
		m["sendnotificationwhendown"] = strconv.Itoa(ck.SendNotificationWhenDown)
	}

	// ShouldContain and ShouldNotContain are mutually exclusive.
	// But we must define one so they can be emptied if required.
	if ck.ShouldContain != "" {
		m["shouldcontain"] = ck.ShouldContain
	} else {
		m["shouldnotcontain"] = ck.ShouldNotContain
	}

	// Convert auth
	if ck.Username != "" {
		m["auth"] = fmt.Sprintf("%s:%s", ck.Username, ck.Password)
	}

	// Convert headers
	var headers []string
	for k := range ck.RequestHeaders {
		headers = append(headers, k)
	}
	sort.Strings(headers)
	for i, k := range headers {
		m[fmt.Sprintf("requestheader%d", i)] = fmt.Sprintf("%s:%s", k, ck.RequestHeaders[k])
	}

	return m
}

// Params returns a map of parameters for an HttpCheck that can be sent along
// with an HTTP POST request. They are the same than the Put params, but
// empty strings cleared out, to avoid Pingdom API reject the request.
func (ck *HttpCheck) PostParams() map[string]string {
	params := ck.PutParams()

	for k, v := range params {
		if v == "" {
			delete(params, k)
		}
	}
	params["type"] = "http"

	return params
}

// Determine whether the HttpCheck contains valid fields.  This can be
// used to guard against sending illegal values to the Pingdom API
func (ck *HttpCheck) Valid() error {
	if ck.Name == "" {
		return fmt.Errorf("Invalid value for `Name`.  Must contain non-empty string")
	}

	if ck.Hostname == "" {
		return fmt.Errorf("Invalid value for `Hostname`.  Must contain non-empty string")
	}

	if ck.Resolution != 1 && ck.Resolution != 5 && ck.Resolution != 15 &&
		ck.Resolution != 30 && ck.Resolution != 60 {
		return fmt.Errorf("Invalid value %v for `Resolution`.  Allowed values are [1,5,15,30,60].", ck.Resolution)
	}

	if ck.ShouldContain != "" && ck.ShouldNotContain != "" {
		return fmt.Errorf("`ShouldContain` and `ShouldNotContain` must not be declared at the same time")
	}

	return nil
}

// Params returns a map of parameters for a PingCheck that can be sent along
// with an HTTP PUT request
func (ck *PingCheck) PutParams() map[string]string {
	m := map[string]string{
		"name":             ck.Name,
		"host":             ck.Hostname,
		"resolution":       strconv.Itoa(ck.Resolution),
		"paused":           strconv.FormatBool(ck.Paused),
		"notifyagainevery": strconv.Itoa(ck.NotifyAgainEvery),
		"notifywhenbackup": strconv.FormatBool(ck.NotifyWhenBackup),
		"integrationids":   intListToCDString(ck.IntegrationIds),
		"probe_filters":    ck.ProbeFilters,
		"userids":          intListToCDString(ck.UserIds),
		"teamids":          intListToCDString(ck.TeamIds),
	}

	if ck.SendNotificationWhenDown != 0 {
		m["sendnotificationwhendown"] = strconv.Itoa(ck.SendNotificationWhenDown)
	}

	return m
}

// Params returns a map of parameters for a PingCheck that can be sent along
// with an HTTP POST request. Same as PUT.
func (ck *PingCheck) PostParams() map[string]string {
	params := ck.PutParams()

	for k, v := range params {
		if v == "" {
			delete(params, k)
		}
	}

	params["type"] = "ping"
	return params
}

// Determine whether the PingCheck contains valid fields.  This can be
// used to guard against sending illegal values to the Pingdom API
func (ck *PingCheck) Valid() error {
	if ck.Name == "" {
		return fmt.Errorf("Invalid value for `Name`.  Must contain non-empty string")
	}

	if ck.Hostname == "" {
		return fmt.Errorf("Invalid value for `Hostname`.  Must contain non-empty string")
	}

	if ck.Resolution != 1 && ck.Resolution != 5 && ck.Resolution != 15 &&
		ck.Resolution != 30 && ck.Resolution != 60 {
		return fmt.Errorf("Invalid value %v for `Resolution`.  Allowed values are [1,5,15,30,60].", ck.Resolution)
	}
	return nil
}

// Params returns a map of parameters for a TCPCheck that can be sent along
// with an HTTP PUT request
func (ck *TCPCheck) PutParams() map[string]string {
	m := map[string]string{
		"name":             ck.Name,
		"host":             ck.Hostname,
		"resolution":       strconv.Itoa(ck.Resolution),
		"paused":           strconv.FormatBool(ck.Paused),
		"notifyagainevery": strconv.Itoa(ck.NotifyAgainEvery),
		"notifywhenbackup": strconv.FormatBool(ck.NotifyWhenBackup),
		"integrationids":   intListToCDString(ck.IntegrationIds),
		"probe_filters":    ck.ProbeFilters,
		"userids":          intListToCDString(ck.UserIds),
		"teamids":          intListToCDString(ck.TeamIds),
		"port":             strconv.Itoa(ck.Port),
	}

	if ck.SendNotificationWhenDown != 0 {
		m["sendnotificationwhendown"] = strconv.Itoa(ck.SendNotificationWhenDown)
	}

	if ck.StringToSend != "" {
		m["stringtosend"] = ck.StringToSend
	}

	if ck.StringToExpect != "" {
		m["stringtoexpect"] = ck.StringToExpect
	}

	return m
}

// Params returns a map of parameters for a TCPCheck that can be sent along
// with an HTTP POST request. Same as PUT.
func (ck *TCPCheck) PostParams() map[string]string {
	params := ck.PutParams()

	for k, v := range params {
		if v == "" {
			delete(params, k)
		}
	}

	params["type"] = "tcp"
	return params
}

// Determine whether the TCPCheck contains valid fields.  This can be
// used to guard against sending illegal values to the Pingdom API
func (ck *TCPCheck) Valid() error {
	if ck.Name == "" {
		return fmt.Errorf("Invalid value for `Name`.  Must contain non-empty string")
	}

	if ck.Hostname == "" {
		return fmt.Errorf("Invalid value for `Hostname`.  Must contain non-empty string")
	}

	if ck.Resolution != 1 && ck.Resolution != 5 && ck.Resolution != 15 &&
		ck.Resolution != 30 && ck.Resolution != 60 {
		return fmt.Errorf("Invalid value %v for `Resolution`.  Allowed values are [1,5,15,30,60].", ck.Resolution)
	}

	if ck.Port < 1 {
		return fmt.Errorf("Invalid value for `Port`.  Must contain an integer >= 1")
	}

	return nil
}

func intListToCDString(integers []int) string {
	var CDString string
	for i, item := range integers {
		if i == 0 {
			CDString = strconv.Itoa(item)
		} else {
			CDString = fmt.Sprintf("%v,%d", CDString, item)
		}
	}
	return CDString
}
<commit_msg>Added support for tags on PingCheck and TCPCheck<commit_after>package pingdom

import (
	"fmt"
	"sort"
	"strconv"
)

// HttpCheck represents a Pingdom http check.
type HttpCheck struct {
	Name                     string            `json:"name"`
	Hostname                 string            `json:"hostname,omitempty"`
	Resolution               int               `json:"resolution,omitempty"`
	Paused                   bool              `json:"paused,omitempty"`
	SendNotificationWhenDown int               `json:"sendnotificationwhendown,omitempty"`
	NotifyAgainEvery         int               `json:"notifyagainevery,omitempty"`
	NotifyWhenBackup         bool              `json:"notifywhenbackup,omitempty"`
	Url                      string            `json:"url,omitempty"`
	Encryption               bool              `json:"encryption,omitempty"`
	Port                     int               `json:"port,omitempty"`
	Username                 string            `json:"username,omitempty"`
	Password                 string            `json:"password,omitempty"`
	ShouldContain            string            `json:"shouldcontain,omitempty"`
	ShouldNotContain         string            `json:"shouldnotcontain,omitempty"`
	PostData                 string            `json:"postdata,omitempty"`
	RequestHeaders           map[string]string `json:"requestheaders,omitempty"`
	IntegrationIds           []int             `json:"integrationids,omitempty"`
	Tags                     string            `json:"tags,omitempty"`
	ProbeFilters             string            `json:"probe_filters,omitempty"`
	UserIds                  []int             `json:"userids,omitempty"`
	TeamIds                  []int             `json:"teamids,omitempty"`
}

// PingCheck represents a Pingdom ping check
type PingCheck struct {
	Name                     string `json:"name"`
	Hostname                 string `json:"hostname,omitempty"`
	Resolution               int    `json:"resolution,omitempty"`
	Paused                   bool   `json:"paused,omitempty"`
	SendNotificationWhenDown int    `json:"sendnotificationwhendown,omitempty"`
	NotifyAgainEvery         int    `json:"notifyagainevery,omitempty"`
	NotifyWhenBackup         bool   `json:"notifywhenbackup,omitempty"`
	IntegrationIds           []int  `json:"integrationids,omitempty"`
	Tags                     string `json:"tags,omitempty"`
	ProbeFilters             string `json:"probe_filters,omitempty"`
	UserIds                  []int  `json:"userids,omitempty"`
	TeamIds                  []int  `json:"teamids,omitempty"`
}

// TCPCheck represents a Pingdom TCP check
type TCPCheck struct {
	Name                     string `json:"name"`
	Hostname                 string `json:"hostname,omitempty"`
	Resolution               int    `json:"resolution,omitempty"`
	Paused                   bool   `json:"paused,omitempty"`
	SendNotificationWhenDown int    `json:"sendnotificationwhendown,omitempty"`
	NotifyAgainEvery         int    `json:"notifyagainevery,omitempty"`
	NotifyWhenBackup         bool   `json:"notifywhenbackup,omitempty"`
	IntegrationIds           []int  `json:"integrationids,omitempty"`
	Tags                     string `json:"tags,omitempty"`
	ProbeFilters             string `json:"probe_filters,omitempty"`
	UserIds                  []int  `json:"userids,omitempty"`
	TeamIds                  []int  `json:"teamids,omitempty"`
	Port                     int    `json:"port"`
	StringToSend             string `json:"stringtosend,omitempty"`
	StringToExpect           string `json:"stringtoexpect,omitempty"`
}

// Params returns a map of parameters for an HttpCheck that can be sent along
// with an HTTP PUT request
func (ck *HttpCheck) PutParams() map[string]string {
	m := map[string]string{
		"name":             ck.Name,
		"host":             ck.Hostname,
		"resolution":       strconv.Itoa(ck.Resolution),
		"paused":           strconv.FormatBool(ck.Paused),
		"notifyagainevery": strconv.Itoa(ck.NotifyAgainEvery),
		"notifywhenbackup": strconv.FormatBool(ck.NotifyWhenBackup),
		"url":              ck.Url,
		"encryption":       strconv.FormatBool(ck.Encryption),
		"postdata":         ck.PostData,
		"integrationids":   intListToCDString(ck.IntegrationIds),
		"tags":             ck.Tags,
		"probe_filters":    ck.ProbeFilters,
		"userids":          intListToCDString(ck.UserIds),
		"teamids":          intListToCDString(ck.TeamIds),
	}

	// Ignore zero values
	if ck.Port != 0 {
		m["port"] = strconv.Itoa(ck.Port)
	}

	if ck.SendNotificationWhenDown != 0 {
		m["sendnotificationwhendown"] = strconv.Itoa(ck.SendNotificationWhenDown)
	}

	// ShouldContain and ShouldNotContain are mutually exclusive.
	// But we must define one so they can be emptied if required.
	if ck.ShouldContain != "" {
		m["shouldcontain"] = ck.ShouldContain
	} else {
		m["shouldnotcontain"] = ck.ShouldNotContain
	}

	// Convert auth
	if ck.Username != "" {
		m["auth"] = fmt.Sprintf("%s:%s", ck.Username, ck.Password)
	}

	// Convert headers
	var headers []string
	for k := range ck.RequestHeaders {
		headers = append(headers, k)
	}
	sort.Strings(headers)
	for i, k := range headers {
		m[fmt.Sprintf("requestheader%d", i)] = fmt.Sprintf("%s:%s", k, ck.RequestHeaders[k])
	}

	return m
}

// Params returns a map of parameters for an HttpCheck that can be sent along
// with an HTTP POST request. They are the same than the Put params, but
// empty strings cleared out, to avoid Pingdom API reject the request.
func (ck *HttpCheck) PostParams() map[string]string {
	params := ck.PutParams()

	for k, v := range params {
		if v == "" {
			delete(params, k)
		}
	}
	params["type"] = "http"

	return params
}

// Determine whether the HttpCheck contains valid fields.  This can be
// used to guard against sending illegal values to the Pingdom API
func (ck *HttpCheck) Valid() error {
	if ck.Name == "" {
		return fmt.Errorf("Invalid value for `Name`.  Must contain non-empty string")
	}

	if ck.Hostname == "" {
		return fmt.Errorf("Invalid value for `Hostname`.  Must contain non-empty string")
	}

	if ck.Resolution != 1 && ck.Resolution != 5 && ck.Resolution != 15 &&
		ck.Resolution != 30 && ck.Resolution != 60 {
		return fmt.Errorf("Invalid value %v for `Resolution`.  Allowed values are [1,5,15,30,60].", ck.Resolution)
	}

	if ck.ShouldContain != "" && ck.ShouldNotContain != "" {
		return fmt.Errorf("`ShouldContain` and `ShouldNotContain` must not be declared at the same time")
	}

	return nil
}

// Params returns a map of parameters for a PingCheck that can be sent along
// with an HTTP PUT request
func (ck *PingCheck) PutParams() map[string]string {
	m := map[string]string{
		"name":             ck.Name,
		"host":             ck.Hostname,
		"resolution":       strconv.Itoa(ck.Resolution),
		"paused":           strconv.FormatBool(ck.Paused),
		"notifyagainevery": strconv.Itoa(ck.NotifyAgainEvery),
		"notifywhenbackup": strconv.FormatBool(ck.NotifyWhenBackup),
		"integrationids":   intListToCDString(ck.IntegrationIds),
		"probe_filters":    ck.ProbeFilters,
		"userids":          intListToCDString(ck.UserIds),
		"teamids":          intListToCDString(ck.TeamIds),
	}

	if ck.SendNotificationWhenDown != 0 {
		m["sendnotificationwhendown"] = strconv.Itoa(ck.SendNotificationWhenDown)
	}

	return m
}

// Params returns a map of parameters for a PingCheck that can be sent along
// with an HTTP POST request. Same as PUT.
func (ck *PingCheck) PostParams() map[string]string {
	params := ck.PutParams()

	for k, v := range params {
		if v == "" {
			delete(params, k)
		}
	}

	params["type"] = "ping"
	return params
}

// Determine whether the PingCheck contains valid fields.  This can be
// used to guard against sending illegal values to the Pingdom API
func (ck *PingCheck) Valid() error {
	if ck.Name == "" {
		return fmt.Errorf("Invalid value for `Name`.  Must contain non-empty string")
	}

	if ck.Hostname == "" {
		return fmt.Errorf("Invalid value for `Hostname`.  Must contain non-empty string")
	}

	if ck.Resolution != 1 && ck.Resolution != 5 && ck.Resolution != 15 &&
		ck.Resolution != 30 && ck.Resolution != 60 {
		return fmt.Errorf("Invalid value %v for `Resolution`.  Allowed values are [1,5,15,30,60].", ck.Resolution)
	}
	return nil
}

// Params returns a map of parameters for a TCPCheck that can be sent along
// with an HTTP PUT request
func (ck *TCPCheck) PutParams() map[string]string {
	m := map[string]string{
		"name":             ck.Name,
		"host":             ck.Hostname,
		"resolution":       strconv.Itoa(ck.Resolution),
		"paused":           strconv.FormatBool(ck.Paused),
		"notifyagainevery": strconv.Itoa(ck.NotifyAgainEvery),
		"notifywhenbackup": strconv.FormatBool(ck.NotifyWhenBackup),
		"integrationids":   intListToCDString(ck.IntegrationIds),
		"probe_filters":    ck.ProbeFilters,
		"userids":          intListToCDString(ck.UserIds),
		"teamids":          intListToCDString(ck.TeamIds),
		"port":             strconv.Itoa(ck.Port),
	}

	if ck.SendNotificationWhenDown != 0 {
		m["sendnotificationwhendown"] = strconv.Itoa(ck.SendNotificationWhenDown)
	}

	if ck.StringToSend != "" {
		m["stringtosend"] = ck.StringToSend
	}

	if ck.StringToExpect != "" {
		m["stringtoexpect"] = ck.StringToExpect
	}

	return m
}

// Params returns a map of parameters for a TCPCheck that can be sent along
// with an HTTP POST request. Same as PUT.
func (ck *TCPCheck) PostParams() map[string]string {
	params := ck.PutParams()

	for k, v := range params {
		if v == "" {
			delete(params, k)
		}
	}

	params["type"] = "tcp"
	return params
}

// Determine whether the TCPCheck contains valid fields.  This can be
// used to guard against sending illegal values to the Pingdom API
func (ck *TCPCheck) Valid() error {
	if ck.Name == "" {
		return fmt.Errorf("Invalid value for `Name`.  Must contain non-empty string")
	}

	if ck.Hostname == "" {
		return fmt.Errorf("Invalid value for `Hostname`.  Must contain non-empty string")
	}

	if ck.Resolution != 1 && ck.Resolution != 5 && ck.Resolution != 15 &&
		ck.Resolution != 30 && ck.Resolution != 60 {
		return fmt.Errorf("Invalid value %v for `Resolution`.  Allowed values are [1,5,15,30,60].", ck.Resolution)
	}

	if ck.Port < 1 {
		return fmt.Errorf("Invalid value for `Port`.  Must contain an integer >= 1")
	}

	return nil
}

func intListToCDString(integers []int) string {
	var CDString string
	for i, item := range integers {
		if i == 0 {
			CDString = strconv.Itoa(item)
		} else {
			CDString = fmt.Sprintf("%v,%d", CDString, item)
		}
	}
	return CDString
}
<|endoftext|>


<commit_before>package autonat

import (
	"context"
	"sync"
	"time"

	pb "github.com/libp2p/go-libp2p-autonat/pb"

	ggio "github.com/gogo/protobuf/io"
	libp2p "github.com/libp2p/go-libp2p"
	autonat "github.com/libp2p/go-libp2p-autonat"
	host "github.com/libp2p/go-libp2p-host"
	inet "github.com/libp2p/go-libp2p-net"
	peer "github.com/libp2p/go-libp2p-peer"
	pstore "github.com/libp2p/go-libp2p-peerstore"
	ma "github.com/multiformats/go-multiaddr"
	manet "github.com/multiformats/go-multiaddr-net"
)

const P_CIRCUIT = 290

var (
	AutoNATServiceDialTimeout   = 15 * time.Second
	AutoNATServiceResetInterval = 1 * time.Minute

	AutoNATServiceThrottle = 3
)

// AutoNATService provides NAT autodetection services to other peers
type AutoNATService struct {
	ctx    context.Context
	dialer host.Host

	// rate limiter
	mx   sync.Mutex
	reqs map[peer.ID]int
}

// NewAutoNATService creates a new AutoNATService instance attached to a host
func NewAutoNATService(ctx context.Context, h host.Host, opts ...libp2p.Option) (*AutoNATService, error) {
	opts = append(opts, libp2p.NoListenAddrs)
	dialer, err := libp2p.New(ctx, opts...)
	if err != nil {
		return nil, err
	}

	as := &AutoNATService{
		ctx:    ctx,
		dialer: dialer,
		reqs:   make(map[peer.ID]int),
	}
	h.SetStreamHandler(autonat.AutoNATProto, as.handleStream)

	go as.resetRateLimiter()

	return as, nil
}

func (as *AutoNATService) handleStream(s inet.Stream) {
	defer s.Close()

	pid := s.Conn().RemotePeer()
	log.Debugf("New stream from %s", pid.Pretty())

	r := ggio.NewDelimitedReader(s, inet.MessageSizeMax)
	w := ggio.NewDelimitedWriter(s)

	var req pb.Message
	var res pb.Message

	err := r.ReadMsg(&req)
	if err != nil {
		log.Debugf("Error reading message from %s: %s", pid.Pretty(), err.Error())
		s.Reset()
		return
	}

	t := req.GetType()
	if t != pb.Message_DIAL {
		log.Debugf("Unexpected message from %s: %s (%d)", pid.Pretty(), t.String(), t)
		s.Reset()
		return
	}

	dr := as.handleDial(pid, s.Conn().RemoteMultiaddr(), req.GetDial().GetPeer())
	res.Type = pb.Message_DIAL_RESPONSE.Enum()
	res.DialResponse = dr

	err = w.WriteMsg(&res)
	if err != nil {
		log.Debugf("Error writing response to %s: %s", pid.Pretty(), err.Error())
		s.Reset()
		return
	}
}

func (as *AutoNATService) handleDial(p peer.ID, obsaddr ma.Multiaddr, mpi *pb.Message_PeerInfo) *pb.Message_DialResponse {
	if mpi == nil {
		return newDialResponseError(pb.Message_E_BAD_REQUEST, "missing peer info")
	}

	mpid := mpi.GetId()
	if mpid != nil {
		mp, err := peer.IDFromBytes(mpid)
		if err != nil {
			return newDialResponseError(pb.Message_E_BAD_REQUEST, "bad peer id")
		}

		if mp != p {
			return newDialResponseError(pb.Message_E_BAD_REQUEST, "peer id mismatch")
		}
	}

	addrs := make([]ma.Multiaddr, 0)
	seen := make(map[string]struct{})

	// add observed addr to the list of addresses to dial
	if !as.skipDial(obsaddr) {
		addrs = append(addrs, obsaddr)
		seen[obsaddr.String()] = struct{}{}
	}

	for _, maddr := range mpi.GetAddrs() {
		addr, err := ma.NewMultiaddrBytes(maddr)
		if err != nil {
			log.Debugf("Error parsing multiaddr: %s", err.Error())
			continue
		}

		if as.skipDial(addr) {
			continue
		}

		str := addr.String()
		_, ok := seen[str]
		if ok {
			continue
		}

		addrs = append(addrs, addr)
		seen[str] = struct{}{}
	}

	if len(addrs) == 0 {
		return newDialResponseError(pb.Message_E_DIAL_ERROR, "no dialable addresses")
	}

	return as.doDial(pstore.PeerInfo{ID: p, Addrs: addrs})
}

func (as *AutoNATService) skipDial(addr ma.Multiaddr) bool {
	// skip relay addresses
	_, err := addr.ValueForProtocol(P_CIRCUIT)
	if err == nil {
		return true
	}

	// skip private network (unroutable) addresses
	if !manet.IsPublicAddr(addr) {
		return true
	}

	return false
}

func (as *AutoNATService) doDial(pi pstore.PeerInfo) *pb.Message_DialResponse {
	// rate limit check
	as.mx.Lock()
	count := as.reqs[pi.ID]
	if count >= AutoNATServiceThrottle {
		as.mx.Unlock()
		return newDialResponseError(pb.Message_E_DIAL_REFUSED, "too many dials")
	}
	as.reqs[pi.ID] = count + 1
	as.mx.Unlock()

	ctx, cancel := context.WithTimeout(as.ctx, AutoNATServiceDialTimeout)
	defer cancel()

	err := as.dialer.Connect(ctx, pi)
	if err != nil {
		log.Debugf("error dialing %s: %s", pi.ID.Pretty(), err.Error())
		// wait for the context to timeout to avoid leaking timing information
		// this renders the service ineffective as a port scanner
		<-ctx.Done()
		return newDialResponseError(pb.Message_E_DIAL_ERROR, "dial failed")
	}

	conns := as.dialer.Network().ConnsToPeer(pi.ID)
	if len(conns) == 0 {
		log.Errorf("supposedly connected to %s, but no connection to peer", pi.ID.Pretty())
		return newDialResponseError(pb.Message_E_INTERNAL_ERROR, "internal service error")
	}

	ra := conns[0].RemoteMultiaddr()
	as.dialer.Network().ClosePeer(pi.ID)
	return newDialResponseOK(ra)
}

func (as *AutoNATService) resetRateLimiter() {
	ticker := time.NewTicker(AutoNATServiceResetInterval)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			as.mx.Lock()
			as.reqs = make(map[peer.ID]int)
			as.mx.Unlock()

		case <-as.ctx.Done():
			return
		}
	}
}
<commit_msg>full close the autonat stream<commit_after>package autonat

import (
	"context"
	"sync"
	"time"

	pb "github.com/libp2p/go-libp2p-autonat/pb"

	ggio "github.com/gogo/protobuf/io"
	libp2p "github.com/libp2p/go-libp2p"
	autonat "github.com/libp2p/go-libp2p-autonat"
	host "github.com/libp2p/go-libp2p-host"
	inet "github.com/libp2p/go-libp2p-net"
	peer "github.com/libp2p/go-libp2p-peer"
	pstore "github.com/libp2p/go-libp2p-peerstore"
	ma "github.com/multiformats/go-multiaddr"
	manet "github.com/multiformats/go-multiaddr-net"
)

const P_CIRCUIT = 290

var (
	AutoNATServiceDialTimeout   = 15 * time.Second
	AutoNATServiceResetInterval = 1 * time.Minute

	AutoNATServiceThrottle = 3
)

// AutoNATService provides NAT autodetection services to other peers
type AutoNATService struct {
	ctx    context.Context
	dialer host.Host

	// rate limiter
	mx   sync.Mutex
	reqs map[peer.ID]int
}

// NewAutoNATService creates a new AutoNATService instance attached to a host
func NewAutoNATService(ctx context.Context, h host.Host, opts ...libp2p.Option) (*AutoNATService, error) {
	opts = append(opts, libp2p.NoListenAddrs)
	dialer, err := libp2p.New(ctx, opts...)
	if err != nil {
		return nil, err
	}

	as := &AutoNATService{
		ctx:    ctx,
		dialer: dialer,
		reqs:   make(map[peer.ID]int),
	}
	h.SetStreamHandler(autonat.AutoNATProto, as.handleStream)

	go as.resetRateLimiter()

	return as, nil
}

func (as *AutoNATService) handleStream(s inet.Stream) {
	defer inet.FullClose(s)

	pid := s.Conn().RemotePeer()
	log.Debugf("New stream from %s", pid.Pretty())

	r := ggio.NewDelimitedReader(s, inet.MessageSizeMax)
	w := ggio.NewDelimitedWriter(s)

	var req pb.Message
	var res pb.Message

	err := r.ReadMsg(&req)
	if err != nil {
		log.Debugf("Error reading message from %s: %s", pid.Pretty(), err.Error())
		s.Reset()
		return
	}

	t := req.GetType()
	if t != pb.Message_DIAL {
		log.Debugf("Unexpected message from %s: %s (%d)", pid.Pretty(), t.String(), t)
		s.Reset()
		return
	}

	dr := as.handleDial(pid, s.Conn().RemoteMultiaddr(), req.GetDial().GetPeer())
	res.Type = pb.Message_DIAL_RESPONSE.Enum()
	res.DialResponse = dr

	err = w.WriteMsg(&res)
	if err != nil {
		log.Debugf("Error writing response to %s: %s", pid.Pretty(), err.Error())
		s.Reset()
		return
	}
}

func (as *AutoNATService) handleDial(p peer.ID, obsaddr ma.Multiaddr, mpi *pb.Message_PeerInfo) *pb.Message_DialResponse {
	if mpi == nil {
		return newDialResponseError(pb.Message_E_BAD_REQUEST, "missing peer info")
	}

	mpid := mpi.GetId()
	if mpid != nil {
		mp, err := peer.IDFromBytes(mpid)
		if err != nil {
			return newDialResponseError(pb.Message_E_BAD_REQUEST, "bad peer id")
		}

		if mp != p {
			return newDialResponseError(pb.Message_E_BAD_REQUEST, "peer id mismatch")
		}
	}

	addrs := make([]ma.Multiaddr, 0)
	seen := make(map[string]struct{})

	// add observed addr to the list of addresses to dial
	if !as.skipDial(obsaddr) {
		addrs = append(addrs, obsaddr)
		seen[obsaddr.String()] = struct{}{}
	}

	for _, maddr := range mpi.GetAddrs() {
		addr, err := ma.NewMultiaddrBytes(maddr)
		if err != nil {
			log.Debugf("Error parsing multiaddr: %s", err.Error())
			continue
		}

		if as.skipDial(addr) {
			continue
		}

		str := addr.String()
		_, ok := seen[str]
		if ok {
			continue
		}

		addrs = append(addrs, addr)
		seen[str] = struct{}{}
	}

	if len(addrs) == 0 {
		return newDialResponseError(pb.Message_E_DIAL_ERROR, "no dialable addresses")
	}

	return as.doDial(pstore.PeerInfo{ID: p, Addrs: addrs})
}

func (as *AutoNATService) skipDial(addr ma.Multiaddr) bool {
	// skip relay addresses
	_, err := addr.ValueForProtocol(P_CIRCUIT)
	if err == nil {
		return true
	}

	// skip private network (unroutable) addresses
	if !manet.IsPublicAddr(addr) {
		return true
	}

	return false
}

func (as *AutoNATService) doDial(pi pstore.PeerInfo) *pb.Message_DialResponse {
	// rate limit check
	as.mx.Lock()
	count := as.reqs[pi.ID]
	if count >= AutoNATServiceThrottle {
		as.mx.Unlock()
		return newDialResponseError(pb.Message_E_DIAL_REFUSED, "too many dials")
	}
	as.reqs[pi.ID] = count + 1
	as.mx.Unlock()

	ctx, cancel := context.WithTimeout(as.ctx, AutoNATServiceDialTimeout)
	defer cancel()

	err := as.dialer.Connect(ctx, pi)
	if err != nil {
		log.Debugf("error dialing %s: %s", pi.ID.Pretty(), err.Error())
		// wait for the context to timeout to avoid leaking timing information
		// this renders the service ineffective as a port scanner
		<-ctx.Done()
		return newDialResponseError(pb.Message_E_DIAL_ERROR, "dial failed")
	}

	conns := as.dialer.Network().ConnsToPeer(pi.ID)
	if len(conns) == 0 {
		log.Errorf("supposedly connected to %s, but no connection to peer", pi.ID.Pretty())
		return newDialResponseError(pb.Message_E_INTERNAL_ERROR, "internal service error")
	}

	ra := conns[0].RemoteMultiaddr()
	as.dialer.Network().ClosePeer(pi.ID)
	return newDialResponseOK(ra)
}

func (as *AutoNATService) resetRateLimiter() {
	ticker := time.NewTicker(AutoNATServiceResetInterval)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			as.mx.Lock()
			as.reqs = make(map[peer.ID]int)
			as.mx.Unlock()

		case <-as.ctx.Done():
			return
		}
	}
}
<|endoftext|>


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

package pixiecore // import "go.universe.tf/netboot/pixiecore"

import (
	"bytes"
	"fmt"
	"io"
	"net"
	"strings"
	"text/template"

	"go.universe.tf/netboot/dhcp4"
)

const (
	portDHCP = 67
	portTFTP = 69
	portHTTP = 80
	portPXE  = 4011
)

// An ID is an identifier used by Booters to reference files.
type ID string

// Architecture describes a kind of CPU architecture.
type Architecture int

// Architecture types that Pixiecore knows how to boot.
//
// These architectures are self-reported by the booting machine. The
// machine may support additional execution modes. For example, legacy
// PC BIOS reports itself as an ArchIA32, but may also support ArchX64
// execution.
const (
	// ArchIA32 is a 32-bit x86 machine. It _may_ also support X64
	// execution, but Pixiecore has no way of knowing.
	ArchIA32 Architecture = iota
	// ArchX64 is a 64-bit x86 machine (aka amd64 aka X64).
	ArchX64
)

// A Machine describes a machine that is attempting to boot.
type Machine struct {
	MAC  net.HardwareAddr
	Arch Architecture
}

// A Spec describes a kernel and associated configuration.
type Spec struct {
	// The kernel to boot
	Kernel ID
	// Optional init ramdisks for linux kernels
	Initrd []ID
	// Optional kernel commandline. This string is evaluated as a
	// text/template template, in which "ID(x)" function is
	// available. Invoking ID(x) returns a URL that will call
	// Booter.ReadBootFile(x) when fetched.
	Cmdline string
	// Message to print on the client machine before booting.
	Message string
}

func expandCmdline(tpl string, funcs template.FuncMap) (string, error) {
	tmpl, err := template.New("cmdline").Option("missingkey=error").Funcs(funcs).Parse(tpl)
	if err != nil {
		return "", fmt.Errorf("parsing cmdline %q: %s", tpl, err)
	}
	var out bytes.Buffer
	if err = tmpl.Execute(&out, nil); err != nil {
		return "", fmt.Errorf("expanding cmdline template %q: %s", tpl, err)
	}
	cmdline := strings.TrimSpace(out.String())
	if strings.Contains(cmdline, "\n") {
		return "", fmt.Errorf("cmdline %q contains a newline", cmdline)
	}
	return cmdline, nil
}

// A Booter provides boot instructions and files for machines.
//
// Due to the stateless nature of various boot protocols, BootSpec()
// will be called multiple times in the course of a single boot
// attempt.
type Booter interface {
	// The given MAC address wants to know what it should
	// boot. What should Pixiecore make it boot?
	//
	// Returning an error or a nil BootSpec will make Pixiecore ignore
	// the client machine's request.
	BootSpec(m Machine) (*Spec, error)
	// Get the bytes corresponding to an ID given in Spec.
	ReadBootFile(id ID) (io.ReadCloser, error)
	// Write the given Reader to an ID given in Spec.
	WriteBootFile(id ID, body io.Reader) error
}

// Firmware describes a kind of firmware attempting to boot.
//
// This should only be used for selecting the right bootloader within
// Pixiecore, kernel selection should key off the more generic
// Architecture.
type Firmware int

// The bootloaders that Pixiecore knows how to handle.
const (
	// Note the values match the values from RFC4578.
	FirmwareX86PC Firmware = 0 // "Classic" x86 BIOS with PXE/UNDI support.
	FirmwareEFI32          = 6 // 32-bit x86 processor running EFI
	FirmwareEFI64          = 7 // 64-bit x86 processor running EFI
)

var fwToArch = map[Firmware]Architecture{
	FirmwareX86PC: ArchIA32,
	FirmwareEFI32: ArchIA32,
	FirmwareEFI64: ArchX64,
}

// A Server boots machines using a Booter.
type Server struct {
	Booter Booter

	// Address to listen on, or empty for all interfaces.
	Address  string
	HTTPPort int

	// Ipxe lists the supported bootable Firmwares, and their
	// associated ipxe binary.
	Ipxe map[Firmware][]byte

	// Log receives logs on Pixiecore's operation. If nil, logging
	// is suppressed.
	Log func(subsystem, msg string)
	// Debug receives extensive logging on Pixiecore's internals. Very
	// useful for debugging, but very verbose.
	Debug func(subsystem, msg string)

	// These ports can technically be set for testing, but the
	// protocols burned in firmware on the client side hardcode these,
	// so if you change them in production, nothing will work.
	DHCPPort int
	TFTPPort int
	PXEPort  int

	errs chan error
}

// Serve listens for machines attempting to boot, and uses Booter to
// help them.
func (s *Server) Serve() error {
	if s.DHCPPort == 0 {
		s.DHCPPort = portDHCP
	}
	if s.TFTPPort == 0 {
		s.TFTPPort = portTFTP
	}
	if s.PXEPort == 0 {
		s.PXEPort = portPXE
	}
	if s.HTTPPort == 0 {
		s.HTTPPort = portHTTP
	}

	dhcp, err := dhcp4.NewConn(fmt.Sprintf("%s:%d", s.Address, s.DHCPPort))
	if err != nil {
		return err
	}
	tftp, err := net.ListenPacket("udp", fmt.Sprintf("%s:%d", s.Address, s.TFTPPort))
	if err != nil {
		dhcp.Close()
		return err
	}
	pxe, err := net.ListenPacket("udp", fmt.Sprintf("%s:%d", s.Address, s.PXEPort))
	if err != nil {
		dhcp.Close()
		tftp.Close()
		return err
	}
	http, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.Address, s.HTTPPort))
	if err != nil {
		dhcp.Close()
		tftp.Close()
		pxe.Close()
		return err
	}

	// 5 buffer slots, one for each goroutine, plus one for
	// Shutdown(). We only ever pull the first error out, but shutdown
	// will likely generate some spurious errors from the other
	// goroutines, and we want them to be able to dump them without
	// blocking.
	s.errs = make(chan error, 5)

	go func() { s.errs <- s.serveDHCP(dhcp) }()
	go func() { s.errs <- s.servePXE(pxe) }()
	go func() { s.errs <- s.serveTFTP(tftp) }()
	go func() { s.errs <- s.serveHTTP(http) }()

	// Wait for either a fatal error, or Shutdown().
	err = <-s.errs
	dhcp.Close()
	tftp.Close()
	pxe.Close()
	http.Close()
	return err
}

func (s *Server) Shutdown() {
	select {
	case s.errs <- nil:
	default:
	}
}
<commit_msg>pixiecore: document Shutdown.<commit_after>// Copyright 2016 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package pixiecore // import "go.universe.tf/netboot/pixiecore"

import (
	"bytes"
	"fmt"
	"io"
	"net"
	"strings"
	"text/template"

	"go.universe.tf/netboot/dhcp4"
)

const (
	portDHCP = 67
	portTFTP = 69
	portHTTP = 80
	portPXE  = 4011
)

// An ID is an identifier used by Booters to reference files.
type ID string

// Architecture describes a kind of CPU architecture.
type Architecture int

// Architecture types that Pixiecore knows how to boot.
//
// These architectures are self-reported by the booting machine. The
// machine may support additional execution modes. For example, legacy
// PC BIOS reports itself as an ArchIA32, but may also support ArchX64
// execution.
const (
	// ArchIA32 is a 32-bit x86 machine. It _may_ also support X64
	// execution, but Pixiecore has no way of knowing.
	ArchIA32 Architecture = iota
	// ArchX64 is a 64-bit x86 machine (aka amd64 aka X64).
	ArchX64
)

// A Machine describes a machine that is attempting to boot.
type Machine struct {
	MAC  net.HardwareAddr
	Arch Architecture
}

// A Spec describes a kernel and associated configuration.
type Spec struct {
	// The kernel to boot
	Kernel ID
	// Optional init ramdisks for linux kernels
	Initrd []ID
	// Optional kernel commandline. This string is evaluated as a
	// text/template template, in which "ID(x)" function is
	// available. Invoking ID(x) returns a URL that will call
	// Booter.ReadBootFile(x) when fetched.
	Cmdline string
	// Message to print on the client machine before booting.
	Message string
}

func expandCmdline(tpl string, funcs template.FuncMap) (string, error) {
	tmpl, err := template.New("cmdline").Option("missingkey=error").Funcs(funcs).Parse(tpl)
	if err != nil {
		return "", fmt.Errorf("parsing cmdline %q: %s", tpl, err)
	}
	var out bytes.Buffer
	if err = tmpl.Execute(&out, nil); err != nil {
		return "", fmt.Errorf("expanding cmdline template %q: %s", tpl, err)
	}
	cmdline := strings.TrimSpace(out.String())
	if strings.Contains(cmdline, "\n") {
		return "", fmt.Errorf("cmdline %q contains a newline", cmdline)
	}
	return cmdline, nil
}

// A Booter provides boot instructions and files for machines.
//
// Due to the stateless nature of various boot protocols, BootSpec()
// will be called multiple times in the course of a single boot
// attempt.
type Booter interface {
	// The given MAC address wants to know what it should
	// boot. What should Pixiecore make it boot?
	//
	// Returning an error or a nil BootSpec will make Pixiecore ignore
	// the client machine's request.
	BootSpec(m Machine) (*Spec, error)
	// Get the bytes corresponding to an ID given in Spec.
	ReadBootFile(id ID) (io.ReadCloser, error)
	// Write the given Reader to an ID given in Spec.
	WriteBootFile(id ID, body io.Reader) error
}

// Firmware describes a kind of firmware attempting to boot.
//
// This should only be used for selecting the right bootloader within
// Pixiecore, kernel selection should key off the more generic
// Architecture.
type Firmware int

// The bootloaders that Pixiecore knows how to handle.
const (
	// Note the values match the values from RFC4578.
	FirmwareX86PC Firmware = 0 // "Classic" x86 BIOS with PXE/UNDI support.
	FirmwareEFI32          = 6 // 32-bit x86 processor running EFI
	FirmwareEFI64          = 7 // 64-bit x86 processor running EFI
)

var fwToArch = map[Firmware]Architecture{
	FirmwareX86PC: ArchIA32,
	FirmwareEFI32: ArchIA32,
	FirmwareEFI64: ArchX64,
}

// A Server boots machines using a Booter.
type Server struct {
	Booter Booter

	// Address to listen on, or empty for all interfaces.
	Address  string
	HTTPPort int

	// Ipxe lists the supported bootable Firmwares, and their
	// associated ipxe binary.
	Ipxe map[Firmware][]byte

	// Log receives logs on Pixiecore's operation. If nil, logging
	// is suppressed.
	Log func(subsystem, msg string)
	// Debug receives extensive logging on Pixiecore's internals. Very
	// useful for debugging, but very verbose.
	Debug func(subsystem, msg string)

	// These ports can technically be set for testing, but the
	// protocols burned in firmware on the client side hardcode these,
	// so if you change them in production, nothing will work.
	DHCPPort int
	TFTPPort int
	PXEPort  int

	errs chan error
}

// Serve listens for machines attempting to boot, and uses Booter to
// help them.
func (s *Server) Serve() error {
	if s.DHCPPort == 0 {
		s.DHCPPort = portDHCP
	}
	if s.TFTPPort == 0 {
		s.TFTPPort = portTFTP
	}
	if s.PXEPort == 0 {
		s.PXEPort = portPXE
	}
	if s.HTTPPort == 0 {
		s.HTTPPort = portHTTP
	}

	dhcp, err := dhcp4.NewConn(fmt.Sprintf("%s:%d", s.Address, s.DHCPPort))
	if err != nil {
		return err
	}
	tftp, err := net.ListenPacket("udp", fmt.Sprintf("%s:%d", s.Address, s.TFTPPort))
	if err != nil {
		dhcp.Close()
		return err
	}
	pxe, err := net.ListenPacket("udp", fmt.Sprintf("%s:%d", s.Address, s.PXEPort))
	if err != nil {
		dhcp.Close()
		tftp.Close()
		return err
	}
	http, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.Address, s.HTTPPort))
	if err != nil {
		dhcp.Close()
		tftp.Close()
		pxe.Close()
		return err
	}

	// 5 buffer slots, one for each goroutine, plus one for
	// Shutdown(). We only ever pull the first error out, but shutdown
	// will likely generate some spurious errors from the other
	// goroutines, and we want them to be able to dump them without
	// blocking.
	s.errs = make(chan error, 5)

	go func() { s.errs <- s.serveDHCP(dhcp) }()
	go func() { s.errs <- s.servePXE(pxe) }()
	go func() { s.errs <- s.serveTFTP(tftp) }()
	go func() { s.errs <- s.serveHTTP(http) }()

	// Wait for either a fatal error, or Shutdown().
	err = <-s.errs
	dhcp.Close()
	tftp.Close()
	pxe.Close()
	http.Close()
	return err
}

// Shutdown causes Serve() to exit, cleaning up behind itself.
func (s *Server) Shutdown() {
	select {
	case s.errs <- nil:
	default:
	}
}
<|endoftext|>


<commit_before>package adaptor

import (
	"encoding/json"
	"fmt"
	"regexp"
	"strings"

	"github.com/compose/transporter/pkg/pipe"
)

// ErrAdaptor gives the details of the failed adaptor
type ErrAdaptor struct {
	name string
}

func (a ErrAdaptor) Error() string {
	return fmt.Sprintf("adaptor '%s' not found in registry", a.name)
}

// StopStartListener defines the interface that all database connectors and nodes must follow.
// Start() consumes data from the interface,
// Listen() listens on a pipe, processes data, and then emits it.
// Stop() shuts down the adaptor
type StopStartListener interface {
	SampleConfig() string
	Description() string
	Connect() error
	Start() error
	Listen() error
	Stop() error
}

// Createadaptor instantiates an adaptor given the adaptor type and the Config.
// An Adaptor is expected to add themselves to the Adaptors map in the init() func
//   func init() {
//     adaptors.Add("TYPE", func(p *pipe.Pipe, path string, extra adaptors.Config) (adaptors.StopStartListener, error) {
//     })
//   }
// and are expected to confirm to the Adaptor interface
func Createadaptor(kind, path string, extra Config, p *pipe.Pipe) (adaptor StopStartListener, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("cannot create node: %v", r)
		}
	}()

	creator, ok := Adaptors[kind]
	if !ok {
		return nil, ErrAdaptor{kind}
	}

	adaptor, err = creator(p, path, extra)
	if err != nil {
		return nil, ErrAdaptor{kind}
	}

	if err := adaptor.Connect(); err != nil {
		return nil, ErrAdaptor{kind}
	}

	return adaptor, nil
}

// Config is an alias to map[string]interface{} and helps us
// turn a fuzzy document into a conrete named struct
type Config map[string]interface{}

// Construct will Marshal the Config and then Unmarshal it into a
// named struct the generic map into a proper struct
func (c *Config) Construct(conf interface{}) error {
	b, err := json.Marshal(c)
	if err != nil {
		return err
	}

	err = json.Unmarshal(b, conf)
	if err != nil {
		return err
	}
	return nil
}

// GetString returns value stored in the config under the given key, or
// an empty string if the key doesn't exist, or isn't a string value
func (c Config) GetString(key string) string {
	i, ok := c[key]
	if !ok {
		return ""
	}
	s, ok := i.(string)
	if !ok {
		return ""
	}
	return s
}

// split a namespace into it's elements
// this covers a few standard cases, elasticsearch, mongo, rethink, but it's
// expected to be all inclusive.
func (c *Config) splitNamespace() (string, string, error) {
	fields := strings.SplitN(c.GetString("namespace"), ".", 2)

	if len(fields) != 2 {
		return "", "", fmt.Errorf("malformed namespace, expected a '.' deliminated string")
	}
	return fields[0], fields[1], nil
}

// CompileNamespace split's on the first '.' and then compiles the second portion to use as the msg filter
func (c *Config) CompileNamespace() (string, *regexp.Regexp, error) {
	field0, field1, err := c.splitNamespace()

	if err != nil {
		return "", nil, err
	}

	compiledNs, err := regexp.Compile(strings.Trim(field1, "/"))
	return field0, compiledNs, err
}

// DbConfig is a standard typed config struct to use for as general purpose config for most databases.
type DbConfig struct {
	URI       string `json:"uri" doc:"the uri to connect to, in the form mongo://user:password@host.com:8080/database"` // the database uri
	Namespace string `json:"namespace" doc:"mongo namespace to read/write"`                                             // namespace
	Debug     bool   `json:"debug" doc:"display debug information"`                                                     // debug mode
}
<commit_msg>update adaptor interface comments<commit_after>package adaptor

import (
	"encoding/json"
	"fmt"
	"regexp"
	"strings"

	"github.com/compose/transporter/pkg/pipe"
)

// ErrAdaptor gives the details of the failed adaptor
type ErrAdaptor struct {
	name string
}

func (a ErrAdaptor) Error() string {
	return fmt.Sprintf("adaptor '%s' not found in registry", a.name)
}

// StopStartListener defines the interface that all database connectors and nodes must follow.
// SampleConfig() returns an example YAML structure to configure the adaptor
// Description() provides contextual information for what the adaptor is for
// Connect() allows the adaptor an opportunity to setup connections prior to Start()
// Start() consumes data from the interface,
// Listen() listens on a pipe, processes data, and then emits it.
// Stop() shuts down the adaptor
type StopStartListener interface {
	SampleConfig() string
	Description() string
	Connect() error
	Start() error
	Listen() error
	Stop() error
}

// Createadaptor instantiates an adaptor given the adaptor type and the Config.
// An Adaptor is expected to add themselves to the Adaptors map in the init() func
//   func init() {
//     adaptors.Add("TYPE", func(p *pipe.Pipe, path string, extra adaptors.Config) (adaptors.StopStartListener, error) {
//     })
//   }
// and are expected to confirm to the Adaptor interface
func Createadaptor(kind, path string, extra Config, p *pipe.Pipe) (adaptor StopStartListener, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("cannot create node: %v", r)
		}
	}()

	creator, ok := Adaptors[kind]
	if !ok {
		return nil, ErrAdaptor{kind}
	}

	adaptor, err = creator(p, path, extra)
	if err != nil {
		return nil, ErrAdaptor{kind}
	}

	if err := adaptor.Connect(); err != nil {
		return nil, ErrAdaptor{kind}
	}

	return adaptor, nil
}

// Config is an alias to map[string]interface{} and helps us
// turn a fuzzy document into a conrete named struct
type Config map[string]interface{}

// Construct will Marshal the Config and then Unmarshal it into a
// named struct the generic map into a proper struct
func (c *Config) Construct(conf interface{}) error {
	b, err := json.Marshal(c)
	if err != nil {
		return err
	}

	err = json.Unmarshal(b, conf)
	if err != nil {
		return err
	}
	return nil
}

// GetString returns value stored in the config under the given key, or
// an empty string if the key doesn't exist, or isn't a string value
func (c Config) GetString(key string) string {
	i, ok := c[key]
	if !ok {
		return ""
	}
	s, ok := i.(string)
	if !ok {
		return ""
	}
	return s
}

// split a namespace into it's elements
// this covers a few standard cases, elasticsearch, mongo, rethink, but it's
// expected to be all inclusive.
func (c *Config) splitNamespace() (string, string, error) {
	fields := strings.SplitN(c.GetString("namespace"), ".", 2)

	if len(fields) != 2 {
		return "", "", fmt.Errorf("malformed namespace, expected a '.' deliminated string")
	}
	return fields[0], fields[1], nil
}

// CompileNamespace split's on the first '.' and then compiles the second portion to use as the msg filter
func (c *Config) CompileNamespace() (string, *regexp.Regexp, error) {
	field0, field1, err := c.splitNamespace()

	if err != nil {
		return "", nil, err
	}

	compiledNs, err := regexp.Compile(strings.Trim(field1, "/"))
	return field0, compiledNs, err
}

// DbConfig is a standard typed config struct to use for as general purpose config for most databases.
type DbConfig struct {
	URI       string `json:"uri" doc:"the uri to connect to, in the form mongo://user:password@host.com:8080/database"` // the database uri
	Namespace string `json:"namespace" doc:"mongo namespace to read/write"`                                             // namespace
	Debug     bool   `json:"debug" doc:"display debug information"`                                                     // debug mode
}
<|endoftext|>


<commit_before>package api

import (
	"crypto/rand"
	"crypto/tls"
	"crypto/x509"
	"encoding/base64"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"

	"golang.org/x/net/context"
	"golang.org/x/oauth2"

	"github.com/grafana/grafana/pkg/bus"
	"github.com/grafana/grafana/pkg/log"
	"github.com/grafana/grafana/pkg/metrics"
	"github.com/grafana/grafana/pkg/middleware"
	m "github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/setting"
	"github.com/grafana/grafana/pkg/social"
)

var (
	ErrProviderDeniedRequest = errors.New("Login provider denied login request")
	ErrEmailNotAllowed       = errors.New("Required email domain not fulfilled")
	ErrSignUpNotAllowed      = errors.New("Signup is not allowed for this adapter")
	ErrUsersQuotaReached     = errors.New("Users quota reached")
	ErrNoEmail               = errors.New("Login provider didn't return an email address")
	oauthLogger              = log.New("oauth.login")
)

func GenStateString() string {
	rnd := make([]byte, 32)
	rand.Read(rnd)
	return base64.StdEncoding.EncodeToString(rnd)
}

func OAuthLogin(ctx *middleware.Context) {
	if setting.OAuthService == nil {
		ctx.Handle(404, "login.OAuthLogin(oauth service not enabled)", nil)
		return
	}

	name := ctx.Params(":name")
	connect, ok := social.SocialMap[name]
	if !ok {
		ctx.Handle(404, "login.OAuthLogin(social login not enabled)", errors.New(name))
		return
	}

	errorParam := ctx.Query("error")
	if errorParam != "" {
		errorDesc := ctx.Query("error_description")
		oauthLogger.Error("failed to login ", "error", errorParam, "errorDesc", errorDesc)
		redirectWithError(ctx, ErrProviderDeniedRequest, "error", errorParam, "errorDesc", errorDesc)
		return
	}

	code := ctx.Query("code")
	if code == "" {
		state := GenStateString()
		ctx.Session.Set(middleware.SESS_KEY_OAUTH_STATE, state)
		if setting.OAuthService.OAuthInfos[name].HostedDomain == "" {
			ctx.Redirect(connect.AuthCodeURL(state, oauth2.AccessTypeOnline))
		} else {
			ctx.Redirect(connect.AuthCodeURL(state, oauth2.SetAuthURLParam("hd", setting.OAuthService.OAuthInfos[name].HostedDomain), oauth2.AccessTypeOnline))
		}
		return
	}

	savedState, ok := ctx.Session.Get(middleware.SESS_KEY_OAUTH_STATE).(string)
	if !ok {
		ctx.Handle(500, "login.OAuthLogin(missing saved state)", nil)
		return
	}

	queryState := ctx.Query("state")
	if savedState != queryState {
		ctx.Handle(500, "login.OAuthLogin(state mismatch)", nil)
		return
	}

	// handle call back
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{
			InsecureSkipVerify: setting.OAuthService.OAuthInfos[name].TlsSkipVerify,
		},
	}
	oauthClient := &http.Client{
		Transport: tr,
	}

	if setting.OAuthService.OAuthInfos[name].TlsClientCert != "" || setting.OAuthService.OAuthInfos[name].TlsClientKey != "" {
		cert, err := tls.LoadX509KeyPair(setting.OAuthService.OAuthInfos[name].TlsClientCert, setting.OAuthService.OAuthInfos[name].TlsClientKey)
		if err != nil {
			log.Fatal(1, "Failed to setup TlsClientCert", "oauth provider", name, "error", err)
		}

		tr.TLSClientConfig.Certificates = append(tr.TLSClientConfig.Certificates, cert)
	}

	if setting.OAuthService.OAuthInfos[name].TlsClientCa != "" {
		caCert, err := ioutil.ReadFile(setting.OAuthService.OAuthInfos[name].TlsClientCa)
		if err != nil {
			log.Fatal(1, "Failed to setup TlsClientCa", "oauth provider", name, "error", err)
		}
		caCertPool := x509.NewCertPool()
		caCertPool.AppendCertsFromPEM(caCert)

		tr.TLSClientConfig.RootCAs = caCertPool
	}

	oauthCtx := context.WithValue(context.Background(), oauth2.HTTPClient, oauthClient)

	// get token from provider
	token, err := connect.Exchange(oauthCtx, code)
	if err != nil {
		ctx.Handle(500, "login.OAuthLogin(NewTransportWithCode)", err)
		return
	}
	// token.TokenType was defaulting to "bearer", which is out of spec, so we explicitly set to "Bearer"
	token.TokenType = "Bearer"

	ctx.Logger.Debug("OAuthLogin Got token")

	// set up oauth2 client
	client := connect.Client(oauthCtx, token)

	// get user info
	userInfo, err := connect.UserInfo(client)
	if err != nil {
		if sErr, ok := err.(*social.Error); ok {
			redirectWithError(ctx, sErr)
		} else {
			ctx.Handle(500, fmt.Sprintf("login.OAuthLogin(get info from %s)", name), err)
		}
		return
	}

	ctx.Logger.Debug("OAuthLogin got user info", "userInfo", userInfo)

	// validate that we got at least an email address
	if userInfo.Email == "" {
		redirectWithError(ctx, ErrNoEmail)
		return
	}

	// validate that the email is allowed to login to grafana
	if !connect.IsEmailAllowed(userInfo.Email) {
		redirectWithError(ctx, ErrEmailNotAllowed)
		return
	}

	userQuery := m.GetUserByEmailQuery{Email: userInfo.Email}
	err = bus.Dispatch(&userQuery)

	// create account if missing
	if err == m.ErrUserNotFound {
		if !connect.IsSignupAllowed() {
			redirectWithError(ctx, ErrSignUpNotAllowed)
			return
		}
		limitReached, err := middleware.QuotaReached(ctx, "user")
		if err != nil {
			ctx.Handle(500, "Failed to get user quota", err)
			return
		}
		if limitReached {
			redirectWithError(ctx, ErrUsersQuotaReached)
			return
		}
		cmd := m.CreateUserCommand{
			Login:          userInfo.Login,
			Email:          userInfo.Email,
			Name:           userInfo.Name,
			Company:        userInfo.Company,
			DefaultOrgRole: userInfo.Role,
		}

		if err = bus.Dispatch(&cmd); err != nil {
			ctx.Handle(500, "Failed to create account", err)
			return
		}

		userQuery.Result = &cmd.Result
	} else if err != nil {
		ctx.Handle(500, "Unexpected error", err)
	}

	// login
	loginUserWithUser(userQuery.Result, ctx)

	metrics.M_Api_Login_OAuth.Inc()

	if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 {
		ctx.SetCookie("redirect_to", "", -1, setting.AppSubUrl+"/")
		ctx.Redirect(redirectTo)
		return
	}

	ctx.Redirect(setting.AppSubUrl + "/")
}

func redirectWithError(ctx *middleware.Context, err error, v ...interface{}) {
	ctx.Logger.Info(err.Error(), v...)
	// TODO: we can use the flash storage here once it's implemented
	ctx.Session.Set("loginError", err.Error())
	ctx.Redirect(setting.AppSubUrl + "/login")
}
<commit_msg>Use URLEncoding instead of StdEncoding to be sure state value will be corectly decoded (#10512)<commit_after>package api

import (
	"crypto/rand"
	"crypto/tls"
	"crypto/x509"
	"encoding/base64"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"

	"golang.org/x/net/context"
	"golang.org/x/oauth2"

	"github.com/grafana/grafana/pkg/bus"
	"github.com/grafana/grafana/pkg/log"
	"github.com/grafana/grafana/pkg/metrics"
	"github.com/grafana/grafana/pkg/middleware"
	m "github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/setting"
	"github.com/grafana/grafana/pkg/social"
)

var (
	ErrProviderDeniedRequest = errors.New("Login provider denied login request")
	ErrEmailNotAllowed       = errors.New("Required email domain not fulfilled")
	ErrSignUpNotAllowed      = errors.New("Signup is not allowed for this adapter")
	ErrUsersQuotaReached     = errors.New("Users quota reached")
	ErrNoEmail               = errors.New("Login provider didn't return an email address")
	oauthLogger              = log.New("oauth.login")
)

func GenStateString() string {
	rnd := make([]byte, 32)
	rand.Read(rnd)
	return base64.URLEncoding.EncodeToString(rnd)
}

func OAuthLogin(ctx *middleware.Context) {
	if setting.OAuthService == nil {
		ctx.Handle(404, "login.OAuthLogin(oauth service not enabled)", nil)
		return
	}

	name := ctx.Params(":name")
	connect, ok := social.SocialMap[name]
	if !ok {
		ctx.Handle(404, "login.OAuthLogin(social login not enabled)", errors.New(name))
		return
	}

	errorParam := ctx.Query("error")
	if errorParam != "" {
		errorDesc := ctx.Query("error_description")
		oauthLogger.Error("failed to login ", "error", errorParam, "errorDesc", errorDesc)
		redirectWithError(ctx, ErrProviderDeniedRequest, "error", errorParam, "errorDesc", errorDesc)
		return
	}

	code := ctx.Query("code")
	if code == "" {
		state := GenStateString()
		ctx.Session.Set(middleware.SESS_KEY_OAUTH_STATE, state)
		if setting.OAuthService.OAuthInfos[name].HostedDomain == "" {
			ctx.Redirect(connect.AuthCodeURL(state, oauth2.AccessTypeOnline))
		} else {
			ctx.Redirect(connect.AuthCodeURL(state, oauth2.SetAuthURLParam("hd", setting.OAuthService.OAuthInfos[name].HostedDomain), oauth2.AccessTypeOnline))
		}
		return
	}

	savedState, ok := ctx.Session.Get(middleware.SESS_KEY_OAUTH_STATE).(string)
	if !ok {
		ctx.Handle(500, "login.OAuthLogin(missing saved state)", nil)
		return
	}

	queryState := ctx.Query("state")
	if savedState != queryState {
		ctx.Handle(500, "login.OAuthLogin(state mismatch)", nil)
		return
	}

	// handle call back
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{
			InsecureSkipVerify: setting.OAuthService.OAuthInfos[name].TlsSkipVerify,
		},
	}
	oauthClient := &http.Client{
		Transport: tr,
	}

	if setting.OAuthService.OAuthInfos[name].TlsClientCert != "" || setting.OAuthService.OAuthInfos[name].TlsClientKey != "" {
		cert, err := tls.LoadX509KeyPair(setting.OAuthService.OAuthInfos[name].TlsClientCert, setting.OAuthService.OAuthInfos[name].TlsClientKey)
		if err != nil {
			log.Fatal(1, "Failed to setup TlsClientCert", "oauth provider", name, "error", err)
		}

		tr.TLSClientConfig.Certificates = append(tr.TLSClientConfig.Certificates, cert)
	}

	if setting.OAuthService.OAuthInfos[name].TlsClientCa != "" {
		caCert, err := ioutil.ReadFile(setting.OAuthService.OAuthInfos[name].TlsClientCa)
		if err != nil {
			log.Fatal(1, "Failed to setup TlsClientCa", "oauth provider", name, "error", err)
		}
		caCertPool := x509.NewCertPool()
		caCertPool.AppendCertsFromPEM(caCert)

		tr.TLSClientConfig.RootCAs = caCertPool
	}

	oauthCtx := context.WithValue(context.Background(), oauth2.HTTPClient, oauthClient)

	// get token from provider
	token, err := connect.Exchange(oauthCtx, code)
	if err != nil {
		ctx.Handle(500, "login.OAuthLogin(NewTransportWithCode)", err)
		return
	}
	// token.TokenType was defaulting to "bearer", which is out of spec, so we explicitly set to "Bearer"
	token.TokenType = "Bearer"

	ctx.Logger.Debug("OAuthLogin Got token")

	// set up oauth2 client
	client := connect.Client(oauthCtx, token)

	// get user info
	userInfo, err := connect.UserInfo(client)
	if err != nil {
		if sErr, ok := err.(*social.Error); ok {
			redirectWithError(ctx, sErr)
		} else {
			ctx.Handle(500, fmt.Sprintf("login.OAuthLogin(get info from %s)", name), err)
		}
		return
	}

	ctx.Logger.Debug("OAuthLogin got user info", "userInfo", userInfo)

	// validate that we got at least an email address
	if userInfo.Email == "" {
		redirectWithError(ctx, ErrNoEmail)
		return
	}

	// validate that the email is allowed to login to grafana
	if !connect.IsEmailAllowed(userInfo.Email) {
		redirectWithError(ctx, ErrEmailNotAllowed)
		return
	}

	userQuery := m.GetUserByEmailQuery{Email: userInfo.Email}
	err = bus.Dispatch(&userQuery)

	// create account if missing
	if err == m.ErrUserNotFound {
		if !connect.IsSignupAllowed() {
			redirectWithError(ctx, ErrSignUpNotAllowed)
			return
		}
		limitReached, err := middleware.QuotaReached(ctx, "user")
		if err != nil {
			ctx.Handle(500, "Failed to get user quota", err)
			return
		}
		if limitReached {
			redirectWithError(ctx, ErrUsersQuotaReached)
			return
		}
		cmd := m.CreateUserCommand{
			Login:          userInfo.Login,
			Email:          userInfo.Email,
			Name:           userInfo.Name,
			Company:        userInfo.Company,
			DefaultOrgRole: userInfo.Role,
		}

		if err = bus.Dispatch(&cmd); err != nil {
			ctx.Handle(500, "Failed to create account", err)
			return
		}

		userQuery.Result = &cmd.Result
	} else if err != nil {
		ctx.Handle(500, "Unexpected error", err)
	}

	// login
	loginUserWithUser(userQuery.Result, ctx)

	metrics.M_Api_Login_OAuth.Inc()

	if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 {
		ctx.SetCookie("redirect_to", "", -1, setting.AppSubUrl+"/")
		ctx.Redirect(redirectTo)
		return
	}

	ctx.Redirect(setting.AppSubUrl + "/")
}

func redirectWithError(ctx *middleware.Context, err error, v ...interface{}) {
	ctx.Logger.Info(err.Error(), v...)
	// TODO: we can use the flash storage here once it's implemented
	ctx.Session.Set("loginError", err.Error())
	ctx.Redirect(setting.AppSubUrl + "/login")
}
<|endoftext|>