<commit_before>package nobots

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"regexp"

	"github.com/mholt/caddy"
	"github.com/mholt/caddy/caddyhttp/httpserver"
)

// botUA config representation
type botUA struct {
	uas  []string
	bomb string
	re   []*regexp.Regexp
}

// BotUA plugin struct
type BotUA struct {
	Next httpserver.Handler
	UA   *botUA
}

func init() {
	caddy.RegisterPlugin("nobots", caddy.Plugin{
		ServerType: "http",
		Action:     setup,
	})
}

// setup callback for parsing the config
func setup(c *caddy.Controller) error {
	ua, err := parseUA(c)
	if err != nil {
		return err
	}

	// Verfies whether bomb exist
	if _, err := os.Stat(ua.bomb); os.IsNotExist(err) {
		return fmt.Errorf("Bomb %s not found.", ua.bomb)
	}

	// Setup de middleware
	cfg := httpserver.GetConfig(c)
	mid := func(next httpserver.Handler) httpserver.Handler {
		return BotUA{Next: next, UA: ua}
	}
	cfg.AddMiddleware(mid)

	return nil
}

// parseUA propper config parser that generates a botUA object
func parseUA(c *caddy.Controller) (*botUA, error) {
	var ua botUA
	for c.Next() {
		if !c.NextArg() {
			return nil, c.ArgErr()
		}
		ua.bomb = c.Val()
		for c.NextBlock() {
			switch c.Val() {
			case "regexp":
				if !c.NextArg() {
					return nil, c.ArgErr()
				}
				ua.re = append(ua.re, regexp.MustCompile(c.Val()))
			default:
				ua.uas = append(ua.uas, c.Val())
			}
		}
	}
	return &ua, nil
}

func (b BotUA) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	// Get request UA
	rua := r.UserAgent()

	// Check if the UA is a evil one
	if b.IsEvil(rua) {
		serveBomb(w, r, b.UA.bomb)
	}

	// Nothing happens carry on with next stuff
	return b.Next.ServeHTTP(w, r)
}

// IsEvil check the remote UA against evil UAs
func (b BotUA) IsEvil(rua string) bool {
	// In case there are regexp
	if len(b.UA.re) > 0 {
		for _, re := range b.UA.re {
			if re.MatchString(rua) {
				return true
			}
		}
	}
	// In case there are strings
	if len(b.UA.uas) > 0 {
		for _, ua := range b.UA.uas {
			if ua == rua {
				return true
			}
		}
	}
	// UA is not evil
	return false
}

// serveBomb provides the bomb to front-end
func serveBomb(w http.ResponseWriter, r *http.Request, bomb string) (int, error) {
	file, err := ioutil.ReadFile(bomb)
	if err != nil {
		fmt.Println(err)
		return http.StatusNotFound, nil
	}

	w.Header().Set("Content-Type", "text/html; charset=UTF-8")
	w.Header().Set("Content-Encoding", "gzip")
	w.Header().Set("Content-Length", fmt.Sprintf("%d", len(file)))
	w.Write(file)
	return 200, nil
}
<commit_msg>Removed println<commit_after>package nobots

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"regexp"

	"github.com/mholt/caddy"
	"github.com/mholt/caddy/caddyhttp/httpserver"
)

// botUA config representation
type botUA struct {
	uas  []string
	bomb string
	re   []*regexp.Regexp
}

// BotUA plugin struct
type BotUA struct {
	Next httpserver.Handler
	UA   *botUA
}

func init() {
	caddy.RegisterPlugin("nobots", caddy.Plugin{
		ServerType: "http",
		Action:     setup,
	})
}

// setup callback for parsing the config
func setup(c *caddy.Controller) error {
	ua, err := parseUA(c)
	if err != nil {
		return err
	}

	// Verfies whether bomb exist
	if _, err := os.Stat(ua.bomb); os.IsNotExist(err) {
		return fmt.Errorf("Bomb %s not found.", ua.bomb)
	}

	// Setup de middleware
	cfg := httpserver.GetConfig(c)
	mid := func(next httpserver.Handler) httpserver.Handler {
		return BotUA{Next: next, UA: ua}
	}
	cfg.AddMiddleware(mid)

	return nil
}

// parseUA propper config parser that generates a botUA object
func parseUA(c *caddy.Controller) (*botUA, error) {
	var ua botUA
	for c.Next() {
		if !c.NextArg() {
			return nil, c.ArgErr()
		}
		ua.bomb = c.Val()
		for c.NextBlock() {
			switch c.Val() {
			case "regexp":
				if !c.NextArg() {
					return nil, c.ArgErr()
				}
				ua.re = append(ua.re, regexp.MustCompile(c.Val()))
			default:
				ua.uas = append(ua.uas, c.Val())
			}
		}
	}
	return &ua, nil
}

func (b BotUA) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	// Get request UA
	rua := r.UserAgent()

	// Check if the UA is a evil one
	if b.IsEvil(rua) {
		serveBomb(w, r, b.UA.bomb)
	}

	// Nothing happens carry on with next stuff
	return b.Next.ServeHTTP(w, r)
}

// IsEvil check the remote UA against evil UAs
func (b BotUA) IsEvil(rua string) bool {
	// In case there are regexp
	if len(b.UA.re) > 0 {
		for _, re := range b.UA.re {
			if re.MatchString(rua) {
				return true
			}
		}
	}
	// In case there are strings
	if len(b.UA.uas) > 0 {
		for _, ua := range b.UA.uas {
			if ua == rua {
				return true
			}
		}
	}
	// UA is not evil
	return false
}

