<commit_before>package redius

import (
	"github.com/mediocregopher/radix.v2/pool"
	"github.com/mediocregopher/radix.v2/redis"
	"log"
)

type Redius struct {
	Addr     string
	Network  string
	Poolsize int
	Password string
	_pool    *pool.Pool
}

func (this *Redius) InitClient() {
	//log.Printf("%d rediu initializing from %s:%s", this.Poolsize, this.Network, this.Addr)

	var err error
	if "" == this.Password {
		this._pool, err = pool.New(this.Network, this.Addr, this.Poolsize)
	} else {
		df := func(network, addr string) (*redis.Client, error) {
			client, err := redis.Dial(network, addr)
			if err != nil {
				return nil, err
			}
			if err = client.Cmd("AUTH", this.Password).Err; err != nil {
				client.Close()
				return nil, err
			}
			return client, nil
		}

		this._pool, err = pool.NewCustom(this.Network, this.Addr, this.Poolsize, df)
	}

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

// SET key
func (this *Redius) SET(key string, val string) error {
	c, err := this._pool.Get()
	if err != nil {
		return err
	}
	err = c.Cmd("SET", key, val).Err
	this._pool.Put(c)
	return err
}

// SETEX key seconds val
func (this *Redius) SETEX(key string, secs int, val string) error {
	c, err := this._pool.Get()
	if err != nil {
		return err
	}
	err = c.Cmd("SETEX", key, secs, val).Err
	this._pool.Put(c)
	return err
}

// GET key
func (this *Redius) GET(key string) (val string, err error) {
	c, err := this._pool.Get()
	if err != nil {
		return
	}
	val, err = c.Cmd("GET", key).Str()
	this._pool.Put(c)
	return
}

func (this *Redius) MGET(keys []string) (val []string, err error) {
	c, err := this._pool.Get()
	if err != nil {
		return
	}
	r := c.Cmd("MGET", keys)
	if r.Err != nil {
		return nil, r.Err
	}
	val, err = r.List()
	this._pool.Put(c)
	return
}

//
// hset key field val
func (this *Redius) HSET(key, field string, val string) error {
	c, err := this._pool.Get()
	if err != nil {
		return err
	}
	err = c.Cmd("HSET", key, field, val).Err
	this._pool.Put(c)
	return err
}

// HSETNX key field val timeout
func (this *Redius) HSETNX(key, field string, val string) (err error) {
	c, err := this._pool.Get()
	if err != nil {
		return
	}
	err = c.Cmd("HSETNX", key, field, val).Err
	this._pool.Put(c)
	return
}

// HMSET key field val [field val ...]
func (this *Redius) HMSET(key string, val map[string]interface{}) (err error) {
	c, err := this._pool.Get()
	if err != nil {
		return
	}
	err = c.Cmd("HMSET", key, val).Err
	this._pool.Put(c)
	return
}

// HGET key field
func (this *Redius) HGET(key, field string) (val string, err error) {
	c, err := this._pool.Get()
	if err != nil {
		return
	}
	val, err = c.Cmd("HGET", key, field).Str()
	this._pool.Put(c)
	return
}

// HMGET key field [field ...]
func (this *Redius) HMGET(key string, fields []string) (val []string, err error) {
	c, err := this._pool.Get()
	if err != nil {
		return
	}
	val, err = c.Cmd("HMGET", key, fields).List()
	this._pool.Put(c)
	return
}

func (this *Redius) HGETALL(key string) (val []string, err error) {
	c, err := this._pool.Get()
	if err != nil {
		return
	}
	val, err = c.Cmd("HGETALL", key).List()
	this._pool.Put(c)
	return
}

// redis command:
// HDEL key field
func (this *Redius) HDEL(key, field string) error {
	c, err := this._pool.Get()
	if err != nil {
		return err
	}
	err = c.Cmd("HDEL", key, field).Err
	this._pool.Put(c)
	return err
}

// redis command:
// HDELALL key
func (this *Redius) HDELALL(key string) {
	c, err := this._pool.Get()
	if err != nil {
		return
	}
	_ = c.Cmd("DEL", key).Err
	this._pool.Put(c)
}

// redis zset command:
// ZADD key score val
func (this *Redius) ZADD(key string, score int, val string) error {
	c, err := this._pool.Get()
	if err != nil {
		return err
	}
	err = c.Cmd("ZADD", key, score, val).Err
	this._pool.Put(c)
	return err
}

func (this *Redius) ZADDINT(key string, score int, val int) error {
	c, err := this._pool.Get()
	if err != nil {
		return err
	}
	err = c.Cmd("ZADD", key, score, val).Err
	this._pool.Put(c)
	return err
}

// ZINCRBY key increment member
func (this *Redius) ZINCRBY(key string, increment int, val string) error {
	c, err := this._pool.Get()
	if err != nil {
		return err
	}
	err = c.Cmd("ZINCRBY", key, increment, val).Err
	this._pool.Put(c)
	return err
}

// ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
func (this *Redius) ZREVRANGEBYSCORE(key string, start, step int, withsocres bool) (val []string, err error) {
	c, err := this._pool.Get()
	if err != nil {
		return nil, err
	}

	if withsocres {
		val, err = c.Cmd("ZREVRANGEBYSCORE", key, "+inf", "-inf", "WITHSCORES", "LIMIT", start, step).List()
	} else {
		val, err = c.Cmd("ZREVRANGEBYSCORE", key, "+inf", "-inf", "LIMIT", start, step).List()
	}

	this._pool.Put(c)
	return
}

// 返回有序集 key 中，所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。按 score 值递增(从小到大)次序排列。
// ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
func (this *Redius) ZRANGEBYSCORE(key string, min, max, start, step int, withsocres bool) (val []string, err error) {
	c, err := this._pool.Get()
	if err != nil {
		return nil, err
	}
	if withsocres {
		val, err = c.Cmd("ZRANGEBYSCORE", key, min, max, "WITHSCORES", "LIMIT", start, step).List()
	} else {
		val, err = c.Cmd("ZRANGEBYSCORE", key, min, max, "LIMIT", start, step).List()
	}
	this._pool.Put(c)
	return
}

// 删除
// ZREMRANGEBYSCORE key min max
func (this *Redius) ZREMRANGEBYSCORE(key string, min, max int) (err error) {
	c, err := this._pool.Get()
	if err != nil {
		return err
	}

	err = c.Cmd("ZREMRANGEBYSCORE", key, min, max).Err

	this._pool.Put(c)
	return err
}

// KEY 操作
func (this *Redius) DEL(key string) (err error) {
	c, err := this._pool.Get()
	if err != nil {
		return err
	}
	err = c.Cmd("DEL", key).Err
	this._pool.Put(c)
	return err
}

func (this *Redius) DELALL(keys []string) (err error) {
	c, err := this._pool.Get()
	if err != nil {
		return err
	}
	err = c.Cmd("DEL", keys).Err
	this._pool.Put(c)
	return err
}

// EXPIREAT key timestamp
func (this *Redius) EXPIREAT(key string, timestamp int) {
	c, err := this._pool.Get()
	if err != nil {
		return
	}
	_ = c.Cmd("EXPIREAT", key, timestamp).Err
	this._pool.Put(c)
}

func (this *Redius) KEYS(key string) (val []string, err error) {
	c, err := this._pool.Get()
	if err != nil {
		return nil, err
	}
	val, err = c.Cmd("KEYS", key).List()
	this._pool.Put(c)
	return
}
<commit_msg>[Add] add hkeys<commit_after>package redius

import (
	"github.com/mediocregopher/radix.v2/pool"
	"github.com/mediocregopher/radix.v2/redis"
	"log"
)

type Redius struct {
	Addr     string
	Network  string
	Poolsize int
	Password string
	_pool    *pool.Pool
}

func (this *Redius) InitClient() {
	//log.Printf("%d rediu initializing from %s:%s", this.Poolsize, this.Network, this.Addr)

	var err error
	if "" == this.Password {
		this._pool, err = pool.New(this.Network, this.Addr, this.Poolsize)
	} else {
		df := func(network, addr string) (*redis.Client, error) {
			client, err := redis.Dial(network, addr)
			if err != nil {
				return nil, err
			}
			if err = client.Cmd("AUTH", this.Password).Err; err != nil {
				client.Close()
				return nil, err
			}
			return client, nil
		}

		this._pool, err = pool.NewCustom(this.Network, this.Addr, this.Poolsize, df)
	}

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

// SET key
func (this *Redius) SET(key string, val string) error {
	c, err := this._pool.Get()
	if err != nil {
		return err
	}
	err = c.Cmd("SET", key, val).Err
	this._pool.Put(c)
	return err
}

// SETEX key seconds val
func (this *Redius) SETEX(key string, secs int, val string) error {
	c, err := this._pool.Get()
	if err != nil {
		return err
	}
	err = c.Cmd("SETEX", key, secs, val).Err
	this._pool.Put(c)
	return err
}

// GET key
func (this *Redius) GET(key string) (val string, err error) {
	c, err := this._pool.Get()
	if err != nil {
		return
	}
	val, err = c.Cmd("GET", key).Str()
	this._pool.Put(c)
	return
}

func (this *Redius) MGET(keys []string) (val []string, err error) {
	c, err := this._pool.Get()
	if err != nil {
		return
	}
	r := c.Cmd("MGET", keys)
	if r.Err != nil {
		return nil, r.Err
	}
	val, err = r.List()
	this._pool.Put(c)
	return
}

//
// hset key field val
func (this *Redius) HSET(key, field string, val string) error {
	c, err := this._pool.Get()
	if err != nil {
		return err
	}
	err = c.Cmd("HSET", key, field, val).Err
	this._pool.Put(c)
	return err
}

// HSETNX key field val timeout
func (this *Redius) HSETNX(key, field string, val string) (err error) {
	c, err := this._pool.Get()
	if err != nil {
		return
	}
	err = c.Cmd("HSETNX", key, field, val).Err
	this._pool.Put(c)
	return
}

// HMSET key field val [field val ...]
func (this *Redius) HMSET(key string, val map[string]interface{}) (err error) {
	c, err := this._pool.Get()
	if err != nil {
		return
	}
	err = c.Cmd("HMSET", key, val).Err
	this._pool.Put(c)
	return
}

// HGET key field
func (this *Redius) HGET(key, field string) (val string, err error) {
	c, err := this._pool.Get()
	if err != nil {
		return
	}
	val, err = c.Cmd("HGET", key, field).Str()
	this._pool.Put(c)
	return
}

// HMGET key field [field ...]
func (this *Redius) HMGET(key string, fields []string) (val []string, err error) {
	c, err := this._pool.Get()
	if err != nil {
		return
	}
	val, err = c.Cmd("HMGET", key, fields).List()
	this._pool.Put(c)
	return
}

func (this *Redius) HGETALL(key string) (val []string, err error) {
	c, err := this._pool.Get()
	if err != nil {
		return
	}
	val, err = c.Cmd("HGETALL", key).List()
	this._pool.Put(c)
	return
}

func (this *Redius) HKEYS(key string) (val []string, err error) {
	c, err := this._pool.Get()
	if err != nil {
		return
	}
	val, err = c.Cmd("HKEYS", key).List()
	this._pool.Put(c)
	return
}

// redis command:
// HDEL key field
func (this *Redius) HDEL(key, field string) error {
	c, err := this._pool.Get()
	if err != nil {
		return err
	}
	err = c.Cmd("HDEL", key, field).Err
	this._pool.Put(c)
	return err
}

// redis command:
// HDELALL key
func (this *Redius) HDELALL(key string) {
	c, err := this._pool.Get()
	if err != nil {
		return
	}
	_ = c.Cmd("DEL", key).Err
	this._pool.Put(c)
}

// redis zset command:
// ZADD key score val
func (this *Redius) ZADD(key string, score int, val string) error {
	c, err := this._pool.Get()
	if err != nil {
		return err
	}
	err = c.Cmd("ZADD", key, score, val).Err
	this._pool.Put(c)
	return err
}

func (this *Redius) ZADDINT(key string, score int, val int) error {
	c, err := this._pool.Get()
	if err != nil {
		return err
	}
	err = c.Cmd("ZADD", key, score, val).Err
	this._pool.Put(c)
	return err
}

// ZINCRBY key increment member
func (this *Redius) ZINCRBY(key string, increment int, val string) error {
	c, err := this._pool.Get()
	if err != nil {
		return err
	}
	err = c.Cmd("ZINCRBY", key, increment, val).Err
	this._pool.Put(c)
	return err
}

// ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
func (this *Redius) ZREVRANGEBYSCORE(key string, start, step int, withsocres bool) (val []string, err error) {
	c, err := this._pool.Get()
	if err != nil {
		return nil, err
	}

	if withsocres {
		val, err = c.Cmd("ZREVRANGEBYSCORE", key, "+inf", "-inf", "WITHSCORES", "LIMIT", start, step).List()
	} else {
		val, err = c.Cmd("ZREVRANGEBYSCORE", key, "+inf", "-inf", "LIMIT", start, step).List()
	}

	this._pool.Put(c)
	return
}

// 返回有序集 key 中，所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。按 score 值递增(从小到大)次序排列。
// ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
func (this *Redius) ZRANGEBYSCORE(key string, min, max, start, step int, withsocres bool) (val []string, err error) {
	c, err := this._pool.Get()
	if err != nil {
		return nil, err
	}
	if withsocres {
		val, err = c.Cmd("ZRANGEBYSCORE", key, min, max, "WITHSCORES", "LIMIT", start, step).List()
	} else {
		val, err = c.Cmd("ZRANGEBYSCORE", key, min, max, "LIMIT", start, step).List()
	}
	this._pool.Put(c)
	return
}

// 删除
// ZREMRANGEBYSCORE key min max
func (this *Redius) ZREMRANGEBYSCORE(key string, min, max int) (err error) {
	c, err := this._pool.Get()
	if err != nil {
		return err
	}

	err = c.Cmd("ZREMRANGEBYSCORE", key, min, max).Err

	this._pool.Put(c)
	return err
}

// KEY 操作
func (this *Redius) DEL(key string) (err error) {
	c, err := this._pool.Get()
	if err != nil {
		return err
	}
	err = c.Cmd("DEL", key).Err
	this._pool.Put(c)
	return err
}

func (this *Redius) DELALL(keys []string) (err error) {
	c, err := this._pool.Get()
	if err != nil {
		return err
	}
	err = c.Cmd("DEL", keys).Err
	this._pool.Put(c)
	return err
}

// EXPIREAT key timestamp
func (this *Redius) EXPIREAT(key string, timestamp int) {
	c, err := this._pool.Get()
	if err != nil {
		return
	}
	_ = c.Cmd("EXPIREAT", key, timestamp).Err
	this._pool.Put(c)
}

func (this *Redius) KEYS(key string) (val []string, err error) {
	c, err := this._pool.Get()
	if err != nil {
		return nil, err
	}
	val, err = c.Cmd("KEYS", key).List()
	this._pool.Put(c)
	return
}
<|endoftext|>


<commit_before>package dhcpv6

import (
	"encoding/binary"
	"errors"
	"fmt"
	"net"
	"time"
)

var (
	errOptionTooShort = errors.New("option too short")
	errOptionTooLong  = errors.New("option too long")
)

// DHCPv6 option type
type OptionType uint8

//  Option types as described in RFC3315 and RFC3646
const (
	_ OptionType = iota
	OptionTypeClientID
	OptionTypeServerID
	OptionTypeIANA
	OptionTypeIATA
	OptionTypeIAAddress
	OptionTypeOptionRequest
	OptionTypePreference
	OptionTypeElapsedTime
	OptionTypeRelayMessage
	_
	OptionTypeAuthentication
	OptionTypeServerUnicast
	OptionTypeStatusCode
	OptionTypeRapidCommit
	OptionTypeUserClass
	OptionTypeVendorClass
	OptionTypeVendorOption
	OptionTypeInterfaceID
	OptionTypeReconfigureMessage
	OptionTypeReconfigureAccept
	_
	_
	OptionTypeDNSServer
	OptionTypeDNSSearchList
)

func (t OptionType) String() string {
	name := func() string {
		switch t {
		case OptionTypeClientID:
			return "Client Identifier"
		case OptionTypeServerID:
			return "Server Identifier"
		case OptionTypeIANA:
			return "Identity Association for Non-temporary Addresses"
		case OptionTypeIATA:
			return "Identity Association for Temporary Addresses"
		case OptionTypeIAAddress:
			return "Identity Association Address"
		case OptionTypeOptionRequest:
			return "Option Request"
		case OptionTypePreference:
			return "Preference"
		case OptionTypeElapsedTime:
			return "Elapsed Time"
		case OptionTypeRelayMessage:
			return "Relay Message"
		case OptionTypeAuthentication:
			return "Authentication"
		case OptionTypeServerUnicast:
			return "Server Unicast"
		case OptionTypeStatusCode:
			return "Status Code"
		case OptionTypeRapidCommit:
			return "Rapid Commit"
		case OptionTypeUserClass:
			return "User Class"
		case OptionTypeVendorClass:
			return "Vendor Class"
		case OptionTypeVendorOption:
			return "Vendor-specific Information"
		case OptionTypeInterfaceID:
			return "Interface-ID"
		case OptionTypeReconfigureMessage:
			return "Reconfigure Message"
		case OptionTypeReconfigureAccept:
			return "Reconfigure Accept"
		case OptionTypeDNSServer:
			return "DNS Server"
		case OptionTypeDNSSearchList:
			return "DNS Search List"
		default:
			return "Unknown"
		}
	}
	return fmt.Sprintf("option type %s (%d)", name(), t)
}

// base struct to be embedded by all DHCPv6 options
type optionBase struct {
	OptionType OptionType
}

func (o optionBase) Type() OptionType {
	return o.OptionType
}

// Option -- interface to build various DHCPv6 options on
type Option interface {
	String() string
	Type() OptionType
}

// OptionClientID implements the Client Identifier option as described at
// https://tools.ietf.org/html/rfc3315#section-22.2
type OptionClientID struct {
	*optionBase
	DUID DUID
}

func (o OptionClientID) String() string {
	return fmt.Sprintf("client-ID %s", o.DUID)
}

// OptionServerID implements the Server Identifier option as described at
// https://tools.ietf.org/html/rfc3315#section-22.3
type OptionServerID struct {
	*optionBase
	DUID DUID
}

func (o OptionServerID) String() string {
	return fmt.Sprintf("server-ID %s", o.DUID)
}

// OptionIANA implements the Identity Association for Non-temporary Addresses
// option as described at https://tools.ietf.org/html/rfc3315#section-22.4
type OptionIANA struct {
	*optionBase
	IAID    uint32
	T1      time.Duration // delay before Renew
	T2      time.Duration // delay before Rebind
	Options Options
}

func (o OptionIANA) String() string {
	output := fmt.Sprintf("IA_NA IAID:%d T1:%d T2:%d", o.IAID, o.T1, o.T2)
	if len(o.Options) > 0 {
		output += fmt.Sprintf(" %s", o.Options)
	}
	return output
}

// HasOption returns Option if this IA_NA option has OptionType t as option or
// nil otherwise
func (o OptionIANA) HasOption(t OptionType) Option {
	for _, opt := range o.Options {
		if opt.Type() == t {
			return opt
		}
	}
	return nil
}

// OptionIAAddress implements the IA Address option as described at
// https://tools.ietf.org/html/rfc3315#section-22.6
type OptionIAAddress struct {
	*optionBase
	Address           net.IP
	PreferredLifetime time.Duration
	ValidLifetime     time.Duration
	// TODO: options
}

func (o OptionIAAddress) String() string {
	return fmt.Sprintf("IA_ADDR %s pltime:%d vltime:%d", o.Address, o.PreferredLifetime, o.ValidLifetime)
}

// OptionOptionRequest implements the Option Request option as described at
// https://tools.ietf.org/html/rfc3315#section-22.7
type OptionOptionRequest struct {
	*optionBase
	Options []OptionType
}

func (o OptionOptionRequest) String() string {
	output := "option-request"
	for _, opt := range o.Options {
		output += fmt.Sprintf(" %s", opt)
	}
	return output
}

// helper function to parse the DHCPv6 options requested in this specific option
func (o *OptionOptionRequest) parseOptions(data []byte) error {
	var options []OptionType
	for {
		if len(data) < 2 {
			break
		}
		optionType := OptionType(binary.BigEndian.Uint16(data[:2]))
		options = append(options, optionType)
		data = data[2:]
	}

	o.Options = options
	return nil
}

// OptionElapsedTime implements the Elapsed Time option as described at
// https://tools.ietf.org/html/rfc3315#section-22.9
type OptionElapsedTime struct {
	*optionBase
	ElapsedTime time.Duration
}

func (o OptionElapsedTime) String() string {
	return fmt.Sprintf("elapsed-time %v", o.ElapsedTime)
}

// OptionRapidCommit implements the Rapid Commit option as described at
// https://tools.ietf.org/html/rfc3315#section-22.14
// this option acts basically as a flag for the message carrying it
// and has no further contents
type OptionRapidCommit struct {
	*optionBase
}

func (o OptionRapidCommit) String() string {
	return "rapid-commit"
}

// ParseOptions takes DHCPv6 option bytes and parses every handled option,
// looking at its type and the given length, and returns a slice containing all
// decoded structs
func ParseOptions(data []byte) (Options, error) {
	// empty container
	list := Options{}

	// the first 4 bytes of a  option contain option type and data length
	// so that's the least amount of bytes expected
	if len(data) < 4 {
		return list, errOptionTooShort
	}

	for {
		optionType := OptionType(binary.BigEndian.Uint16(data[0:2]))
		optionLen := binary.BigEndian.Uint16(data[2:4])
		// check if we have at least the same amount of bytes this option's length
		// is prescribing
		if len(data) < int(optionLen+4) {
			return list, errOptionTooShort
		}

		var currentOption Option
		switch optionType {
		case OptionTypeClientID:
			currentOption = &OptionClientID{
				optionBase: &optionBase{
					OptionType: optionType,
				},
			}
			duid, err := parseDUID(data[4 : 4+optionLen])
			if err != nil {
				return list, errOptionTooShort
			}
			currentOption.(*OptionClientID).DUID = duid
		case OptionTypeServerID:
			currentOption = &OptionServerID{
				optionBase: &optionBase{
					OptionType: optionType,
				},
			}
			duid, err := parseDUID(data[4 : 4+optionLen])
			if err != nil {
				return list, errOptionTooShort
			}
			currentOption.(*OptionServerID).DUID = duid
		case OptionTypeIANA:
			if optionLen < 12 {
				return list, errOptionTooShort
			}
			currentOption = &OptionIANA{
				optionBase: &optionBase{
					OptionType: optionType,
				},
			}
			currentOption.(*OptionIANA).IAID = binary.BigEndian.Uint32(data[4:8])
			currentOption.(*OptionIANA).T1 = time.Duration(binary.BigEndian.Uint32(data[8:12]))
			currentOption.(*OptionIANA).T2 = time.Duration(binary.BigEndian.Uint32(data[12:16]))
			if optionLen > 12 {
				var err error
				currentOption.(*OptionIANA).Options, err = ParseOptions(data[16 : optionLen+4])
				if err != nil {
					return list, err
				}
			}
		case OptionTypeIAAddress:
			if optionLen < 24 {
				return list, errOptionTooShort
			}
			currentOption = &OptionIAAddress{
				optionBase: &optionBase{
					OptionType: optionType,
				},
				Address:           data[4:20],
				PreferredLifetime: time.Duration(binary.BigEndian.Uint32(data[20:24])),
				ValidLifetime:     time.Duration(binary.BigEndian.Uint32(data[24:28])),
			}
		case OptionTypeOptionRequest:
			currentOption = &OptionOptionRequest{
				optionBase: &optionBase{
					OptionType: optionType,
				},
			}
			if optionLen > 0 {
				currentOption.(*OptionOptionRequest).parseOptions(data[4 : 4+optionLen])
			}
		case OptionTypeElapsedTime:
			if optionLen != 2 {
				return list, errOptionTooShort
			}
			currentOption = &OptionElapsedTime{
				optionBase: &optionBase{
					OptionType: optionType,
				},
				// elapsed time is expressed in hundredths of a second
				// hence the 10 * millisecond
				ElapsedTime: (time.Duration(binary.BigEndian.Uint16(data[4:4+optionLen])) * time.Millisecond * 10),
			}
		case OptionTypeRapidCommit:
			if optionLen != 0 {
				return list, errOptionTooLong
			}

			currentOption = &OptionRapidCommit{
				optionBase: &optionBase{
					OptionType: optionType,
				},
			}
		default:
			fmt.Printf("unhandled option type: %s\n", optionType)
		}

		// append last parsed option to list
		list = append(list, currentOption)

		// chop off bytes and go on to next option
		if len(data) <= int((4 + optionLen)) {
			break
		}
		data = data[4+optionLen:]
	}

	return list, nil
}
<commit_msg>implemented OptionStatusCode<commit_after>package dhcpv6

import (
	"encoding/binary"
	"errors"
	"fmt"
	"net"
	"time"
)

var (
	errOptionTooShort = errors.New("option too short")
	errOptionTooLong  = errors.New("option too long")
)

// DHCPv6 option type
type OptionType uint8

//  Option types as described in RFC3315 and RFC3646
const (
	_ OptionType = iota
	OptionTypeClientID
	OptionTypeServerID
	OptionTypeIANA
	OptionTypeIATA
	OptionTypeIAAddress
	OptionTypeOptionRequest
	OptionTypePreference
	OptionTypeElapsedTime
	OptionTypeRelayMessage
	_
	OptionTypeAuthentication
	OptionTypeServerUnicast
	OptionTypeStatusCode
	OptionTypeRapidCommit
	OptionTypeUserClass
	OptionTypeVendorClass
	OptionTypeVendorOption
	OptionTypeInterfaceID
	OptionTypeReconfigureMessage
	OptionTypeReconfigureAccept
	_
	_
	OptionTypeDNSServer
	OptionTypeDNSSearchList
)

func (t OptionType) String() string {
	name := func() string {
		switch t {
		case OptionTypeClientID:
			return "Client Identifier"
		case OptionTypeServerID:
			return "Server Identifier"
		case OptionTypeIANA:
			return "Identity Association for Non-temporary Addresses"
		case OptionTypeIATA:
			return "Identity Association for Temporary Addresses"
		case OptionTypeIAAddress:
			return "Identity Association Address"
		case OptionTypeOptionRequest:
			return "Option Request"
		case OptionTypePreference:
			return "Preference"
		case OptionTypeElapsedTime:
			return "Elapsed Time"
		case OptionTypeRelayMessage:
			return "Relay Message"
		case OptionTypeAuthentication:
			return "Authentication"
		case OptionTypeServerUnicast:
			return "Server Unicast"
		case OptionTypeStatusCode:
			return "Status Code"
		case OptionTypeRapidCommit:
			return "Rapid Commit"
		case OptionTypeUserClass:
			return "User Class"
		case OptionTypeVendorClass:
			return "Vendor Class"
		case OptionTypeVendorOption:
			return "Vendor-specific Information"
		case OptionTypeInterfaceID:
			return "Interface-ID"
		case OptionTypeReconfigureMessage:
			return "Reconfigure Message"
		case OptionTypeReconfigureAccept:
			return "Reconfigure Accept"
		case OptionTypeDNSServer:
			return "DNS Server"
		case OptionTypeDNSSearchList:
			return "DNS Search List"
		default:
			return "Unknown"
		}
	}
	return fmt.Sprintf("option type %s (%d)", name(), t)
}

// base struct to be embedded by all DHCPv6 options
type optionBase struct {
	OptionType OptionType
}

func (o optionBase) Type() OptionType {
	return o.OptionType
}

// Option -- interface to build various DHCPv6 options on
type Option interface {
	String() string
	Type() OptionType
}

// OptionClientID implements the Client Identifier option as described at
// https://tools.ietf.org/html/rfc3315#section-22.2
type OptionClientID struct {
	*optionBase
	DUID DUID
}

func (o OptionClientID) String() string {
	return fmt.Sprintf("client-ID %s", o.DUID)
}

// OptionServerID implements the Server Identifier option as described at
// https://tools.ietf.org/html/rfc3315#section-22.3
type OptionServerID struct {
	*optionBase
	DUID DUID
}

func (o OptionServerID) String() string {
	return fmt.Sprintf("server-ID %s", o.DUID)
}

// OptionIANA implements the Identity Association for Non-temporary Addresses
// option as described at https://tools.ietf.org/html/rfc3315#section-22.4
type OptionIANA struct {
	*optionBase
	IAID    uint32
	T1      time.Duration // delay before Renew
	T2      time.Duration // delay before Rebind
	Options Options
}

func (o OptionIANA) String() string {
	output := fmt.Sprintf("IA_NA IAID:%d T1:%d T2:%d", o.IAID, o.T1, o.T2)
	if len(o.Options) > 0 {
		output += fmt.Sprintf(" %s", o.Options)
	}
	return output
}

// HasOption returns Option if this IA_NA option has OptionType t as option or
// nil otherwise
func (o OptionIANA) HasOption(t OptionType) Option {
	for _, opt := range o.Options {
		if opt.Type() == t {
			return opt
		}
	}
	return nil
}

// OptionIAAddress implements the IA Address option as described at
// https://tools.ietf.org/html/rfc3315#section-22.6
type OptionIAAddress struct {
	*optionBase
	Address           net.IP
	PreferredLifetime time.Duration
	ValidLifetime     time.Duration
	// TODO: options
}

func (o OptionIAAddress) String() string {
	return fmt.Sprintf("IA_ADDR %s pltime:%d vltime:%d", o.Address, o.PreferredLifetime, o.ValidLifetime)
}

// OptionOptionRequest implements the Option Request option as described at
// https://tools.ietf.org/html/rfc3315#section-22.7
type OptionOptionRequest struct {
	*optionBase
	Options []OptionType
}

func (o OptionOptionRequest) String() string {
	output := "option-request"
	for _, opt := range o.Options {
		output += fmt.Sprintf(" %s", opt)
	}
	return output
}

// helper function to parse the DHCPv6 options requested in this specific option
func (o *OptionOptionRequest) parseOptions(data []byte) error {
	var options []OptionType
	for {
		if len(data) < 2 {
			break
		}
		optionType := OptionType(binary.BigEndian.Uint16(data[:2]))
		options = append(options, optionType)
		data = data[2:]
	}

	o.Options = options
	return nil
}

// OptionElapsedTime implements the Elapsed Time option as described at
// https://tools.ietf.org/html/rfc3315#section-22.9
type OptionElapsedTime struct {
	*optionBase
	ElapsedTime time.Duration
}

func (o OptionElapsedTime) String() string {
	return fmt.Sprintf("elapsed-time %v", o.ElapsedTime)
}

type StatusCode uint16

// Status codes as described at https://tools.ietf.org/html/rfc3315#section-24.4
const (
	StatusCodeSuccess StatusCode = iota
	StatusCodeUnspecFail
	StatusCodeNoAddrsAvail
	StatusCodeNoBinding
	StatusCodeNotOnLink
	StatusCodeUseMulticast
)

func (s StatusCode) String() string {
	name := func() string {
		switch s {
		case StatusCodeSuccess:
			return "Success"
		case StatusCodeUnspecFail:
			return "UnspecFail"
		case StatusCodeNoAddrsAvail:
			return "NoAddrsAvail"
		case StatusCodeNoBinding:
			return "NoBinding"
		case StatusCodeNotOnLink:
			return "NotOnLink"
		case StatusCodeUseMulticast:
			return "UseMulticast"
		default:
			return "Unknown"
		}
	}

	return fmt.Sprintf("%s (%d)", name(), s)
}

// OptionStatusCode implements the Status Code option as described at
// https://tools.ietf.org/html/rfc3315#section-22.13
type OptionStatusCode struct {
	*optionBase
	Code    StatusCode
	Message string
}

func (o OptionStatusCode) String() string {
	return fmt.Sprintf("status-code %s: %s", o.Code, o.Message)
}

// OptionRapidCommit implements the Rapid Commit option as described at
// https://tools.ietf.org/html/rfc3315#section-22.14
// this option acts basically as a flag for the message carrying it
// and has no further contents
type OptionRapidCommit struct {
	*optionBase
}

func (o OptionRapidCommit) String() string {
	return "rapid-commit"
}

// ParseOptions takes DHCPv6 option bytes and parses every handled option,
// looking at its type and the given length, and returns a slice containing all
// decoded structs
func ParseOptions(data []byte) (Options, error) {
	// empty container
	list := Options{}

	// the first 4 bytes of a  option contain option type and data length
	// so that's the least amount of bytes expected
	if len(data) < 4 {
		return list, errOptionTooShort
	}

	for {
		optionType := OptionType(binary.BigEndian.Uint16(data[0:2]))
		optionLen := binary.BigEndian.Uint16(data[2:4])
		// check if we have at least the same amount of bytes this option's length
		// is prescribing
		if len(data) < int(optionLen+4) {
			return list, errOptionTooShort
		}

		var currentOption Option
		switch optionType {
		case OptionTypeClientID:
			currentOption = &OptionClientID{
				optionBase: &optionBase{
					OptionType: optionType,
				},
			}
			duid, err := parseDUID(data[4 : 4+optionLen])
			if err != nil {
				return list, errOptionTooShort
			}
			currentOption.(*OptionClientID).DUID = duid
		case OptionTypeServerID:
			currentOption = &OptionServerID{
				optionBase: &optionBase{
					OptionType: optionType,
				},
			}
			duid, err := parseDUID(data[4 : 4+optionLen])
			if err != nil {
				return list, errOptionTooShort
			}
			currentOption.(*OptionServerID).DUID = duid
		case OptionTypeIANA:
			if optionLen < 12 {
				return list, errOptionTooShort
			}
			currentOption = &OptionIANA{
				optionBase: &optionBase{
					OptionType: optionType,
				},
			}
			currentOption.(*OptionIANA).IAID = binary.BigEndian.Uint32(data[4:8])
			currentOption.(*OptionIANA).T1 = time.Duration(binary.BigEndian.Uint32(data[8:12]))
			currentOption.(*OptionIANA).T2 = time.Duration(binary.BigEndian.Uint32(data[12:16]))
			if optionLen > 12 {
				var err error
				currentOption.(*OptionIANA).Options, err = ParseOptions(data[16 : optionLen+4])
				if err != nil {
					return list, err
				}
			}
		case OptionTypeIAAddress:
			if optionLen < 24 {
				return list, errOptionTooShort
			}
			currentOption = &OptionIAAddress{
				optionBase: &optionBase{
					OptionType: optionType,
				},
				Address:           data[4:20],
				PreferredLifetime: time.Duration(binary.BigEndian.Uint32(data[20:24])),
				ValidLifetime:     time.Duration(binary.BigEndian.Uint32(data[24:28])),
			}
		case OptionTypeOptionRequest:
			currentOption = &OptionOptionRequest{
				optionBase: &optionBase{
					OptionType: optionType,
				},
			}
			if optionLen > 0 {
				currentOption.(*OptionOptionRequest).parseOptions(data[4 : 4+optionLen])
			}
		case OptionTypeElapsedTime:
			if optionLen != 2 {
				return list, errOptionTooShort
			}
			currentOption = &OptionElapsedTime{
				optionBase: &optionBase{
					OptionType: optionType,
				},
				// elapsed time is expressed in hundredths of a second
				// hence the 10 * millisecond
				ElapsedTime: (time.Duration(binary.BigEndian.Uint16(data[4:4+optionLen])) * time.Millisecond * 10),
			}
		case OptionTypeStatusCode:
			if optionLen < 2 {
				return list, errOptionTooShort
			}
			fmt.Printf("status code bytes: %v\n", data)
			currentOption = &OptionStatusCode{
				optionBase: &optionBase{
					OptionType: optionType,
				},
				Code:    StatusCode(binary.BigEndian.Uint16(data[4:6])),
				Message: string(data[6 : optionLen+4]),
			}
		case OptionTypeRapidCommit:
			if optionLen != 0 {
				return list, errOptionTooLong
			}

			currentOption = &OptionRapidCommit{
				optionBase: &optionBase{
					OptionType: optionType,
				},
			}
		default:
			fmt.Printf("unhandled option type: %s\n", optionType)
		}

		// append last parsed option to list
		list = append(list, currentOption)

		// chop off bytes and go on to next option
		if len(data) <= int((4 + optionLen)) {
			break
		}
		data = data[4+optionLen:]
	}

	return list, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bitbucket.org/chrj/smtpd"
	"crypto/tls"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"github.com/miekg/dns"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/smtp"
	"os"
	"os/signal"
	"strconv"
	"strings"
	"syscall"
	"time"
)

type Config struct {
	Cert string
	Key  string
	Host string
	Bind string
	Port string
	Tls  string
	Time string
	Url  string
}

type Alias struct {
	Source      string
	Destination string
}

var config_file = flag.String("c", "/etc/relayd/relayd.conf", "config file")
var cert_file = flag.String("cf", "", "certificate file")
var cert_key = flag.String("ck", "", "certificate key file")
var force_tls = flag.Bool("tls", true, "force tls")
var bind_port = flag.Int("p", 25, "server port")
var bind_interface = flag.String("i", "", "server interface")
var hostname = flag.String("h", "localhost.localdomain", "server hostname")
var refresh_time = flag.Int("r", 300, "refresh time in seconds")
var alias_url = flag.String("u", "", "aliases fetch url")
var show_help = flag.Bool("help", false, "show help")
var show_version = flag.Bool("version", false, "print version")

func init() {

}

func GetOutboundIP() string {
	conn, err := net.Dial("udp", "1.2.3.4:80")
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	localAddr := conn.LocalAddr().String()
	idx := strings.LastIndex(localAddr, ":")

	return localAddr[0:idx]
}

func fetchEmailAliases(url string) ([]Alias, error) {
	var httpClient = &http.Client{Timeout: 10 * time.Second}
	var aliases []Alias

	response, err := httpClient.Get(url)

	if response != nil {
		defer response.Body.Close()
	}

	if err != nil {
		return nil, err
	}

	if response.StatusCode != 200 {
		return nil, errors.New("failed to fetch aliases")
	}

	data, err := ioutil.ReadAll(response.Body)
	body := string(data)

	lines := strings.Split(body, "\n")

	for _, line := range lines {
		ix := strings.IndexAny(line, " \t")
		if ix > 0 {
			source := strings.TrimSpace(line[:ix])
			dest := strings.TrimSpace(line[ix+1:])
			alias := Alias{source, dest}
			aliases = append(aliases, alias)
		}
	}

	log.Printf("fetched %d aliases", len(aliases))

	return aliases, err
}

func getAlias(aliases []Alias, recipient string) (Alias, error) {
	var err error
	for _, alias := range aliases {
		if alias.Source == recipient {
			return alias, err
		}
	}
	return Alias{}, errors.New("recipient not found in alias table")
}

func getMX(domain_name string) string {
	config, _ := dns.ClientConfigFromFile("/etc/resolv.conf")
	c := new(dns.Client)
	m := new(dns.Msg)
	fqdn := domain_name + "."
	m.SetQuestion(fqdn, dns.TypeMX)
	m.RecursionDesired = true
	r, _, err := c.Exchange(m, config.Servers[0]+":"+config.Port)
	if err != nil {
		log.Println(err)
		return ""
	}
	if r.Rcode != dns.RcodeSuccess {
		log.Println("name lookup failed with code ", r.Rcode)
		return ""
	}

	for _, a := range r.Answer {
		if mx, ok := a.(*dns.MX); ok {
			str := mx.String()
			ix := strings.LastIndexAny(str, " \t")
			if ix > 0 && len(str) > 3 {
				str = strings.TrimSpace(str[ix+1:])
				return str[:len(str)-1]
			}
		}
	}

	return ""
}

func main() {
	var config Config

	flag.Parse()

	if *show_help != false {
		flag.PrintDefaults()
		os.Exit(0)
	}

	if *show_version != false {
		fmt.Println("Relayd v0.1.0")
		os.Exit(0)
	}

	if *config_file != "" {
		log.Println("loading", *config_file)
	}

	json_data, err := ioutil.ReadFile(*config_file)
	json.Unmarshal(json_data, &config)

	if err != nil {
		fmt.Println(err)
		os.Exit(-1)
	}

	if config.Host == "" {
		config.Host = *hostname
	}

	if config.Bind == "" {
		if *bind_interface == "" {
			config.Bind = GetOutboundIP()
		} else {
			config.Bind = *bind_interface
		}
	}

	if config.Port == "" {
		config.Port = strconv.Itoa(*bind_port)
	}

	if *cert_file != "" {
		config.Cert = *cert_file
	}

	if *cert_key != "" {
		config.Key = *cert_key
	}

	if config.Tls != "" {
		if config.Tls == "false" {
			*force_tls = false
		} else if config.Tls == "true" {
			*force_tls = true
		}
	}

	if config.Time != "" {
		i, strerr := strconv.Atoi(config.Time)
		if strerr == nil {
			*refresh_time = i
		}
	}

	if config.Url != "" {
		if *alias_url == "" {
			*alias_url = config.Url
		}
	}

	if *alias_url == "" {
		log.Fatal("need alias fetch url")
		os.Exit(-3)
	}

	log.Println("loading certificate", config.Cert, config.Key)
	cert, err := tls.LoadX509KeyPair(config.Cert, config.Key)

	if err != nil {
		fmt.Println(err)
		os.Exit(-4)
	}

	signal_chan := make(chan os.Signal, 1)
	signal.Notify(signal_chan, syscall.SIGHUP)

	aliases, err := fetchEmailAliases(*alias_url)

	go func() {
		for {
			s := <-signal_chan
			switch s {
			case syscall.SIGHUP:
				aliases, err = fetchEmailAliases(*alias_url)
			}
		}

	}()

	periodic := time.NewTicker(time.Duration(*refresh_time) * time.Second)
	go func() {
		for {
			select {
			case <-periodic.C:
				syscall.Kill(syscall.Getpid(), syscall.SIGHUP)
			}
		}
	}()

	server := &smtpd.Server{

		Hostname: config.Host,

		Handler: func(peer smtpd.Peer, env smtpd.Envelope) error {
			for _, recipient := range env.Recipients {

				// get alias email source -> destination
				alias, err := getAlias(aliases, recipient)

				if err == nil {
					ix := strings.Index(alias.Destination, "@")
					domain := alias.Destination[ix+1:]
					servername := getMX(domain)

					if servername != "" {
						log.Println("received email for " + recipient + " and forwarding to " + alias.Destination + " via " + servername)
						mailhost := servername + ":smtp"
						smtpConn, connErr := net.Dial("tcp", mailhost)

						if connErr != nil {
							log.Println("connect error for "+mailhost, connErr)
							return connErr
						}

						client, smtpErr := smtp.NewClient(smtpConn, servername)
						if smtpErr != nil {
							log.Println("failed to create client for "+mailhost, smtpErr)
							return smtpErr
						}
						err = client.Mail(env.Sender)
						if err != nil {
							log.Println("mail-from error", err)
							return err
						}
						err = client.Rcpt(alias.Destination)
						if err != nil {
							log.Println("rcpt-to error", err)
							return err
						}

						data, writeErr := client.Data()

						_, writeErr = data.Write(env.Data)

						if writeErr != nil {
							log.Println("failed to write data to "+mailhost, writeErr)
							return writeErr
						}

						data.Close()
						client.Quit()
					}
				}

			}
			return nil
		},

		RecipientChecker: func(peer smtpd.Peer, addr string) error {
			return nil
		},

		TLSConfig: &tls.Config{
			Certificates: []tls.Certificate{cert},
		},

		ForceTLS: *force_tls,
	}

	server_bind := config.Bind + ":" + config.Port
	log.Println("listening on " + server_bind)

	err = server.ListenAndServe(server_bind)

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

	log.Println("terminating")
}
<commit_msg>using defer<commit_after>package main

import (
	"bitbucket.org/chrj/smtpd"
	"crypto/tls"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"github.com/miekg/dns"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/smtp"
	"os"
	"os/signal"
	"strconv"
	"strings"
	"syscall"
	"time"
)

type Config struct {
	Cert string
	Key  string
	Host string
	Bind string
	Port string
	Tls  string
	Time string
	Url  string
}

type Alias struct {
	Source      string
	Destination string
}

var config_file = flag.String("c", "/etc/relayd/relayd.conf", "config file")
var cert_file = flag.String("cf", "", "certificate file")
var cert_key = flag.String("ck", "", "certificate key file")
var force_tls = flag.Bool("tls", true, "force tls")
var bind_port = flag.Int("p", 25, "server port")
var bind_interface = flag.String("i", "", "server interface")
var hostname = flag.String("h", "localhost.localdomain", "server hostname")
var refresh_time = flag.Int("r", 300, "refresh time in seconds")
var alias_url = flag.String("u", "", "aliases fetch url")
var show_help = flag.Bool("help", false, "show help")
var show_version = flag.Bool("version", false, "print version")

func init() {

}

func GetOutboundIP() string {
	conn, err := net.Dial("udp", "1.2.3.4:80")
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	localAddr := conn.LocalAddr().String()
	idx := strings.LastIndex(localAddr, ":")

	return localAddr[0:idx]
}

func fetchEmailAliases(url string) ([]Alias, error) {
	var httpClient = &http.Client{Timeout: 10 * time.Second}
	var aliases []Alias

	response, err := httpClient.Get(url)

	if response != nil {
		defer response.Body.Close()
	}

	if err != nil {
		return nil, err
	}

	if response.StatusCode != 200 {
		return nil, errors.New("failed to fetch aliases")
	}

	data, err := ioutil.ReadAll(response.Body)
	body := string(data)

	lines := strings.Split(body, "\n")

	for _, line := range lines {
		ix := strings.IndexAny(line, " \t")
		if ix > 0 {
			source := strings.TrimSpace(line[:ix])
			dest := strings.TrimSpace(line[ix+1:])
			alias := Alias{source, dest}
			aliases = append(aliases, alias)
		}
	}

	log.Printf("fetched %d aliases", len(aliases))

	return aliases, err
}

func getAlias(aliases []Alias, recipient string) (Alias, error) {
	var err error
	for _, alias := range aliases {
		if alias.Source == recipient {
			return alias, err
		}
	}
	return Alias{}, errors.New("recipient not found in alias table")
}

func getMX(domain_name string) string {
	config, _ := dns.ClientConfigFromFile("/etc/resolv.conf")
	c := new(dns.Client)
	m := new(dns.Msg)
	fqdn := domain_name + "."
	m.SetQuestion(fqdn, dns.TypeMX)
	m.RecursionDesired = true
	r, _, err := c.Exchange(m, config.Servers[0]+":"+config.Port)
	if err != nil {
		log.Println(err)
		return ""
	}
	if r.Rcode != dns.RcodeSuccess {
		log.Println("name lookup failed with code ", r.Rcode)
		return ""
	}

	for _, a := range r.Answer {
		if mx, ok := a.(*dns.MX); ok {
			str := mx.String()
			ix := strings.LastIndexAny(str, " \t")
			if ix > 0 && len(str) > 3 {
				str = strings.TrimSpace(str[ix+1:])
				return str[:len(str)-1]
			}
		}
	}

	return ""
}

func main() {
	var config Config

	flag.Parse()

	if *show_help != false {
		flag.PrintDefaults()
		os.Exit(0)
	}

	if *show_version != false {
		fmt.Println("Relayd v0.1.0")
		os.Exit(0)
	}

	if *config_file != "" {
		log.Println("loading", *config_file)
	}

	json_data, err := ioutil.ReadFile(*config_file)
	json.Unmarshal(json_data, &config)

	if err != nil {
		fmt.Println(err)
		os.Exit(-1)
	}

	if config.Host == "" {
		config.Host = *hostname
	}

	if config.Bind == "" {
		if *bind_interface == "" {
			config.Bind = GetOutboundIP()
		} else {
			config.Bind = *bind_interface
		}
	}

	if config.Port == "" {
		config.Port = strconv.Itoa(*bind_port)
	}

	if *cert_file != "" {
		config.Cert = *cert_file
	}

	if *cert_key != "" {
		config.Key = *cert_key
	}

	if config.Tls != "" {
		if config.Tls == "false" {
			*force_tls = false
		} else if config.Tls == "true" {
			*force_tls = true
		}
	}

	if config.Time != "" {
		i, strerr := strconv.Atoi(config.Time)
		if strerr == nil {
			*refresh_time = i
		}
	}

	if config.Url != "" {
		if *alias_url == "" {
			*alias_url = config.Url
		}
	}

	if *alias_url == "" {
		log.Fatal("need alias fetch url")
		os.Exit(-3)
	}

	log.Println("loading certificate", config.Cert, config.Key)
	cert, err := tls.LoadX509KeyPair(config.Cert, config.Key)

	if err != nil {
		fmt.Println(err)
		os.Exit(-4)
	}

	signal_chan := make(chan os.Signal, 1)
	signal.Notify(signal_chan, syscall.SIGHUP)

	aliases, err := fetchEmailAliases(*alias_url)

	go func() {
		for {
			s := <-signal_chan
			switch s {
			case syscall.SIGHUP:
				aliases, err = fetchEmailAliases(*alias_url)
			}
		}

	}()

	periodic := time.NewTicker(time.Duration(*refresh_time) * time.Second)
	go func() {
		for {
			select {
			case <-periodic.C:
				syscall.Kill(syscall.Getpid(), syscall.SIGHUP)
			}
		}
	}()

	server := &smtpd.Server{

		Hostname: config.Host,

		Handler: func(peer smtpd.Peer, env smtpd.Envelope) error {
			for _, recipient := range env.Recipients {

				// get alias email source -> destination
				alias, err := getAlias(aliases, recipient)

				if err == nil {
					ix := strings.Index(alias.Destination, "@")
					domain := alias.Destination[ix+1:]
					servername := getMX(domain)

					if servername != "" {
						log.Println("received email for " + recipient + " and forwarding to " + alias.Destination + " via " + servername)
						mailhost := servername + ":smtp"
						smtpConn, connErr := net.Dial("tcp", mailhost)

						if connErr != nil {
							log.Println("connect error for "+mailhost, connErr)
							return connErr
						}

						client, smtpErr := smtp.NewClient(smtpConn, servername)
						if smtpErr != nil {
							log.Println("failed to create client for "+mailhost, smtpErr)
							return smtpErr
						}
						err = client.Mail(env.Sender)
						defer client.Quit()
						if err != nil {
							log.Println("mail-from error", err)
							return err
						}
						err = client.Rcpt(alias.Destination)
						if err != nil {
							log.Println("rcpt-to error", err)
							return err
						}

						data, writeErr := client.Data()
						defer data.Close()

						_, writeErr = data.Write(env.Data)

						if writeErr != nil {
							log.Println("failed to write data to "+mailhost, writeErr)
							return writeErr
						}

					}
				}

			}
			return nil
		},

		RecipientChecker: func(peer smtpd.Peer, addr string) error {
			return nil
		},

		TLSConfig: &tls.Config{
			Certificates: []tls.Certificate{cert},
		},

		ForceTLS: *force_tls,
	}

	server_bind := config.Bind + ":" + config.Port
	log.Println("listening on " + server_bind)

	err = server.ListenAndServe(server_bind)

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

	log.Println("terminating")
}
<|endoftext|>


<commit_before>package sirius

import (
	"encoding/json"
	"github.com/kayex/sirius/slack"
	"net/http"
)

type Remote struct {
	url    string
	token  string
	client *http.Client
}

type RemoteUser struct {
	IDHash string                 `json:"id_hash_sha256"`
	Token  string                 `json:"slack_token"`
	Config map[string]interface{} `json:"config"`
}

func NewRemote(url, token string) *Remote {
	return &Remote{
		url:    url,
		token:  token,
		client: &http.Client{},
	}
}

func (ru *RemoteUser) Convert() *User {
	u := NewUser(ru.Token)
	u.ID = slack.SecureID{ru.IDHash}

	for eid, c := range ru.Config {
		cfg := NewConfiguration(EID(eid))

		if conf, ok := c.(ExtensionConfig); ok {
			cfg.Cfg = conf
		}

		u.Configurations = append(u.Configurations, &cfg)
	}

	return u
}

func (r *Remote) Request(endpoint string) (*http.Response, error) {
	url := r.url + endpoint + "?token=" + r.token

	return r.client.Get(url)
}

func (r *Remote) GetUsers() ([]User, error) {
	var remoteUsers []RemoteUser
	res, err := r.Request("/configs")

	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	if err = json.NewDecoder(res.Body).Decode(&remoteUsers); err != nil {
		return nil, err
	}

	var users []User

	for _, ru := range remoteUsers {
		users = append(users, *ru.Convert())
	}

	return users, nil
}
<commit_msg>Unexport remote related functions<commit_after>package sirius

import (
	"encoding/json"
	"github.com/kayex/sirius/slack"
	"net/http"
)

type Remote struct {
	url    string
	token  string
	client *http.Client
}

type RemoteUser struct {
	IDHash string                 `json:"id_hash_sha256"`
	Token  string                 `json:"slack_token"`
	Config map[string]interface{} `json:"config"`
}

func NewRemote(url, token string) *Remote {
	return &Remote{
		url:    url,
		token:  token,
		client: &http.Client{},
	}
}

func (ru *RemoteUser) convert() *User {
	u := NewUser(ru.Token)
	u.ID = slack.SecureID{ru.IDHash}

	for eid, c := range ru.Config {
		cfg := NewConfiguration(EID(eid))

		if conf, ok := c.(ExtensionConfig); ok {
			cfg.Cfg = conf
		}

		u.Configurations = append(u.Configurations, &cfg)
	}

	return u
}

func (r *Remote) request(endpoint string) (*http.Response, error) {
	url := r.url + endpoint + "?token=" + r.token

	return r.client.Get(url)
}

func (r *Remote) GetUsers() ([]User, error) {
	var remoteUsers []RemoteUser
	res, err := r.request("/configs")

	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	if err = json.NewDecoder(res.Body).Decode(&remoteUsers); err != nil {
		return nil, err
	}

	var users []User

	for _, ru := range remoteUsers {
		users = append(users, *ru.convert())
	}

	return users, nil
}
<|endoftext|>


<commit_before>package gxutil

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"time"

	rname "github.com/jbenet/go-os-rename"
	. "github.com/whyrusleeping/stump"
)

type ErrAlreadyInstalled struct {
	pkg string
}

func IsErrAlreadyInstalled(err error) bool {
	_, ok := err.(ErrAlreadyInstalled)
	return ok
}

func (eai ErrAlreadyInstalled) Error() string {
	return fmt.Sprintf("package %s already installed", eai.pkg)
}

func (pm *PM) GetPackageTo(hash, out string) (*Package, error) {
	var pkg Package
	_, err := os.Stat(out)
	if err == nil {
		err := FindPackageInDir(&pkg, out)
		if err == nil {
			return &pkg, nil
		} else if !os.IsNotExist(err) {
			return nil, err
		}
	}

	if err != nil && !os.IsNotExist(err) {
		return nil, err
	}

	outtemp := out + ".part"

	// check if a fetch was previously started and failed, cleanup if found
	_, err = os.Stat(outtemp)
	if err == nil {
		VLog("Found previously failed fetch, cleaning up...")
		if err := os.RemoveAll(outtemp); err != nil {
			Error("cleaning up aborted transfer: %s", err)
		}
	}

	begin := time.Now()
	VLog("  - fetching %s via ipfs api", hash)
	tries := 3
	for i := 0; i < tries; i++ {
		if err := pm.Shell().Get(hash, outtemp); err != nil {
			Error("from shell.Get(): %s", err)

			rmerr := os.RemoveAll(outtemp)
			if rmerr != nil {
				Error("cleaning up temp download directory: %s", rmerr)
			}

			if i == tries-1 {
				return nil, err
			}
			Log("retrying fetch %s after a second...", hash)
			time.Sleep(time.Second)
		} else {
			if err := rname.Rename(outtemp, out); err != nil {
				return nil, err
			}
			break
		}
	}
	VLog("  - fetch finished in %s", time.Now().Sub(begin))

	err = FindPackageInDir(&pkg, out)
	if err != nil {
		return nil, err
	}

	return &pkg, nil
}

func FindPackageInDir(pkg interface{}, dir string) error {
	if err := LoadPackageFile(pkg, filepath.Join(dir, PkgFileName)); err == nil {
		return nil
	}

	name, err := PackageNameInDir(dir)
	if err != nil {
		return err
	}
	return LoadPackageFile(pkg, filepath.Join(dir, name, PkgFileName))
}

func PackageNameInDir(dir string) (string, error) {
	fs, err := ioutil.ReadDir(dir)
	if err != nil {
		return "", err
	}

	if len(fs) == 0 {
		return "", fmt.Errorf("no package found in hashdir: %s", dir)
	}

	if len(fs) > 1 {
		return "", fmt.Errorf("found multiple packages in hashdir: %s", dir)
	}

	return fs[0].Name(), nil
}
<commit_msg>cleanup of dot import<commit_after>package gxutil

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"time"

	rname "github.com/jbenet/go-os-rename"
	stump "github.com/whyrusleeping/stump"
)

