<commit_before>package kickbox

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"time"
)

// Client holds the HTTP client for interacting with the API, and the associated
// API key
type Client struct {
	apiKey string
	http   http.Client
}

// NewClient returns an instance of a Kickbox API Client
func NewClient(apiKey string) *Client {
	client := &Client{
		apiKey: apiKey,
		http:   http.Client{},
	}

	// Set the default timeout to 3 seconds
	client.SetTimeout(time.Second * 3)
	return client
}

// SetTimeout configures the request timeout value (includes connecting, waiting for a response, and reading the response)
func (c *Client) SetTimeout(time time.Duration) {
	c.http.Timeout = time
}

// Verify the given email address using Kickbox.io
func (c Client) Verify(address string) (*Result, error) {
	return c.verify(KickboxResultBuilder{}, c.url(address))
}

// Build the API endpoint given the email address and API key
func (c Client) url(address string) string {
	return fmt.Sprintf("https://api.kickbox.io/v2/verify?email=%s&apikey=%s", url.QueryEscape(address), url.QueryEscape(c.apiKey))
}

// Request and read the response of the HTTP request, returning a new Result struct
func (c Client) verify(rb ResultBuilder, url string) (*Result, error) {
	// Send our API request
	response, err := c.http.Get(url)
	if err != nil {
		return nil, err
	}
	defer response.Body.Close()

	// Did we get a HTTP 200?
	if response.StatusCode != 200 {
		msg := fmt.Sprintf("Kickbox API returned HTTP %d", response.Status)
		return nil, errors.New(msg)
	}

	// Read the response
	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	// Build our Result struct
	result, err := rb.NewResult(body)
	if err != nil {
		return nil, err
	}

	return result, nil
}
<commit_msg>Change Status to StatusCode<commit_after>package kickbox

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"time"
)

// Client holds the HTTP client for interacting with the API, and the associated
// API key
type Client struct {
	apiKey string
	http   http.Client
}

// NewClient returns an instance of a Kickbox API Client
func NewClient(apiKey string) *Client {
	client := &Client{
		apiKey: apiKey,
		http:   http.Client{},
	}

	// Set the default timeout to 3 seconds
	client.SetTimeout(time.Second * 3)
	return client
}

// SetTimeout configures the request timeout value (includes connecting, waiting for a response, and reading the response)
func (c *Client) SetTimeout(time time.Duration) {
	c.http.Timeout = time
}

// Verify the given email address using Kickbox.io
func (c Client) Verify(address string) (*Result, error) {
	return c.verify(KickboxResultBuilder{}, c.url(address))
}

// Build the API endpoint given the email address and API key
func (c Client) url(address string) string {
	return fmt.Sprintf("https://api.kickbox.io/v2/verify?email=%s&apikey=%s", url.QueryEscape(address), url.QueryEscape(c.apiKey))
}

// Request and read the response of the HTTP request, returning a new Result struct
func (c Client) verify(rb ResultBuilder, url string) (*Result, error) {
	// Send our API request
	response, err := c.http.Get(url)
	if err != nil {
		return nil, err
	}
	defer response.Body.Close()

	// Did we get a HTTP 200?
	if response.StatusCode != 200 {
		msg := fmt.Sprintf("Kickbox API returned HTTP %d", response.StatusCode)
		return nil, errors.New(msg)
	}

	// Read the response
	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	// Build our Result struct
	result, err := rb.NewResult(body)
	if err != nil {
		return nil, err
	}

	return result, nil
}
<|endoftext|>


<commit_before>// Package kinesis provide GOlang API for http://aws.amazon.com/kinesis/
package kinesis

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"time"
)

const (
	ACTION_KEY = "Action"
)

type Region struct {
	Name string
}

var (
	USEast1      = Region{"us-east-1"}
	USWest2      = Region{"us-west-2"}
	EUWest1      = Region{"eu-west-1"}
	EUCentral1   = Region{"eu-central-1"}
	APSouthEast1 = Region{"ap-southeast-1"}
	APSouthEast2 = Region{"ap-southeast-2"}
	APNortheast1 = Region{"ap-northeast-1"}

	timeNow = time.Now
)

// Structure for kinesis client
type Kinesis struct {
	client   *Client
	endpoint string
	Region   string
	Version  string
}

// Interface implemented by Kinesis
type KinesisClient interface {
	CreateStream(StreamName string, ShardCount int) error
	DeleteStream(StreamName string) error
	DescribeStream(args *RequestArgs) (resp *DescribeStreamResp, err error)
	GetRecords(args *RequestArgs) (resp *GetRecordsResp, err error)
	GetShardIterator(args *RequestArgs) (resp *GetShardIteratorResp, err error)
	ListStreams(args *RequestArgs) (resp *ListStreamsResp, err error)
	MergeShards(args *RequestArgs) error
	PutRecord(args *RequestArgs) (resp *PutRecordResp, err error)
	PutRecords(args *RequestArgs) (resp *PutRecordsResp, err error)
	SplitShard(args *RequestArgs) error
}

// New returns an initialized AWS Kinesis client using the canonical live “production” endpoint
// for AWS Kinesis, i.e. https://kinesis.{region}.amazonaws.com
func New(auth *Auth, region Region) *Kinesis {
	endpoint := fmt.Sprintf("https://kinesis.%s.amazonaws.com", GetRegion(region))
	return NewWithEndpoint(auth, region, endpoint)
}

// NewWithEndpoint returns an initialized AWS Kinesis client using the specified endpoint.
// This is generally useful for testing, so a local Kinesis server can be used.
func NewWithEndpoint(auth *Auth, region Region, endpoint string) *Kinesis {
	// TODO: remove trailing slash on endpoint if there is one? does it matter?
	// TODO: validate endpoint somehow?
	return &Kinesis{client: NewClient(auth), Version: "20131202", Region: GetRegion(region), endpoint: endpoint}
}

// Create params object for request
func makeParams(action string) map[string]string {
	params := make(map[string]string)
	params[ACTION_KEY] = action
	return params
}

// RequestArgs store params for request
type RequestArgs struct {
	params  map[string]interface{}
	Records []PutRecordsRecord
}

// NewFilter creates a new Filter.
func NewArgs() *RequestArgs {
	return &RequestArgs{
		params: make(map[string]interface{}),
	}
}

// Add appends a filtering parameter with the given name and value(s).
func (f *RequestArgs) Add(name string, value interface{}) {
	f.params[name] = value
}

func (f *RequestArgs) AddData(value []byte) {
	f.params["Data"] = value
}

// Error represent error from Kinesis API
type Error struct {
	// HTTP status code (200, 403, ...)
	StatusCode int
	// error code ("UnsupportedOperation", ...)
	Code string
	// The human-oriented error message
	Message   string
	RequestId string
}

// Return error message from error object
func (err *Error) Error() string {
	if err.Code == "" {
		return err.Message
	}
	return fmt.Sprintf("%s (%s)", err.Message, err.Code)
}

type jsonErrors struct {
	Message string
}

func buildError(r *http.Response) error {
	// Reading the body into a []byte because we might need to put it into an error
	// message after having the JSON decoding fail to produce a message.
	body, _ := ioutil.ReadAll(r.Body)

	errors := jsonErrors{}
	json.NewDecoder(bytes.NewReader(body)).Decode(&errors)

	var err Error
	err.Message = errors.Message
	err.StatusCode = r.StatusCode
	if err.Message == "" {
		err.Message = fmt.Sprintf("%s: %s", r.Status, body)
	}
	return &err
}

// Query by AWS API
func (kinesis *Kinesis) query(params map[string]string, data interface{}, resp interface{}) error {
	jsonData, err := json.Marshal(data)
	if err != nil {
		return err
	}

	// request
	request, err := http.NewRequest(
		"POST",
		kinesis.endpoint,
		bytes.NewReader(jsonData),
	)

	if err != nil {
		return err
	}

	// headers
	request.Header.Set("Content-Type", "application/x-amz-json-1.1")
	request.Header.Set("X-Amz-Target", fmt.Sprintf("Kinesis_%s.%s", kinesis.Version, params[ACTION_KEY]))
	request.Header.Set("User-Agent", "Golang Kinesis")

	// response
	response, err := kinesis.client.Do(request)
	if err != nil {
		return err
	}
	defer response.Body.Close()

	if response.StatusCode != 200 {
		return buildError(response)
	}

	if resp == nil {
		return nil
	}

	return json.NewDecoder(response.Body).Decode(resp)
}

// CreateStream adds a new Amazon Kinesis stream to your AWS account
// StreamName is a name of stream, ShardCount is number of shards
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_CreateStream.html
func (kinesis *Kinesis) CreateStream(StreamName string, ShardCount int) error {
	params := makeParams("CreateStream")
	requestParams := struct {
		StreamName string
		ShardCount int
	}{
		StreamName,
		ShardCount,
	}
	err := kinesis.query(params, requestParams, nil)
	if err != nil {
		return err
	}
	return nil
}

// DeleteStream deletes a stream and all of its shards and data from your AWS account
// StreamName is a name of stream
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_DeleteStream.html
func (kinesis *Kinesis) DeleteStream(StreamName string) error {
	params := makeParams("DeleteStream")
	requestParams := struct {
		StreamName string
	}{
		StreamName,
	}
	err := kinesis.query(params, requestParams, nil)
	if err != nil {
		return err
	}
	return nil
}

// MergeShards merges two adjacent shards in a stream and combines them into a single shard to reduce the stream's capacity to ingest and transport data
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_MergeShards.html
func (kinesis *Kinesis) MergeShards(args *RequestArgs) error {
	params := makeParams("MergeShards")
	err := kinesis.query(params, args.params, nil)
	if err != nil {
		return err
	}
	return nil
}

// SplitShard splits a shard into two new shards in the stream, to increase the stream's capacity to ingest and transport data
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_SplitShard.html
func (kinesis *Kinesis) SplitShard(args *RequestArgs) error {
	params := makeParams("SplitShard")
	err := kinesis.query(params, args.params, nil)
	if err != nil {
		return err
	}
	return nil
}

// ListStreamsResp stores the information that provides by ListStreams API call
type ListStreamsResp struct {
	HasMoreStreams bool
	StreamNames    []string
}

// ListStreams returns an array of the names of all the streams that are associated with the AWS account making the ListStreams request
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_ListStreams.html
func (kinesis *Kinesis) ListStreams(args *RequestArgs) (resp *ListStreamsResp, err error) {
	params := makeParams("ListStreams")
	resp = &ListStreamsResp{}
	err = kinesis.query(params, args.params, resp)
	if err != nil {
		return nil, err
	}
	return
}

// DescribeStreamShards stores the information about list of shards inside DescribeStreamResp
type DescribeStreamShards struct {
	AdjacentParentShardId string
	HashKeyRange          struct {
		EndingHashKey   string
		StartingHashKey string
	}
	ParentShardId       string
	SequenceNumberRange struct {
		EndingSequenceNumber   string
		StartingSequenceNumber string
	}
	ShardId string
}

// DescribeStreamResp stores the information that provides by DescribeStream API call
type DescribeStreamResp struct {
	StreamDescription struct {
		HasMoreShards bool
		Shards        []DescribeStreamShards
		StreamARN     string
		StreamName    string
		StreamStatus  string
	}
}

// DescribeStream returns the following information about the stream: the current status of the stream,
// the stream Amazon Resource Name (ARN), and an array of shard objects that comprise the stream.
// For each shard object there is information about the hash key and sequence number ranges that
// the shard spans, and the IDs of any earlier shards that played in a role in a MergeShards or
// SplitShard operation that created the shard
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_DescribeStream.html
func (kinesis *Kinesis) DescribeStream(args *RequestArgs) (resp *DescribeStreamResp, err error) {
	params := makeParams("DescribeStream")
	resp = &DescribeStreamResp{}
	err = kinesis.query(params, args.params, resp)
	if err != nil {
		return nil, err
	}
	return
}

// GetShardIteratorResp stores the information that provides by GetShardIterator API call
type GetShardIteratorResp struct {
	ShardIterator string
}

// GetShardIterator returns a shard iterator
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_GetShardIterator.html
func (kinesis *Kinesis) GetShardIterator(args *RequestArgs) (resp *GetShardIteratorResp, err error) {
	params := makeParams("GetShardIterator")
	resp = &GetShardIteratorResp{}
	err = kinesis.query(params, args.params, resp)
	if err != nil {
		return nil, err
	}
	return
}

// GetNextRecordsRecords stores the information that provides by GetNextRecordsResp
type GetRecordsRecords struct {
	Data           []byte
	PartitionKey   string
	SequenceNumber string
}

func (r GetRecordsRecords) GetData() []byte {
	return r.Data
}

// GetNextRecordsResp stores the information that provides by GetNextRecords API call
type GetRecordsResp struct {
	NextShardIterator string
	Records           []GetRecordsRecords
}

// GetRecords returns one or more data records from a shard
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_GetRecords.html
func (kinesis *Kinesis) GetRecords(args *RequestArgs) (resp *GetRecordsResp, err error) {
	params := makeParams("GetRecords")
	resp = &GetRecordsResp{}
	err = kinesis.query(params, args.params, resp)
	if err != nil {
		return nil, err
	}
	return
}