// serveBomb provides the bomb to front-end
func serveBomb(w http.ResponseWriter, r *http.Request, bomb string) (int, error) {
	file, err := ioutil.ReadFile(bomb)
	if err != nil {
		return http.StatusNotFound, nil
	}

	w.Header().Set("Content-Type", "text/html; charset=UTF-8")
	w.Header().Set("Content-Encoding", "gzip")
	w.Header().Set("Content-Length", fmt.Sprintf("%d", len(file)))
	w.Write(file)
	return 200, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors.

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

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

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

package autoscaling

import (
	"time"

	"k8s.io/kubernetes/test/e2e/framework"
	e2eautoscaling "k8s.io/kubernetes/test/e2e/framework/autoscaling"

	"github.com/onsi/ginkgo"
)

var _ = SIGDescribe("[Feature:HPA] [Serial] [Slow] Horizontal pod autoscaling (non-default behavior)", func() {
	f := framework.NewDefaultFramework("horizontal-pod-autoscaling")

	ginkgo.Describe("with low downscale stabilization window", func() {
		ginkgo.It("should scale down soon after the stabilization period", func() {
			ginkgo.By("setting up resource consumer and HPA")
			podCPURequest := 500
			targetCPUUtilizationPercent := 25
			usageForSingleReplica := 110
			initPods := 1
			initCPUUsageTotal := initPods * usageForSingleReplica
			downScaleStabilization := 1 * time.Minute

			rc := e2eautoscaling.NewDynamicResourceConsumer(
				"consumer", f.Namespace.Name, e2eautoscaling.KindDeployment, initPods,
				initCPUUsageTotal, 0, 0, int64(podCPURequest), 200,
				f.ClientSet, f.ScalesGetter, e2eautoscaling.Disable, e2eautoscaling.Idle,
			)
			defer rc.CleanUp()

			hpa := e2eautoscaling.CreateCPUHorizontalPodAutoscalerWithBehavior(
				rc, int32(targetCPUUtilizationPercent), 1, 5, int32(downScaleStabilization.Seconds()),
			)
			defer e2eautoscaling.DeleteHPAWithBehavior(rc, hpa.Name)

			// 30s of metrics window and
			// up to 30s until the old metrics window finishes
			// and additional 15s until metrics available on a new pod
			metricsAvailableDelay := 75 * time.Second
			hpaReconciliationInterval := 15 * time.Second
			actuationDelay := 10 * time.Second
			maxHPAReactionTime := metricsAvailableDelay + hpaReconciliationInterval + actuationDelay

			maxConsumeCPUDelay := 30 * time.Second
			waitForReplicasPollInterval := 20 * time.Second
			maxResourceConsumerDelay := maxConsumeCPUDelay + waitForReplicasPollInterval

			waitBuffer := 1 * time.Minute

			ginkgo.By("triggering scale up to record a recommendation")
			rc.ConsumeCPU(3 * usageForSingleReplica)
			rc.WaitForReplicas(3, maxHPAReactionTime+maxResourceConsumerDelay+waitBuffer)

			ginkgo.By("triggering scale down by lowering consumption")
			rc.ConsumeCPU(2 * usageForSingleReplica)
			waitStart := time.Now()
			rc.WaitForReplicas(2, downScaleStabilization+maxHPAReactionTime+maxResourceConsumerDelay+waitBuffer)
			timeWaited := time.Now().Sub(waitStart)

			ginkgo.By("verifying time waited for a scale down")
			framework.Logf("time waited for scale down: %s", timeWaited)
			framework.ExpectEqual(timeWaited > downScaleStabilization, true, "waited %s, wanted more than %s", timeWaited, downScaleStabilization)
			deadline := downScaleStabilization + maxHPAReactionTime + maxResourceConsumerDelay
			framework.ExpectEqual(timeWaited < deadline, true, "waited %s, wanted less than %s", timeWaited, deadline)
		})
	})
})
<commit_msg>Small review fixes<commit_after>/*
Copyright 2022 The Kubernetes Authors.

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

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

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

package autoscaling

import (
	"time"

	"k8s.io/kubernetes/test/e2e/framework"
	e2eautoscaling "k8s.io/kubernetes/test/e2e/framework/autoscaling"

	"github.com/onsi/ginkgo"
)

var _ = SIGDescribe("[Feature:HPA] [Serial] [Slow] Horizontal pod autoscaling (non-default behavior)", func() {
	f := framework.NewDefaultFramework("horizontal-pod-autoscaling")

	ginkgo.Describe("with short downscale stabilization window", func() {
		ginkgo.It("should scale down soon after the stabilization period", func() {
			ginkgo.By("setting up resource consumer and HPA")
			podCPURequest := 500
			targetCPUUtilizationPercent := 25
			usageForSingleReplica := 110
			initPods := 1
			initCPUUsageTotal := initPods * usageForSingleReplica
			downScaleStabilization := 1 * time.Minute

			rc := e2eautoscaling.NewDynamicResourceConsumer(
				"consumer", f.Namespace.Name, e2eautoscaling.KindDeployment, initPods,
				initCPUUsageTotal, 0, 0, int64(podCPURequest), 200,
				f.ClientSet, f.ScalesGetter, e2eautoscaling.Disable, e2eautoscaling.Idle,
			)
			defer rc.CleanUp()

			hpa := e2eautoscaling.CreateCPUHorizontalPodAutoscalerWithBehavior(
				rc, int32(targetCPUUtilizationPercent), 1, 5, int32(downScaleStabilization.Seconds()),
			)
			defer e2eautoscaling.DeleteHPAWithBehavior(rc, hpa.Name)

			fullWindowOfNewUsage := 30 * time.Second
			windowWithOldUsagePasses := 30 * time.Second
			newPodMetricsDelay := 15 * time.Second
			metricsAvailableDelay := fullWindowOfNewUsage + windowWithOldUsagePasses + newPodMetricsDelay

			hpaReconciliationInterval := 15 * time.Second
			actuationDelay := 10 * time.Second
			maxHPAReactionTime := metricsAvailableDelay + hpaReconciliationInterval + actuationDelay

			maxConsumeCPUDelay := 30 * time.Second
			waitForReplicasPollInterval := 20 * time.Second
			maxResourceConsumerDelay := maxConsumeCPUDelay + waitForReplicasPollInterval

			waitBuffer := 1 * time.Minute

			// making sure HPA is ready, doing its job and already has a recommendation recorded
			// for stabilization logic before lowering the consumption
			ginkgo.By("triggering scale up to record a recommendation")
			rc.ConsumeCPU(3 * usageForSingleReplica)
			rc.WaitForReplicas(3, maxHPAReactionTime+maxResourceConsumerDelay+waitBuffer)

			ginkgo.By("triggering scale down by lowering consumption")
			rc.ConsumeCPU(2 * usageForSingleReplica)
			waitStart := time.Now()
			rc.WaitForReplicas(2, downScaleStabilization+maxHPAReactionTime+maxResourceConsumerDelay+waitBuffer)
			timeWaited := time.Now().Sub(waitStart)

			ginkgo.By("verifying time waited for a scale down")
			framework.Logf("time waited for scale down: %s", timeWaited)
			framework.ExpectEqual(timeWaited > downScaleStabilization, true, "waited %s, wanted more than %s", timeWaited, downScaleStabilization)
			deadline := downScaleStabilization + maxHPAReactionTime + maxResourceConsumerDelay
			framework.ExpectEqual(timeWaited < deadline, true, "waited %s, wanted less than %s", timeWaited, deadline)
		})
	})
})
<|endoftext|>


<commit_before>package main

/*
* Link Shortener, with a Redis backend.
*
* Released under and MIT License, please see the LICENSE.md file.
*
* John Nye
*
 */
import (
	"./utils"
	"encoding/json"
	"flag"
	"fmt"
	"github.com/garyburd/redigo/redis"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
)

var host = flag.String("h", "localhost", "Bind address to listen on")
var base = flag.String("b", "http://localhost/", "Base URL for the shortener")
var port = flag.String("p", "8080", "Port you want to listen on, defaults to 8080")
var maxConnections = flag.Int("c", 512, "The maximum number of active connections") //Currently Not Used
var redisConn = flag.String("r", "localhost:6379", "Redis Address, defaults to localhost:6379")

type Data struct {
	Original  string
	Short     string
	FullShort string
	HitCount  int
}

var redisPool = &redis.Pool{
	MaxIdle:   3,
	MaxActive: 50, // max number of connections
	Dial: func() (redis.Conn, error) {
		c, err := redis.Dial("tcp", *redisConn)
		if err != nil {
			panic(err.Error())
		}
		return c, err
	},
}

func handler(w http.ResponseWriter, r *http.Request) {

	log.Println(r.UserAgent())

	type NewURL struct {
		URL string
	}
	var url NewURL
	var domain Data

	conn := redisPool.Get()

	if r.Method == "GET" {
		domain = getLongURL(r.URL.Path[1:], conn)
		if len(domain.Original) > 0 {
			http.Redirect(w, r, domain.Original, http.StatusFound)
			return
		}
		http.ServeFile(w, r, "./index.html")
		log.Println("Served Homepage")
		return
	}

	create, err := ioutil.ReadAll(r.Body)

	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	err = json.Unmarshal(create, &url)
	if err != nil {
		log.Print(err)
		http.Error(w, err.Error(), 500)
		return
	}

	search := strings.Join([]string{"*||", url.URL}, "")

	keys, err := redis.Strings(conn.Do("KEYS", search))

	if len(keys) < 1 {
		domain = createShortURL(url.URL, conn)
	} else {
		domain = getInfoForKey(keys[0], conn)
	}

	if err != nil {
		log.Print(err)
		http.Error(w, err.Error(), 500)
		return
	}

	output, err := json.Marshal(domain)

	if err != nil {
		log.Print(err)
		http.Error(w, err.Error(), 500)
		return
	}
	fmt.Fprintf(w, "%s", output)
	conn.Close()
}

func getInfoForKey(key string, conn redis.Conn) Data {
	var d Data
	parts := strings.Split(key, "||")
	d.Short = parts[0]
	d.Original = parts[1]
	d.FullShort = strings.Join([]string{*base, parts[0]}, "")
	newCount, err := redis.Int(conn.Do("HGET", key, "count"))
	if err != nil {
		log.Print(err)
	}
	d.HitCount = newCount
	return d
}

func createShortURL(url string, conn redis.Conn) Data {
	var d Data
	count, err := redis.Int(conn.Do("INCR", "global:size"))
	if err != nil {
		log.Print(err)
		return d
	}
	log.Print("Total: ",count)
	encodedVar := base62.EncodeInt(int64(count))
	key := strings.Join([]string{encodedVar, url}, "||")
	conn.Send("MULTI")
	conn.Send("HSET", key, "count", 0)
	_, err2 := conn.Do("EXEC")

	if err2 != nil {
		log.Print(err)
		return d
	}

	d.Original = url
	d.HitCount = 0
	d.Short = encodedVar
	d.FullShort = strings.Join([]string{*base, encodedVar}, "")

	return d
}

func getLongURL(short string, conn redis.Conn) Data {
	var d Data

	search := strings.Join([]string{short, "||*"}, "")
	fmt.Println(search)
	n, err := redis.Strings(conn.Do("KEYS", search))

	if err != nil {
		log.Print(err)
		return d
	}

	if len(n) < 1 {
		log.Print("Nothing Found")
	} else {
		parts := strings.Split(n[0], "||")

		d.Short = parts[0]
		d.Original = parts[1]
		d.FullShort = strings.Join([]string{*base, parts[0]}, "")
		newCount, err := redis.Int(conn.Do("HINCRBY", n[0], "count", 1))
		if err != nil {
			log.Println(err)
		}
		d.HitCount = newCount
	}
	log.Println("Served: ",d.Original)
	return d
}

func main() {
	flag.Parse()

	http.HandleFunc("/", handler)
	err := http.ListenAndServe(*host+":"+*port, nil)
	if err != nil {
		fmt.Println(err)
	}
}
<commit_msg>closing the connection pool correctly.<commit_after>package main

/*
* Link Shortener, with a Redis backend.
*
* Released under and MIT License, please see the LICENSE.md file.
*
* John Nye
*
 */
import (
	"./utils"
	"encoding/json"
	"flag"
	"fmt"
	"github.com/garyburd/redigo/redis"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
)

var host = flag.String("h", "localhost", "Bind address to listen on")
var base = flag.String("b", "http://localhost/", "Base URL for the shortener")
var port = flag.String("p", "8080", "Port you want to listen on, defaults to 8080")
var maxConnections = flag.Int("c", 512, "The maximum number of active connections") //Currently Not Used
var redisConn = flag.String("r", "localhost:6379", "Redis Address, defaults to localhost:6379")

type Data struct {
	Original  string
	Short     string
	FullShort string
	HitCount  int
}

var redisPool = &redis.Pool{
	MaxIdle:   3,
	MaxActive: 50, // max number of connections
	Dial: func() (redis.Conn, error) {
		c, err := redis.Dial("tcp", *redisConn)
		if err != nil {
			panic(err.Error())
		}
		return c, err
	},
}

func handler(w http.ResponseWriter, r *http.Request) {

	log.Println(r.UserAgent())

	type NewURL struct {
		URL string
	}
	var url NewURL
	var domain Data

	conn := redisPool.Get()

	if r.Method == "GET" {
		domain = getLongURL(r.URL.Path[1:], conn)
		if len(domain.Original) > 0 {
			http.Redirect(w, r, domain.Original, http.StatusFound)
			return
		}
		http.ServeFile(w, r, "./index.html")
		log.Println("Served Homepage")
		conn.Close()
		return
	}

	create, err := ioutil.ReadAll(r.Body)

	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	err = json.Unmarshal(create, &url)
	if err != nil {
		log.Print(err)
		http.Error(w, err.Error(), 500)
		return
	}

	search := strings.Join([]string{"*||", url.URL}, "")

	keys, err := redis.Strings(conn.Do("KEYS", search))

	if len(keys) < 1 {
		domain = createShortURL(url.URL, conn)
	} else {
		domain = getInfoForKey(keys[0], conn)
	}

	if err != nil {
		log.Print(err)
		http.Error(w, err.Error(), 500)
		return
	}

	output, err := json.Marshal(domain)

	if err != nil {
		log.Print(err)
		http.Error(w, err.Error(), 500)
		return
	}
	fmt.Fprintf(w, "%s", output)
	conn.Close()
}

func getInfoForKey(key string, conn redis.Conn) Data {
	var d Data
	parts := strings.Split(key, "||")
	d.Short = parts[0]
	d.Original = parts[1]
	d.FullShort = strings.Join([]string{*base, parts[0]}, "")
	newCount, err := redis.Int(conn.Do("HGET", key, "count"))
	if err != nil {
		log.Print(err)
	}
	d.HitCount = newCount
	return d
}

func createShortURL(url string, conn redis.Conn) Data {
	var d Data
	count, err := redis.Int(conn.Do("INCR", "global:size"))
	if err != nil {
		log.Print(err)
		return d
	}
	log.Print("Total: ",count)
	encodedVar := base62.EncodeInt(int64(count))
	key := strings.Join([]string{encodedVar, url}, "||")
	conn.Send("MULTI")
	conn.Send("HSET", key, "count", 0)
	_, err2 := conn.Do("EXEC")

	if err2 != nil {
		log.Print(err)
		return d
	}

	d.Original = url
	d.HitCount = 0
	d.Short = encodedVar
	d.FullShort = strings.Join([]string{*base, encodedVar}, "")

	return d
}

func getLongURL(short string, conn redis.Conn) Data {
	var d Data

	search := strings.Join([]string{short, "||*"}, "")
	fmt.Println(search)
	n, err := redis.Strings(conn.Do("KEYS", search))

	if err != nil {
		log.Print(err)
		return d
	}

	if len(n) < 1 {
		log.Print("Nothing Found")
	} else {
		parts := strings.Split(n[0], "||")

		d.Short = parts[0]
		d.Original = parts[1]
		d.FullShort = strings.Join([]string{*base, parts[0]}, "")
		newCount, err := redis.Int(conn.Do("HINCRBY", n[0], "count", 1))
		if err != nil {
			log.Println(err)
		}
		d.HitCount = newCount
	}
	log.Println("Served: ",d.Original)
	return d
}

func main() {
	flag.Parse()

	http.HandleFunc("/", handler)
	err := http.ListenAndServe(*host+":"+*port, nil)
	if err != nil {
		fmt.Println(err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"

	"errors"

	"./db"
	"./rollbar"
)

const (
	slackUnfurlURL      = "https://slack.com/api/chat.unfurl"
	slackOauthAccessURL = "https://slack.com/api/oauth.access"
)

type slackOauthAccessResponse struct {
	OK          bool
	Error       string
	AccessToken string `json:"access_token"`
	Scope       string
	UserID      string `json:"user_id"`
	TeamName    string `json:"team_name"`
	TeamID      string `json:"team_id"`
}

type slackUnfurlPayload struct {
	Token   string
	Channel string
	TS      string
	Unfurls string
}

type slackAttachment struct {
	//Text string
	Fallback string                 `json:"fallback"`
	Title    string                 `json:"title"`
	TS       int64                  `json:"ts"`
	Fields   []slackAttachmentField `json:"fields"`
}

type slackAttachmentField struct {
	Title string `json:"title"`
	Value string `json:"value"`
	Short bool   `json:"short"`
}

type slackOuterEvent struct {
	Token     string
	TeamID    string `json:"team_id"`
	APIAppID  string `json:"api_app_id"`
	Event     slackEvent
	Challenge string
	Type      string
	EventID   string `json:"event_id"`
}

type slackEvent struct {
	Type string
	//link_shared-specific fields
	Channel   string
	User      string
	MessageTS string `json:"message_ts"`
	Links     []struct {
		Domain string
		URL    string
	}
	//tokens_revoked-specific fields
	Tokens struct {
		OAuth []string
	}
}

func oauthCallbackHandler(w http.ResponseWriter, r *http.Request) {
	code := r.FormValue("code")
	if code != "" {
		err := exchangeOauthCodeForToken(code)
		if err != nil {
			// serve error page?
		}
	}
	serveFile(w, "static/thanks.html")
}

func slashCommandHandler(w http.ResponseWriter, r *http.Request) {
	token := r.FormValue("token")
	if token != config.SlackVerificationToken {
		log.Printf("Token %s did not match the configured one at /slash", token)
		http.Error(w, "Token mismatch", 403)
		return
	}
	team := r.FormValue("team_id")
	user := r.FormValue("user_id")
	command := r.FormValue("command")
	text := r.FormValue("text")
	log.Printf("Received slash command (team %s, user %s): %s %s", team, user, command, text)
	switch command {
	case "/rollbar":
		processRollbarSlashCommand(w, text, team)
	default:
		log.Printf("Unsupported slack command %s", command)
	}
}

type slackSlashCommandResponse struct {
	ResponseType string `json:"response_type"`
	Text         string `json:"text"`
}

const (
	rollbarCmdUsage = "Usage:\n" +
		"`/rollbar set <project url> <project token>` - set read access token for project\n" +
		"`/rollbar clear <project url>` - clear access token for project\n" +
		"`/rollbar list` - list all projects that I will unfurl\n\n" +
		"For example: `/rollbar set https://rollbar.com/MyOrganization/MyProject/` abcdef12345"
	rollbarInvalidProjectURL = "Sorry, %s doesn't look like a Rollbar project URL. It should look like this: " +
		"https://rollbar.com/MyOrganization/MyProject/"
	rollbarInvalidToken = "Sorry, Rollbar reports %s is not a valid access token. Please copy the _read_ token from " +
		"https://rollbar.com/%s/settings/access_tokens/"
	rollbarTokenAdded           = "Thanks! I will now unfurl links from https://rollbar.com/%s/items/ for you."
	rollbarTokenRemoved         = "Done! I will no longer unfurl links from https://rollbar.com/%s/items/."
	rollbarGeneralError         = "An error occurred while executing the command. Please try again!"
	rollbarNoProjectsConfigured = "No  Rollbar projects have been configured for your team.\n" +
		"Use `/rollbar set` to add one."
	rollbarProjectList = "I will unfurl links from the following projects:\n%s"
)

func processRollbarSlashCommand(w http.ResponseWriter, commandText, team string) {
	resp := slackSlashCommandResponse{
		ResponseType: "ephemeral",
	}
	parts := strings.Split(commandText, " ")
	switch parts[0] {
	case "list":
		projects := db.GetProjects(team)
		for k, p := range projects {
			projects[k] = fmt.Sprintf("https://rollbar.com/%s/", p)
		}
		if len(projects) == 0 {
			resp.Text = rollbarNoProjectsConfigured
			break
		}
		resp.Text = fmt.Sprintf(rollbarProjectList, strings.Join(projects, "\n"))
	case "set":
		if len(parts) != 3 {
			resp.Text = rollbarCmdUsage
			break
		}
		projectURL := parts[1]
		matches := rollbarProjectRegex.FindStringSubmatch(projectURL)
		if len(matches) != 3 {
			resp.Text = fmt.Sprintf(rollbarInvalidProjectURL, projectURL)
			break
		}
		project := strings.ToLower(matches[1])
		token := parts[2]
		if !rollbar.IsValidToken(token) {
			resp.Text = fmt.Sprintf(rollbarInvalidToken, token, project)
			break
		}
		//finally, all is well
		err := db.SaveProjectToken(team, project, token)
		if err != nil {
			resp.Text = rollbarGeneralError
			break
		}
		resp.Text = fmt.Sprintf(rollbarTokenAdded, project)
	case "clear":
		if len(parts) != 2 {
			resp.Text = rollbarCmdUsage
			break
		}
		projectURL := parts[1]
		matches := rollbarProjectRegex.FindStringSubmatch(projectURL)
		if len(matches) != 3 {
			resp.Text = fmt.Sprintf(rollbarInvalidProjectURL, projectURL)
			break
		}
		project := strings.ToLower(matches[1])
		db.DeleteProjectToken(team, project)
		resp.Text = fmt.Sprintf(rollbarTokenRemoved, project)
	default:
		resp.Text = rollbarCmdUsage
	}

	b, _ := json.Marshal(resp)
	w.Header().Set("Content-Type", "application/json")
	w.Write(b)
}

func exchangeOauthCodeForToken(code string) error {
	form := url.Values{}
	form.Add("client_id", config.ClientID)
	form.Add("client_secret", config.ClientSecret)
	form.Add("code", code)

	log.Print("Posting oauth.access")

	resp, err := http.PostForm(slackOauthAccessURL, form)
	if err != nil {
		log.Printf("error when posting oauth.access: %s", err.Error())
		return err
	}
	defer resp.Body.Close()
	var oauthResponse slackOauthAccessResponse
	err = json.NewDecoder(resp.Body).Decode(&oauthResponse)
	if err != nil {
		log.Printf("error when deserializing oauth.access response: %s", err.Error())
		return err
	}
	if !oauthResponse.OK {
		log.Printf("oauth.access reported an error: %s", oauthResponse.Error)
		return errors.New(oauthResponse.Error)
	}
	err = db.SaveAuthToken(oauthResponse.TeamID, oauthResponse.UserID, oauthResponse.AccessToken)
	if err != nil {
		log.Printf("Could not save auth token: %s", err.Error())
		return err
	}
	log.Printf("Saved auth token for user %s/team %s (%s)", oauthResponse.UserID, oauthResponse.TeamID, oauthResponse.TeamName)
	return nil
}

func slackEventHandler(w http.ResponseWriter, r *http.Request) {
	event := new(slackOuterEvent)

	err := json.NewDecoder(r.Body).Decode(&event)
	if err != nil {
		log.Print("Invalid JSON received: " + err.Error())
		http.Error(w, err.Error(), 400)
		return
	}

	if event.Token != config.SlackVerificationToken {
		log.Printf("Request verification token %s did not match the one configured!", event.Token)
		http.Error(w, "Token mismatch", 403)
		return
	}

	team := event.TeamID
	log.Printf("Received event of type %s/%s from team %s", event.Type, event.Event.Type, team)

	switch event.Type {
	case "url_verification":
		processURLVerification(w, event)
	case "event_callback":
		innerEventType := event.Event.Type
		switch innerEventType {
		case "link_shared":
			processLinkSharedEvent(&event.Event, team)
		case "tokens_revoked":
			processTokensRevokedEvent(&event.Event, team)
		case "app_uninstalled":
			processAppUninstalledEvent(team)
		default:
			log.Printf("Unsupported event subtype %s", innerEventType)
		}
	default:
		log.Printf("Unknown event type %s", event.Type)
		//ignore the event and return the default 200 OK / empty response
	}

}

func processLinkSharedEvent(e *slackEvent, team string) {
	logLine := fmt.Sprintf("link shared event (channel=%s,ts=%s), links:\n", e.Channel, e.MessageTS)
	for _, v := range e.Links {
		logLine += fmt.Sprintf("-- %s\n", v.URL)
	}
	log.Print(logLine)
	go addLinkPreviews(e, team)
}

func processTokensRevokedEvent(e *slackEvent, team string) {
	for _, v := range e.Tokens.OAuth {
		log.Printf("Deleting oAuth token for user %s (team %s) ", v, team)
		db.DeleteUserToken(team, v)
	}
}

func processAppUninstalledEvent(team string) {
	log.Printf("Deleting team %s's data", team)
	db.DeleteTeam(team)
}

func processURLVerification(w http.ResponseWriter, e *slackOuterEvent) {
	// just respond by printing the challenge back
	fmt.Fprintf(w, "%s", e.Challenge)
}

var rollbarItemRegex = regexp.MustCompile(`(\w+\/\w+)\/items\/(\d+)/?`)
var rollbarProjectRegex = regexp.MustCompile(`https?:\/\/rollbar.com\/(\w+\/\w+)($|\/?.*)`)

func addLinkPreviews(event *slackEvent, team string) {
	linkData := make(map[string]slackAttachment)
	for _, link := range event.Links {
		url := link.URL
		matches := rollbarItemRegex.FindStringSubmatch(url)
		if len(matches) < 3 {
			log.Printf("%s is not a Rollbar item link", url)
			continue
		}
		project := strings.ToLower(matches[1])
		counter := matches[2]
		token := db.GetProjectToken(team, project)
		if token == "" {
			log.Printf("Project %s isn't configured for team %s", project, team)
			continue
		}
		item, err := rollbar.GetItemData(counter, token)
		if err != nil {
			log.Printf("error getting data for %s: %s", link.URL, err.Error())
		} else {
			linkData[link.URL] = getUnfurlData(item)
		}
	}

	if len(linkData) == 0 {
		//none of the links posted were able to be processed
		log.Printf("No links processed (channel=%s,ts=%s)", event.Channel, event.MessageTS)
		return
	}

	unfurls, err := json.Marshal(linkData)
	if err != nil {
		log.Printf("Unfurls serialization failed (channel=%s,ts=%s): %s", event.Channel, event.MessageTS, err.Error())
		return
	}

	apiToken := db.GetAuthToken(team)
	if apiToken == "" {
		log.Printf("Couldn't retrieve oAuth token for team %s", team)
		return
	}

	payload := slackUnfurlPayload{
		Token:   apiToken,
		Channel: event.Channel,
		TS:      event.MessageTS,
		Unfurls: string(unfurls),
	}

	postToSlack(payload)
}

func getTimeAgoString(d time.Duration) string {
	t := d.Seconds()
	if t < 1 {
		return "just now"
	}
	if t < 60 {
		return fmt.Sprintf("%.fs ago", t)
	}
	t /= 60 // t is now minutes
	if t < 60 {
		return fmt.Sprintf("%.fm ago", t)
	}
	t /= 60 // t is now hours
	if t < 24 {
		return fmt.Sprintf("%.fh ago", t)
	}
	t /= 24 // t is now days
	return fmt.Sprintf("%.fd ago", t)
}

func getUnfurlData(item *rollbar.Item) slackAttachment {
	now := time.Now()
	attachment := slackAttachment{
		Title:    item.Title,
		Fallback: item.Title,
		TS:       now.Unix(),
		Fields:   make([]slackAttachmentField, 4),
	}

	attachment.Fields[0] = slackAttachmentField{
		Title: "Status",
		Value: item.Status,
		Short: true,
	}
	attachment.Fields[1] = slackAttachmentField{
		Title: "Occurrences",
		Value: strconv.Itoa(item.TotalOccurrences),
		Short: true,
	}
	attachment.Fields[2] = slackAttachmentField{
		Title: "First seen",
		Value: time.Unix(int64(item.FirstOccurrenceTimestamp), 0).Format("Jan 2 15:04:05"),
		Short: true,
	}
	lastSeenAgo := now.Sub(time.Unix(int64(item.LastOccurrenceTimestamp), 0))
	attachment.Fields[3] = slackAttachmentField{
		Title: "Last seen",
		Value: getTimeAgoString(lastSeenAgo),
		Short: true,
	}

	return attachment
}

func postToSlack(message slackUnfurlPayload) {
	form := url.Values{}
	form.Add("token", message.Token)
	form.Add("channel", message.Channel)
	form.Add("ts", message.TS)
	form.Add("unfurls", message.Unfurls)

	log.Printf("Posting chat.unfurl (channel=%s,ts=%s)", message.Channel, message.TS)

	resp, err := http.PostForm(slackUnfurlURL, form)
	if err != nil {
		log.Printf("error when posting chat.unfurl: %s", err.Error())
	}
	defer resp.Body.Close()
	b, _ := ioutil.ReadAll(resp.Body)
	log.Printf("chat.unfurl resp: %s", string(b))
}
<commit_msg>allow dashes & numbers in project names<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"

	"errors"

	"./db"
	"./rollbar"
)

const (
	slackUnfurlURL      = "https://slack.com/api/chat.unfurl"
	slackOauthAccessURL = "https://slack.com/api/oauth.access"
)

type slackOauthAccessResponse struct {
	OK          bool
	Error       string
	AccessToken string `json:"access_token"`
	Scope       string
	UserID      string `json:"user_id"`
	TeamName    string `json:"team_name"`
	TeamID      string `json:"team_id"`
}

type slackUnfurlPayload struct {
	Token   string
	Channel string
	TS      string
	Unfurls string
}

type slackAttachment struct {
	//Text string
	Fallback string                 `json:"fallback"`
	Title    string                 `json:"title"`
	TS       int64                  `json:"ts"`
	Fields   []slackAttachmentField `json:"fields"`
}

type slackAttachmentField struct {
	Title string `json:"title"`
	Value string `json:"value"`
	Short bool   `json:"short"`
}

type slackOuterEvent struct {
	Token     string
	TeamID    string `json:"team_id"`
	APIAppID  string `json:"api_app_id"`
	Event     slackEvent
	Challenge string
	Type      string
	EventID   string `json:"event_id"`
}

type slackEvent struct {
	Type string
	//link_shared-specific fields
	Channel   string
	User      string
	MessageTS string `json:"message_ts"`
	Links     []struct {
		Domain string
		URL    string
	}
	//tokens_revoked-specific fields
	Tokens struct {
		OAuth []string
	}
}

func oauthCallbackHandler(w http.ResponseWriter, r *http.Request) {
	code := r.FormValue("code")
	if code != "" {
		err := exchangeOauthCodeForToken(code)
		if err != nil {
			// serve error page?
		}
	}
	serveFile(w, "static/thanks.html")
}

func slashCommandHandler(w http.ResponseWriter, r *http.Request) {
	token := r.FormValue("token")
	if token != config.SlackVerificationToken {
		log.Printf("Token %s did not match the configured one at /slash", token)
		http.Error(w, "Token mismatch", 403)
		return
	}
	team := r.FormValue("team_id")
	user := r.FormValue("user_id")
	command := r.FormValue("command")
	text := r.FormValue("text")
	log.Printf("Received slash command (team %s, user %s): %s %s", team, user, command, text)
	switch command {
	case "/rollbar":
		processRollbarSlashCommand(w, text, team)
	default:
		log.Printf("Unsupported slack command %s", command)
	}
}

type slackSlashCommandResponse struct {
	ResponseType string `json:"response_type"`
	Text         string `json:"text"`
}

const (
	rollbarCmdUsage = "Usage:\n" +
		"`/rollbar set <project url> <project token>` - set read access token for project\n" +
		"`/rollbar clear <project url>` - clear access token for project\n" +
		"`/rollbar list` - list all projects that I will unfurl\n\n" +
		"For example: `/rollbar set https://rollbar.com/MyOrganization/MyProject/` abcdef12345"
	rollbarInvalidProjectURL = "Sorry, %s doesn't look like a Rollbar project URL. It should look like this: " +
		"https://rollbar.com/MyOrganization/MyProject/"
	rollbarInvalidToken = "Sorry, Rollbar reports %s is not a valid access token. Please copy the _read_ token from " +
		"https://rollbar.com/%s/settings/access_tokens/"
	rollbarTokenAdded           = "Thanks! I will now unfurl links from https://rollbar.com/%s/items/ for you."
	rollbarTokenRemoved         = "Done! I will no longer unfurl links from https://rollbar.com/%s/items/."
	rollbarGeneralError         = "An error occurred while executing the command. Please try again!"
	rollbarNoProjectsConfigured = "No  Rollbar projects have been configured for your team.\n" +
		"Use `/rollbar set` to add one."
	rollbarProjectList = "I will unfurl links from the following projects:\n%s"
)

func processRollbarSlashCommand(w http.ResponseWriter, commandText, team string) {
	resp := slackSlashCommandResponse{
		ResponseType: "ephemeral",
	}
	parts := strings.Split(commandText, " ")
	switch parts[0] {
	case "list":
		projects := db.GetProjects(team)
		for k, p := range projects {
			projects[k] = fmt.Sprintf("https://rollbar.com/%s/", p)
		}
		if len(projects) == 0 {
			resp.Text = rollbarNoProjectsConfigured
			break
		}
		resp.Text = fmt.Sprintf(rollbarProjectList, strings.Join(projects, "\n"))
	case "set":
		if len(parts) != 3 {
			resp.Text = rollbarCmdUsage
			break
		}
		projectURL := parts[1]
		matches := rollbarProjectRegex.FindStringSubmatch(projectURL)
		if len(matches) != 3 {
			resp.Text = fmt.Sprintf(rollbarInvalidProjectURL, projectURL)
			break
		}
		project := strings.ToLower(matches[1])
		token := parts[2]
		if !rollbar.IsValidToken(token) {
			resp.Text = fmt.Sprintf(rollbarInvalidToken, token, project)
			break
		}
		//finally, all is well
		err := db.SaveProjectToken(team, project, token)
		if err != nil {
			resp.Text = rollbarGeneralError
			break
		}
		resp.Text = fmt.Sprintf(rollbarTokenAdded, project)
	case "clear":
		if len(parts) != 2 {
			resp.Text = rollbarCmdUsage
			break
		}
		projectURL := parts[1]
		matches := rollbarProjectRegex.FindStringSubmatch(projectURL)
		if len(matches) != 3 {
			resp.Text = fmt.Sprintf(rollbarInvalidProjectURL, projectURL)
			break
		}
		project := strings.ToLower(matches[1])
		db.DeleteProjectToken(team, project)
		resp.Text = fmt.Sprintf(rollbarTokenRemoved, project)
	default:
		resp.Text = rollbarCmdUsage
	}

	b, _ := json.Marshal(resp)
	w.Header().Set("Content-Type", "application/json")
	w.Write(b)
}

func exchangeOauthCodeForToken(code string) error {
	form := url.Values{}
	form.Add("client_id", config.ClientID)
	form.Add("client_secret", config.ClientSecret)
	form.Add("code", code)

	log.Print("Posting oauth.access")

	resp, err := http.PostForm(slackOauthAccessURL, form)
	if err != nil {
		log.Printf("error when posting oauth.access: %s", err.Error())
		return err
	}
	defer resp.Body.Close()
	var oauthResponse slackOauthAccessResponse
	err = json.NewDecoder(resp.Body).Decode(&oauthResponse)
	if err != nil {
		log.Printf("error when deserializing oauth.access response: %s", err.Error())
		return err
	}
	if !oauthResponse.OK {
		log.Printf("oauth.access reported an error: %s", oauthResponse.Error)
		return errors.New(oauthResponse.Error)
	}
	err = db.SaveAuthToken(oauthResponse.TeamID, oauthResponse.UserID, oauthResponse.AccessToken)
	if err != nil {
		log.Printf("Could not save auth token: %s", err.Error())
		return err
	}
	log.Printf("Saved auth token for user %s/team %s (%s)", oauthResponse.UserID, oauthResponse.TeamID, oauthResponse.TeamName)
	return nil
}

func slackEventHandler(w http.ResponseWriter, r *http.Request) {
	event := new(slackOuterEvent)

	err := json.NewDecoder(r.Body).Decode(&event)
	if err != nil {
		log.Print("Invalid JSON received: " + err.Error())
		http.Error(w, err.Error(), 400)
		return
	}

	if event.Token != config.SlackVerificationToken {
		log.Printf("Request verification token %s did not match the one configured!", event.Token)
		http.Error(w, "Token mismatch", 403)
		return
	}

	team := event.TeamID
	log.Printf("Received event of type %s/%s from team %s", event.Type, event.Event.Type, team)

	switch event.Type {
	case "url_verification":
		processURLVerification(w, event)
	case "event_callback":
		innerEventType := event.Event.Type
		switch innerEventType {
		case "link_shared":
			processLinkSharedEvent(&event.Event, team)
		case "tokens_revoked":
			processTokensRevokedEvent(&event.Event, team)
		case "app_uninstalled":
			processAppUninstalledEvent(team)
		default:
			log.Printf("Unsupported event subtype %s", innerEventType)
		}
	default:
		log.Printf("Unknown event type %s", event.Type)
		//ignore the event and return the default 200 OK / empty response
	}

}

func processLinkSharedEvent(e *slackEvent, team string) {
	logLine := fmt.Sprintf("link shared event (channel=%s,ts=%s), links:\n", e.Channel, e.MessageTS)
	for _, v := range e.Links {
		logLine += fmt.Sprintf("-- %s\n", v.URL)
	}
	log.Print(logLine)
	go addLinkPreviews(e, team)
}

func processTokensRevokedEvent(e *slackEvent, team string) {
	for _, v := range e.Tokens.OAuth {
		log.Printf("Deleting oAuth token for user %s (team %s) ", v, team)
		db.DeleteUserToken(team, v)
	}
}

func processAppUninstalledEvent(team string) {
	log.Printf("Deleting team %s's data", team)
	db.DeleteTeam(team)
}

func processURLVerification(w http.ResponseWriter, e *slackOuterEvent) {
	// just respond by printing the challenge back
	fmt.Fprintf(w, "%s", e.Challenge)
}

var rollbarItemRegex = regexp.MustCompile(`(\w+\/\w+)\/items\/(\d+)/?`)
var rollbarProjectRegex = regexp.MustCompile(`https?:\/\/rollbar.com\/([a-zA-Z0-9_\-\.]+\/[a-zA-Z0-9_\-\.]+)($|\/?.*)`)

func addLinkPreviews(event *slackEvent, team string) {
	linkData := make(map[string]slackAttachment)
	for _, link := range event.Links {
		url := link.URL
		matches := rollbarItemRegex.FindStringSubmatch(url)
		if len(matches) < 3 {
			log.Printf("%s is not a Rollbar item link", url)
			continue
		}
		project := strings.ToLower(matches[1])
		counter := matches[2]
		token := db.GetProjectToken(team, project)
		if token == "" {
			log.Printf("Project %s isn't configured for team %s", project, team)
			continue
		}
		item, err := rollbar.GetItemData(counter, token)
		if err != nil {
			log.Printf("error getting data for %s: %s", link.URL, err.Error())
		} else {
			linkData[link.URL] = getUnfurlData(item)
		}
	}

	if len(linkData) == 0 {
		//none of the links posted were able to be processed
		log.Printf("No links processed (channel=%s,ts=%s)", event.Channel, event.MessageTS)
		return
	}

	unfurls, err := json.Marshal(linkData)
	if err != nil {
		log.Printf("Unfurls serialization failed (channel=%s,ts=%s): %s", event.Channel, event.MessageTS, err.Error())
		return
	}

	apiToken := db.GetAuthToken(team)
	if apiToken == "" {
		log.Printf("Couldn't retrieve oAuth token for team %s", team)
		return
	}

	payload := slackUnfurlPayload{
		Token:   apiToken,
		Channel: event.Channel,
		TS:      event.MessageTS,
		Unfurls: string(unfurls),
	}

	postToSlack(payload)
}

func getTimeAgoString(d time.Duration) string {
	t := d.Seconds()
	if t < 1 {
		return "just now"
	}
	if t < 60 {
		return fmt.Sprintf("%.fs ago", t)
	}
	t /= 60 // t is now minutes
	if t < 60 {
		return fmt.Sprintf("%.fm ago", t)
	}
	t /= 60 // t is now hours
	if t < 24 {
		return fmt.Sprintf("%.fh ago", t)
	}
	t /= 24 // t is now days
	return fmt.Sprintf("%.fd ago", t)
}

func getUnfurlData(item *rollbar.Item) slackAttachment {
	now := time.Now()
	attachment := slackAttachment{
		Title:    item.Title,
		Fallback: item.Title,
		TS:       now.Unix(),
		Fields:   make([]slackAttachmentField, 4),
	}

	attachment.Fields[0] = slackAttachmentField{
		Title: "Status",
		Value: item.Status,
		Short: true,
	}
	attachment.Fields[1] = slackAttachmentField{
		Title: "Occurrences",
		Value: strconv.Itoa(item.TotalOccurrences),
		Short: true,
	}
	attachment.Fields[2] = slackAttachmentField{
		Title: "First seen",
		Value: time.Unix(int64(item.FirstOccurrenceTimestamp), 0).Format("Jan 2 15:04:05"),
		Short: true,
	}
	lastSeenAgo := now.Sub(time.Unix(int64(item.LastOccurrenceTimestamp), 0))
	attachment.Fields[3] = slackAttachmentField{
		Title: "Last seen",
		Value: getTimeAgoString(lastSeenAgo),
		Short: true,
	}

	return attachment
}

func postToSlack(message slackUnfurlPayload) {
	form := url.Values{}
	form.Add("token", message.Token)
	form.Add("channel", message.Channel)
	form.Add("ts", message.TS)
	form.Add("unfurls", message.Unfurls)

	log.Printf("Posting chat.unfurl (channel=%s,ts=%s)", message.Channel, message.TS)

	resp, err := http.PostForm(slackUnfurlURL, form)
	if err != nil {
		log.Printf("error when posting chat.unfurl: %s", err.Error())
	}
	defer resp.Body.Close()
	b, _ := ioutil.ReadAll(resp.Body)
	log.Printf("chat.unfurl resp: %s", string(b))
}
<|endoftext|>


<commit_before>/*

mybot - Illustrative Slack bot in Go

Copyright (c) 2015 RapidLoop

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"sync/atomic"

	"golang.org/x/net/websocket"
)

// These two structures represent the response of the Slack API rtm.start.
// Only some fields are included. The rest are ignored by json.Unmarshal.

type responseRtmStart struct {
	Ok    bool         `json:"ok"`
	Error string       `json:"error"`
	Url   string       `json:"url"`
	Self  responseSelf `json:"self"`
}

type responseSelf struct {
	Id string `json:"id"`
}

// slackStart does a rtm.start, and returns a websocket URL and user ID. The
// websocket URL can be used to initiate an RTM session.
func slackStart(token string) (wsurl, id string, err error) {
	url := fmt.Sprintf("https://slack.com/api/rtm.start?token=%s", token)
	resp, err := http.Get(url)
	if err != nil {
		return
	}
	if resp.StatusCode != 200 {
		err = fmt.Errorf("API request failed with code %d", resp.StatusCode)
		return
	}
	body, err := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil {
		return
	}
	var respObj responseRtmStart
	err = json.Unmarshal(body, &respObj)
	if err != nil {
		return
	}

	if !respObj.Ok {
		err = fmt.Errorf("Slack error: %s", respObj.Error)
		return
	}

	wsurl = respObj.Url
	id = respObj.Self.Id
	return
}

// These are the messages read off and written into the websocket. Since this
// struct serves as both read and write, we include the "Id" field which is
// required only for writing.

type Message struct {
	Id      uint64 `json:"id"`
	Type    string `json:"type"`
	Channel string `json:"channel"`
	User    string `json:"user"`
	Text    string `json:"text"`
}

func getMessage(ws *websocket.Conn) (m Message, err error) {
	err = websocket.JSON.Receive(ws, &m)
	return
}

var counter uint64

func postMessage(ws *websocket.Conn, m Message) error {
	m.Id = atomic.AddUint64(&counter, 1)
	return websocket.JSON.Send(ws, m)
}

// Starts a websocket-based Real Time API session and return the websocket
// and the ID of the (bot-)user whom the token belongs to.
func slackConnect(token string) (*websocket.Conn, string) {
	wsurl, id, err := slackStart(token)
	if err != nil {
		log.Fatal(err)
	}

	ws, err := websocket.Dial(wsurl, "", "https://api.slack.com/")
	if err != nil {
		log.Fatal(err)
	}

	return ws, id
}
<commit_msg>Experiment with things<commit_after>/*

mybot - Illustrative Slack bot in Go

Copyright (c) 2015 RapidLoop

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"sync/atomic"

	"golang.org/x/net/websocket"
)

// These two structures represent the response of the Slack API rtm.start.
// Only some fields are included. The rest are ignored by json.Unmarshal.

type responseRtmStart struct {
	Ok    bool         `json:"ok"`
	Error string       `json:"error"`
	Url   string       `json:"url"`
	Self  responseSelf `json:"self"`
}

type responseSelf struct {
	Id string `json:"id"`
}

// slackStart does a rtm.start, and returns a websocket URL and user ID. The
// websocket URL can be used to initiate an RTM session.
func slackStart(token string) (wsurl, id string, err error) {
	url := fmt.Sprintf("https://slack.com/api/rtm.start?token=%s", token)
	resp, err := http.Get(url)
	if err != nil {
		return
	}
	if resp.StatusCode != 200 {
		err = fmt.Errorf("API request failed with code %d", resp.StatusCode)
		return
	}
	body, err := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil {
		return
	}
	var respObj responseRtmStart
	err = json.Unmarshal(body, &respObj)
	if err != nil {
		return
	}

	if !respObj.Ok {
		err = fmt.Errorf("Slack error: %s", respObj.Error)
		return
	}

	wsurl = respObj.Url
	id = respObj.Self.Id
	return
}

// These are the messages read off and written into the websocket. Since this
// struct serves as both read and write, we include the "Id" field which is
// required only for writing.

type Message struct {
	Id        uint64 `json:"id"`
	Type      string `json:"type"`
	Channel   string `json:"channel"`
	User      string `json:"user"`
	Text      string `json:"text"`
	LinkNames int    `json:"link_names"`
	AsUser    bool   `json:"as_user"`
}

func getMessage(ws *websocket.Conn) (m Message, err error) {
	err = websocket.JSON.Receive(ws, &m)
	return
}

var counter uint64

func postMessage(ws *websocket.Conn, m Message) error {
	m.LinkNames = 1
	m.AsUser = true
	m.Id = atomic.AddUint64(&counter, 1)
	return websocket.JSON.Send(ws, m)
}

// Starts a websocket-based Real Time API session and return the websocket
// and the ID of the (bot-)user whom the token belongs to.
func slackConnect(token string) (*websocket.Conn, string) {
	wsurl, id, err := slackStart(token)
	if err != nil {
		log.Fatal(err)
	}

	ws, err := websocket.Dial(wsurl, "", "https://api.slack.com/")
	if err != nil {
		log.Fatal(err)
	}

	return ws, id
}
<|endoftext|>


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

package com

// AppendStr appends string to slice with no duplicate.
func AppendStr(strs []string, str string) []string {
	for _, s := range strs {
		if s == str {
			return strs
		}
	}
	return append(strs, str)
}

// CompareSliceStr compares two 'string' type slices.
// It returns true if elements and indexes are both the same.
func CompareSliceStr(s1, s2 []string) bool {
	if len(s1) != len(s2) {
		return false
	}

	for i := range s1 {
		if s1[i] != s2[i] {
			return false
		}
	}

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

package com

// AppendStr appends string to slice with no duplicate.
func AppendStr(strs []string, str string) []string {
	for _, s := range strs {
		if s == str {
			return strs
		}
	}
	return append(strs, str)
}

// CompareSliceStr compares two 'string' type slices.
// It returns true if elements and indexes are both the same.
func CompareSliceStr(s1, s2 []string) bool {
	if len(s1) != len(s2) {
		return false
	}

	for i := range s1 {
		if s1[i] != s2[i] {
			return false
		}
	}

	return true
}

// CompareSliceStr compares two 'string' type slices.
// It returns true if elements are the same, do not bother the order of list.
func CompareSliceStrU(s1, s2 []string) bool {
	if len(s1) != len(s2) {
		return false
	}

	for i := range s1 {
		for j := len(s2) - 1; j >= 0; j-- {
			if s1[i] == s2[j] {
				s2 = append(s2[:j], s2[j+1]...)
				break
			}
		}
	}
	if len(s2) > 0 {
		return false
	}
	return true
}
<|endoftext|>


<commit_before>/*
 * Pure Go SMART library
 * Copyright 2017 Daniel Swarbrick
 */

package smart

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"unsafe"

	"github.com/BurntSushi/toml"
)

type attrConv struct {
	Conv string
	Name string
}

type driveModel struct {
	Family         string
	ModelRegex     string
	FirmwareRegex  string
	WarningMsg     string
	Presets        map[string]attrConv
	CompiledRegexp *regexp.Regexp
}

type driveDb struct {
	Drives []driveModel
}

var nativeEndian binary.ByteOrder

// Determine native endianness of system
func init() {
	i := uint32(1)
	b := (*[4]byte)(unsafe.Pointer(&i))
	if b[0] == 1 {
		nativeEndian = binary.LittleEndian
	} else {
		nativeEndian = binary.BigEndian
	}
}

// lookupDrive returns the most appropriate driveModel for a given ATA IDENTIFY value
func (db *driveDb) lookupDrive(ident []byte) driveModel {
	var model, defaultModel driveModel

	for _, d := range db.Drives {
		// Skip placeholder entry
		if strings.HasPrefix(d.Family, "$Id") {
			continue
		}

		if d.Family == "DEFAULT" {
			defaultModel = d
			continue
		}

		if d.CompiledRegexp.Match(ident) {
			model = d

			// Inherit presets from defaultModel
			for id, p := range defaultModel.Presets {
				if _, exists := model.Presets[id]; exists {
					// Some drives override the conv but don't specify a name, so copy it from default
					if model.Presets[id].Name == "" {
						model.Presets[id] = attrConv{Name: p.Name, Conv: model.Presets[id].Conv}
					}
				} else {
					model.Presets[id] = p
				}
			}

			break
		}
	}

	return model
}

// openDriveDb opens a .toml formatted drive database, unmarshalls it, and returns a driveDb
func openDriveDb(dbfile string) (driveDb, error) {
	var db driveDb

	if _, err := toml.DecodeFile(dbfile, &db); err != nil {
		return db, fmt.Errorf("Cannot open / parse drive DB: %s", err)
	}

	for i, d := range db.Drives {
		db.Drives[i].CompiledRegexp, _ = regexp.Compile(d.ModelRegex)
	}

	return db, nil
}

// Swap bytes in a byte slice
func swapBytes(s []byte) []byte {
	for i := 0; i < len(s); i += 2 {
		s[i], s[i+1] = s[i+1], s[i]
	}

	return s
}

func ReadSMART(device string) error {
	dev, err := openDevice(device)
	if err != nil {
		return fmt.Errorf("Cannot open device %s: %v", device, err)
	}

	defer dev.close()

	io_hdr := sgIoHdr{interface_id: 'S', dxfer_direction: SG_DXFER_FROM_DEV, timeout: 20000}

	inquiry_cdb := SCSICDB6{OpCode: SCSI_INQUIRY, AllocLen: INQ_REPLY_LEN}
	inqBuff := make([]byte, INQ_REPLY_LEN)
	sense_buf := make([]byte, 32)

	io_hdr.cmd_len = uint8(unsafe.Sizeof(inquiry_cdb))
	io_hdr.mx_sb_len = uint8(len(sense_buf))
	io_hdr.dxfer_len = uint32(len(inqBuff))
	io_hdr.dxferp = uintptr(unsafe.Pointer(&inqBuff[0]))
	io_hdr.cmdp = uintptr(unsafe.Pointer(&inquiry_cdb))
	io_hdr.sbp = uintptr(unsafe.Pointer(&sense_buf[0]))

	if err = dev.execGenericIO(&io_hdr); err != nil {
		return fmt.Errorf("SgExecute INQUIRY: %v", err)
	}

	fmt.Printf("SCSI INQUIRY: %.8s  %.16s  %.4s\n", inqBuff[8:], inqBuff[16:], inqBuff[32:])

	cdb16 := [16]byte{}

	io_hdr = sgIoHdr{interface_id: 'S', dxfer_direction: SG_DXFER_FROM_DEV, timeout: 20000}
	// 0x08 : ATA protocol (4 << 1, PIO data-in)
	// 0x0e : BYT_BLOK = 1, T_LENGTH = 2, T_DIR = 1
	cdb16 = [16]byte{SCSI_ATA_PASSTHRU_16, 0x08, 0x0e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ATA_IDENTIFY_DEVICE, 0x00}
	ident_buf := IdentifyDeviceData{}

	io_hdr.cmd_len = uint8(len(cdb16))
	io_hdr.mx_sb_len = uint8(len(sense_buf))
	io_hdr.dxfer_len = uint32(unsafe.Sizeof(ident_buf))
	io_hdr.dxferp = uintptr(unsafe.Pointer(&ident_buf))
	io_hdr.cmdp = uintptr(unsafe.Pointer(&cdb16))
	io_hdr.sbp = uintptr(unsafe.Pointer(&sense_buf[0]))

	if err = dev.execGenericIO(&io_hdr); err != nil {
		return fmt.Errorf("SgExecute ATA IDENTIFY: %v", err)
	}

	fmt.Println("\nATA IDENTIFY data follows:")
	fmt.Printf("Serial Number: %s\n", swapBytes(ident_buf.SerialNumber[:]))
	fmt.Printf("Firmware Revision: %s\n", swapBytes(ident_buf.FirmwareRevision[:]))
	fmt.Printf("Model Number: %s\n", swapBytes(ident_buf.ModelNumber[:]))

	db, err := openDriveDb("drivedb.toml")
	if err != nil {
		return err
	}

	thisDrive := db.lookupDrive(ident_buf.ModelNumber[:])
	fmt.Printf("Drive DB contains %d entries. Using model: %s\n", len(db.Drives), thisDrive.Family)

	// FIXME: Check that device supports SMART before trying to read data page

	/*
	 * SMART READ DATA
	 * command code B0h, feature register D0h
	 * LBA mid register 4Fh, LBA high register C2h
	 */
	io_hdr = sgIoHdr{interface_id: 'S', dxfer_direction: SG_DXFER_FROM_DEV, timeout: 20000}
	// 0x08 : ATA protocol (4 << 1, PIO data-in)
	// 0x0e : BYT_BLOK = 1, T_LENGTH = 2, T_DIR = 1
	cdb16 = [16]byte{SCSI_ATA_PASSTHRU_16, 0x08, 0x0e, 0x00, SMART_READ_DATA, 0x00, 0x01, 0x00, 0x00, 0x00, 0x4f, 0x00, 0xc2, 0x00, ATA_SMART, 0x00}
	resp_buf := [512]byte{}

	io_hdr.cmd_len = uint8(len(cdb16))
	io_hdr.mx_sb_len = uint8(len(sense_buf))
	io_hdr.dxfer_len = uint32(len(resp_buf))
	io_hdr.dxferp = uintptr(unsafe.Pointer(&resp_buf))
	io_hdr.cmdp = uintptr(unsafe.Pointer(&cdb16))
	io_hdr.sbp = uintptr(unsafe.Pointer(&sense_buf[0]))

	if err = dev.execGenericIO(&io_hdr); err != nil {
		return fmt.Errorf("SgExecute SMART READ DATA: %v", err)
	}

	smart := smartPage{}
	binary.Read(bytes.NewBuffer(resp_buf[:362]), nativeEndian, &smart)

	fmt.Printf("\nSMART structure version: %d\n", smart.Version)
	fmt.Printf("ID# ATTRIBUTE_NAME           FLAG     VALUE WORST RESERVED RAW_VALUE     VENDOR_BYTES\n")

	for _, attr := range smart.Attrs {
		var rawValue uint64

		if attr.Id == 0 {
			break
		}

		attrconv := thisDrive.Presets[strconv.Itoa(int(attr.Id))]

		switch attrconv.Conv {
		case "raw24(raw8)":
			// Big-endian 24-bit number, optionally with 8 bit values
			for i := 2; i >= 0; i-- {
				rawValue |= uint64(attr.VendorBytes[i]) << uint64(i*8)
			}
		case "raw48":
			// Big-endian 48-bit number
			for i := 5; i >= 0; i-- {
				rawValue |= uint64(attr.VendorBytes[i]) << uint64(i*8)
			}
		case "tempminmax":
			rawValue = uint64(attr.VendorBytes[0])
		}

		fmt.Printf("%3d %-24s %#04x   %03d   %03d   %03d      %-12d  %v (%s)\n",
			attr.Id, attrconv.Name, attr.Flags, attr.Value, attr.Worst, attr.Reserved,
			rawValue, attr.VendorBytes, attrconv.Conv)
	}

	return nil
}

func ScanDevices() {
	// Find all SCSI disk devices
	files, err := filepath.Glob("/dev/sd*[^1-9]")
	if err != nil {
		return
	}

	for _, file := range files {
		fmt.Println(file)
	}
}
<commit_msg>Move SMART print code to function<commit_after>/*
 * Pure Go SMART library
 * Copyright 2017 Daniel Swarbrick
 */

package smart

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"unsafe"

	"github.com/BurntSushi/toml"
)