type ErrAlreadyInstalled struct {
	pkg string
}

func IsErrAlreadyInstalled(err error) bool {
	_, ok := err.(ErrAlreadyInstalled)
	return ok
}

func (eai ErrAlreadyInstalled) Error() string {
	return fmt.Sprintf("package %s already installed", eai.pkg)
}

func (pm *PM) GetPackageTo(hash, out string) (*Package, error) {
	var pkg Package
	_, err := os.Stat(out)
	if err == nil {
		err := FindPackageInDir(&pkg, out)
		if err == nil {
			return &pkg, nil
		} else if !os.IsNotExist(err) {
			return nil, err
		}
	}

	if err != nil && !os.IsNotExist(err) {
		return nil, err
	}

	outtemp := out + ".part"

	// check if a fetch was previously started and failed, cleanup if found
	_, err = os.Stat(outtemp)
	if err == nil {
		stump.VLog("Found previously failed fetch, cleaning up...")
		if err := os.RemoveAll(outtemp); err != nil {
			stump.Error("cleaning up previous aborted transfer: %s", err)
		}
	}

	begin := time.Now()
	stump.VLog("  - fetching %s via ipfs api", hash)
	tries := 3
	for i := 0; i < tries; i++ {
		if err := pm.Shell().Get(hash, outtemp); err != nil {
			stump.Error("from shell.Get(): %v", err)

			rmerr := os.RemoveAll(outtemp)
			if rmerr != nil {
				stump.Error("cleaning up temp download directory: %s", rmerr)
			}

			if i == tries-1 {
				return nil, err
			}
			stump.Log("retrying fetch %s after a second...", hash)
			time.Sleep(time.Second)
		} else {
			if err := rname.Rename(outtemp, out); err != nil {
				return nil, err
			}
			break
		}
	}
	stump.VLog("  - fetch finished in %s", time.Now().Sub(begin))

	err = FindPackageInDir(&pkg, out)
	if err != nil {
		return nil, err
	}

	return &pkg, nil
}