// PutRecordResp stores the information that provides by PutRecord API call
type PutRecordResp struct {
	SequenceNumber string
	ShardId        string
}

// PutRecord puts a data record into an Amazon Kinesis stream from a producer
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_PutRecord.html
func (kinesis *Kinesis) PutRecord(args *RequestArgs) (resp *PutRecordResp, err error) {
	params := makeParams("PutRecord")
	resp = &PutRecordResp{}
	err = kinesis.query(params, args.params, resp)
	if err != nil {
		return nil, err
	}
	return
}

// PutRecords puts multiple data records into an Amazon Kinesis stream from a producer
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_PutRecords.html
func (kinesis *Kinesis) PutRecords(args *RequestArgs) (resp *PutRecordsResp, err error) {
	params := makeParams("PutRecords")
	resp = &PutRecordsResp{}
	args.Add("Records", args.Records)
	err = kinesis.query(params, args.params, resp)

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

// PutRecordsResp stores the information that provides by PutRecord API call
type PutRecordsResp struct {
	FailedRecordCount int
	Records           []PutRecordsRespRecord
}

// RecordResp stores individual Record information provided by PutRecords API call
type PutRecordsRespRecord struct {
	ErrorCode      string
	ErrorMessage   string
	SequenceNumber string
	ShardId        string
}

// Add data and partition for sending multiple Records to Kinesis in one API call
func (f *RequestArgs) AddRecord(value []byte, partitionKey string) {
	r := PutRecordsRecord{
		Data:         value,
		PartitionKey: partitionKey,
	}
	f.Records = append(f.Records, r)
}

// PutRecordsRecord stores the Data and PartitionKey for batch calls to Kinesis API
type PutRecordsRecord struct {
	Data         []byte
	PartitionKey string
}
<commit_msg>Check the err from ioutil.ReadAll<commit_after>// Package kinesis provide GOlang API for http://aws.amazon.com/kinesis/
package kinesis

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"time"
)

const (
	ACTION_KEY = "Action"
)

type Region struct {
	Name string
}

var (
	USEast1      = Region{"us-east-1"}
	USWest2      = Region{"us-west-2"}
	EUWest1      = Region{"eu-west-1"}
	EUCentral1   = Region{"eu-central-1"}
	APSouthEast1 = Region{"ap-southeast-1"}
	APSouthEast2 = Region{"ap-southeast-2"}
	APNortheast1 = Region{"ap-northeast-1"}

	timeNow = time.Now
)

// Structure for kinesis client
type Kinesis struct {
	client   *Client
	endpoint string
	Region   string
	Version  string
}

// Interface implemented by Kinesis
type KinesisClient interface {
	CreateStream(StreamName string, ShardCount int) error
	DeleteStream(StreamName string) error
	DescribeStream(args *RequestArgs) (resp *DescribeStreamResp, err error)
	GetRecords(args *RequestArgs) (resp *GetRecordsResp, err error)
	GetShardIterator(args *RequestArgs) (resp *GetShardIteratorResp, err error)
	ListStreams(args *RequestArgs) (resp *ListStreamsResp, err error)
	MergeShards(args *RequestArgs) error
	PutRecord(args *RequestArgs) (resp *PutRecordResp, err error)
	PutRecords(args *RequestArgs) (resp *PutRecordsResp, err error)
	SplitShard(args *RequestArgs) error
}

// New returns an initialized AWS Kinesis client using the canonical live “production” endpoint
// for AWS Kinesis, i.e. https://kinesis.{region}.amazonaws.com
func New(auth *Auth, region Region) *Kinesis {
	endpoint := fmt.Sprintf("https://kinesis.%s.amazonaws.com", GetRegion(region))
	return NewWithEndpoint(auth, region, endpoint)
}

// NewWithEndpoint returns an initialized AWS Kinesis client using the specified endpoint.
// This is generally useful for testing, so a local Kinesis server can be used.
func NewWithEndpoint(auth *Auth, region Region, endpoint string) *Kinesis {
	// TODO: remove trailing slash on endpoint if there is one? does it matter?
	// TODO: validate endpoint somehow?
	return &Kinesis{client: NewClient(auth), Version: "20131202", Region: GetRegion(region), endpoint: endpoint}
}

// Create params object for request
func makeParams(action string) map[string]string {
	params := make(map[string]string)
	params[ACTION_KEY] = action
	return params
}

// RequestArgs store params for request
type RequestArgs struct {
	params  map[string]interface{}
	Records []PutRecordsRecord
}

// NewFilter creates a new Filter.
func NewArgs() *RequestArgs {
	return &RequestArgs{
		params: make(map[string]interface{}),
	}
}

// Add appends a filtering parameter with the given name and value(s).
func (f *RequestArgs) Add(name string, value interface{}) {
	f.params[name] = value
}

func (f *RequestArgs) AddData(value []byte) {
	f.params["Data"] = value
}

// Error represent error from Kinesis API
type Error struct {
	// HTTP status code (200, 403, ...)
	StatusCode int
	// error code ("UnsupportedOperation", ...)
	Code string
	// The human-oriented error message
	Message   string
	RequestId string
}

// Return error message from error object
func (err *Error) Error() string {
	if err.Code == "" {
		return err.Message
	}
	return fmt.Sprintf("%s (%s)", err.Message, err.Code)
}

type jsonErrors struct {
	Message string
}

func buildError(r *http.Response) error {
	// Reading the body into a []byte because we might need to put it into an error
	// message after having the JSON decoding fail to produce a message.
	body, ioerr := ioutil.ReadAll(r.Body)
	if ioerr != nil {
		return fmt.Errorf("Could not read response body: %s", ioerr)
	}

	errors := jsonErrors{}
	json.NewDecoder(bytes.NewReader(body)).Decode(&errors)

	var err Error
	err.Message = errors.Message
	err.StatusCode = r.StatusCode
	if err.Message == "" {
		err.Message = fmt.Sprintf("%s: %s", r.Status, body)
	}
	return &err
}

// Query by AWS API
func (kinesis *Kinesis) query(params map[string]string, data interface{}, resp interface{}) error {
	jsonData, err := json.Marshal(data)
	if err != nil {
		return err
	}

	// request
	request, err := http.NewRequest(
		"POST",
		kinesis.endpoint,
		bytes.NewReader(jsonData),
	)

	if err != nil {
		return err
	}

	// headers
	request.Header.Set("Content-Type", "application/x-amz-json-1.1")
	request.Header.Set("X-Amz-Target", fmt.Sprintf("Kinesis_%s.%s", kinesis.Version, params[ACTION_KEY]))
	request.Header.Set("User-Agent", "Golang Kinesis")

	// response
	response, err := kinesis.client.Do(request)
	if err != nil {
		return err
	}
	defer response.Body.Close()

	if response.StatusCode != 200 {
		return buildError(response)
	}

	if resp == nil {
		return nil
	}

	return json.NewDecoder(response.Body).Decode(resp)
}

// CreateStream adds a new Amazon Kinesis stream to your AWS account
// StreamName is a name of stream, ShardCount is number of shards
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_CreateStream.html
func (kinesis *Kinesis) CreateStream(StreamName string, ShardCount int) error {
	params := makeParams("CreateStream")
	requestParams := struct {
		StreamName string
		ShardCount int
	}{
		StreamName,
		ShardCount,
	}
	err := kinesis.query(params, requestParams, nil)
	if err != nil {
		return err
	}
	return nil
}

// DeleteStream deletes a stream and all of its shards and data from your AWS account
// StreamName is a name of stream
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_DeleteStream.html
func (kinesis *Kinesis) DeleteStream(StreamName string) error {
	params := makeParams("DeleteStream")
	requestParams := struct {
		StreamName string
	}{
		StreamName,
	}
	err := kinesis.query(params, requestParams, nil)
	if err != nil {
		return err
	}
	return nil
}

// MergeShards merges two adjacent shards in a stream and combines them into a single shard to reduce the stream's capacity to ingest and transport data
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_MergeShards.html
func (kinesis *Kinesis) MergeShards(args *RequestArgs) error {
	params := makeParams("MergeShards")
	err := kinesis.query(params, args.params, nil)
	if err != nil {
		return err
	}
	return nil
}

// SplitShard splits a shard into two new shards in the stream, to increase the stream's capacity to ingest and transport data
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_SplitShard.html
func (kinesis *Kinesis) SplitShard(args *RequestArgs) error {
	params := makeParams("SplitShard")
	err := kinesis.query(params, args.params, nil)
	if err != nil {
		return err
	}
	return nil
}

// ListStreamsResp stores the information that provides by ListStreams API call
type ListStreamsResp struct {
	HasMoreStreams bool
	StreamNames    []string
}

// ListStreams returns an array of the names of all the streams that are associated with the AWS account making the ListStreams request
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_ListStreams.html
func (kinesis *Kinesis) ListStreams(args *RequestArgs) (resp *ListStreamsResp, err error) {
	params := makeParams("ListStreams")
	resp = &ListStreamsResp{}
	err = kinesis.query(params, args.params, resp)
	if err != nil {
		return nil, err
	}
	return
}

// DescribeStreamShards stores the information about list of shards inside DescribeStreamResp
type DescribeStreamShards struct {
	AdjacentParentShardId string
	HashKeyRange          struct {
		EndingHashKey   string
		StartingHashKey string
	}
	ParentShardId       string
	SequenceNumberRange struct {
		EndingSequenceNumber   string
		StartingSequenceNumber string
	}
	ShardId string
}

// DescribeStreamResp stores the information that provides by DescribeStream API call
type DescribeStreamResp struct {
	StreamDescription struct {
		HasMoreShards bool
		Shards        []DescribeStreamShards
		StreamARN     string
		StreamName    string
		StreamStatus  string
	}
}

// DescribeStream returns the following information about the stream: the current status of the stream,
// the stream Amazon Resource Name (ARN), and an array of shard objects that comprise the stream.
// For each shard object there is information about the hash key and sequence number ranges that
// the shard spans, and the IDs of any earlier shards that played in a role in a MergeShards or
// SplitShard operation that created the shard
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_DescribeStream.html
func (kinesis *Kinesis) DescribeStream(args *RequestArgs) (resp *DescribeStreamResp, err error) {
	params := makeParams("DescribeStream")
	resp = &DescribeStreamResp{}
	err = kinesis.query(params, args.params, resp)
	if err != nil {
		return nil, err
	}
	return
}

// GetShardIteratorResp stores the information that provides by GetShardIterator API call
type GetShardIteratorResp struct {
	ShardIterator string
}

// GetShardIterator returns a shard iterator
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_GetShardIterator.html
func (kinesis *Kinesis) GetShardIterator(args *RequestArgs) (resp *GetShardIteratorResp, err error) {
	params := makeParams("GetShardIterator")
	resp = &GetShardIteratorResp{}
	err = kinesis.query(params, args.params, resp)
	if err != nil {
		return nil, err
	}
	return
}

// GetNextRecordsRecords stores the information that provides by GetNextRecordsResp
type GetRecordsRecords struct {
	Data           []byte
	PartitionKey   string
	SequenceNumber string
}

func (r GetRecordsRecords) GetData() []byte {
	return r.Data
}

// GetNextRecordsResp stores the information that provides by GetNextRecords API call
type GetRecordsResp struct {
	NextShardIterator string
	Records           []GetRecordsRecords
}

// GetRecords returns one or more data records from a shard
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_GetRecords.html
func (kinesis *Kinesis) GetRecords(args *RequestArgs) (resp *GetRecordsResp, err error) {
	params := makeParams("GetRecords")
	resp = &GetRecordsResp{}
	err = kinesis.query(params, args.params, resp)
	if err != nil {
		return nil, err
	}
	return
}

// PutRecordResp stores the information that provides by PutRecord API call
type PutRecordResp struct {
	SequenceNumber string
	ShardId        string
}

// PutRecord puts a data record into an Amazon Kinesis stream from a producer
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_PutRecord.html
func (kinesis *Kinesis) PutRecord(args *RequestArgs) (resp *PutRecordResp, err error) {
	params := makeParams("PutRecord")
	resp = &PutRecordResp{}
	err = kinesis.query(params, args.params, resp)
	if err != nil {
		return nil, err
	}
	return
}