type attrConv struct {
	Conv string
	Name string
}

type driveModel struct {
	Family         string
	ModelRegex     string
	FirmwareRegex  string
	WarningMsg     string
	Presets        map[string]attrConv
	CompiledRegexp *regexp.Regexp
}

type driveDb struct {
	Drives []driveModel
}

var nativeEndian binary.ByteOrder

// Determine native endianness of system
func init() {
	i := uint32(1)
	b := (*[4]byte)(unsafe.Pointer(&i))
	if b[0] == 1 {
		nativeEndian = binary.LittleEndian
	} else {
		nativeEndian = binary.BigEndian
	}
}

// lookupDrive returns the most appropriate driveModel for a given ATA IDENTIFY value
func (db *driveDb) lookupDrive(ident []byte) driveModel {
	var model, defaultModel driveModel

	for _, d := range db.Drives {
		// Skip placeholder entry
		if strings.HasPrefix(d.Family, "$Id") {
			continue
		}

		if d.Family == "DEFAULT" {
			defaultModel = d
			continue
		}

		if d.CompiledRegexp.Match(ident) {
			model = d

			// Inherit presets from defaultModel
			for id, p := range defaultModel.Presets {
				if _, exists := model.Presets[id]; exists {
					// Some drives override the conv but don't specify a name, so copy it from default
					if model.Presets[id].Name == "" {
						model.Presets[id] = attrConv{Name: p.Name, Conv: model.Presets[id].Conv}
					}
				} else {
					model.Presets[id] = p
				}
			}

			break
		}
	}

	return model
}