func FindPackageInDir(pkg interface{}, dir string) error {
	if err := LoadPackageFile(pkg, filepath.Join(dir, PkgFileName)); err == nil {
		return nil
	}

	name, err := PackageNameInDir(dir)
	if err != nil {
		return err
	}
	return LoadPackageFile(pkg, filepath.Join(dir, name, PkgFileName))
}

func PackageNameInDir(dir string) (string, error) {
	fs, err := ioutil.ReadDir(dir)
	if err != nil {
		return "", err
	}

	if len(fs) == 0 {
		return "", fmt.Errorf("no package found in hashdir: %s", dir)
	}

	if len(fs) > 1 {
		return "", fmt.Errorf("found multiple packages in hashdir: %s", dir)
	}

	return fs[0].Name(), nil
}
<|endoftext|>


<commit_before>// ReqManager is the gocui manager for requestors.
package goublu

import (
	"fmt"
	"github.com/jwoehr/gocui"
	// "github.com/jwoehr/termbox-go"
	// "io"
	// "io/ioutil"
	// "log"
	// "strings"
)

// A gocui manager for Requestors.
type ReqManager struct {
	UM        *UbluManager
	G         *gocui.Gui
	X         int
	Y         int
	Title     string
	BgColor   gocui.Attribute
	FgColor   gocui.Attribute
	ReqEditor gocui.Editor
	Text      string
	// UbluinBuf  string
	UbluoutBuf string
}