// PutRecords puts multiple data records into an Amazon Kinesis stream from a producer
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_PutRecords.html
func (kinesis *Kinesis) PutRecords(args *RequestArgs) (resp *PutRecordsResp, err error) {
	params := makeParams("PutRecords")
	resp = &PutRecordsResp{}
	args.Add("Records", args.Records)
	err = kinesis.query(params, args.params, resp)

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

// PutRecordsResp stores the information that provides by PutRecord API call
type PutRecordsResp struct {
	FailedRecordCount int
	Records           []PutRecordsRespRecord
}

// RecordResp stores individual Record information provided by PutRecords API call
type PutRecordsRespRecord struct {
	ErrorCode      string
	ErrorMessage   string
	SequenceNumber string
	ShardId        string
}

// Add data and partition for sending multiple Records to Kinesis in one API call
func (f *RequestArgs) AddRecord(value []byte, partitionKey string) {
	r := PutRecordsRecord{
		Data:         value,
		PartitionKey: partitionKey,
	}
	f.Records = append(f.Records, r)
}

// PutRecordsRecord stores the Data and PartitionKey for batch calls to Kinesis API
type PutRecordsRecord struct {
	Data         []byte
	PartitionKey string
}
<|endoftext|>


<commit_before>package kinetic

import (
	"errors"
	"net"
	"net/http"
	"sync"
	"sync/atomic"
	"time"

	"github.com/aws/aws-sdk-go/aws"

	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	awsKinesis "github.com/aws/aws-sdk-go/service/kinesis"
	"github.com/aws/aws-sdk-go/service/kinesis/kinesisiface"
)

const (
	atSequenceNumber = iota
	afterSequenceNumber
	trimHorizon
	latest

	statusCreating = iota
	statusDeleting
	statusActive
	staticUpdating

	kinesisWritesPerSec int = 1000
	kinesisReadsPerSec  int = 2
	// Timeout TODO
)

var (
	// HTTPTimeout is the timeout for http for requests.
	HTTPTimeout = 60 * time.Second
	// DialTimeout is the timeout for the tpc dial.
	DialTimeout = 5 * time.Second
	// HandShakeTimeout is the timeout for the tpc handshake.
	HandShakeTimeout = 5 * time.Second
)

// ErrNilShardIterator is an error for when we get back a nil shard iterator
var ErrNilShardIterator = errors.New("Nil shard iterator")

// ErrNilShardStatus is an error for when we get back a nil stream status
var ErrNilShardStatus = errors.New("Nil stream status")

// Empty is an empty struct.  It is mostly used for counting semaphore purposes
type Empty struct{}

var (
	conf = getConfig()

	// ShardIterTypes are the types of iterators to use within Kinesis
	ShardIterTypes shardIteratorTypes = map[int]string{
		atSequenceNumber:    "AT_SEQUENCE_NUMBER",
		afterSequenceNumber: "AFTER_SEQUENCE_NUMBER",
		trimHorizon:         "TRIM_HORIZON",
		latest:              "LATEST",
	}

	streamStatuses streamStatusTypes = map[int]string{
		statusCreating: "CREATING",
		statusDeleting: "DELETING",
		statusActive:   "ACTIVE",
		staticUpdating: "UPDATING",
	}
)

type msgFn func([]byte, *sync.WaitGroup)
type shardIteratorTypes map[int]string
type streamStatusTypes map[int]string

type kinesis struct {
	stream            string
	shard             string
	endPoint          string
	shardIteratorType string
	shardIterator     string
	sequenceNumber    string
	sequenceNumberMu  sync.Mutex

	client kinesisiface.KinesisAPI

	msgCount int64
	errCount int64

	startTime *time.Time
}

func (k *kinesis) initWithStartTime(stream, shard, shardIteratorType, accessKey, secretKey, region string, endpoint string, startTime *time.Time) (*kinesis, error) {
	k.startTime = startTime

	return k.init(stream, shard, shardIteratorType, accessKey, secretKey, region, endpoint)
}

func (k *kinesis) init(stream, shard, shardIteratorType, accessKey, secretKey, region string, endpoint string) (*kinesis, error) {
	httpClient := &http.Client{
		Transport: &http.Transport{
			Dial: (&net.Dialer{
				Timeout: DialTimeout,
			}).Dial,
			TLSHandshakeTimeout: HandShakeTimeout},
		Timeout: HTTPTimeout,
	}
	sess, err := authenticate(accessKey, secretKey)
	awsConf := aws.NewConfig().WithRegion(region).WithHTTPClient(httpClient)
	if conf.Debug.Verbose {
		awsConf = awsConf.WithLogLevel(aws.LogDebugWithRequestRetries | aws.LogDebugWithRequestErrors)
	}

	if k.endPoint != "" {
		awsConf = awsConf.WithEndpoint(k.endPoint)
	} else if endpoint != "" {
		awsConf = awsConf.WithEndpoint(endpoint)
	}

	client := awsKinesis.New(sess, awsConf)

	k = &kinesis{
		stream:            stream,
		shard:             shard,
		shardIteratorType: shardIteratorType,
		client:            client,
	}
	if err != nil {
		return k, err
	}

	err = k.initShardIterator()
	return k, err
}

func (k *kinesis) initShardIterator() error {
	var awsSeqNumber *string
	var err error
	var resp *awsKinesis.GetShardIteratorOutput

	if k.shardIteratorType == "AT_TIMESTAMP" && k.sequenceNumber == "" {
		resp, err = k.client.GetShardIterator(&awsKinesis.GetShardIteratorInput{
			ShardId:           aws.String(k.shard),
			ShardIteratorType: &k.shardIteratorType,
			StreamName:        aws.String(k.stream),
			Timestamp:         k.startTime,
		})

		k.shardIteratorType = ShardIterTypes[atSequenceNumber]
	} else {
		if k.sequenceNumber != "" {
			awsSeqNumber = aws.String(k.sequenceNumber)
			k.shardIteratorType = ShardIterTypes[atSequenceNumber]
		}

		resp, err = k.client.GetShardIterator(&awsKinesis.GetShardIteratorInput{
			ShardId:                aws.String(k.shard),             // Required
			ShardIteratorType:      aws.String(k.shardIteratorType), // Required
			StreamName:             aws.String(k.stream),            // Required
			StartingSequenceNumber: awsSeqNumber,
		})
	}

	if err != nil {
		return err
	}
	if resp.ShardIterator != nil {
		return k.setShardIterator(*resp.ShardIterator)
	}

	return ErrNilShardIterator
}

func (k *kinesis) setSequenceNumber(sequenceNum string) {
	if sequenceNum == "" || len(sequenceNum) == 0 {
		return
	}

	k.sequenceNumberMu.Lock()
	k.sequenceNumber = sequenceNum
	k.sequenceNumberMu.Unlock()
}

func (k *kinesis) setShardIterator(shardIter string) error {
	if shardIter == "" || len(shardIter) == 0 {
		return errors.New("Attempted to set shard iterator with empty value")
	}

	k.shardIterator = shardIter

	return nil
}

func (k *kinesis) checkActive() (bool, error) {
	status, err := k.client.DescribeStream(&awsKinesis.DescribeStreamInput{
		StreamName: aws.String(k.stream), // Required
	})
	if err != nil {
		return false, err
	}
	if status.StreamDescription.StreamStatus == nil {
		return false, ErrNilShardStatus
	}
	if streamStatuses[statusActive] == *status.StreamDescription.StreamStatus {
		return true, nil
	}
	return false, nil
}

func (k *kinesis) newClient(endpoint, stream string) (*awsKinesis.Kinesis, error) {
	k.endPoint = endpoint
	conf := &aws.Config{}
	conf = conf.WithCredentials(
		credentials.NewStaticCredentials("BAD_ACCESS_KEY", "BAD_SECRET_KEY", "BAD_TOKEN"),
	).WithEndpoint(endpoint).WithRegion("us-east-1").WithDisableSSL(true).WithMaxRetries(3)

	// fake region
	sess, err := session.NewSessionWithOptions(session.Options{Config: *conf})
	return awsKinesis.New(sess, conf), err
}

func (k *kinesis) refreshClient(accessKey, secretKey, region string) error {
	sess, err := authenticate(accessKey, secretKey)
	conf := aws.NewConfig().WithRegion(region).WithEndpoint(k.endPoint)
	if err != nil {
		return err
	}
	k.client = awsKinesis.New(sess, conf)
	return nil
}

func (k *kinesis) decMsgCount() {
	atomic.AddInt64(&k.msgCount, -1)
}

func (k *kinesis) incMsgCount() {
	atomic.AddInt64(&k.msgCount, 1)
}

func (k *kinesis) getMsgCount() int64 {
	return atomic.LoadInt64(&k.msgCount)
}

func (k *kinesis) decErrCount() {
	atomic.AddInt64(&k.errCount, -1)
}

func (k *kinesis) incErrCount() {
	atomic.AddInt64(&k.errCount, 1)
}

func (k *kinesis) getErrCount() int64 {
	return atomic.LoadInt64(&k.errCount)
}

func getLock(sem chan Empty) {
	sem <- Empty{}
}
<commit_msg>Sets shardIteratorType now<commit_after>package kinetic

import (
	"errors"
	"net"
	"net/http"
	"sync"
	"sync/atomic"
	"time"

	"github.com/aws/aws-sdk-go/aws"

	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	awsKinesis "github.com/aws/aws-sdk-go/service/kinesis"
	"github.com/aws/aws-sdk-go/service/kinesis/kinesisiface"
)

const (
	atSequenceNumber = iota
	afterSequenceNumber
	trimHorizon
	latest

	statusCreating = iota
	statusDeleting
	statusActive
	staticUpdating

	kinesisWritesPerSec int = 1000
	kinesisReadsPerSec  int = 2
	// Timeout TODO
)

var (
	// HTTPTimeout is the timeout for http for requests.
	HTTPTimeout = 60 * time.Second
	// DialTimeout is the timeout for the tpc dial.
	DialTimeout = 5 * time.Second
	// HandShakeTimeout is the timeout for the tpc handshake.
	HandShakeTimeout = 5 * time.Second
)

// ErrNilShardIterator is an error for when we get back a nil shard iterator
var ErrNilShardIterator = errors.New("Nil shard iterator")

// ErrNilShardStatus is an error for when we get back a nil stream status
var ErrNilShardStatus = errors.New("Nil stream status")

// Empty is an empty struct.  It is mostly used for counting semaphore purposes
type Empty struct{}

var (
	conf = getConfig()

	// ShardIterTypes are the types of iterators to use within Kinesis
	ShardIterTypes shardIteratorTypes = map[int]string{
		atSequenceNumber:    "AT_SEQUENCE_NUMBER",
		afterSequenceNumber: "AFTER_SEQUENCE_NUMBER",
		trimHorizon:         "TRIM_HORIZON",
		latest:              "LATEST",
	}

	streamStatuses streamStatusTypes = map[int]string{
		statusCreating: "CREATING",
		statusDeleting: "DELETING",
		statusActive:   "ACTIVE",
		staticUpdating: "UPDATING",
	}
)

type msgFn func([]byte, *sync.WaitGroup)
type shardIteratorTypes map[int]string
type streamStatusTypes map[int]string

type kinesis struct {
	stream            string
	shard             string
	endPoint          string
	shardIteratorType string
	shardIterator     string
	sequenceNumber    string
	sequenceNumberMu  sync.Mutex

	client kinesisiface.KinesisAPI

	msgCount int64
	errCount int64

	startTime *time.Time
}

func (k *kinesis) initWithStartTime(stream, shard, shardIteratorType, accessKey, secretKey, region string, endpoint string, startTime *time.Time) (*kinesis, error) {
	k.startTime = startTime
	k.shardIteratorType = "AT_TIMESTAMP"

	return k.init(stream, shard, shardIteratorType, accessKey, secretKey, region, endpoint)
}

func (k *kinesis) init(stream, shard, shardIteratorType, accessKey, secretKey, region string, endpoint string) (*kinesis, error) {
	httpClient := &http.Client{
		Transport: &http.Transport{
			Dial: (&net.Dialer{
				Timeout: DialTimeout,
			}).Dial,
			TLSHandshakeTimeout: HandShakeTimeout},
		Timeout: HTTPTimeout,
	}
	sess, err := authenticate(accessKey, secretKey)
	awsConf := aws.NewConfig().WithRegion(region).WithHTTPClient(httpClient)
	if conf.Debug.Verbose {
		awsConf = awsConf.WithLogLevel(aws.LogDebugWithRequestRetries | aws.LogDebugWithRequestErrors)
	}

	if k.endPoint != "" {
		awsConf = awsConf.WithEndpoint(k.endPoint)
	} else if endpoint != "" {
		awsConf = awsConf.WithEndpoint(endpoint)
	}

	client := awsKinesis.New(sess, awsConf)

	k = &kinesis{
		stream:            stream,
		shard:             shard,
		shardIteratorType: shardIteratorType,
		client:            client,
	}
	if err != nil {
		return k, err
	}

	err = k.initShardIterator()
	return k, err
}

func (k *kinesis) initShardIterator() error {
	var awsSeqNumber *string
	var err error
	var resp *awsKinesis.GetShardIteratorOutput

	if k.shardIteratorType == "AT_TIMESTAMP" && k.sequenceNumber == "" {
		resp, err = k.client.GetShardIterator(&awsKinesis.GetShardIteratorInput{
			ShardId:           aws.String(k.shard),
			ShardIteratorType: &k.shardIteratorType,
			StreamName:        aws.String(k.stream),
			Timestamp:         k.startTime,
		})

		k.shardIteratorType = ShardIterTypes[atSequenceNumber]
	} else {
		if k.sequenceNumber != "" {
			awsSeqNumber = aws.String(k.sequenceNumber)
			k.shardIteratorType = ShardIterTypes[atSequenceNumber]
		}

		resp, err = k.client.GetShardIterator(&awsKinesis.GetShardIteratorInput{
			ShardId:                aws.String(k.shard),             // Required
			ShardIteratorType:      aws.String(k.shardIteratorType), // Required
			StreamName:             aws.String(k.stream),            // Required
			StartingSequenceNumber: awsSeqNumber,
		})
	}

	if err != nil {
		return err
	}
	if resp.ShardIterator != nil {
		return k.setShardIterator(*resp.ShardIterator)
	}

	return ErrNilShardIterator
}

func (k *kinesis) setSequenceNumber(sequenceNum string) {
	if sequenceNum == "" || len(sequenceNum) == 0 {
		return
	}

	k.sequenceNumberMu.Lock()
	k.sequenceNumber = sequenceNum
	k.sequenceNumberMu.Unlock()
}

func (k *kinesis) setShardIterator(shardIter string) error {
	if shardIter == "" || len(shardIter) == 0 {
		return errors.New("Attempted to set shard iterator with empty value")
	}

	k.shardIterator = shardIter

	return nil
}

func (k *kinesis) checkActive() (bool, error) {
	status, err := k.client.DescribeStream(&awsKinesis.DescribeStreamInput{
		StreamName: aws.String(k.stream), // Required
	})
	if err != nil {
		return false, err
	}
	if status.StreamDescription.StreamStatus == nil {
		return false, ErrNilShardStatus
	}
	if streamStatuses[statusActive] == *status.StreamDescription.StreamStatus {
		return true, nil
	}
	return false, nil
}

func (k *kinesis) newClient(endpoint, stream string) (*awsKinesis.Kinesis, error) {
	k.endPoint = endpoint
	conf := &aws.Config{}
	conf = conf.WithCredentials(
		credentials.NewStaticCredentials("BAD_ACCESS_KEY", "BAD_SECRET_KEY", "BAD_TOKEN"),
	).WithEndpoint(endpoint).WithRegion("us-east-1").WithDisableSSL(true).WithMaxRetries(3)

	// fake region
	sess, err := session.NewSessionWithOptions(session.Options{Config: *conf})
	return awsKinesis.New(sess, conf), err
}

func (k *kinesis) refreshClient(accessKey, secretKey, region string) error {
	sess, err := authenticate(accessKey, secretKey)
	conf := aws.NewConfig().WithRegion(region).WithEndpoint(k.endPoint)
	if err != nil {
		return err
	}
	k.client = awsKinesis.New(sess, conf)
	return nil
}

func (k *kinesis) decMsgCount() {
	atomic.AddInt64(&k.msgCount, -1)
}

func (k *kinesis) incMsgCount() {
	atomic.AddInt64(&k.msgCount, 1)
}

func (k *kinesis) getMsgCount() int64 {
	return atomic.LoadInt64(&k.msgCount)
}

func (k *kinesis) decErrCount() {
	atomic.AddInt64(&k.errCount, -1)
}

func (k *kinesis) incErrCount() {
	atomic.AddInt64(&k.errCount, 1)
}

func (k *kinesis) getErrCount() int64 {
	return atomic.LoadInt64(&k.errCount)
}

func getLock(sem chan Empty) {
	sem <- Empty{}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/signal"
	"sort"
	"strings"
	"syscall"
	"time"

	"github.com/bwmarrin/discordgo"

	"github.com/jasonlvhit/gocron"
)

func init() {
	flag.StringVar(&token, "t", "", "Bot Token")
	flag.Parse()
}

type Config struct {
	Admin       string
	Bots        []string
	Coins       string
	DefaultChan string
	LogID       string
	LogMessage  bool
	LogStatus   bool
	LogVoice    bool
	Monitor     []string
	Noise       bool
	Prefix      string
	Status      string
	Test        []string
}

var config = Config{}
var self *discordgo.User
var token string
var curChan *discordgo.VoiceConnection

//Read in the config into the Config structure
func ReadConfig() {
	file, _ := os.Open("conf.json")
	decoder := json.NewDecoder(file)
	err := decoder.Decode(&config)
	if err != nil {
		fmt.Println("error: ", err)
	}
	file.Close()
}

//Write out the current Config structure to file, indented nicely
func WriteConfig() {
	//Indent so its readable
	configData, err := json.MarshalIndent(config, "", "    ")
	if err != nil {
		panic(err)
	}
	//Open file
	jsonFile, err := os.Create("conf.json")
	if err != nil {
		panic(err)
	}
	//Write to file
	_, err = jsonFile.Write(configData)
	if err != nil {
		panic(err)
	}
	//Cleanup
	jsonFile.Close()
}

//Function to call once a day
func ResetDailies() {
	for j := range USArray.Users {
		USArray.Users[j].Dailies = false
	}
	WriteUserFile()
}

func main() {
	//Read in files
	if _, err := os.Stat("users.json"); os.IsNotExist(err) {
		InitUserFile()
	}
	ReadConfig()
	WriteConfig()
	ReadUserFile()

	for j := range USArray.Users {
		USArray.Users[j].PlayAnthem = true
	}
	WriteUserFile()

	go func() {
		gocron.Every(1).Day().At("20:00").Do(ResetDailies)
		<-gocron.Start()
	}()

	//Account for no token at runtime
	if token == "" {
		fmt.Println("No token provided. Please run: kylixor -t <bot token>")
		return
	}

	// Create a new Discord session using the provided bot token.
	ky, err := discordgo.New("Bot " + token)
	if err != nil {
		fmt.Println("Error creating Discord session: ", err)
		return
	}

	// Register ready as a callback for the ready events
	ky.AddHandlerOnce(ready)

	// Register messageCreate as a callback for the messageCreate events
	ky.AddHandler(messageCreate)

	// Register messageDelete as a callback for the messageDelete events
	ky.AddHandler(messageDelete)

	// Register presenceUpdate to see who is online
	ky.AddHandler(presenceUpdate)

	// Register VoiceStateUpdate to check when users enter channel
	ky.AddHandler(VoiceStateUpdate)

	// Register other things
	// ky.AddHandler(messageReactionAdd)
	// ky.AddHandler(messageReactionRemove)

	// Open the websocket and begin listening.
	err = ky.Open()
	if err != nil {
		fmt.Println("Error opening Discord session: ", err)
	}

	// Wait here until CTRL-C or other term signal is received.
	fmt.Println("Kylixor is now running.  Press CTRL-C to exit.")
	sc := make(chan os.Signal, 1)
	signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
	<-sc

	// Cleanly close down the Discord session.
	if curChan.ChannelID != "" {
		curChan.Disconnect()
	}
	ky.Close()
}

// This function will be called (due to AddHandler above) when the bot receives
// the "ready" event from Discord.
func ready(s *discordgo.Session, event *discordgo.Ready) {
	// Set the playing status.
	s.UpdateStatus(0, config.Status)
	self = event.User
	USArray.GID = event.Guilds[0].ID
}

// This function will be called each time certain (or all) users change their
// online status
func presenceUpdate(s *discordgo.Session, p *discordgo.PresenceUpdate) {
	//Go through the range of who to monitor and log if needed
	for _, b := range config.Monitor {
		if b == p.User.ID {
			Log(s, p, "STATUS")
		}
	}
}

type Pair struct {
	Key   string
	Value int
}

type PairList []Pair

func (p PairList) Len() int           { return len(p) }
func (p PairList) Less(i, j int) bool { return p[i].Value < p[j].Value }
func (p PairList) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

// This function will be called when a user changes their voice state
// (mute, deafen, join channel, leave channel, etc.)
func VoiceStateUpdate(s *discordgo.Session, v *discordgo.VoiceStateUpdate) {
	//Update the user and if it is a real update log it
	VoiceChannelChange := UpdateUser(s, v, "VOICE")
	if VoiceChannelChange {
		Log(s, v, "VOICE")
		//If the VoiceStateUpdate is a join channel
		if v.ChannelID != "" {
			usr, _ := ReadUser(s, v, "VOICE")
			if usr.PlayAnthem && usr.Anthem != "" {
				PlayAnthem(s, v, usr.Anthem)
			}
		}
		//Join channel with most people
		g, _ := s.State.Guild(USArray.GID)
		if len(g.VoiceStates) > 0 {
			m := make(map[string]int)
			for i := range g.VoiceStates {
				m[g.VoiceStates[i].ChannelID] += 1
			}

			pl := make(PairList, len(m))
			i := 0
			for k, v := range m {
				pl[i] = Pair{k, v}
				i++
			}
			sort.Sort(sort.Reverse(pl))

			//If bot is the only one left, leave
			if pl[0].Value == 1 && curChan != nil {
				curChan.Disconnect()
				curChan.ChannelID = ""
			} else {
				//Join channel with most people
				curChan, _ = s.ChannelVoiceJoin(USArray.GID, pl[0].Key, false, false)
			}
		}
	}
}

func messageDelete(s *discordgo.Session, m *discordgo.MessageDelete) {
	if m.ChannelID != config.LogID {
		Log(s, m, "DEL")
	}
}

// This function will be called (due to AddHandler above) every time a new
// message is created on any channel that the autenticated bot has access to.
func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
	// Ignore all messages created by the bots
	// This isn't required in this specific example but it's a good practice.
	if m.Author.Bot {
		return
	}

	// Log every message into the log channel
	if m.ChannelID != config.LogID {
		Log(s, m, "MSG")
	}

	if m.Content == "(╯°□°）╯︵ ┻━┻" {
		s.ChannelMessageSend(m.ChannelID, "┬─┬ノ( º _ ºノ)")
	}

	if strings.HasPrefix(m.Content, config.Prefix) {
		// Remove prefix for 'performance'
		input := strings.TrimPrefix(m.Content, config.Prefix)

		switch input {

		case "account":
			usr, _ := ReadUser(s, m, "MSG")
			s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("💵 | You have a total of **%d** %s coins", usr.NoiseCredits, config.Coins))
			break

		case "anthem":
			SetAnthem(s, m)
			break

		case "bitconnect":
			PlayClip(s, m, "bitconnect")
			break

		case "dailies":
			_, i := ReadUser(s, m, "MSG")
			usr := &USArray.Users[i]
			if !usr.Dailies {
				usr.NoiseCredits += 100
				usr.Dailies = true
				s.ChannelMessageSend(m.ChannelID, fmt.Sprintf(
					"💵 | Dailies received! Total %s coins: **%d**",
					config.Coins, usr.NoiseCredits))
			} else {
				_, nextRuntime := gocron.NextRun()
				timeUntil := time.Until(nextRuntime)
				hour := timeUntil / time.Hour
				timeUntil -= hour * time.Hour
				min := timeUntil / time.Minute
				timeUntil -= min * time.Minute
				sec := timeUntil / time.Second
				s.ChannelMessageSend(m.ChannelID, fmt.Sprintf(
					"💵 | You have already collected today's dailies.\nDailies reset in %d hour(s), %d minute(s) and %d second(s).",
					hour, min, sec))
			}
			WriteUserFile()
			break

		case "help":
			readme, err := ioutil.ReadFile("README.md")
			if err != nil {
				panic(err)
			}
			// Print readme in code brackets so it doesn't look awful
			s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("```"+string(readme)+"```"))
			break

		case "ping":
			pongMessage, _ := s.ChannelMessageSend(m.ChannelID, "Pong!")
			// Format Discord time to readable time
			pongStamp, _ := time.Parse("2006-01-02T15:04:05-07:00", string(pongMessage.Timestamp))
			duration := time.Since(pongStamp)
			pingTime := duration.Nanoseconds() / 1000000
			// Print duration from message being send to message being posted
			s.ChannelMessageEdit(m.ChannelID, pongMessage.ID, fmt.Sprintf("Pong! %vms", pingTime))
			break

		case "pizza":
			s.ChannelMessageSend(m.ChannelID, "🍕 here it is, come get it. \nI ain't your delivery bitch.")
			break

		case "quoteclear":
			if m.Author.ID == config.Admin {
				// Create empty file to overwrite old quote DANGER: CAN'T UNDO
				_, _ = os.Create("quotes.txt")
				s.ChannelMessageSend(m.ChannelID, "Quote file cleared")
			}
			break

		case "quotelist":
			// List all quote, with lag
			entries := ListQuote(s, m)
			if entries <= 1 {
				s.ChannelMessageSend(m.ChannelID, "No quotes in file")
			}
			break

		case "randquote":
			ShowRandQuote(s, m)
			break

		case "reload":
			if m.Author.ID == config.Admin {
				ReadConfig()
				ReadUserFile()
				s.ChannelMessageSend(m.ChannelID, "Config reloaded")
			}
			break

		case "status":
			if m.Author.ID == config.Admin {
				ReadConfig()
				s.UpdateStatus(0, config.Status)
				s.ChannelMessageSend(m.ChannelID, "Status refreshed")
			}
			break

		case "test":
			if m.Author.ID == config.Admin {
				Test(s, m)
			}
			break

		case "yee":
			PlayClip(s, m, "yee")
			break

		default:
			s.ChannelMessageSend(m.ChannelID, "Not a command I'm pretty sure")
		}
	}
	if strings.HasPrefix(m.Content, "quote ") {
		//NEEDS IMPROVEMENTS
		quote := strings.TrimPrefix(m.Content, "quote ")
		Vote(s, m, quote)
	}

}
<commit_msg>cleaned up exiting of bot<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/signal"
	"sort"
	"strings"
	"syscall"
	"time"

	"github.com/bwmarrin/discordgo"

	"github.com/jasonlvhit/gocron"
)