// openDriveDb opens a .toml formatted drive database, unmarshalls it, and returns a driveDb
func openDriveDb(dbfile string) (driveDb, error) {
	var db driveDb

	if _, err := toml.DecodeFile(dbfile, &db); err != nil {
		return db, fmt.Errorf("Cannot open / parse drive DB: %s", err)
	}

	for i, d := range db.Drives {
		db.Drives[i].CompiledRegexp, _ = regexp.Compile(d.ModelRegex)
	}

	return db, nil
}

// Swap bytes in a byte slice
func swapBytes(s []byte) []byte {
	for i := 0; i < len(s); i += 2 {
		s[i], s[i+1] = s[i+1], s[i]
	}

	return s
}

func printSMART(smart smartPage, drive driveModel) {
	fmt.Printf("\nSMART structure version: %d\n", smart.Version)
	fmt.Printf("ID# ATTRIBUTE_NAME           FLAG     VALUE WORST RESERVED RAW_VALUE     VENDOR_BYTES\n")

	for _, attr := range smart.Attrs {
		var rawValue uint64

		if attr.Id == 0 {
			break
		}

		attrconv := drive.Presets[strconv.Itoa(int(attr.Id))]

		switch attrconv.Conv {
		case "raw24(raw8)":
			// Big-endian 24-bit number, optionally with 8 bit values
			for i := 2; i >= 0; i-- {
				rawValue |= uint64(attr.VendorBytes[i]) << uint64(i*8)
			}
		case "raw48":
			// Big-endian 48-bit number
			for i := 5; i >= 0; i-- {
				rawValue |= uint64(attr.VendorBytes[i]) << uint64(i*8)
			}
		case "tempminmax":
			rawValue = uint64(attr.VendorBytes[0])
		}

		fmt.Printf("%3d %-24s %#04x   %03d   %03d   %03d      %-12d  %v (%s)\n",
			attr.Id, attrconv.Name, attr.Flags, attr.Value, attr.Worst, attr.Reserved,
			rawValue, attr.VendorBytes, attrconv.Conv)
	}
}