// Instances a new manager.
func NewReqManager(um *UbluManager, g *gocui.Gui, x int, y int, title string, bgc gocui.Attribute, fgc gocui.Attribute, text string) (rm *ReqManager) {
	rm = &ReqManager{
		UM:      um,
		G:       g,
		X:       x,
		Y:       y,
		Title:   title,
		BgColor: bgc,
		FgColor: fgc,
		Text:    text,
	}
	rm.ReqEditor = gocui.EditorFunc(func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) {
		gx, gy := rm.G.Size()
		cx, cy := v.Cursor()

		// Shut up compiler
		gx = gx
		gy = gy
		cx = cx
		cy = cy

		switch {
		case key == gocui.KeyArrowDown:
			v.MoveCursor(0, 1, false)
		case key == gocui.KeyArrowUp:
			v.MoveCursor(0, -1, false)
		case key == gocui.KeyArrowLeft:
			v.MoveCursor(-1, 0, false)
		case key == gocui.KeyArrowRight:
			v.MoveCursor(1, 0, false)
		/*
		case key == gocui.MouseWheelUp:
			v.MoveCursor(0, -1, false)
			case key == gocui.MouseWheelDown:
			v.MoveCursor(0, 1, false)
		*/
		case key == gocui.KeyF3:
			rm.EndReq()
		}
	})

	return rm
}