func init() {
	flag.StringVar(&token, "t", "", "Bot Token")
	flag.Parse()
}

type Config struct {
	Admin       string
	Bots        []string
	Coins       string
	DefaultChan string
	LogID       string
	LogMessage  bool
	LogStatus   bool
	LogVoice    bool
	Monitor     []string
	Noise       bool
	Prefix      string
	Status      string
	Test        []string
}

var config = Config{}
var self *discordgo.User
var token string
var curChan *discordgo.VoiceConnection

//Read in the config into the Config structure
func ReadConfig() {
	file, _ := os.Open("conf.json")
	decoder := json.NewDecoder(file)
	err := decoder.Decode(&config)
	if err != nil {
		fmt.Println("error: ", err)
	}
	file.Close()
}

//Write out the current Config structure to file, indented nicely
func WriteConfig() {
	//Indent so its readable
	configData, err := json.MarshalIndent(config, "", "    ")
	if err != nil {
		panic(err)
	}
	//Open file
	jsonFile, err := os.Create("conf.json")
	if err != nil {
		panic(err)
	}
	//Write to file
	_, err = jsonFile.Write(configData)
	if err != nil {
		panic(err)
	}
	//Cleanup
	jsonFile.Close()
}

//Function to call once a day
func ResetDailies() {
	for j := range USArray.Users {
		USArray.Users[j].Dailies = false
	}
	WriteUserFile()
}