func ReadSMART(device string) error {
	dev, err := openDevice(device)
	if err != nil {
		return fmt.Errorf("Cannot open device %s: %v", device, err)
	}

	defer dev.close()

	io_hdr := sgIoHdr{interface_id: 'S', dxfer_direction: SG_DXFER_FROM_DEV, timeout: 20000}

	inquiry_cdb := SCSICDB6{OpCode: SCSI_INQUIRY, AllocLen: INQ_REPLY_LEN}
	inqBuff := make([]byte, INQ_REPLY_LEN)
	sense_buf := make([]byte, 32)

	io_hdr.cmd_len = uint8(unsafe.Sizeof(inquiry_cdb))
	io_hdr.mx_sb_len = uint8(len(sense_buf))
	io_hdr.dxfer_len = uint32(len(inqBuff))
	io_hdr.dxferp = uintptr(unsafe.Pointer(&inqBuff[0]))
	io_hdr.cmdp = uintptr(unsafe.Pointer(&inquiry_cdb))
	io_hdr.sbp = uintptr(unsafe.Pointer(&sense_buf[0]))

	if err = dev.execGenericIO(&io_hdr); err != nil {
		return fmt.Errorf("SgExecute INQUIRY: %v", err)
	}

	fmt.Printf("SCSI INQUIRY: %.8s  %.16s  %.4s\n", inqBuff[8:], inqBuff[16:], inqBuff[32:])

	cdb16 := [16]byte{}

	io_hdr = sgIoHdr{interface_id: 'S', dxfer_direction: SG_DXFER_FROM_DEV, timeout: 20000}
	// 0x08 : ATA protocol (4 << 1, PIO data-in)
	// 0x0e : BYT_BLOK = 1, T_LENGTH = 2, T_DIR = 1
	cdb16 = [16]byte{SCSI_ATA_PASSTHRU_16, 0x08, 0x0e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ATA_IDENTIFY_DEVICE, 0x00}
	ident_buf := IdentifyDeviceData{}

	io_hdr.cmd_len = uint8(len(cdb16))
	io_hdr.mx_sb_len = uint8(len(sense_buf))
	io_hdr.dxfer_len = uint32(unsafe.Sizeof(ident_buf))
	io_hdr.dxferp = uintptr(unsafe.Pointer(&ident_buf))
	io_hdr.cmdp = uintptr(unsafe.Pointer(&cdb16))
	io_hdr.sbp = uintptr(unsafe.Pointer(&sense_buf[0]))

	if err = dev.execGenericIO(&io_hdr); err != nil {
		return fmt.Errorf("SgExecute ATA IDENTIFY: %v", err)
	}

	fmt.Println("\nATA IDENTIFY data follows:")
	fmt.Printf("Serial Number: %s\n", swapBytes(ident_buf.SerialNumber[:]))
	fmt.Printf("Firmware Revision: %s\n", swapBytes(ident_buf.FirmwareRevision[:]))
	fmt.Printf("Model Number: %s\n", swapBytes(ident_buf.ModelNumber[:]))

	db, err := openDriveDb("drivedb.toml")
	if err != nil {
		return err
	}

	thisDrive := db.lookupDrive(ident_buf.ModelNumber[:])
	fmt.Printf("Drive DB contains %d entries. Using model: %s\n", len(db.Drives), thisDrive.Family)

	// FIXME: Check that device supports SMART before trying to read data page

	/*
	 * SMART READ DATA
	 * command code B0h, feature register D0h
	 * LBA mid register 4Fh, LBA high register C2h
	 */
	io_hdr = sgIoHdr{interface_id: 'S', dxfer_direction: SG_DXFER_FROM_DEV, timeout: 20000}
	// 0x08 : ATA protocol (4 << 1, PIO data-in)
	// 0x0e : BYT_BLOK = 1, T_LENGTH = 2, T_DIR = 1
	cdb16 = [16]byte{SCSI_ATA_PASSTHRU_16, 0x08, 0x0e, 0x00, SMART_READ_DATA, 0x00, 0x01, 0x00, 0x00, 0x00, 0x4f, 0x00, 0xc2, 0x00, ATA_SMART, 0x00}
	resp_buf := [512]byte{}

	io_hdr.cmd_len = uint8(len(cdb16))
	io_hdr.mx_sb_len = uint8(len(sense_buf))
	io_hdr.dxfer_len = uint32(len(resp_buf))
	io_hdr.dxferp = uintptr(unsafe.Pointer(&resp_buf))
	io_hdr.cmdp = uintptr(unsafe.Pointer(&cdb16))
	io_hdr.sbp = uintptr(unsafe.Pointer(&sense_buf[0]))

	if err = dev.execGenericIO(&io_hdr); err != nil {
		return fmt.Errorf("SgExecute SMART READ DATA: %v", err)
	}

	smart := smartPage{}
	binary.Read(bytes.NewBuffer(resp_buf[:362]), nativeEndian, &smart)
	printSMART(smart, thisDrive)

	return nil
}