func (rm *ReqManager) calcPos(maxX int, maxY int) (x1 int, y1 int, x2 int, y2 int) {
	x1 = Max((maxX-rm.X)/2, 0)
	x2 = maxX - x1
	y1 = Max((maxY-rm.Y)/2, 0)
	y2 = maxY - y1
	return x1, y1, x2, y2
}

// Unshows the requestor.
func (rm *ReqManager) EndReq() {
	rm.G.SetManager(rm.UM)
	rm.UM.Layout(rm.G)
	v, _ := rm.G.View("Ubluin")
	// fmt.Fprint(v, rm.UbluinBuf)
	v, _ = rm.G.View("Ubluout")
	fmt.Fprint(v, rm.UbluoutBuf)
}

// Shows the requestor.
func (rm *ReqManager) StartReq() {
	v, _ := rm.G.View("Ubluin")
	// rm.UbluinBuf = v.Buffer()
	v, _ = rm.G.View("Ubluout")
	rm.UbluoutBuf = v.Buffer()
	rm.G.SetManager(rm.UM, rm)
}

// Obligatory layout redraw function
func (rm *ReqManager) Layout(g *gocui.Gui) error {
	x1, y1, x2, y2 := rm.calcPos(g.Size())
	if v, err := g.SetView(rm.Title, x1, y1, x2, y2); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}
		v.Title = " " + rm.Title + " "
		// v.Autoscroll = true
		v.Editable = true
		v.Editor = rm.ReqEditor
		v.Wrap = true
		v.BgColor = rm.BgColor
		v.FgColor = rm.FgColor
		fmt.Fprintf(v, "%s", rm.Text)
	}
	if _, err := g.SetCurrentView(rm.Title); err != nil {
		return err
	}
	return nil
}
<commit_msg>cleanup<commit_after>// ReqManager is the gocui manager for requestors.
package goublu