func main() {
	//Read in files
	if _, err := os.Stat("users.json"); os.IsNotExist(err) {
		InitUserFile()
	}
	ReadConfig()
	WriteConfig()
	ReadUserFile()

	for j := range USArray.Users {
		USArray.Users[j].PlayAnthem = true
	}
	WriteUserFile()

	go func() {
		gocron.Every(1).Day().At("20:00").Do(ResetDailies)
		<-gocron.Start()
	}()

	//Account for no token at runtime
	if token == "" {
		fmt.Println("No token provided. Please run: kylixor -t <bot token>")
		return
	}

	// Create a new Discord session using the provided bot token.
	ky, err := discordgo.New("Bot " + token)
	if err != nil {
		fmt.Println("Error creating Discord session: ", err)
		return
	}

	// Register ready as a callback for the ready events
	ky.AddHandlerOnce(ready)

	// Register messageCreate as a callback for the messageCreate events
	ky.AddHandler(messageCreate)

	// Register messageDelete as a callback for the messageDelete events
	ky.AddHandler(messageDelete)

	// Register presenceUpdate to see who is online
	ky.AddHandler(presenceUpdate)

	// Register VoiceStateUpdate to check when users enter channel
	ky.AddHandler(VoiceStateUpdate)

	// Register other things
	// ky.AddHandler(messageReactionAdd)
	// ky.AddHandler(messageReactionRemove)

	// Open the websocket and begin listening.
	err = ky.Open()
	if err != nil {
		fmt.Println("Error opening Discord session: ", err)
	}

	// Wait here until CTRL-C or other term signal is received.
	fmt.Println("Kylixor is now running.  Press CTRL-C to exit.")
	sc := make(chan os.Signal, 1)
	signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
	<-sc

	// Cleanly close down the Discord session.
	if curChan != nil {
		if curChan.ChannelID != "" {
			curChan.Disconnect()
		}
	}
	ky.Close()
}

// This function will be called (due to AddHandler above) when the bot receives
// the "ready" event from Discord.
func ready(s *discordgo.Session, event *discordgo.Ready) {
	// Set the playing status.
	s.UpdateStatus(0, config.Status)
	self = event.User
	USArray.GID = event.Guilds[0].ID
}

// This function will be called each time certain (or all) users change their
// online status
func presenceUpdate(s *discordgo.Session, p *discordgo.PresenceUpdate) {
	//Go through the range of who to monitor and log if needed
	for _, b := range config.Monitor {
		if b == p.User.ID {
			Log(s, p, "STATUS")
		}
	}
}

type Pair struct {
	Key   string
	Value int
}

type PairList []Pair

func (p PairList) Len() int           { return len(p) }
func (p PairList) Less(i, j int) bool { return p[i].Value < p[j].Value }
func (p PairList) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

// This function will be called when a user changes their voice state
// (mute, deafen, join channel, leave channel, etc.)
func VoiceStateUpdate(s *discordgo.Session, v *discordgo.VoiceStateUpdate) {
	//Update the user and if it is a real update log it
	VoiceChannelChange := UpdateUser(s, v, "VOICE")
	if VoiceChannelChange {
		Log(s, v, "VOICE")
		//If the VoiceStateUpdate is a join channel
		if v.ChannelID != "" {
			usr, _ := ReadUser(s, v, "VOICE")
			if usr.PlayAnthem && usr.Anthem != "" {
				PlayAnthem(s, v, usr.Anthem)
			}
		}
		//Join channel with most people
		g, _ := s.State.Guild(USArray.GID)
		if len(g.VoiceStates) > 0 {
			m := make(map[string]int)
			for i := range g.VoiceStates {
				m[g.VoiceStates[i].ChannelID] += 1
			}

			pl := make(PairList, len(m))
			i := 0
			for k, v := range m {
				pl[i] = Pair{k, v}
				i++
			}
			sort.Sort(sort.Reverse(pl))

			//If bot is the only one left, leave
			if pl[0].Value == 1 && curChan != nil {
				curChan.Disconnect()
				curChan.ChannelID = ""
			} else {
				//Join channel with most people
				curChan, _ = s.ChannelVoiceJoin(USArray.GID, pl[0].Key, false, false)
			}
		}
	}
}

func messageDelete(s *discordgo.Session, m *discordgo.MessageDelete) {
	if m.ChannelID != config.LogID {
		Log(s, m, "DEL")
	}
}

// This function will be called (due to AddHandler above) every time a new
// message is created on any channel that the autenticated bot has access to.
func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
	// Ignore all messages created by the bots
	// This isn't required in this specific example but it's a good practice.
	if m.Author.Bot {
		return
	}

	// Log every message into the log channel
	if m.ChannelID != config.LogID {
		Log(s, m, "MSG")
	}

	if m.Content == "(╯°□°）╯︵ ┻━┻" {
		s.ChannelMessageSend(m.ChannelID, "┬─┬ノ( º _ ºノ)")
	}

	if strings.HasPrefix(m.Content, config.Prefix) {
		// Remove prefix for 'performance'
		input := strings.TrimPrefix(m.Content, config.Prefix)

		switch input {

		case "account":
			usr, _ := ReadUser(s, m, "MSG")
			s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("💵 | You have a total of **%d** %s coins", usr.NoiseCredits, config.Coins))
			break

		case "anthem":
			SetAnthem(s, m)
			break

		case "bitconnect":
			PlayClip(s, m, "bitconnect")
			break

		case "dailies":
			_, i := ReadUser(s, m, "MSG")
			usr := &USArray.Users[i]
			if !usr.Dailies {
				usr.NoiseCredits += 100
				usr.Dailies = true
				s.ChannelMessageSend(m.ChannelID, fmt.Sprintf(
					"💵 | Dailies received! Total %s coins: **%d**",
					config.Coins, usr.NoiseCredits))
			} else {
				_, nextRuntime := gocron.NextRun()
				timeUntil := time.Until(nextRuntime)
				hour := timeUntil / time.Hour
				timeUntil -= hour * time.Hour
				min := timeUntil / time.Minute
				timeUntil -= min * time.Minute
				sec := timeUntil / time.Second
				s.ChannelMessageSend(m.ChannelID, fmt.Sprintf(
					"💵 | You have already collected today's dailies.\nDailies reset in %d hour(s), %d minute(s) and %d second(s).",
					hour, min, sec))
			}
			WriteUserFile()
			break

		case "help":
			readme, err := ioutil.ReadFile("README.md")
			if err != nil {
				panic(err)
			}
			// Print readme in code brackets so it doesn't look awful
			s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("```"+string(readme)+"```"))
			break

		case "ping":
			pongMessage, _ := s.ChannelMessageSend(m.ChannelID, "Pong!")
			// Format Discord time to readable time
			pongStamp, _ := time.Parse("2006-01-02T15:04:05-07:00", string(pongMessage.Timestamp))
			duration := time.Since(pongStamp)
			pingTime := duration.Nanoseconds() / 1000000
			// Print duration from message being send to message being posted
			s.ChannelMessageEdit(m.ChannelID, pongMessage.ID, fmt.Sprintf("Pong! %vms", pingTime))
			break

		case "pizza":
			s.ChannelMessageSend(m.ChannelID, "🍕 here it is, come get it. \nI ain't your delivery bitch.")
			break

		case "quoteclear":
			if m.Author.ID == config.Admin {
				// Create empty file to overwrite old quote DANGER: CAN'T UNDO
				_, _ = os.Create("quotes.txt")
				s.ChannelMessageSend(m.ChannelID, "Quote file cleared")
			}
			break

		case "quotelist":
			// List all quote, with lag
			entries := ListQuote(s, m)
			if entries <= 1 {
				s.ChannelMessageSend(m.ChannelID, "No quotes in file")
			}
			break

		case "randquote":
			ShowRandQuote(s, m)
			break

		case "reload":
			if m.Author.ID == config.Admin {
				ReadConfig()
				ReadUserFile()
				s.ChannelMessageSend(m.ChannelID, "Config reloaded")
			}
			break

		case "status":
			if m.Author.ID == config.Admin {
				ReadConfig()
				s.UpdateStatus(0, config.Status)
				s.ChannelMessageSend(m.ChannelID, "Status refreshed")
			}
			break

		case "test":
			if m.Author.ID == config.Admin {
				Test(s, m)
			}
			break

		case "yee":
			PlayClip(s, m, "yee")
			break

		default:
			s.ChannelMessageSend(m.ChannelID, "Not a command I'm pretty sure")
		}
	}
	if strings.HasPrefix(m.Content, "quote ") {
		//NEEDS IMPROVEMENTS
		quote := strings.TrimPrefix(m.Content, "quote ")
		Vote(s, m, quote)
	}

}
<|endoftext|>


<commit_before>// Copyright (C) 2013 Miquel Sabaté Solà <mikisabate@gmail.com>
// This file is licensed under the MIT license.
// See the LICENSE file.

package lib

import (
	"net/http"

	"github.com/gorilla/sessions"
	"github.com/mssola/go-utils/security"
)

// Global variable that holds the cookie store for this application. It gets
// initialized by calling the InitSession function.
var store *sessions.CookieStore

const (
	// The name of the session to be used for the safe cookies.
	sessionName = "todo"

	// Max-Age of a whole year.
	maxAge = 60 * 60 * 24 * 30 * 12
)

// Initialize the global cookie store.
func InitSession() {
	store = sessions.NewCookieStore([]byte(security.NewAuthToken()))
	store.Options = &sessions.Options{Path: "/", MaxAge: maxAge}
}

// Tries to get the cookie store for the given request. It panics if it fails.
func GetStore(req *http.Request) *sessions.Session {
	// Try to cache the session.
	s, err := store.Get(req, sessionName)
	if err != nil {
		// Missed! Let's generate a new session. Moreover, Gorilla's
		// documentation says that this method *never* fails on creating
		// a new session, so it's safe to ignore the given error.
		s, _ = store.New(req, sessionName)
	}
	return s
}

// Returns the value for the specified cookie. If the cookie does not exist,
// then an empty interface{} gets returned.
func GetCookie(req *http.Request, name string) interface{} {
	s := GetStore(req)
	return s.Values[name]
}

// Sets the given value to the specified cookie.
func SetCookie(res http.ResponseWriter, req *http.Request, key, value string) {
	s := GetStore(req)
	s.Values[key] = value
	s.Save(req, res)
}

// Deletes the specified cookie.
func DeleteCookie(res http.ResponseWriter, req *http.Request, key string) {
	s := GetStore(req)
	delete(s.Values, key)
	s.Save(req, res)
}
<commit_msg>lib: fixed copyright notice.<commit_after>// Copyright (C) 2014 Miquel Sabaté Solà <mikisabate@gmail.com>
// This file is licensed under the MIT license.
// See the LICENSE file.

package lib

import (
	"net/http"

	"github.com/gorilla/sessions"
	"github.com/mssola/go-utils/security"
)

// Global variable that holds the cookie store for this application. It gets
// initialized by calling the InitSession function.
var store *sessions.CookieStore

const (
	// The name of the session to be used for the safe cookies.
	sessionName = "todo"

	// Max-Age of a whole year.
	maxAge = 60 * 60 * 24 * 30 * 12
)

// Initialize the global cookie store.
func InitSession() {
	store = sessions.NewCookieStore([]byte(security.NewAuthToken()))
	store.Options = &sessions.Options{Path: "/", MaxAge: maxAge}
}