func ScanDevices() {
	// Find all SCSI disk devices
	files, err := filepath.Glob("/dev/sd*[^1-9]")
	if err != nil {
		return
	}

	for _, file := range files {
		fmt.Println(file)
	}
}
<|endoftext|>


<commit_before>package parser

// What is done:
// - variables
// - function
// What is not:
// - types
// - traits

type unresolvedName struct {
	moduleNames []string
	name        string
	modules     map[string]*Module
}

func (v unresolvedName) String() string {
	ret := ""
	for _, mod := range v.moduleNames {
		ret += mod + "::"
	}
	return ret + v.name
}

func (v *semanticAnalyzer) errResolve(thing Locatable, name unresolvedName) {
	v.err(thing, "Cannot resolve `%s`", name.String())
}

func (v *semanticAnalyzer) resolve(modules map[string]*Module) {
	v.modules = modules

	for _, node := range v.module.Nodes {
		node.resolve(v, v.module.GlobalScope)
	}
}

func (v *Block) resolve(sem *semanticAnalyzer, s *Scope) {
	for _, n := range v.Nodes {
		n.resolve(sem, v.scope)
	}
}

/**
 * Declarations
 */

func (v *VariableDecl) resolve(sem *semanticAnalyzer, s *Scope) {
	if v.Assignment != nil {
		v.Assignment.resolve(sem, s)
	}

	if v.Variable.Type != nil {
		v.Variable.Type = v.Variable.Type.resolveType(v, sem, s)
	}
}

func (v *StructDecl) resolve(sem *semanticAnalyzer, s *Scope) {
	v.Struct = v.Struct.resolveType(v, sem, s).(*StructType)
}

func (v *EnumDecl) resolve(sem *semanticAnalyzer, s *Scope) {
	// TODO: this is a noop, right?
}

func (v *TraitDecl) resolve(sem *semanticAnalyzer, s *Scope) {
	v.Trait = v.Trait.resolveType(v, sem, s).(*TraitType)
}

func (v *ImplDecl) resolve(sem *semanticAnalyzer, s *Scope) {
	for _, fun := range v.Functions {
		fun.resolve(sem, s)
	}
}

func (v *FunctionDecl) resolve(sem *semanticAnalyzer, s *Scope) {
	for _, param := range v.Function.Parameters {
		param.resolve(sem, s)
	}

	if v.Function.ReturnType != nil {
		v.Function.ReturnType = v.Function.ReturnType.resolveType(v, sem, s)
	}

	if !v.Prototype {
		v.Function.Body.resolve(sem, s)
	}
}

func (v *UseDecl) resolve(sem *semanticAnalyzer, s *Scope) {
	// later...
}

func (v *ModuleDecl) resolve(sem *semanticAnalyzer, s *Scope) {

}

/*
 * Statements
 */

func (v *ReturnStat) resolve(sem *semanticAnalyzer, s *Scope) {
	if v.Value != nil {
		v.Value.resolve(sem, s)
	}
}

func (v *IfStat) resolve(sem *semanticAnalyzer, s *Scope) {
	for _, expr := range v.Exprs {
		expr.resolve(sem, s)
	}

	for _, body := range v.Bodies {
		body.resolve(sem, s)
	}

	if v.Else != nil {
		v.Else.resolve(sem, s)
	}

}

func (v *BlockStat) resolve(sem *semanticAnalyzer, s *Scope) {
	v.Block.resolve(sem, s)
}

func (v *CallStat) resolve(sem *semanticAnalyzer, s *Scope) {
	v.Call.resolve(sem, s)
}

func (v *AssignStat) resolve(sem *semanticAnalyzer, s *Scope) {
	v.Assignment.resolve(sem, s)

	if v.Deref != nil {
		v.Deref.resolve(sem, s)
	} else if v.Access != nil {
		v.Access.resolve(sem, s)
	}
}

func (v *LoopStat) resolve(sem *semanticAnalyzer, s *Scope) {
	v.Body.resolve(sem, s)

	switch v.LoopType {
	case LOOP_TYPE_INFINITE:
	case LOOP_TYPE_CONDITIONAL:
		v.Condition.resolve(sem, s)
	default:
		panic("invalid loop type")
	}
}

func (v *MatchStat) resolve(sem *semanticAnalyzer, s *Scope) {
	v.Target.resolve(sem, s)

	for pattern, stmt := range v.Branches {
		pattern.resolve(sem, s)
		stmt.resolve(sem, s)
	}
}

/*
 * Expressions
 */

func (v *IntegerLiteral) resolve(sem *semanticAnalyzer, s *Scope)  {}
func (v *FloatingLiteral) resolve(sem *semanticAnalyzer, s *Scope) {}
func (v *StringLiteral) resolve(sem *semanticAnalyzer, s *Scope)   {}
func (v *RuneLiteral) resolve(sem *semanticAnalyzer, s *Scope)     {}
func (v *BoolLiteral) resolve(sem *semanticAnalyzer, s *Scope)     {}

func (v *UnaryExpr) resolve(sem *semanticAnalyzer, s *Scope) {
	v.Expr.resolve(sem, s)
}

func (v *BinaryExpr) resolve(sem *semanticAnalyzer, s *Scope) {
	v.Lhand.resolve(sem, s)
	v.Rhand.resolve(sem, s)
}

func (v *ArrayLiteral) resolve(sem *semanticAnalyzer, s *Scope) {
	for _, mem := range v.Members {
		mem.resolve(sem, s)
	}
}

func (v *CastExpr) resolve(sem *semanticAnalyzer, s *Scope) {
	v.Type = v.Type.resolveType(v, sem, s)
	v.Expr.resolve(sem, s)
}

func (v *CallExpr) resolve(sem *semanticAnalyzer, s *Scope) {
	v.Function = s.GetFunction(v.functionName)
	if v.Function == nil {
		sem.errResolve(v, v.functionName)
	}

	for _, arg := range v.Arguments {
		arg.resolve(sem, s)
	}
}

// this whole function is so bad
// why does this even exist
// please get around to rewriting this in a way that doesn't suck
// good luck changing anything here without rewriting everything
// at least it works
// - MovingtoMars
func (v *AccessExpr) resolve(sem *semanticAnalyzer, s *Scope) {
	// resolve the first name
	firstVar := v.Accesses[0]
	firstVar.Variable = s.GetVariable(firstVar.variableName)

	if v.Accesses[0].Variable == nil {
		sem.errResolve(v, firstVar.variableName)
	}

	// resolve everything else
	for i := 0; i < len(v.Accesses); i++ {
		switch v.Accesses[i].AccessType {
		case ACCESS_ARRAY:
			v.Accesses[i].Subscript.resolve(sem, s)

		case ACCESS_STRUCT:
			v.Accesses[i].Variable.Type.resolveType(v, sem, s)
			structType, ok := v.Accesses[i].Variable.Type.(*StructType)
			if !ok {
				sem.err(v, "Cannot access member of `%s`, type `%s`", v.Accesses[i].Variable.Name, v.Accesses[i].Variable.Type.TypeName())
			}

			memberName := v.Accesses[i+1].variableName.name // TODO check no mod access
			decl := structType.getVariableDecl(memberName)
			if decl == nil {
				sem.err(v, "Struct `%s` does not contain member `%s`", structType.TypeName(), memberName)
			}
			v.Accesses[i+1].Variable = decl.Variable
		case ACCESS_VARIABLE:
			// nothing to do

		case ACCESS_TUPLE:
			// nothing to do

		default:
			panic("unhandled access type")
		}
	}
}

func (v *AddressOfExpr) resolve(sem *semanticAnalyzer, s *Scope) {
	v.Access.resolve(sem, s)
}

func (v *DerefExpr) resolve(sem *semanticAnalyzer, s *Scope) {
	v.Expr.resolve(sem, s)
}

func (v *SizeofExpr) resolve(sem *semanticAnalyzer, s *Scope) {
	if v.Expr != nil {
		v.Expr.resolve(sem, s)
	} else if v.Type != nil {
		v.Type = v.Type.resolveType(v, sem, s)
	} else {
		panic("invalid state")
	}
}

func (v *TupleLiteral) resolve(sem *semanticAnalyzer, s *Scope) {
	for _, mem := range v.Members {
		mem.resolve(sem, s)
	}
}

func (v *DefaultMatchBranch) resolve(sem *semanticAnalyzer, s *Scope) {}

/*
 * Types
 */

func (v PrimitiveType) resolveType(src Locatable, sem *semanticAnalyzer, s *Scope) Type {
	return v
}

func (v *StructType) resolveType(src Locatable, sem *semanticAnalyzer, s *Scope) Type {
	for _, vari := range v.Variables {
		vari.resolve(sem, s)
	}
	return v
}

func (v ArrayType) resolveType(src Locatable, sem *semanticAnalyzer, s *Scope) Type {
	return arrayOf(v.MemberType.resolveType(src, sem, s))
}

func (v *TraitType) resolveType(src Locatable, sem *semanticAnalyzer, s *Scope) Type {
	for _, fun := range v.Functions {
		fun.resolve(sem, s)
	}
	return v
}

func (v PointerType) resolveType(src Locatable, sem *semanticAnalyzer, s *Scope) Type {
	return pointerTo(v.Addressee.resolveType(src, sem, s))
}

func (v *TupleType) resolveType(src Locatable, sem *semanticAnalyzer, s *Scope) Type {
	for idx, mem := range v.Members {
		v.Members[idx] = mem.resolveType(src, sem, s)
	}
	return v
}

func (v *UnresolvedType) resolveType(src Locatable, sem *semanticAnalyzer, s *Scope) Type {
	typ := s.GetType(v.Name)
	if typ == nil {
		sem.err(src, "Cannot resolve `%s`", v.Name)
	}
	return typ
}
<commit_msg>small fix<commit_after>package parser

// What is done:
// - variables
// - function
// What is not:
// - types
// - traits

type unresolvedName struct {
	moduleNames []string
	name        string
	modules     map[string]*Module
}

func (v unresolvedName) String() string {
	ret := ""
	for _, mod := range v.moduleNames {
		ret += mod + "::"
	}
	return ret + v.name
}

func (v *semanticAnalyzer) errResolve(thing Locatable, name unresolvedName) {
	v.err(thing, "Cannot resolve `%s`", name.String())
}

func (v *semanticAnalyzer) resolve(modules map[string]*Module) {
	v.modules = modules

	for _, node := range v.module.Nodes {
		node.resolve(v, v.module.GlobalScope)
	}
}

func (v *Block) resolve(sem *semanticAnalyzer, s *Scope) {
	for _, n := range v.Nodes {
		n.resolve(sem, v.scope)
	}
}

/**
 * Declarations
 */

func (v *VariableDecl) resolve(sem *semanticAnalyzer, s *Scope) {
	if v.Assignment != nil {
		v.Assignment.resolve(sem, s)
	}

	if v.Variable.Type != nil {
		v.Variable.Type = v.Variable.Type.resolveType(v, sem, s)
	}
}