import (
	"fmt"
	"github.com/jwoehr/gocui"
	"strings"
)

// A gocui manager for Requestors.
type ReqManager struct {
	UM         *UbluManager
	G          *gocui.Gui
	X          int
	Y          int
	Title      string
	BgColor    gocui.Attribute
	FgColor    gocui.Attribute
	ReqEditor  gocui.Editor
	Text       string
	UbluinBuf  string
	UbluoutBuf string
}

// Instances a new manager.
func NewReqManager(um *UbluManager, g *gocui.Gui, x int, y int, title string, bgc gocui.Attribute, fgc gocui.Attribute, text string) (rm *ReqManager) {
	rm = &ReqManager{
		UM:      um,
		G:       g,
		X:       x,
		Y:       y,
		Title:   title,
		BgColor: bgc,
		FgColor: fgc,
		Text:    text,
	}
	rm.ReqEditor = gocui.EditorFunc(func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) {
		gx, gy := rm.G.Size()
		cx, cy := v.Cursor()

		// Shut up compiler
		gx = gx
		gy = gy
		cx = cx
		cy = cy

		switch {
		case key == gocui.KeyArrowDown:
			v.MoveCursor(0, 1, false)
		case key == gocui.KeyArrowUp:
			v.MoveCursor(0, -1, false)
		case key == gocui.KeyArrowLeft:
			v.MoveCursor(-1, 0, false)
		case key == gocui.KeyArrowRight:
			v.MoveCursor(1, 0, false)
		/*
			case key == gocui.MouseWheelUp:
				v.MoveCursor(0, -1, false)
				case key == gocui.MouseWheelDown:
				v.MoveCursor(0, 1, false)
		*/
		case key == gocui.KeyF3:
			rm.EndReq()
		}
	})

	return rm
}

func (rm *ReqManager) calcPos(maxX int, maxY int) (x1 int, y1 int, x2 int, y2 int) {
	x1 = Max((maxX-rm.X)/2, 0)
	x2 = maxX - x1
	y1 = Max((maxY-rm.Y)/2, 0)
	y2 = maxY - y1
	return x1, y1, x2, y2
}

// Unshows the requestor.
func (rm *ReqManager) EndReq() {
	rm.G.SetManager(rm.UM)
	rm.UM.Layout(rm.G)
	v, _ := rm.G.View("Ubluin")
	v.Clear()
	for _, ch := range strings.Trim(strings.TrimSpace(rm.UbluinBuf), "\000") {
		v.EditWrite(ch)
	}
	v, _ = rm.G.View("Ubluout")
	fmt.Fprintf(v, "%s\n", strings.Trim(strings.TrimSpace(rm.UbluoutBuf), "\000"))
}

// Shows the requestor.
func (rm *ReqManager) StartReq() {
	v, _ := rm.G.View("Ubluin")
	rm.UbluinBuf = v.Buffer()
	v, _ = rm.G.View("Ubluout")
	rm.UbluoutBuf = v.Buffer()
	rm.G.SetManager(rm.UM, rm)
}

// Obligatory layout redraw function
func (rm *ReqManager) Layout(g *gocui.Gui) error {
	x1, y1, x2, y2 := rm.calcPos(g.Size())
	if v, err := g.SetView(rm.Title, x1, y1, x2, y2); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}
		v.Title = " " + rm.Title + " "
		v.Editable = true
		v.Editor = rm.ReqEditor
		v.Wrap = true
		v.BgColor = rm.BgColor
		v.FgColor = rm.FgColor
		fmt.Fprintf(v, "%s", rm.Text)
	}
	if _, err := g.SetCurrentView(rm.Title); err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package hack

// +build tools

// Add tools that hack scripts depend on here, to ensure they are vendored.
import (
	_ "github.com/bazelbuild/bazel-gazelle/cmd/gazelle"
	_ "k8s.io/code-generator/cmd/client-gen"
	_ "k8s.io/code-generator/cmd/deepcopy-gen"
	_ "k8s.io/code-generator/cmd/informer-gen"
	_ "k8s.io/code-generator/cmd/lister-gen"
	_ "k8s.io/repo-infra/kazel"
)
<commit_msg>Fix tooling build tag<commit_after>// +build tools

/*
Copyright 2019 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package hack

// Add tools that hack scripts depend on here, to ensure they are vendored.
import (
	_ "github.com/bazelbuild/bazel-gazelle/cmd/gazelle"
	_ "k8s.io/code-generator/cmd/client-gen"
	_ "k8s.io/code-generator/cmd/deepcopy-gen"
	_ "k8s.io/code-generator/cmd/informer-gen"
	_ "k8s.io/code-generator/cmd/lister-gen"
	_ "k8s.io/repo-infra/kazel"
)
<|endoftext|>


<commit_before>package tql

/*
  Tql, Simple SQL-Like query language

  BNF:
  select <property> [, <property> ...] | *]
    [from <from>]
    [where <condition> [and <condition> ...]]

  <condition> := <property> {< | <= | > | >= | = | != | in} <value>
*/

import (
	"regexp"
	"strconv"
	"strings"
)

var tokenize_regex = `(?:"[^"\n\r]*")+|(?:'[^'\n\r]*')+|<=|>=|!=|=|<|>|,|\*|-?\d+(?:\.\d+)?|\w+(?:\.\w+)*|(?:"[^"\s]+")+|\(|\)|\S+`

type Tql struct {
	tokens []string
	query  string
	pos    int
	props  []string
	from   string
	conds  []Cond
    limit  int64
    offset int64
}

const (
	ValString = iota
	ValInt
	ValFloat
	ValQuoteString
	ValBool
	ValNull
	ValReference
    ValList
)

type Val struct {
	valType int
	val     interface{}
}

type Cond struct {
	identifier string
	op         string
	val        Val
}

func NewTql(query string) *Tql {
	t := new(Tql)
	t.pos = 0
	t.query = strings.ToLower(query)
    t.limit = -1
    t.offset = -1
	if re, err := regexp.Compile(tokenize_regex); err != nil {
		return nil
	} else {
		t.tokens = re.FindAllString(t.query, -1)
		if len(t.tokens) <= 0 {
			return nil
		}
	}
	t.__Select()
	return t
}

func (t *Tql) __Expect(expect string) {
	if t.__Consume(expect) == false {
		panic("token error")
	}
}

func (t *Tql) __Consume(expect string) bool {
	if t.pos < len(t.tokens) {
		if t.tokens[t.pos] == expect {
			t.pos += 1
			return true
		}
	}
	return false
}

func (t *Tql) __ConsumeRegexp(regex string) (bool, string) {
	if t.pos < len(t.tokens) {
		token := t.tokens[t.pos]
		re, _ := regexp.Compile(regex)
		if re.MatchString(token) {
			t.pos += 1
			return true, token
		}
	}
	return false, ""
}

// consume a identifier an return
var identifier_regex = `(\w+(?:\.\w+)*)$`
func (t *Tql) __Identifier() (bool, string) {
	if b, ident := t.__ConsumeRegexp(identifier_regex); b {
		return true, ident
	}
	return false, ""
}

func (t *Tql) __ExpectIdentifier() string {
	if b, ident := t.__Identifier(); b {
		return ident
	}
	panic("identifier error")
}

func (t *Tql) __Select() bool {
	t.__Expect("select")
	if !t.__Consume("*") {
		t.props = append(t.props, t.__ExpectIdentifier())
		for t.__Consume(",") {
			// TODO add prop
			t.props = append(t.props, t.__ExpectIdentifier())
		}
	} else {
		t.props = append(t.props, "*")
	}
	return t.__From()
}

func (t *Tql) __From() bool {
	if t.__Consume("from") {
		t.from = t.__ExpectIdentifier()
	} else {
		return false
	}
	return t.__Where()
}

func (t *Tql) __Where() bool {
	if t.__Consume("where") {
		return t.__ParseFilterList()
	}
    return t.__Limit()
}

var num_regex = `(\d+)$`
func (t *Tql) __Limit() bool {
    if t.__Consume("limit") {
        _, limit := t.__ConsumeRegexp(num_regex)
        n, err := strconv.ParseInt(limit, 10, 64)
        if err != nil {
            return false
        }
        if t.__Consume(",") {
            t.offset = n
            _, limit := t.__ConsumeRegexp(num_regex)
            n, err = strconv.ParseInt(limit, 10, 64)
            if err != nil {
                return false
            }
        }
        t.limit = n
    }
    return t.__Offset()
}

func (t *Tql) __Offset() bool {
    if t.__Consume("offset") {
        b, offset := t.__ConsumeRegexp(num_regex)
        if !b {
            return false
        }
        n, err := strconv.ParseInt(offset, 10, 64)
        if err != nil {
            return false
        }
        t.offset = n
        return true
    }
    return true
}

var quoted_string_regex = `((?:\'[^\'\n\r]*\')+)|((?:"[^"\n\r]*")+)`
func (t *Tql) __Value() (bool, Val) {
	if t.pos < len(t.tokens) {
		token := t.tokens[t.pos]
		// try int
		i, err := strconv.ParseInt(token, 10, 64)
		if err == nil {
			t.pos += 1
			return true, Val{ValInt, i}
		}
		// try float
		f, err := strconv.ParseFloat(token, 64)
		if err == nil {
			t.pos += 1
			return true, Val{ValFloat, f}
		}
		// try quote string
		b, val := t.__ConsumeRegexp(quoted_string_regex)
		if b {
            if t.tokens[t.pos-1][0] == '\'' {
                val = strings.Replace(val, "''", "'", -1)
            } else {
                val = strings.Replace(val, `""`, `"`, -1)
            }
			return true, Val{ValQuoteString, val}
		}
		// try bool
		b = t.__Consume("true")
		if b {
			return true, Val{ValBool, true}
		}
		b = t.__Consume("false")
		if b {
			return true, Val{ValBool, false}
		}
		// try null
		b = t.__Consume("null")
		if b {
			return true, Val{ValNull, nil}
		}
	}
	return false, Val{}
}