// Tries to get the cookie store for the given request. It panics if it fails.
func GetStore(req *http.Request) *sessions.Session {
	// Try to cache the session.
	s, err := store.Get(req, sessionName)
	if err != nil {
		// Missed! Let's generate a new session. Moreover, Gorilla's
		// documentation says that this method *never* fails on creating
		// a new session, so it's safe to ignore the given error.
		s, _ = store.New(req, sessionName)
	}
	return s
}

// Returns the value for the specified cookie. If the cookie does not exist,
// then an empty interface{} gets returned.
func GetCookie(req *http.Request, name string) interface{} {
	s := GetStore(req)
	return s.Values[name]
}

// Sets the given value to the specified cookie.
func SetCookie(res http.ResponseWriter, req *http.Request, key, value string) {
	s := GetStore(req)
	s.Values[key] = value
	s.Save(req, res)
}

// Deletes the specified cookie.
func DeleteCookie(res http.ResponseWriter, req *http.Request, key string) {
	s := GetStore(req)
	delete(s.Values, key)
	s.Save(req, res)
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git"
	"net/url"
	"os"
)

// tcp://user:password@host:port
func pushMsg(clientId, brokerUrl, topic, msg string) (bool, error) {

	uri, _ := url.Parse(brokerUrl)
	opts := mqtt.NewClientOptions()

	opts.AddBroker(fmt.Sprintf("tcp://%s", uri.Host))

	user := uri.User.Username()
	if user != "" {
		opts.SetUsername(uri.User.Username())
		password, _ := uri.User.Password()
		if password != "" {
			opts.SetPassword(password)
		}
	}

	opts.SetClientId(clientId)

	client := mqtt.NewClient(opts)
	_, err := client.Start()
	if err != nil {
		return false, errors.New("Error starting the MQTT Client: " + err.Error())
	}

	<-client.Publish(0, topic, msg)

	return true, nil
}

func main() {
	pushMsg("pushmtr", os.Getenv("MQTT_URL"), "topic", "message")
}
<commit_msg>Better URI parsing<commit_after>package main

import (
	"errors"
	"fmt"
	"git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git"
	"net/url"
	"os"
)

// tcp://user:password@host:port
func PushMsg(clientId, brokerUrl, topic, msg string) error {

	if brokerUrl == "" {
		panic("Invalid broker URL")
	}

	uri, _ := url.Parse(brokerUrl)
	if uri.Scheme != "tcp" {
		panic("Invalid broker URL scheme")
	}

	opts := mqtt.NewClientOptions()

	opts.AddBroker(fmt.Sprintf("tcp://%s", uri.Host))

	if uri.User != nil {
		user := uri.User.Username()
		opts.SetUsername(user)
		password, _ := uri.User.Password()
		if password != "" {
			opts.SetPassword(password)
		}
	}

	opts.SetClientId(clientId)

	client := mqtt.NewClient(opts)
	_, err := client.Start()
	if err != nil {
		return errors.New("Error starting the MQTT Client: " + err.Error())
	}

	<-client.Publish(0, topic, msg)

	return nil
}
<|endoftext|>


<commit_before>// Copyright © 2015 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package main

import (
	"flag"
	"fmt"
	"strconv"
	"strings"

	. "github.com/TheThingsNetwork/ttn/core"
	"github.com/TheThingsNetwork/ttn/core/adapters/http/broadcast"
	"github.com/TheThingsNetwork/ttn/core/adapters/semtech"
	"github.com/TheThingsNetwork/ttn/core/components"
	"github.com/apex/log"
)

func main() {
	// Parse options
	brokers, udpPort := parseOptions()

	// Create Logging Context

	ctx := log.WithFields(log.Fields{
		"component": "Router",
	})

	// Instantiate all components
	gtwAdapter, err := semtech.NewAdapter(uint(udpPort), ctx.WithField("tag", "Gateway Adapter"))
	if err != nil {
		panic(err)
	}

	brkAdapter, err := broadcast.NewAdapter(brokers, ctx.WithField("tag", "Broker Adapter"))
	if err != nil {
		panic(err)
	}

	router, err := components.NewRouter(ctx.WithField("tag", "Router"))
	if err != nil {
		panic(err)
	}

	// Bring the service to life

	// Listen uplink
	go func() {
		for {
			packet, an, err := gtwAdapter.Next()
			if err != nil {
				fmt.Println(err)
				continue
			}
			go func(packet Packet, an AckNacker) {
				if err := router.HandleUp(packet, an, brkAdapter); err != nil {
					fmt.Println(err)
				}
			}(packet, an)
		}
	}()

	// Listen broker registrations
	go func() {
		for {
			reg, an, err := brkAdapter.NextRegistration()
			if err != nil {
				fmt.Println(err)
				continue
			}
			go func(reg Registration, an AckNacker) {
				if err := router.Register(reg, an); err != nil {
					fmt.Println(err)
				}
			}(reg, an)
		}
	}()

	<-make(chan bool)
}

func parseOptions() (brokers []Recipient, udpPort uint64) {
	var brokersFlag string
	var udpPortFlag string
	flag.StringVar(&brokersFlag, "brokers", "", `Broker addresses to which broadcast packets.
 	For instance: 10.10.3.34:8080,thethingsnetwork.broker.com:3000
 	`)
	flag.StringVar(&udpPortFlag, "udp-port", "", "Udp port on which the router should listen to.")
	flag.Parse()

	var err error
	udpPort, err = strconv.ParseUint(udpPortFlag, 10, 64)
	if err != nil {
		panic(err)
	}
	if brokersFlag == "" {
		panic("Need to provide at least one broker address")
	}

	brokersStr := strings.Split(brokersFlag, ",")
	for i := range brokersStr {
		brokers = append(brokers, Recipient{
			Address: strings.Trim(brokersStr[i], " "),
			Id:      i,
		})

	}
	return
}
<commit_msg>Add missing handler for new logger in router integration<commit_after>// Copyright © 2015 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package main

import (
	"flag"
	"fmt"
	"os"
	"strconv"
	"strings"

	. "github.com/TheThingsNetwork/ttn/core"
	"github.com/TheThingsNetwork/ttn/core/adapters/http"
	"github.com/TheThingsNetwork/ttn/core/adapters/http/broadcast"
	"github.com/TheThingsNetwork/ttn/core/adapters/semtech"
	"github.com/TheThingsNetwork/ttn/core/components"
	"github.com/apex/log"
	"github.com/apex/log/handlers/text"
)

func main() {
	// Parse options
	brokers, tcpPort, udpPort := parseOptions()

	// Create Logging Context
	log.SetHandler(text.New(os.Stdout))
	ctx := log.WithFields(log.Fields{
		"component": "Router",
	})

	// Instantiate all components
	gtwAdapter, err := semtech.NewAdapter(uint(udpPort), ctx.WithField("tag", "Gateway Adapter"))
	if err != nil {
		panic(err)
	}

	pktAdapter, err := http.NewAdapter(uint(tcpPort), http.JSONPacketParser{}, ctx.WithField("tag", "Broker Adapter"))
	if err != nil {
		panic(err)
	}

	brkAdapter, err := broadcast.NewAdapter(pktAdapter, brokers, ctx.WithField("tag", "Broker Adapter"))
	if err != nil {
		panic(err)
	}

	router, err := components.NewRouter(ctx.WithField("tag", "Router"))
	if err != nil {
		panic(err)
	}

	// Bring the service to life

	// Listen uplink
	go func() {
		for {
			packet, an, err := gtwAdapter.Next()
			if err != nil {
				fmt.Println(err)
				continue
			}
			go func(packet Packet, an AckNacker) {
				if err := router.HandleUp(packet, an, brkAdapter); err != nil {
					fmt.Println(err)
				}
			}(packet, an)
		}
	}()

	// Listen broker registrations
	go func() {
		for {
			reg, an, err := brkAdapter.NextRegistration()
			if err != nil {
				fmt.Println(err)
				continue
			}
			go func(reg Registration, an AckNacker) {
				if err := router.Register(reg, an); err != nil {
					fmt.Println(err)
				}
			}(reg, an)
		}
	}()

	<-make(chan bool)
}

func parseOptions() (brokers []Recipient, tcpPort uint64, udpPort uint64) {
	var brokersFlag string
	var udpPortFlag string
	var tcpPortFlag string
	flag.StringVar(&brokersFlag, "brokers", "", `Broker addresses to which broadcast packets.
 	For instance: 10.10.3.34:8080,thethingsnetwork.broker.com:3000
 	`)
	flag.StringVar(&udpPortFlag, "udp-port", "", "UDP port on which the router should listen to.")
	flag.StringVar(&tcpPortFlag, "tcp-port", "", "TCP port on which the router should listen to.")
	flag.Parse()

	var err error
	tcpPort, err = strconv.ParseUint(tcpPortFlag, 10, 64)
	if err != nil {
		panic(err)
	}
	udpPort, err = strconv.ParseUint(udpPortFlag, 10, 64)
	if err != nil {
		panic(err)
	}
	if brokersFlag == "" {
		panic("Need to provide at least one broker address")
	}

	brokersStr := strings.Split(brokersFlag, ",")
	for i := range brokersStr {
		brokers = append(brokers, Recipient{
			Address: strings.Trim(brokersStr[i], " "),
			Id:      i,
		})

	}
	return
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"github.com/RangelReale/osin"
	"io/ioutil"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
	"net/http"
	"net/url"
	"path/filepath"
	"regexp"
	"strings"
	"time"
)

// APIDefinition represents the configuration for a single proxied API and it's versions.
type APIDefinition struct {
	ID               bson.ObjectId `bson:"_id,omitempty" json:"id"`
	Name             string        `bson:"name" json:"name"`
	APIID            string        `bson:"api_id" json:"api_id"`
	OrgID            string        `bson:"org_id" json:"org_id"`
	UseKeylessAccess bool          `bson:"use_keyless" json:"use_keyless"`
	UseOauth2        bool          `bson:"use_oauth2" json:"use_oauth2"`
	Oauth2Meta       struct {
		AllowedAccessTypes     []osin.AccessRequestType    `bson:"allowed_access_types" json:"allowed_access_types"`
		AllowedAuthorizeTypes  []osin.AuthorizeRequestType `bson:"allowed_authorize_types" json:"allowed_authorize_types"`
		AuthorizeLoginRedirect string                      `bson:"auth_login_redirect" json:"auth_login_redirect"`
	} `bson:"oauth_meta" json:"oauth_meta"`
	UseBasicAuth         bool                 `bson:"use_basic_auth" json:"use_basic_auth"`
	NotificationsDetails NotificationsManager `bson:"notifications" json:"notifications"`
	VersionDefinition    struct {
		Location string `bson:"location" json:"location"`
		Key      string `bson:"key" json:"key"`
	} `bson:"definition" json:"definition"`
	EnableSignatureChecking bool `bson:"enable_signature_checking" json:"enable_signature_checking"`
	VersionData             struct {
		NotVersioned bool                   `bson:"not_versioned" json:"not_versioned"`
		Versions     map[string]VersionInfo `bson:"versions" json:"versions"`
	} `bson:"version_data" json:"version_data"`
	Proxy struct {
		ListenPath      string `bson:"listen_path" json:"listen_path"`
		TargetURL       string `bson:"target_url" json:"target_url"`
		StripListenPath bool   `bson:"strip_listen_path" json:"strip_listen_path"`
	} `bson:"proxy" json:"proxy"`
//	Auth struct {
//		AuthHeaderName string `bson:"auth_header_name" json:"auth_header_name"`
//	} `bson:"auth" json:"auth"`
	Active bool `bson:"active" json:"active"`
	RawData map[string]interface{} `bson:"raw_data,omitempty" json:"raw_data,omitempty"` // Not used in actual configuration, loaded by config for plugable arc
}

// VersionInfo encapsulates all the data for a specific api_version, elements in the
// Paths array are checked as part of the proxy routing.
type VersionInfo struct {
	Name    string `bson:"name" json:"name"`
	Expires string `bson:"expires" json:"expires"`
	Paths   struct {
		Ignored   []string `bson:"ignored" json:"ignored"`
		WhiteList []string `bson:"white_list" json:"white_list"`
		BlackList []string `bson:"black_list" json:"black_list"`
	} `bson:"paths" json:"paths"`
}

// URLStatus is a custom enum type to avoid collisions
type URLStatus int

// Enums representing the various statuses for a VersionInfo Path match during a
// proxy request
const (
	Ignored   URLStatus = 1
	WhiteList URLStatus = 2
	BlackList URLStatus = 3
)

// RequestStatus is a custom type to avoid collisions
type RequestStatus string

// Statuses of the request, all are false-y except StatusOk and StatusOkAndIgnore
const (
	VersionNotFound                RequestStatus = "Version information not found"
	VersionDoesNotExist            RequestStatus = "This API version doesn't seem to exist"
	VersionPathsNotFound           RequestStatus = "Path information could not be found for version"
	VersionWhiteListStatusNotFound               = "WhiteListStatus for path not found"
	VersionExpired                 RequestStatus = "Api Version has expired, please check documentation or contact administrator"
	EndPointNotAllowed             RequestStatus = "Requested endpoint is forbidden"
	GeneralFailure                 RequestStatus = "An error occured that should have not been possible"
	StatusOkAndIgnore              RequestStatus = "Everything OK, passing and not filtering"
	StatusOk                       RequestStatus = "Everything OK, passing"
)