func (v *StructDecl) resolve(sem *semanticAnalyzer, s *Scope) {
	v.Struct = v.Struct.resolveType(v, sem, s).(*StructType)
}

func (v *EnumDecl) resolve(sem *semanticAnalyzer, s *Scope) {
	// TODO: this is a noop, right?
}

func (v *TraitDecl) resolve(sem *semanticAnalyzer, s *Scope) {
	v.Trait = v.Trait.resolveType(v, sem, s).(*TraitType)
}

func (v *ImplDecl) resolve(sem *semanticAnalyzer, s *Scope) {
	for _, fun := range v.Functions {
		fun.resolve(sem, s)
	}
}

func (v *FunctionDecl) resolve(sem *semanticAnalyzer, s *Scope) {
	for _, param := range v.Function.Parameters {
		param.resolve(sem, s)
	}

	if v.Function.ReturnType != nil {
		v.Function.ReturnType = v.Function.ReturnType.resolveType(v, sem, s)
	}

	if !v.Prototype {
		v.Function.Body.resolve(sem, s)
	}
}

func (v *UseDecl) resolve(sem *semanticAnalyzer, s *Scope) {
	// later...
}

func (v *ModuleDecl) resolve(sem *semanticAnalyzer, s *Scope) {

}

/*
 * Statements
 */

func (v *ReturnStat) resolve(sem *semanticAnalyzer, s *Scope) {
	if v.Value != nil {
		v.Value.resolve(sem, s)
	}
}

func (v *IfStat) resolve(sem *semanticAnalyzer, s *Scope) {
	for _, expr := range v.Exprs {
		expr.resolve(sem, s)
	}

	for _, body := range v.Bodies {
		body.resolve(sem, s)
	}

	if v.Else != nil {
		v.Else.resolve(sem, s)
	}

}

func (v *BlockStat) resolve(sem *semanticAnalyzer, s *Scope) {
	v.Block.resolve(sem, s)
}

func (v *CallStat) resolve(sem *semanticAnalyzer, s *Scope) {
	v.Call.resolve(sem, s)
}

func (v *AssignStat) resolve(sem *semanticAnalyzer, s *Scope) {
	v.Assignment.resolve(sem, s)

	if v.Deref != nil {
		v.Deref.resolve(sem, s)
	} else if v.Access != nil {
		v.Access.resolve(sem, s)
	}
}

func (v *LoopStat) resolve(sem *semanticAnalyzer, s *Scope) {
	v.Body.resolve(sem, s)

	switch v.LoopType {
	case LOOP_TYPE_INFINITE:
	case LOOP_TYPE_CONDITIONAL:
		v.Condition.resolve(sem, s)
	default:
		panic("invalid loop type")
	}
}

func (v *MatchStat) resolve(sem *semanticAnalyzer, s *Scope) {
	v.Target.resolve(sem, s)

	for pattern, stmt := range v.Branches {
		pattern.resolve(sem, s)
		stmt.resolve(sem, s)
	}
}

/*
 * Expressions
 */

func (v *IntegerLiteral) resolve(sem *semanticAnalyzer, s *Scope)  {}
func (v *FloatingLiteral) resolve(sem *semanticAnalyzer, s *Scope) {}
func (v *StringLiteral) resolve(sem *semanticAnalyzer, s *Scope)   {}
func (v *RuneLiteral) resolve(sem *semanticAnalyzer, s *Scope)     {}
func (v *BoolLiteral) resolve(sem *semanticAnalyzer, s *Scope)     {}

func (v *UnaryExpr) resolve(sem *semanticAnalyzer, s *Scope) {
	v.Expr.resolve(sem, s)
}

func (v *BinaryExpr) resolve(sem *semanticAnalyzer, s *Scope) {
	v.Lhand.resolve(sem, s)
	v.Rhand.resolve(sem, s)
}

func (v *ArrayLiteral) resolve(sem *semanticAnalyzer, s *Scope) {
	for _, mem := range v.Members {
		mem.resolve(sem, s)
	}
}

func (v *CastExpr) resolve(sem *semanticAnalyzer, s *Scope) {
	v.Type = v.Type.resolveType(v, sem, s)
	v.Expr.resolve(sem, s)
}

func (v *CallExpr) resolve(sem *semanticAnalyzer, s *Scope) {
	v.Function = s.GetFunction(v.functionName)
	if v.Function == nil {
		sem.errResolve(v, v.functionName)
	}

	for _, arg := range v.Arguments {
		arg.resolve(sem, s)
	}
}

// this whole function is so bad
// why does this even exist
// please get around to rewriting this in a way that doesn't suck
// good luck changing anything here without rewriting everything
// at least it works
// - MovingtoMars
func (v *AccessExpr) resolve(sem *semanticAnalyzer, s *Scope) {
	// resolve the first name
	firstVar := v.Accesses[0]
	firstVar.Variable = s.GetVariable(firstVar.variableName)

	if v.Accesses[0].Variable == nil {
		sem.errResolve(v, firstVar.variableName)
	}

	// resolve everything else
	for i := 0; i < len(v.Accesses); i++ {
		switch v.Accesses[i].AccessType {
		case ACCESS_ARRAY:
			v.Accesses[i].Subscript.resolve(sem, s)

		case ACCESS_STRUCT:
			v.Accesses[i].Variable.Type = v.Accesses[i].Variable.Type.resolveType(v, sem, s)
			structType, ok := v.Accesses[i].Variable.Type.(*StructType)
			if !ok {
				sem.err(v, "Cannot access member of `%s`, type `%s`", v.Accesses[i].Variable.Name, v.Accesses[i].Variable.Type.TypeName())
			}

			memberName := v.Accesses[i+1].variableName.name // TODO check no mod access
			decl := structType.getVariableDecl(memberName)
			if decl == nil {
				sem.err(v, "Struct `%s` does not contain member `%s`", structType.TypeName(), memberName)
			}
			v.Accesses[i+1].Variable = decl.Variable
		case ACCESS_VARIABLE:
			// nothing to do

		case ACCESS_TUPLE:
			// nothing to do

		default:
			panic("unhandled access type")
		}
	}
}

func (v *AddressOfExpr) resolve(sem *semanticAnalyzer, s *Scope) {
	v.Access.resolve(sem, s)
}

func (v *DerefExpr) resolve(sem *semanticAnalyzer, s *Scope) {
	v.Expr.resolve(sem, s)
}

func (v *SizeofExpr) resolve(sem *semanticAnalyzer, s *Scope) {
	if v.Expr != nil {
		v.Expr.resolve(sem, s)
	} else if v.Type != nil {
		v.Type = v.Type.resolveType(v, sem, s)
	} else {
		panic("invalid state")
	}
}

func (v *TupleLiteral) resolve(sem *semanticAnalyzer, s *Scope) {
	for _, mem := range v.Members {
		mem.resolve(sem, s)
	}
}

func (v *DefaultMatchBranch) resolve(sem *semanticAnalyzer, s *Scope) {}

/*
 * Types
 */

func (v PrimitiveType) resolveType(src Locatable, sem *semanticAnalyzer, s *Scope) Type {
	return v
}

func (v *StructType) resolveType(src Locatable, sem *semanticAnalyzer, s *Scope) Type {
	for _, vari := range v.Variables {
		vari.resolve(sem, s)
	}
	return v
}

func (v ArrayType) resolveType(src Locatable, sem *semanticAnalyzer, s *Scope) Type {
	return arrayOf(v.MemberType.resolveType(src, sem, s))
}

func (v *TraitType) resolveType(src Locatable, sem *semanticAnalyzer, s *Scope) Type {
	for _, fun := range v.Functions {
		fun.resolve(sem, s)
	}
	return v
}

func (v PointerType) resolveType(src Locatable, sem *semanticAnalyzer, s *Scope) Type {
	return pointerTo(v.Addressee.resolveType(src, sem, s))
}

func (v *TupleType) resolveType(src Locatable, sem *semanticAnalyzer, s *Scope) Type {
	for idx, mem := range v.Members {
		v.Members[idx] = mem.resolveType(src, sem, s)
	}
	return v
}

func (v *UnresolvedType) resolveType(src Locatable, sem *semanticAnalyzer, s *Scope) Type {
	typ := s.GetType(v.Name)
	if typ == nil {
		sem.err(src, "Cannot resolve `%s`", v.Name)
	}
	return typ
}
<|endoftext|>


<commit_before>package pass

import (
	"fmt"
	"log"
	"os"
	"os/user"
	"path/filepath"
	"testing"
)

func TestDefaultStorePath(t *testing.T) {
	var home, expected, actual string

	usr, err := user.Current()

	if err != nil {
		t.Error(err)
	}

	home = usr.HomeDir

	// default directory
	os.Setenv("PASSWORD_STORE_DIR", "")
	expected = filepath.Join(home, ".password-store")
	actual, _ = defaultStorePath()
	if expected != actual {
		t.Errorf("%s does not match %s", expected, actual)
	}

	// custom directory from $PASSWORD_STORE_DIR
	expected, err = filepath.Abs("browserpass-test")
	if err != nil {
		t.Error(err)
	}

	fmt.Println(expected)
	os.Mkdir(expected, os.ModePerm)
	os.Setenv("PASSWORD_STORE_DIR", expected)
	actual, _ = defaultStorePath()
	if expected != actual {
		t.Errorf("%s does not match %s", expected, actual)
	}

	// clean-up
	os.Setenv("PASSWORD_STORE_DIR", "")
	os.Remove(expected)
}

func TestDiskStore_Search_nomatch(t *testing.T) {
	s, err := NewDefaultStore()
	if err != nil {
		t.Fatal(err)
	}

	domain := "this-most-definitely-does-not-exist"
	logins, err := s.Search(domain)
	if err != nil {
		t.Fatal(err)
	}
	if len(logins) > 0 {
		t.Errorf("%s yielded results, but it should not", domain)
	}
}

func TestDiskStoreSearch(t *testing.T) {
	store := diskStore{"test_store"}
	targetDomain := "abc.com"
	testDomains := []string{"abc.com", "test.abc.com", "testing.test.abc.com"}
	for _, domain := range testDomains {
		searchResults, err := store.Search(domain)
		if err != nil {
			t.Fatal(err)
		}
		// check if result contains abc.com
		found := false
		for _, searchResult := range searchResults {
			if searchResult == targetDomain {
				found = true
				break
			}
		}
		if found != true {
			log.Fatalf("Couldn't find %v in %v", domain, searchResults)
		}
	}
}
<commit_msg>Fix test using log.Fatalf for failure<commit_after>package pass

import (
	"fmt"
	"os"
	"os/user"
	"path/filepath"
	"testing"
)

func TestDefaultStorePath(t *testing.T) {
	var home, expected, actual string

	usr, err := user.Current()

	if err != nil {
		t.Error(err)
	}

	home = usr.HomeDir

	// default directory
	os.Setenv("PASSWORD_STORE_DIR", "")
	expected = filepath.Join(home, ".password-store")
	actual, _ = defaultStorePath()
	if expected != actual {
		t.Errorf("%s does not match %s", expected, actual)
	}

	// custom directory from $PASSWORD_STORE_DIR
	expected, err = filepath.Abs("browserpass-test")
	if err != nil {
		t.Error(err)
	}

	fmt.Println(expected)
	os.Mkdir(expected, os.ModePerm)
	os.Setenv("PASSWORD_STORE_DIR", expected)
	actual, _ = defaultStorePath()
	if expected != actual {
		t.Errorf("%s does not match %s", expected, actual)
	}

	// clean-up
	os.Setenv("PASSWORD_STORE_DIR", "")
	os.Remove(expected)
}

func TestDiskStore_Search_nomatch(t *testing.T) {
	s, err := NewDefaultStore()
	if err != nil {
		t.Fatal(err)
	}

	domain := "this-most-definitely-does-not-exist"
	logins, err := s.Search(domain)
	if err != nil {
		t.Fatal(err)
	}
	if len(logins) > 0 {
		t.Errorf("%s yielded results, but it should not", domain)
	}
}

func TestDiskStoreSearch(t *testing.T) {
	store := diskStore{"test_store"}
	targetDomain := "abc.com"
	testDomains := []string{"abc.com", "test.abc.com", "testing.test.abc.com"}
	for _, domain := range testDomains {
		searchResults, err := store.Search(domain)
		if err != nil {
			t.Fatal(err)
		}
		// check if result contains abc.com
		found := false
		for _, searchResult := range searchResults {
			if searchResult == targetDomain {
				found = true
				break
			}
		}
		if found != true {
			t.Fatalf("Couldn't find %v in %v", domain, searchResults)
		}
	}
}
<|endoftext|>


<commit_before>package pbs

import (
	"encoding/json"
	"fmt"
	"math/rand"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/golang/glog"
	"github.com/spf13/viper"
	"golang.org/x/net/publicsuffix"

	"github.com/prebid/openrtb"
	"github.com/prebid/prebid-server/cache"
	"github.com/prebid/prebid-server/prebid"
)

const MAX_BIDDERS = 8

type ConfigCache interface {
	LoadConfig(string) ([]Bids, error)
}

type Bids struct {
	BidderCode string          `json:"bidder"`
	BidID      string          `json:"bid_id"`
	Params     json.RawMessage `json:"params"`
}

type AdUnit struct {
	Code     string           `json:"code"`
	TopFrame int8             `json:"is_top_frame"`
	Sizes    []openrtb.Format `json:"sizes"`
	Bids     []Bids           `json:"bids"`
	ConfigID string           `json:"config_id"`
}

type PBSAdUnit struct {
	Sizes    []openrtb.Format
	TopFrame int8
	Code     string
	BidID    string
	Params   json.RawMessage
}