func (t *Tql) __ValueList() (bool, Val) {
    var vals []Val
    t.__Expect("(")
    for {
        if b, val := t.__Value(); b {
            vals = append(vals, val)
        } else {
            return false, Val{}
        }
        if !t.__Consume(",") {
            break
        }
    }
    t.__Expect(")")
    return true, Val{ValList, vals}
}

var condition_regex = `(<=|>=|!=|=|<|>|in)$`
func (t *Tql) __ParseFilterList() bool {
	b, ident := t.__Identifier()
	if !b {
		return b
	}
	b, op := t.__ConsumeRegexp(condition_regex)
	if !b {
		return b
	}
	b, val := t.__Value()
	if !b && op == "in" {
        b, val = t.__ValueList()
	}
    if !b {
        return b
    }
	t.conds = append(t.conds, Cond{ident, op, val})
	if t.__Consume("and") {
		return t.__ParseFilterList()
	}
	return t.__Limit()
}
<commit_msg>add limit offset<commit_after>package tql

/*
  Tql, Simple SQL-Like query language

  BNF:
  select <property> [, <property> ...] | *]
    [from <from>]
    [where <condition> [and <condition> ...]] [limit <num> offset <num> | limit <num>, <num>]

  <condition> := <property> {< | <= | > | >= | = | != | in} <value>
*/

import (
	"regexp"
	"strconv"
	"strings"
)

var tokenize_regex = `(?:"[^"\n\r]*")+|(?:'[^'\n\r]*')+|<=|>=|!=|=|<|>|,|\*|-?\d+(?:\.\d+)?|\w+(?:\.\w+)*|(?:"[^"\s]+")+|\(|\)|\S+`

type Tql struct {
	tokens  []string
	query   string
	pos     int
	props   []string
	from    string
	conds   []Cond
	limit   int64
	offset  int64
	orderBy string // TODO
	order   int // TODO
}

const (
	ValString = iota
	ValInt
	ValFloat
	ValQuoteString
	ValBool
	ValNull
	ValReference
	ValList
)

type Val struct {
	valType int
	val     interface{}
}

type Cond struct {
	identifier string
	op         string
	val        Val
}

func NewTql(query string) *Tql {
	t := new(Tql)
	t.pos = 0
	t.query = strings.ToLower(query)
	t.limit = -1
	t.offset = -1
    t.order = -1
	if re, err := regexp.Compile(tokenize_regex); err != nil {
		return nil
	} else {
		t.tokens = re.FindAllString(t.query, -1)
		if len(t.tokens) <= 0 {
			return nil
		}
	}
	t.__Select()
	return t
}

func (t *Tql) __Expect(expect string) {
	if t.__Consume(expect) == false {
		panic("token error")
	}
}

func (t *Tql) __Consume(expect string) bool {
	if t.pos < len(t.tokens) {
		if t.tokens[t.pos] == expect {
			t.pos += 1
			return true
		}
	}
	return false
}

func (t *Tql) __ConsumeRegexp(regex string) (bool, string) {
	if t.pos < len(t.tokens) {
		token := t.tokens[t.pos]
		re, _ := regexp.Compile(regex)
		if re.MatchString(token) {
			t.pos += 1
			return true, token
		}
	}
	return false, ""
}

// consume a identifier an return
var identifier_regex = `(\w+(?:\.\w+)*)$`

func (t *Tql) __Identifier() (bool, string) {
	if b, ident := t.__ConsumeRegexp(identifier_regex); b {
		return true, ident
	}
	return false, ""
}

func (t *Tql) __ExpectIdentifier() string {
	if b, ident := t.__Identifier(); b {
		return ident
	}
	panic("identifier error")
}

func (t *Tql) __Select() bool {
	t.__Expect("select")
	if !t.__Consume("*") {
		t.props = append(t.props, t.__ExpectIdentifier())
		for t.__Consume(",") {
			// TODO add prop
			t.props = append(t.props, t.__ExpectIdentifier())
		}
	} else {
		t.props = append(t.props, "*")
	}
	return t.__From()
}

func (t *Tql) __From() bool {
	if t.__Consume("from") {
		t.from = t.__ExpectIdentifier()
	} else {
		return false
	}
	return t.__Where()
}

func (t *Tql) __Where() bool {
	if t.__Consume("where") {
		return t.__ParseFilterList()
	}
	return t.__Limit()
}

var num_regex = `(\d+)$`

func (t *Tql) __Limit() bool {
	if t.__Consume("limit") {
		_, limit := t.__ConsumeRegexp(num_regex)
		n, err := strconv.ParseInt(limit, 10, 64)
		if err != nil {
			return false
		}
		if t.__Consume(",") {
			t.offset = n
			_, limit := t.__ConsumeRegexp(num_regex)
			n, err = strconv.ParseInt(limit, 10, 64)
			if err != nil {
				return false
			}
		}
		t.limit = n
	}
	return t.__Offset()
}

func (t *Tql) __Offset() bool {
	if t.__Consume("offset") {
		b, offset := t.__ConsumeRegexp(num_regex)
		if !b {
			return false
		}
		n, err := strconv.ParseInt(offset, 10, 64)
		if err != nil {
			return false
		}
		t.offset = n
		return true
	}
	return true
}

var quoted_string_regex = `((?:\'[^\'\n\r]*\')+)|((?:"[^"\n\r]*")+)`

func (t *Tql) __Value() (bool, Val) {
	if t.pos < len(t.tokens) {
		token := t.tokens[t.pos]
		// try int
		i, err := strconv.ParseInt(token, 10, 64)
		if err == nil {
			t.pos += 1
			return true, Val{ValInt, i}
		}
		// try float
		f, err := strconv.ParseFloat(token, 64)
		if err == nil {
			t.pos += 1
			return true, Val{ValFloat, f}
		}
		// try quote string
		b, val := t.__ConsumeRegexp(quoted_string_regex)
		if b {
			if t.tokens[t.pos-1][0] == '\'' {
				val = strings.Replace(val, "''", "'", -1)
			} else {
				val = strings.Replace(val, `""`, `"`, -1)
			}
			return true, Val{ValQuoteString, val}
		}
		// try bool
		b = t.__Consume("true")
		if b {
			return true, Val{ValBool, true}
		}
		b = t.__Consume("false")
		if b {
			return true, Val{ValBool, false}
		}
		// try null
		b = t.__Consume("null")
		if b {
			return true, Val{ValNull, nil}
		}
	}
	return false, Val{}
}

func (t *Tql) __ValueList() (bool, Val) {
	var vals []Val
	t.__Expect("(")
	for {
		if b, val := t.__Value(); b {
			vals = append(vals, val)
		} else {
			return false, Val{}
		}
		if !t.__Consume(",") {
			break
		}
	}
	t.__Expect(")")
	return true, Val{ValList, vals}
}

var condition_regex = `(<=|>=|!=|=|<|>|in)$`

func (t *Tql) __ParseFilterList() bool {
	b, ident := t.__Identifier()
	if !b {
		return b
	}
	b, op := t.__ConsumeRegexp(condition_regex)
	if !b {
		return b
	}
	b, val := t.__Value()
	if !b && op == "in" {
		b, val = t.__ValueList()
	}
	if !b {
		return b
	}
	t.conds = append(t.conds, Cond{ident, op, val})
	if t.__Consume("and") {
		return t.__ParseFilterList()
	}
	return t.__Limit()
}
<|endoftext|>


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

package main

import (
	"bufio"
	"io"
	"log"
	"regexp"
	"strconv"

	"github.com/BurntSushi/xgbutil/xgraphics"
)

// Type EndScan is an artifical Error.
// Raised when parser should stop scanning.
type EndScan struct{}

func (e EndScan) Error() string { return "EndScan" }

// NewBGRA returns a new color definition in X compatible format.
// Input should be a hexagonal representation with alpha, i.e 0xAARRGGBB.
func NewBGRA(color uint64) *xgraphics.BGRA {
	a := uint8(color >> 24)
	r := uint8((color & 0x00ff0000) >> 16)
	g := uint8((color & 0x0000ff00) >> 8)
	b := uint8(color & 0x000000ff)
	return &xgraphics.BGRA{B: b, G: g, R: r, A: a}
}

// TextPiece stores formatting information for a text
// within single pair of brackets.
type TextPiece struct {
	Text       string
	Font       uint
	Foreground *xgraphics.BGRA
	Background *xgraphics.BGRA
	Screens    []uint
}

// TextParser is used to create a set of TextPieces from a textual definition.
type TextParser struct {
	rgbPattern *regexp.Regexp
}

// NewTextParser creates TextParser instance with
// correct necessary regexp definitions.
func NewTextParser() *TextParser {
	return &TextParser{regexp.MustCompile(`^0[xX][0-9a-fA-F]{8}$`)}
}

// Tokenize turns textual definition into a series of valid tokens.
// If no valid token is found at given place, char a 0 position if returned.
func (self *TextParser) Tokenize(
	data []byte, EOF bool,
) (advance int, token []byte, err error) {
	switch {
	case data[0] == '\n':
		err = EndScan{}
	case len(data) < 2:
		advance, token, err = 1, data[:1], nil
	case string(data[:2]) == "{F":
		advance, token, err = 2, data[:2], nil
	case string(data[:2]) == "{S":
		advance, token, err = 2, data[:2], nil
	case len(data) < 3:
		advance, token, err = 1, data[:1], nil
	case string(data[:3]) == "{CF":
		advance, token, err = 3, data[:3], nil
	case string(data[:3]) == "{CB":
		advance, token, err = 3, data[:3], nil
	case len(data) >= 10 && self.rgbPattern.Match(data[:10]):
		advance, token, err = 10, data[:10], nil
	case '0' <= data[0] && data[0] <= '9':
		i := 0
		for _, n := range data {
			if !('0' <= n && n <= '9') {
				break
			}
			i += 1
		}
		advance, token, err = i, data[:i], nil
	default: // Also contains '}' and ','
		// TODO: Parsing whole text piece here, instead of returning
		// char-by-char, should perform better
		advance, token, err = 1, data[:1], nil
	}
	return
}