// URLSpec represents a flattened specification for URLs, used to check if a proxy URL
// path is on any of the white, plack or ignored lists. This is generated as part of the
// configuration init
type URLSpec struct {
	Spec   *regexp.Regexp
	Status URLStatus
}

// APISpec represents a path specification for an API, to avoid enumerating multiple nested lists, a single
// flattened URL list is checked for matching paths and then it's status evaluated if found.
type APISpec struct {
	APIDefinition
	RxPaths          map[string][]URLSpec
	WhiteListEnabled map[string]bool
	target           *url.URL
}

// APIDefinitionLoader will load an Api definition from a storage system. It has two methods LoadDefinitionsFromMongo()
// and LoadDefinitions(), each will pull api specifications from different locations.
type APIDefinitionLoader struct {
	dbSession *mgo.Session
}

// Connect connects to the storage engine - can be null
func (a *APIDefinitionLoader) Connect() {
	var err error
	a.dbSession, err = mgo.Dial(config.AnalyticsConfig.MongoURL)
	if err != nil {
		log.Error("Mongo connection failed:")
		log.Panic(err)
	}
}

// MakeSpec will generate a flattened URLSpec from and APIDefinitions' VersionInfo data. paths are
// keyed to the Api version name, which is determined during routing to speed up lookups
func (a *APIDefinitionLoader) MakeSpec(thisAppConfig APIDefinition) APISpec {
	newAppSpec := APISpec{}
	newAppSpec.APIDefinition = thisAppConfig
	newAppSpec.RxPaths = make(map[string][]URLSpec)
	newAppSpec.WhiteListEnabled = make(map[string]bool)
	for _, v := range thisAppConfig.VersionData.Versions {
		pathSpecs, whiteListSpecs := a.getPathSpecs(v)
		newAppSpec.RxPaths[v.Name] = pathSpecs
		newAppSpec.WhiteListEnabled[v.Name] = whiteListSpecs
	}

	return newAppSpec
}

// LoadDefinitionsFromMongo will connect and download ApiDefintions from a Mongo DB instance.
func (a *APIDefinitionLoader) LoadDefinitionsFromMongo() []APISpec {
	var APISpecs = []APISpec{}

	a.Connect()
	apiCollection := a.dbSession.DB("").C("tyk_apis")

	search := bson.M{
		"active": true,
	}

	var APIDefinitions = []APIDefinition{}
	var StringDefs = make([]bson.M, 0)
	mongoErr := apiCollection.Find(search).All(&APIDefinitions)

	if mongoErr != nil {
		log.Error("Could not find any application configs!")
		return APISpecs
	}

	apiCollection.Find(search).All(&StringDefs)

	for i, thisAppConfig := range APIDefinitions {
		thisAppConfig.RawData = StringDefs[i] // Lets keep a copy for plugable modules

		newAppSpec := a.MakeSpec(thisAppConfig)
		APISpecs = append(APISpecs, newAppSpec)
	}
	return APISpecs
}

func (a *APIDefinitionLoader) ParseDefinition(apiDef []byte) (APIDefinition, map[string]interface{}) {
	thisAppConfig := APIDefinition{}
	err := json.Unmarshal(apiDef, &thisAppConfig)
	if err != nil {
		log.Error("Couldn't unmarshal api configuration")
		log.Error(err)
	}

	// Got the structured version - now lets get a raw copy for modules
	thisRawConfig := make(map[string]interface{})
	json.Unmarshal(apiDef, &thisRawConfig)

	return thisAppConfig, thisRawConfig
}

// LoadDefinitions will load APIDefinitions from a directory on the filesystem. Definitions need
// to be the JSON representation of APIDefinition object
func (a *APIDefinitionLoader) LoadDefinitions(dir string) []APISpec {
	var APISpecs = []APISpec{}
	// Grab json files from directory
	files, _ := ioutil.ReadDir(dir)
	for _, f := range files {
		if strings.Contains(f.Name(), ".json") {
			filePath := filepath.Join(dir, f.Name())
			log.Info("Loading API Specification from ", filePath)
			appConfig, err := ioutil.ReadFile(filePath)
			thisAppConfig, thisRawConfig := a.ParseDefinition(appConfig)
			if err != nil {
				log.Error("Couldn't load app configuration file")
				log.Error(err)
			}

			thisAppConfig.RawData = thisRawConfig // Lets keep a copy for plugable modules
			newAppSpec := a.MakeSpec(thisAppConfig)
			APISpecs = append(APISpecs, newAppSpec)

		}
	}

	return APISpecs
}

func (a *APIDefinitionLoader) getPathSpecs(apiVersionDef VersionInfo) ([]URLSpec, bool) {
	ignoredPaths := a.compilePathSpec(apiVersionDef.Paths.Ignored, Ignored)
	blackListPaths := a.compilePathSpec(apiVersionDef.Paths.BlackList, BlackList)
	whiteListPaths := a.compilePathSpec(apiVersionDef.Paths.WhiteList, WhiteList)

	combinedPath := []URLSpec{}
	combinedPath = append(combinedPath, ignoredPaths...)
	combinedPath = append(combinedPath, blackListPaths...)
	combinedPath = append(combinedPath, whiteListPaths...)

	if len(whiteListPaths) > 0 {
		return combinedPath, true
	}

	return combinedPath, false
}

func (a *APIDefinitionLoader) compilePathSpec(paths []string, specType URLStatus) []URLSpec {

	// transform a configuration URL into an array of URLSpecs
	// This way we can iterate the whole array once, on match we break with status
	apiLangIDsRegex, _ := regexp.Compile("{(.*?)}")
	thisURLSpec := []URLSpec{}

	for _, stringSpec := range paths {
		asRegexStr := apiLangIDsRegex.ReplaceAllString(stringSpec, "(.*?)")
		asRegex, _ := regexp.Compile(asRegexStr)

		newSpec := URLSpec{}
		newSpec.Spec = asRegex
		newSpec.Status = specType
		thisURLSpec = append(thisURLSpec, newSpec)
	}

	return thisURLSpec
}

// IsURLAllowedAndIgnored checks if a url is allowed and ignored.
func (a *APISpec) IsURLAllowedAndIgnored(url string, RxPaths []URLSpec, WhiteListStatus bool) (bool, bool) {
	// Check if ignored
	for _, v := range RxPaths {
		match := v.Spec.MatchString(url)
		if match {
			if v.Status == Ignored {
				// Let it pass, and do not check auth
				return true, true
			} else if v.Status == BlackList {
				// Matched  a blacklist URL, disallow access and filter (irrelevant here)
				return false, false
			} else if v.Status == WhiteList {
				// Matched whitelist, allow request but filter
				return true, false
			}

			// Should not occur, something has gone wrong
			log.Error("URL Status was not one of Ignored, Blacklist or WhiteList! Blocking.")
			return false, false

		}
	}

	// Nothing matched - should we still let it through?
	if WhiteListStatus {
		// We have a whitelist, nothing gets through unless specifically defined
		return false, false
	}

	// No whitelist, but also not in any of the other lists, let it through and filter
	return true, false

}

func (a *APISpec) getVersionFromRequest(r *http.Request) string {
	if a.APIDefinition.VersionDefinition.Location == "header" {
		versionHeaderVal := r.Header.Get(a.APIDefinition.VersionDefinition.Key)
		if versionHeaderVal != "" {
			return versionHeaderVal
		}

		return ""

	} else if a.APIDefinition.VersionDefinition.Location == "url-param" {
		fromParam := r.FormValue(a.APIDefinition.VersionDefinition.Key)
		if fromParam != "" {
			return fromParam
		}

		return ""

	} else {
		return ""
	}

	return ""
}

// IsThisAPIVersionExpired checks if an API version (during a proxied request) is expired
func (a *APISpec) IsThisAPIVersionExpired(versionDef VersionInfo) bool {
	// Never expires
	if versionDef.Expires == "-1" {
		return false
	}

	if versionDef.Expires == "" {
		return false
	}

	// otherwise - calculate the time
	t, err := time.Parse("2006-01-02 15:04", versionDef.Expires)
	if err != nil {
		log.Error("Could not parse expiry date for API, dissallow")
		log.Error(err)
		return true
	}

	remaining := time.Since(t)
	if remaining < 0 {
		// It's in the future, keep going
		return false
	}

	// It's in the past, expire
	return true

}

// IsRequestValid will check if an incoming request has valid version data and return a RequestStatus that
// describes the status of the request
func (a *APISpec) IsRequestValid(r *http.Request) (bool, RequestStatus) {
	versionMetaData, versionPaths, whiteListStatus, stat := a.GetVersionData(r)

	// Screwed up version info - fail and pass through
	if stat != StatusOk {
		return false, stat
	}

	// Is the API version expired?
	if a.IsThisAPIVersionExpired(versionMetaData) == true {
		// Expired - fail
		return false, VersionExpired
	}

	// not expired, let's check path info
	allowURL, ignore := a.IsURLAllowedAndIgnored(r.URL.Path, versionPaths, whiteListStatus)
	if !allowURL {
		return false, EndPointNotAllowed
	}

	if ignore {
		return true, StatusOkAndIgnore
	}

	return true, StatusOk

}

// GetVersionData attempts to extract the version data from a request, depending on where it is stored in the
// request (currently only "header" is supported)
func (a *APISpec) GetVersionData(r *http.Request) (VersionInfo, []URLSpec, bool, RequestStatus) {
	var thisVersion = VersionInfo{}
	var versionKey string
	var versionRxPaths = []URLSpec{}
	var versionWLStatus bool

	// Are we versioned?
	if a.APIDefinition.VersionData.NotVersioned {
		// Get the first one in the list
		for k, v := range a.APIDefinition.VersionData.Versions {
			versionKey = k
			thisVersion = v
			break
		}
	} else {
		// Extract Version Info
		versionKey = a.getVersionFromRequest(r)
		if versionKey == "" {
			return thisVersion, versionRxPaths, versionWLStatus, VersionNotFound
		}
	}

	// Load Version Data - General
	var ok bool
	thisVersion, ok = a.APIDefinition.VersionData.Versions[versionKey]
	if !ok {
		return thisVersion, versionRxPaths, versionWLStatus, VersionDoesNotExist
	}

	// Load path data and whitelist data for version
	RxPaths, rxOk := a.RxPaths[versionKey]
	WhiteListStatus, wlOk := a.WhiteListEnabled[versionKey]

	if !rxOk {
		log.Error("no RX Paths found for version")
		log.Error(versionKey)
		return thisVersion, versionRxPaths, versionWLStatus, VersionDoesNotExist
	}

	if !wlOk {
		log.Error("No whitelist data found")
		return thisVersion, versionRxPaths, versionWLStatus, VersionWhiteListStatusNotFound
	}

	versionRxPaths = RxPaths
	versionWLStatus = WhiteListStatus

	return thisVersion, versionRxPaths, versionWLStatus, StatusOk

}
<commit_msg>Clen up<commit_after>package main

import (
	"encoding/json"
	"github.com/RangelReale/osin"
	"io/ioutil"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
	"net/http"
	"net/url"
	"path/filepath"
	"regexp"
	"strings"
	"time"
)

// APIDefinition represents the configuration for a single proxied API and it's versions.
type APIDefinition struct {
	ID               bson.ObjectId `bson:"_id,omitempty" json:"id"`
	Name             string        `bson:"name" json:"name"`
	APIID            string        `bson:"api_id" json:"api_id"`
	OrgID            string        `bson:"org_id" json:"org_id"`
	UseKeylessAccess bool          `bson:"use_keyless" json:"use_keyless"`
	UseOauth2        bool          `bson:"use_oauth2" json:"use_oauth2"`
	Oauth2Meta       struct {
		AllowedAccessTypes     []osin.AccessRequestType    `bson:"allowed_access_types" json:"allowed_access_types"`
		AllowedAuthorizeTypes  []osin.AuthorizeRequestType `bson:"allowed_authorize_types" json:"allowed_authorize_types"`
		AuthorizeLoginRedirect string                      `bson:"auth_login_redirect" json:"auth_login_redirect"`
	} `bson:"oauth_meta" json:"oauth_meta"`
	UseBasicAuth         bool                 `bson:"use_basic_auth" json:"use_basic_auth"`
	NotificationsDetails NotificationsManager `bson:"notifications" json:"notifications"`
	EnableSignatureChecking bool `bson:"enable_signature_checking" json:"enable_signature_checking"`
	VersionDefinition    struct {
		Location string `bson:"location" json:"location"`
		Key      string `bson:"key" json:"key"`
	} `bson:"definition" json:"definition"`
	VersionData             struct {
		NotVersioned bool                   `bson:"not_versioned" json:"not_versioned"`
		Versions     map[string]VersionInfo `bson:"versions" json:"versions"`
	} `bson:"version_data" json:"version_data"`
	Proxy struct {
		ListenPath      string `bson:"listen_path" json:"listen_path"`
		TargetURL       string `bson:"target_url" json:"target_url"`
		StripListenPath bool   `bson:"strip_listen_path" json:"strip_listen_path"`
	} `bson:"proxy" json:"proxy"`
	Active bool `bson:"active" json:"active"`
	RawData map[string]interface{} `bson:"raw_data,omitempty" json:"raw_data,omitempty"` // Not used in actual configuration, loaded by config for plugable arc
}