type PBSBidder struct {
	BidderCode   string         `json:"bidder"`
	AdUnitCode   string         `json:"ad_unit,omitempty"` // for index to dedup responses
	ResponseTime int            `json:"response_time_ms,omitempty"`
	NumBids      int            `json:"num_bids,omitempty"`
	Error        string         `json:"error,omitempty"`
	NoCookie     bool           `json:"no_cookie,omitempty"`
	NoBid        bool           `json:"no_bid,omitempty"`
	UsersyncInfo *UsersyncInfo  `json:"usersync,omitempty"`
	Debug        []*BidderDebug `json:"debug,omitempty"`

	AdUnits []PBSAdUnit `json:"-"`
}

func (bidder *PBSBidder) LookupBidID(Code string) string {
	for _, unit := range bidder.AdUnits {
		if unit.Code == Code {
			return unit.BidID
		}
	}
	return ""
}

type PBSRequest struct {
	AccountID     string          `json:"account_id"`
	Tid           string          `json:"tid"`
	CacheMarkup   int8            `json:"cache_markup"`
	SortBids      int8            `json:"sort_bids"`
	MaxKeyLength  int8            `json:"max_key_length"`
	Secure        int8            `json:"secure"`
	TimeoutMillis uint64          `json:"timeout_millis"`
	AdUnits       []AdUnit        `json:"ad_units"`
	IsDebug       bool            `json:"is_debug"`
	App           *openrtb.App    `json:"app"`
	Device        *openrtb.Device `json:"device"`

	// internal
	Bidders []*PBSBidder      `json:"-"`
	UserIDs map[string]string `json:"-"`
	Url     string            `json:"-"`
	Domain  string            `json:"-"`
	Start   time.Time
}

func ConfigGet(cache cache.Cache, id string) ([]Bids, error) {
	conf, err := cache.Config().Get(id)
	if err != nil {
		return nil, err
	}

	bids := make([]Bids, 0)
	err = json.Unmarshal([]byte(conf), &bids)
	if err != nil {
		return nil, err
	}

	return bids, nil
}

func ParsePBSRequest(r *http.Request, cache cache.Cache) (*PBSRequest, error) {
	defer r.Body.Close()

	pbsReq := &PBSRequest{}
	err := json.NewDecoder(r.Body).Decode(&pbsReq)
	if err != nil {
		return nil, err
	}
	pbsReq.Start = time.Now()

	if len(pbsReq.AdUnits) == 0 {
		return nil, fmt.Errorf("No ad units specified")
	}

	if pbsReq.TimeoutMillis == 0 || pbsReq.TimeoutMillis > 2000 {
		pbsReq.TimeoutMillis = uint64(viper.GetInt("default_timeout_ms"))
	}

	if pbsReq.Device == nil {
		pbsReq.Device = &openrtb.Device{}
	}

	// use client-side data for web requests
	if pbsReq.App == nil {
		pc := ParseUIDCookie(r)
		pbsReq.UserIDs = pc.UIDs

		// this would be for the shared adnxs.com domain
		if anid, err := r.Cookie("uuid2"); err == nil {
			pbsReq.UserIDs["adnxs"] = anid.Value
		}

		pbsReq.Device.UA = r.Header.Get("User-Agent")
		pbsReq.Device.IP = prebid.GetIP(r)

		pbsReq.Url = r.Header.Get("Referer") // must be specified in the header
		// TODO: this should explicitly put us in test mode
		if r.FormValue("url_override") != "" {
			pbsReq.Url = r.FormValue("url_override")
		}
		if strings.Index(pbsReq.Url, "http") == -1 {
			pbsReq.Url = fmt.Sprintf("http://%s", pbsReq.Url)
		}

		url, err := url.Parse(pbsReq.Url)
		if err != nil {
			return nil, fmt.Errorf("Invalid URL '%s': %v", pbsReq.Url, err)
		}

		if url.Host == "" {
			return nil, fmt.Errorf("Host not found from URL '%v'", url)
		}

		pbsReq.Domain, err = publicsuffix.EffectiveTLDPlusOne(url.Host)
		if err != nil {
			return nil, fmt.Errorf("Invalid URL '%s': %v", url.Host, err)
		}

		// all domains must be in the whitelist
		_, err = cache.Domains().Get(pbsReq.Domain)
		if err != nil {
			return nil, fmt.Errorf("Invalid URL %s", pbsReq.Domain)
		}
	} else {

		_, err = cache.Apps().Get(pbsReq.App.Bundle)
		if err != nil {
			return nil, fmt.Errorf("Invalid app bundle %s", pbsReq.App.Bundle)
		}

	}

	if r.FormValue("debug") == "1" {
		pbsReq.IsDebug = true
	}

	if prebid.IsSecure(r) {
		pbsReq.Secure = 1
	}

	pbsReq.Bidders = make([]*PBSBidder, 0, MAX_BIDDERS)

	for _, unit := range pbsReq.AdUnits {
		bidders := unit.Bids
		if unit.ConfigID != "" {
			bidders, err = ConfigGet(cache, unit.ConfigID)
			if err != nil {
				// proceed with other ad units
				glog.Infof("Unable to load config '%s': %v", unit.ConfigID, err)
				continue
			}
		}

		if glog.V(2) {
			glog.Infof("Ad unit %s has %d bidders for %d sizes", unit.Code, len(bidders), len(unit.Sizes))
		}

		for _, b := range bidders {
			var bidder *PBSBidder
			// index requires a different request for each ad unit
			if b.BidderCode != "indexExchange" {
				for _, pb := range pbsReq.Bidders {
					if pb.BidderCode == b.BidderCode {
						bidder = pb
					}
				}
			}
			if bidder == nil {
				bidder = &PBSBidder{BidderCode: b.BidderCode}
				if b.BidderCode == "indexExchange" {
					bidder.AdUnitCode = unit.Code
				}
				pbsReq.Bidders = append(pbsReq.Bidders, bidder)
			}
			if b.BidID == "" {
				b.BidID = fmt.Sprintf("%d", rand.Int63())
			}

			pau := PBSAdUnit{
				Sizes:    unit.Sizes,
				TopFrame: unit.TopFrame,
				Code:     unit.Code,
				Params:   b.Params,
				BidID:    b.BidID,
			}

			bidder.AdUnits = append(bidder.AdUnits, pau)
		}
	}

	return pbsReq, nil
}

func (req PBSRequest) Elapsed() int {
	return int(time.Since(req.Start) / 1000000)
}

func (req PBSRequest) GetUserID(BidderCode string) string {
	if uid, ok := req.UserIDs[BidderCode]; ok {
		return uid
	}
	return ""
}

func (p PBSRequest) String() string {
	b, _ := json.MarshalIndent(p, "", "    ")
	return string(b)
}
<commit_msg>Remove domain/app whitelisting (#93)<commit_after>package pbs

import (
	"encoding/json"
	"fmt"
	"math/rand"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/golang/glog"
	"github.com/spf13/viper"
	"golang.org/x/net/publicsuffix"

	"github.com/prebid/openrtb"
	"github.com/prebid/prebid-server/cache"
	"github.com/prebid/prebid-server/prebid"
)

const MAX_BIDDERS = 8

type ConfigCache interface {
	LoadConfig(string) ([]Bids, error)
}

type Bids struct {
	BidderCode string          `json:"bidder"`
	BidID      string          `json:"bid_id"`
	Params     json.RawMessage `json:"params"`
}

type AdUnit struct {
	Code     string           `json:"code"`
	TopFrame int8             `json:"is_top_frame"`
	Sizes    []openrtb.Format `json:"sizes"`
	Bids     []Bids           `json:"bids"`
	ConfigID string           `json:"config_id"`
}

type PBSAdUnit struct {
	Sizes    []openrtb.Format
	TopFrame int8
	Code     string
	BidID    string
	Params   json.RawMessage
}

type PBSBidder struct {
	BidderCode   string         `json:"bidder"`
	AdUnitCode   string         `json:"ad_unit,omitempty"` // for index to dedup responses
	ResponseTime int            `json:"response_time_ms,omitempty"`
	NumBids      int            `json:"num_bids,omitempty"`
	Error        string         `json:"error,omitempty"`
	NoCookie     bool           `json:"no_cookie,omitempty"`
	NoBid        bool           `json:"no_bid,omitempty"`
	UsersyncInfo *UsersyncInfo  `json:"usersync,omitempty"`
	Debug        []*BidderDebug `json:"debug,omitempty"`

	AdUnits []PBSAdUnit `json:"-"`
}

func (bidder *PBSBidder) LookupBidID(Code string) string {
	for _, unit := range bidder.AdUnits {
		if unit.Code == Code {
			return unit.BidID
		}
	}
	return ""
}

type PBSRequest struct {
	AccountID     string          `json:"account_id"`
	Tid           string          `json:"tid"`
	CacheMarkup   int8            `json:"cache_markup"`
	SortBids      int8            `json:"sort_bids"`
	MaxKeyLength  int8            `json:"max_key_length"`
	Secure        int8            `json:"secure"`
	TimeoutMillis uint64          `json:"timeout_millis"`
	AdUnits       []AdUnit        `json:"ad_units"`
	IsDebug       bool            `json:"is_debug"`
	App           *openrtb.App    `json:"app"`
	Device        *openrtb.Device `json:"device"`

	// internal
	Bidders []*PBSBidder      `json:"-"`
	UserIDs map[string]string `json:"-"`
	Url     string            `json:"-"`
	Domain  string            `json:"-"`
	Start   time.Time
}

func ConfigGet(cache cache.Cache, id string) ([]Bids, error) {
	conf, err := cache.Config().Get(id)
	if err != nil {
		return nil, err
	}

	bids := make([]Bids, 0)
	err = json.Unmarshal([]byte(conf), &bids)
	if err != nil {
		return nil, err
	}

	return bids, nil
}

func ParsePBSRequest(r *http.Request, cache cache.Cache) (*PBSRequest, error) {
	defer r.Body.Close()

	pbsReq := &PBSRequest{}
	err := json.NewDecoder(r.Body).Decode(&pbsReq)
	if err != nil {
		return nil, err
	}
	pbsReq.Start = time.Now()

	if len(pbsReq.AdUnits) == 0 {
		return nil, fmt.Errorf("No ad units specified")
	}

	if pbsReq.TimeoutMillis == 0 || pbsReq.TimeoutMillis > 2000 {
		pbsReq.TimeoutMillis = uint64(viper.GetInt("default_timeout_ms"))
	}

	if pbsReq.Device == nil {
		pbsReq.Device = &openrtb.Device{}
	}

	// use client-side data for web requests
	if pbsReq.App == nil {
		pc := ParseUIDCookie(r)
		pbsReq.UserIDs = pc.UIDs

		// this would be for the shared adnxs.com domain
		if anid, err := r.Cookie("uuid2"); err == nil {
			pbsReq.UserIDs["adnxs"] = anid.Value
		}

		pbsReq.Device.UA = r.Header.Get("User-Agent")
		pbsReq.Device.IP = prebid.GetIP(r)

		pbsReq.Url = r.Header.Get("Referer") // must be specified in the header
		// TODO: this should explicitly put us in test mode
		if r.FormValue("url_override") != "" {
			pbsReq.Url = r.FormValue("url_override")
		}
		if strings.Index(pbsReq.Url, "http") == -1 {
			pbsReq.Url = fmt.Sprintf("http://%s", pbsReq.Url)
		}

		url, err := url.Parse(pbsReq.Url)
		if err != nil {
			return nil, fmt.Errorf("Invalid URL '%s': %v", pbsReq.Url, err)
		}

		if url.Host == "" {
			return nil, fmt.Errorf("Host not found from URL '%v'", url)
		}

		pbsReq.Domain, err = publicsuffix.EffectiveTLDPlusOne(url.Host)
		if err != nil {
			return nil, fmt.Errorf("Invalid URL '%s': %v", url.Host, err)
		}
	}

	if r.FormValue("debug") == "1" {
		pbsReq.IsDebug = true
	}

	if prebid.IsSecure(r) {
		pbsReq.Secure = 1
	}

	pbsReq.Bidders = make([]*PBSBidder, 0, MAX_BIDDERS)

	for _, unit := range pbsReq.AdUnits {
		bidders := unit.Bids
		if unit.ConfigID != "" {
			bidders, err = ConfigGet(cache, unit.ConfigID)
			if err != nil {
				// proceed with other ad units
				glog.Infof("Unable to load config '%s': %v", unit.ConfigID, err)
				continue
			}
		}

		if glog.V(2) {
			glog.Infof("Ad unit %s has %d bidders for %d sizes", unit.Code, len(bidders), len(unit.Sizes))
		}

		for _, b := range bidders {
			var bidder *PBSBidder
			// index requires a different request for each ad unit
			if b.BidderCode != "indexExchange" {
				for _, pb := range pbsReq.Bidders {
					if pb.BidderCode == b.BidderCode {
						bidder = pb
					}
				}
			}
			if bidder == nil {
				bidder = &PBSBidder{BidderCode: b.BidderCode}
				if b.BidderCode == "indexExchange" {
					bidder.AdUnitCode = unit.Code
				}
				pbsReq.Bidders = append(pbsReq.Bidders, bidder)
			}
			if b.BidID == "" {
				b.BidID = fmt.Sprintf("%d", rand.Int63())
			}

			pau := PBSAdUnit{
				Sizes:    unit.Sizes,
				TopFrame: unit.TopFrame,
				Code:     unit.Code,
				Params:   b.Params,
				BidID:    b.BidID,
			}

			bidder.AdUnits = append(bidder.AdUnits, pau)
		}
	}

	return pbsReq, nil
}

func (req PBSRequest) Elapsed() int {
	return int(time.Since(req.Start) / 1000000)
}

func (req PBSRequest) GetUserID(BidderCode string) string {
	if uid, ok := req.UserIDs[BidderCode]; ok {
		return uid
	}
	return ""
}

func (p PBSRequest) String() string {
	b, _ := json.MarshalIndent(p, "", "    ")
	return string(b)
}
<|endoftext|>