// Scan scans textual definition and returns array of TextPieces.
// Possible empty pieces are omitted in the returned array.
func (self *TextParser) Scan(r io.Reader) ([]*TextPiece, error) {
	var text []*TextPiece

	scanner := bufio.NewScanner(r)

	scanner.Split(self.Tokenize)

	currentText := &TextPiece{}
	text = append(text, currentText)
	currentIndex := 0

	moveCurrent := func(delta int) *TextPiece {
		newCurrent := &TextPiece{}
		*newCurrent = *currentText
		newCurrent.Text = ""
		text = append(text, newCurrent)
		currentIndex += delta
		currentText = newCurrent
		return newCurrent
	}

	logPieceError := func(err error, pieces ...string) {
		log.Print(err)
		for _, piece := range pieces {
			currentText.Text += piece
		}
	}

	screening := false
	escaping := false
	for scanner.Scan() {
		stext := scanner.Text()
		switch {
		case stext == "\\":
			escaping = true
			continue
		case !escaping && stext == "{F":
			scanner.Scan()
			text := scanner.Text()
			font, err := strconv.Atoi(text)
			if err != nil {
				logPieceError(err, stext, text)
			}
			newCurrent := moveCurrent(1)
			newCurrent.Font = uint(font)
		case !escaping && stext == "{S":
			scanner.Scan()
			text := scanner.Text()
			screen, err := strconv.Atoi(text)
			if err != nil {
				logPieceError(err, stext, text)
			}
			newCurrent := moveCurrent(1)
			newCurrent.Screens = append(newCurrent.Screens, uint(screen))
			screening = true
		case !escaping && stext == "{CF":
			scanner.Scan()
			text := scanner.Text()
			fg, err := strconv.ParseUint(text, 0, 32)
			if err != nil {
				logPieceError(err, stext, text)
			}
			newCurrent := moveCurrent(1)
			newCurrent.Foreground = NewBGRA(fg)
		case !escaping && stext == "{CB":
			scanner.Scan()
			text := scanner.Text()
			bg, err := strconv.ParseUint(text, 0, 32)
			if err != nil {
				logPieceError(err, stext, text)
			}
			newCurrent := moveCurrent(1)
			newCurrent.Background = NewBGRA(bg)
		case !escaping && currentIndex > 0 && stext == "}":
			if len(text) > 1 {
				currentText = text[currentIndex - 1]
				moveCurrent(-1)
			}
			screening = false
		default:
			if screening && stext == "," {
				scanner.Scan()
				text := scanner.Text()
				screen, err := strconv.Atoi(text)
				if err != nil {
					logPieceError(err, stext, text)
				}
				currentText.Screens = append(currentText.Screens, uint(screen))
			} else {
				currentText.Text += stext
			}
			escaping = false
		}
	}

	//Remove empty pieces. TODO(Kenji): Merge with main parser
	var text2 []*TextPiece
	for _, piece := range text {
		if piece.Text != "" {
			text2 = append(text2, piece)
		}
	}

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

package main

import (
	"bufio"
	"io"
	"log"
	"regexp"
	"strconv"

	"github.com/BurntSushi/xgbutil/xgraphics"
)

// Type EndScan is an artifical Error.
// Raised when parser should stop scanning.
type EndScan struct{}

func (e EndScan) Error() string { return "EndScan" }

// NewBGRA returns a new color definition in X compatible format.
// Input should be a hexagonal representation with alpha, i.e 0xAARRGGBB.
func NewBGRA(color uint64) *xgraphics.BGRA {
	a := uint8(color >> 24)
	r := uint8((color & 0x00ff0000) >> 16)
	g := uint8((color & 0x0000ff00) >> 8)
	b := uint8(color & 0x000000ff)
	return &xgraphics.BGRA{B: b, G: g, R: r, A: a}
}

// TextPiece stores formatting information for a text
// within single pair of brackets.
type TextPiece struct {
	Text       string
	Font       uint
	Foreground *xgraphics.BGRA
	Background *xgraphics.BGRA
	Screens    []uint
}

// TextParser is used to create a set of TextPieces from a textual definition.
type TextParser struct {
	rgbPattern *regexp.Regexp
}

// NewTextParser creates TextParser instance with
// correct necessary regexp definitions.
func NewTextParser() *TextParser {
	return &TextParser{regexp.MustCompile(`^0[xX][0-9a-fA-F]{8}$`)}
}

// Tokenize turns textual definition into a series of valid tokens.
// If no valid token is found at given place, char a 0 position if returned.
func (self *TextParser) Tokenize(
	data []byte, EOF bool,
) (advance int, token []byte, err error) {
	switch {
	case data[0] == '\n':
		err = EndScan{}
	case len(data) < 2:
		advance, token, err = 1, data[:1], nil
	case string(data[:2]) == "{F":
		advance, token, err = 2, data[:2], nil
	case string(data[:2]) == "{S":
		advance, token, err = 2, data[:2], nil
	case len(data) < 3:
		advance, token, err = 1, data[:1], nil
	case string(data[:3]) == "{CF":
		advance, token, err = 3, data[:3], nil
	case string(data[:3]) == "{CB":
		advance, token, err = 3, data[:3], nil
	case len(data) >= 10 && self.rgbPattern.Match(data[:10]):
		advance, token, err = 10, data[:10], nil
	case '0' <= data[0] && data[0] <= '9':
		i := 0
		for _, n := range data {
			if !('0' <= n && n <= '9') {
				break
			}
			i += 1
		}
		advance, token, err = i, data[:i], nil
	default: // Also contains '}' and ','
		// TODO: Parsing whole text piece here, instead of returning
		// char-by-char, should perform better
		advance, token, err = 1, data[:1], nil
	}
	return
}

// Scan scans textual definition and returns array of TextPieces.
// Possible empty pieces are omitted in the returned array.
func (self *TextParser) Scan(r io.Reader) ([]*TextPiece, error) {
	var text []*TextPiece

	scanner := bufio.NewScanner(r)

	scanner.Split(self.Tokenize)

	currentText := &TextPiece{}
	text = append(text, currentText)
	currentIndex := 0

	moveCurrent := func(delta int) *TextPiece {
		newCurrent := &TextPiece{}
		*newCurrent = *currentText
		newCurrent.Text = ""
		text = append(text, newCurrent)
		currentIndex += delta
		currentText = newCurrent
		return newCurrent
	}

	logPieceError := func(err error, pieces ...string) {
		log.Print(err)
		for _, piece := range pieces {
			currentText.Text += piece
		}
	}

	screening := false
	escaping := false
	for scanner.Scan() {
		stext := scanner.Text()
		switch {
		case stext == "\\":
			escaping = true
			continue
		case !escaping && stext == "{F":
			scanner.Scan()
			text := scanner.Text()
			font, err := strconv.Atoi(text)
			if err != nil {
				logPieceError(err, stext, text)
			}
			newCurrent := moveCurrent(1)
			newCurrent.Font = uint(font)
		case !escaping && stext == "{S":
			scanner.Scan()
			text := scanner.Text()
			screen, err := strconv.Atoi(text)
			if err != nil {
				logPieceError(err, stext, text)
			}
			newCurrent := moveCurrent(1)
			newCurrent.Screens = append(newCurrent.Screens, uint(screen))
			screening = true
		case !escaping && stext == "{CF":
			scanner.Scan()
			text := scanner.Text()
			fg, err := strconv.ParseUint(text, 0, 32)
			if err != nil {
				logPieceError(err, stext, text)
			}
			newCurrent := moveCurrent(1)
			newCurrent.Foreground = NewBGRA(fg)
		case !escaping && stext == "{CB":
			scanner.Scan()
			text := scanner.Text()
			bg, err := strconv.ParseUint(text, 0, 32)
			if err != nil {
				logPieceError(err, stext, text)
			}
			newCurrent := moveCurrent(1)
			newCurrent.Background = NewBGRA(bg)
		case !escaping && currentIndex > 0 && stext == "}":
			if len(text) > 1 {
				currentText = text[currentIndex - 1]
				moveCurrent(-1)
			}
			screening = false
		default:
			if screening && stext == "," {
				scanner.Scan()
				text := scanner.Text()
				screen, err := strconv.Atoi(text)
				if err != nil {
					logPieceError(err, stext, text)
				}
				currentText.Screens = append(currentText.Screens, uint(screen))
			} else {
				currentText.Text += stext
			}
			escaping = false
		}
	}

	//Remove possible empty pieces.
	var text2 []*TextPiece
	for _, piece := range text {
		if piece.Text != "" {
			text2 = append(text2, piece)
		}
	}

	return text2, nil
}
<|endoftext|>


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

package xmpp

import (
	"fmt"
	"os"
	"xml"
)

// This file contains support for roster management, RFC 3921, Section 7.

// Roster query/result
type RosterQuery struct {
	XMLName xml.Name `xml:"jabber:iq:roster query"`
	Item []RosterItem
}

// See RFC 3921, Section 7.1.
type RosterItem struct {
	XMLName xml.Name `xml:"item"`
	Jid string `xml:"attr"`
	Subscription string `xml:"attr"`
	Name string `xml:"attr"`
	Group []string
}

// Implicitly becomes part of NewClient's extStanza arg.
func newRosterQuery(name *xml.Name) interface{} {
	return &RosterQuery{}
}

// Synchronously fetch this entity's roster from the server and cache
// that information.
func (cl *Client) fetchRoster() os.Error {
	iq := &Iq{From: cl.Jid.String(), Id: <- cl.Id, Type: "get",
		Nested: RosterQuery{}}
	ch := make(chan os.Error)
	f := func(st Stanza) bool {
		if iq.Type == "error" {
			ch <- iq.Error
			return false
		}
		rq, ok := st.GetNested().(*RosterQuery)
		if !ok {
			ch <- os.NewError(fmt.Sprintf(
				"Roster query result not query: %v", st))
			return false
		}
		cl.roster = make(map[string] *RosterItem, len(rq.Item))
		for _, item := range(rq.Item) {
			cl.roster[item.Jid] = &item
		}
		ch <- nil
		return false
	}
	cl.HandleStanza(iq.Id, f)
	cl.Out <- iq
	// Wait for f to complete.
	return <- ch
}

// Returns the current roster of other entities which this one has a
// relationship with. Changes to the roster will be signaled by an
// appropriate Iq appearing on Client.In. See RFC 3921, Section 7.4.
func (cl *Client) Roster() map[string] *RosterItem {
	r := make(map[string] *RosterItem)
	for key, val := range(cl.roster) {
		r[key] = val
	}
	return r
}

// The roster filter updates the Client's representation of the
// roster, but it lets the relevant stanzas through.
func (cl *Client) startRosterFilter() {
	out := make(chan Stanza)
	in := cl.AddFilter(out)
	go func(inSave <-chan Stanza, outSave chan<- Stanza) {
		defer close(out)
		in := inSave
		var out chan<- Stanza
		var st Stanza
		var ok bool
		for {
			select {
			case st, ok = <- in:
				if !ok {
					break
				}
				cl.maybeUpdateRoster(st)
				in = nil
				out = outSave
			case out <- st:
				out = nil
				in = inSave
			}
		}
	}(in, out)
}

func (cl *Client) maybeUpdateRoster(st Stanza) {
	rq, ok := st.GetNested().(*RosterQuery)
	if st.GetName() == "iq" && st.GetType() == "set" && ok {
		for _, item := range(rq.Item) {
			cl.roster[item.Jid] = &item
		}
	}
}
<commit_msg>Implemented roster item delete, and added another BUG comment.<commit_after>// Copyright 2011 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package xmpp

import (
	"fmt"
	"os"
	"xml"
)

// This file contains support for roster management, RFC 3921, Section 7.

// Roster query/result
type RosterQuery struct {
	XMLName xml.Name `xml:"jabber:iq:roster query"`
	Item []RosterItem
}

// See RFC 3921, Section 7.1.
type RosterItem struct {
	XMLName xml.Name `xml:"item"`
	Jid string `xml:"attr"`
	Subscription string `xml:"attr"`
	Name string `xml:"attr"`
	Group []string
}

// Implicitly becomes part of NewClient's extStanza arg.
func newRosterQuery(name *xml.Name) interface{} {
	return &RosterQuery{}
}

// Synchronously fetch this entity's roster from the server and cache
// that information.
func (cl *Client) fetchRoster() os.Error {
	iq := &Iq{From: cl.Jid.String(), Id: <- cl.Id, Type: "get",
		Nested: RosterQuery{}}
	ch := make(chan os.Error)
	f := func(st Stanza) bool {
		if iq.Type == "error" {
			ch <- iq.Error
			return false
		}
		rq, ok := st.GetNested().(*RosterQuery)
		if !ok {
			ch <- os.NewError(fmt.Sprintf(
				"Roster query result not query: %v", st))
			return false
		}
		cl.roster = make(map[string] *RosterItem, len(rq.Item))
		for _, item := range(rq.Item) {
			cl.roster[item.Jid] = &item
		}
		ch <- nil
		return false
	}
	cl.HandleStanza(iq.Id, f)
	cl.Out <- iq
	// Wait for f to complete.
	return <- ch
}

// Returns the current roster of other entities which this one has a
// relationship with. Changes to the roster will be signaled by an
// appropriate Iq appearing on Client.In. See RFC 3921, Section 7.4.
func (cl *Client) Roster() map[string] *RosterItem {
	r := make(map[string] *RosterItem)
	for key, val := range(cl.roster) {
		r[key] = val
	}
	return r
}

// The roster filter updates the Client's representation of the
// roster, but it lets the relevant stanzas through.
func (cl *Client) startRosterFilter() {
	out := make(chan Stanza)
	in := cl.AddFilter(out)
	go func(inSave <-chan Stanza, outSave chan<- Stanza) {
		defer close(out)
		in := inSave
		var out chan<- Stanza
		var st Stanza
		var ok bool
		for {
			select {
			case st, ok = <- in:
				if !ok {
					break
				}
				cl.maybeUpdateRoster(st)
				in = nil
				out = outSave
			case out <- st:
				out = nil
				in = inSave
			}
		}
	}(in, out)
}

// BUG(cjyar) This isn't actually thread safe, though it's unlikely it
// will fail in practice. Either the roster should be protected with a
// mutex, or we should make the roster available on a channel instead
// of via a method call.
func (cl *Client) maybeUpdateRoster(st Stanza) {
	rq, ok := st.GetNested().(*RosterQuery)
	if st.GetName() == "iq" && st.GetType() == "set" && ok {
		for _, item := range(rq.Item) {
			if item.Subscription == "remove" {
				cl.roster[item.Jid] = nil
			} else {
				cl.roster[item.Jid] = &item
			}
		}
	}
}
<|endoftext|>