// VersionInfo encapsulates all the data for a specific api_version, elements in the
// Paths array are checked as part of the proxy routing.
type VersionInfo struct {
	Name    string `bson:"name" json:"name"`
	Expires string `bson:"expires" json:"expires"`
	Paths   struct {
		Ignored   []string `bson:"ignored" json:"ignored"`
		WhiteList []string `bson:"white_list" json:"white_list"`
		BlackList []string `bson:"black_list" json:"black_list"`
	} `bson:"paths" json:"paths"`
}

// URLStatus is a custom enum type to avoid collisions
type URLStatus int

// Enums representing the various statuses for a VersionInfo Path match during a
// proxy request
const (
	Ignored   URLStatus = 1
	WhiteList URLStatus = 2
	BlackList URLStatus = 3
)

// RequestStatus is a custom type to avoid collisions
type RequestStatus string

// Statuses of the request, all are false-y except StatusOk and StatusOkAndIgnore
const (
	VersionNotFound                RequestStatus = "Version information not found"
	VersionDoesNotExist            RequestStatus = "This API version doesn't seem to exist"
	VersionPathsNotFound           RequestStatus = "Path information could not be found for version"
	VersionWhiteListStatusNotFound               = "WhiteListStatus for path not found"
	VersionExpired                 RequestStatus = "Api Version has expired, please check documentation or contact administrator"
	EndPointNotAllowed             RequestStatus = "Requested endpoint is forbidden"
	GeneralFailure                 RequestStatus = "An error occured that should have not been possible"
	StatusOkAndIgnore              RequestStatus = "Everything OK, passing and not filtering"
	StatusOk                       RequestStatus = "Everything OK, passing"
)

// URLSpec represents a flattened specification for URLs, used to check if a proxy URL
// path is on any of the white, plack or ignored lists. This is generated as part of the
// configuration init
type URLSpec struct {
	Spec   *regexp.Regexp
	Status URLStatus
}

// APISpec represents a path specification for an API, to avoid enumerating multiple nested lists, a single
// flattened URL list is checked for matching paths and then it's status evaluated if found.
type APISpec struct {
	APIDefinition
	RxPaths          map[string][]URLSpec
	WhiteListEnabled map[string]bool
	target           *url.URL
}

// APIDefinitionLoader will load an Api definition from a storage system. It has two methods LoadDefinitionsFromMongo()
// and LoadDefinitions(), each will pull api specifications from different locations.
type APIDefinitionLoader struct {
	dbSession *mgo.Session
}

// Connect connects to the storage engine - can be null
func (a *APIDefinitionLoader) Connect() {
	var err error
	a.dbSession, err = mgo.Dial(config.AnalyticsConfig.MongoURL)
	if err != nil {
		log.Error("Mongo connection failed:")
		log.Panic(err)
	}
}

// MakeSpec will generate a flattened URLSpec from and APIDefinitions' VersionInfo data. paths are
// keyed to the Api version name, which is determined during routing to speed up lookups
func (a *APIDefinitionLoader) MakeSpec(thisAppConfig APIDefinition) APISpec {
	newAppSpec := APISpec{}
	newAppSpec.APIDefinition = thisAppConfig
	newAppSpec.RxPaths = make(map[string][]URLSpec)
	newAppSpec.WhiteListEnabled = make(map[string]bool)
	for _, v := range thisAppConfig.VersionData.Versions {
		pathSpecs, whiteListSpecs := a.getPathSpecs(v)
		newAppSpec.RxPaths[v.Name] = pathSpecs
		newAppSpec.WhiteListEnabled[v.Name] = whiteListSpecs
	}

	return newAppSpec
}

// LoadDefinitionsFromMongo will connect and download ApiDefintions from a Mongo DB instance.
func (a *APIDefinitionLoader) LoadDefinitionsFromMongo() []APISpec {
	var APISpecs = []APISpec{}

	a.Connect()
	apiCollection := a.dbSession.DB("").C("tyk_apis")

	search := bson.M{
		"active": true,
	}

	var APIDefinitions = []APIDefinition{}
	var StringDefs = make([]bson.M, 0)
	mongoErr := apiCollection.Find(search).All(&APIDefinitions)

	if mongoErr != nil {
		log.Error("Could not find any application configs!")
		return APISpecs
	}

	apiCollection.Find(search).All(&StringDefs)

	for i, thisAppConfig := range APIDefinitions {
		thisAppConfig.RawData = StringDefs[i] // Lets keep a copy for plugable modules

		newAppSpec := a.MakeSpec(thisAppConfig)
		APISpecs = append(APISpecs, newAppSpec)
	}
	return APISpecs
}

func (a *APIDefinitionLoader) ParseDefinition(apiDef []byte) (APIDefinition, map[string]interface{}) {
	thisAppConfig := APIDefinition{}
	err := json.Unmarshal(apiDef, &thisAppConfig)
	if err != nil {
		log.Error("Couldn't unmarshal api configuration")
		log.Error(err)
	}

	// Got the structured version - now lets get a raw copy for modules
	thisRawConfig := make(map[string]interface{})
	json.Unmarshal(apiDef, &thisRawConfig)

	return thisAppConfig, thisRawConfig
}

// LoadDefinitions will load APIDefinitions from a directory on the filesystem. Definitions need
// to be the JSON representation of APIDefinition object
func (a *APIDefinitionLoader) LoadDefinitions(dir string) []APISpec {
	var APISpecs = []APISpec{}
	// Grab json files from directory
	files, _ := ioutil.ReadDir(dir)
	for _, f := range files {
		if strings.Contains(f.Name(), ".json") {
			filePath := filepath.Join(dir, f.Name())
			log.Info("Loading API Specification from ", filePath)
			appConfig, err := ioutil.ReadFile(filePath)
			thisAppConfig, thisRawConfig := a.ParseDefinition(appConfig)
			if err != nil {
				log.Error("Couldn't load app configuration file")
				log.Error(err)
			}

			thisAppConfig.RawData = thisRawConfig // Lets keep a copy for plugable modules
			newAppSpec := a.MakeSpec(thisAppConfig)
			APISpecs = append(APISpecs, newAppSpec)

		}
	}

	return APISpecs
}

func (a *APIDefinitionLoader) getPathSpecs(apiVersionDef VersionInfo) ([]URLSpec, bool) {
	ignoredPaths := a.compilePathSpec(apiVersionDef.Paths.Ignored, Ignored)
	blackListPaths := a.compilePathSpec(apiVersionDef.Paths.BlackList, BlackList)
	whiteListPaths := a.compilePathSpec(apiVersionDef.Paths.WhiteList, WhiteList)

	combinedPath := []URLSpec{}
	combinedPath = append(combinedPath, ignoredPaths...)
	combinedPath = append(combinedPath, blackListPaths...)
	combinedPath = append(combinedPath, whiteListPaths...)

	if len(whiteListPaths) > 0 {
		return combinedPath, true
	}

	return combinedPath, false
}

func (a *APIDefinitionLoader) compilePathSpec(paths []string, specType URLStatus) []URLSpec {

	// transform a configuration URL into an array of URLSpecs
	// This way we can iterate the whole array once, on match we break with status
	apiLangIDsRegex, _ := regexp.Compile("{(.*?)}")
	thisURLSpec := []URLSpec{}

	for _, stringSpec := range paths {
		asRegexStr := apiLangIDsRegex.ReplaceAllString(stringSpec, "(.*?)")
		asRegex, _ := regexp.Compile(asRegexStr)

		newSpec := URLSpec{}
		newSpec.Spec = asRegex
		newSpec.Status = specType
		thisURLSpec = append(thisURLSpec, newSpec)
	}

	return thisURLSpec
}

// IsURLAllowedAndIgnored checks if a url is allowed and ignored.
func (a *APISpec) IsURLAllowedAndIgnored(url string, RxPaths []URLSpec, WhiteListStatus bool) (bool, bool) {
	// Check if ignored
	for _, v := range RxPaths {
		match := v.Spec.MatchString(url)
		if match {
			if v.Status == Ignored {
				// Let it pass, and do not check auth
				return true, true
			} else if v.Status == BlackList {
				// Matched  a blacklist URL, disallow access and filter (irrelevant here)
				return false, false
			} else if v.Status == WhiteList {
				// Matched whitelist, allow request but filter
				return true, false
			}

			// Should not occur, something has gone wrong
			log.Error("URL Status was not one of Ignored, Blacklist or WhiteList! Blocking.")
			return false, false

		}
	}

	// Nothing matched - should we still let it through?
	if WhiteListStatus {
		// We have a whitelist, nothing gets through unless specifically defined
		return false, false
	}

	// No whitelist, but also not in any of the other lists, let it through and filter
	return true, false

}

func (a *APISpec) getVersionFromRequest(r *http.Request) string {
	if a.APIDefinition.VersionDefinition.Location == "header" {
		versionHeaderVal := r.Header.Get(a.APIDefinition.VersionDefinition.Key)
		if versionHeaderVal != "" {
			return versionHeaderVal
		}

		return ""

	} else if a.APIDefinition.VersionDefinition.Location == "url-param" {
		fromParam := r.FormValue(a.APIDefinition.VersionDefinition.Key)
		if fromParam != "" {
			return fromParam
		}

		return ""

	} else {
		return ""
	}

	return ""
}

// IsThisAPIVersionExpired checks if an API version (during a proxied request) is expired
func (a *APISpec) IsThisAPIVersionExpired(versionDef VersionInfo) bool {
	// Never expires
	if versionDef.Expires == "-1" {
		return false
	}

	if versionDef.Expires == "" {
		return false
	}

	// otherwise - calculate the time
	t, err := time.Parse("2006-01-02 15:04", versionDef.Expires)
	if err != nil {
		log.Error("Could not parse expiry date for API, dissallow")
		log.Error(err)
		return true
	}

	remaining := time.Since(t)
	if remaining < 0 {
		// It's in the future, keep going
		return false
	}

	// It's in the past, expire
	return true

}

// IsRequestValid will check if an incoming request has valid version data and return a RequestStatus that
// describes the status of the request
func (a *APISpec) IsRequestValid(r *http.Request) (bool, RequestStatus) {
	versionMetaData, versionPaths, whiteListStatus, stat := a.GetVersionData(r)

	// Screwed up version info - fail and pass through
	if stat != StatusOk {
		return false, stat
	}

	// Is the API version expired?
	if a.IsThisAPIVersionExpired(versionMetaData) == true {
		// Expired - fail
		return false, VersionExpired
	}

	// not expired, let's check path info
	allowURL, ignore := a.IsURLAllowedAndIgnored(r.URL.Path, versionPaths, whiteListStatus)
	if !allowURL {
		return false, EndPointNotAllowed
	}

	if ignore {
		return true, StatusOkAndIgnore
	}

	return true, StatusOk

}

// GetVersionData attempts to extract the version data from a request, depending on where it is stored in the
// request (currently only "header" is supported)
func (a *APISpec) GetVersionData(r *http.Request) (VersionInfo, []URLSpec, bool, RequestStatus) {
	var thisVersion = VersionInfo{}
	var versionKey string
	var versionRxPaths = []URLSpec{}
	var versionWLStatus bool

	// Are we versioned?
	if a.APIDefinition.VersionData.NotVersioned {
		// Get the first one in the list
		for k, v := range a.APIDefinition.VersionData.Versions {
			versionKey = k
			thisVersion = v
			break
		}
	} else {
		// Extract Version Info
		versionKey = a.getVersionFromRequest(r)
		if versionKey == "" {
			return thisVersion, versionRxPaths, versionWLStatus, VersionNotFound
		}
	}

	// Load Version Data - General
	var ok bool
	thisVersion, ok = a.APIDefinition.VersionData.Versions[versionKey]
	if !ok {
		return thisVersion, versionRxPaths, versionWLStatus, VersionDoesNotExist
	}

	// Load path data and whitelist data for version
	RxPaths, rxOk := a.RxPaths[versionKey]
	WhiteListStatus, wlOk := a.WhiteListEnabled[versionKey]

	if !rxOk {
		log.Error("no RX Paths found for version")
		log.Error(versionKey)
		return thisVersion, versionRxPaths, versionWLStatus, VersionDoesNotExist
	}

	if !wlOk {
		log.Error("No whitelist data found")
		return thisVersion, versionRxPaths, versionWLStatus, VersionWhiteListStatusNotFound
	}

	versionRxPaths = RxPaths
	versionWLStatus = WhiteListStatus

	return thisVersion, versionRxPaths, versionWLStatus, StatusOk

}
<|endoftext|>