<commit_before>package main

import "os"

const (
	raw = "https://raw.githubusercontent.com/gonet2/libs/master/services/services.go"
)

func main() {
	if len(os.Args) <= 1 {
		return
	}
	// resp, err := http.Get(raw)
	// if err != nil {
	// 	log.Fatal(err)
	// }
}
<commit_msg>modify discover raw path<commit_after>package main

import "os"

const (
	raw = "https://raw.githubusercontent.com/amorwilliams/bodoni/master/lib/services/services.go"
)

func main() {
	if len(os.Args) <= 1 {
		return
	}
	// resp, err := http.Get(raw)
	// if err != nil {
	// 	log.Fatal(err)
	// }
}
<|endoftext|>


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

package docker

import (
	"fmt"

	dtesting "github.com/fsouza/go-dockerclient/testing"
	"github.com/tsuru/docker-cluster/cluster"
	"github.com/tsuru/tsuru/action"
	"github.com/tsuru/tsuru/app"
	"github.com/tsuru/tsuru/net"
	"github.com/tsuru/tsuru/provision"
	"github.com/tsuru/tsuru/provision/provisiontest"
	"github.com/tsuru/tsuru/safe"
	"gopkg.in/check.v1"
)

func (s *S) TestRebalanceContainersManyAppsSegStress(c *check.C) {
	var nodes []cluster.Node
	var nodeHosts []string
	for i := 0; i < 6; i++ {
		newIp := fmt.Sprintf("127.0.0.%d", i+1)
		otherServer, err := dtesting.NewServer(newIp+":0", nil, nil)
		c.Assert(err, check.IsNil)
		defer otherServer.Stop()
		nodes = append(nodes, cluster.Node{Address: otherServer.URL(), Metadata: map[string]string{"pool": "pool1"}})
		nodeHosts = append(nodeHosts, net.URLToHost(otherServer.URL()))
	}
	var err error
	p := &dockerProvisioner{}
	err = p.Initialize()
	c.Assert(err, check.IsNil)
	p.storage, err = buildClusterStorage()
	c.Assert(err, check.IsNil)
	p.scheduler = &segregatedScheduler{provisioner: p}
	p.cluster, err = cluster.New(p.scheduler, p.storage, nodes...)
	c.Assert(err, check.IsNil)
	opts := provision.AddPoolOptions{Name: "pool1"}
	err = provision.AddPool(opts)
	c.Assert(err, check.IsNil)
	err = provision.AddTeamsToPool("pool1", []string{"team1"})
	c.Assert(err, check.IsNil)
	variation := []int{10, 20, 30, 40, 50, 100}
	maxContainers := 40
	for i := 0; i < maxContainers; i++ {
		appName := fmt.Sprintf("myapp-%d", i)
		err = s.newFakeImage(p, "tsuru/app-"+appName, nil)
		c.Assert(err, check.IsNil)
		appInstance := provisiontest.NewFakeApp(appName, "python", 0)
		defer p.Destroy(appInstance)
		p.Provision(appInstance)
		imageId, aErr := appCurrentImageName(appInstance.GetName())
		c.Assert(aErr, check.IsNil)
		var chosenNode string
		for j := range variation {
			if i < (maxContainers*variation[j])/100 {
				chosenNode = nodeHosts[j]
				break
			}
		}
		args := changeUnitsPipelineArgs{
			app:         appInstance,
			toAdd:       map[string]*containersToAdd{"web": {Quantity: 6}},
			imageId:     imageId,
			provisioner: p,
			toHost:      chosenNode,
		}
		pipeline := action.NewPipeline(
			&provisionAddUnitsToHost,
			&bindAndHealthcheck,
			&addNewRoutes,
			&setRouterHealthcheck,
			&updateAppImage,
		)
		err = pipeline.Execute(args)
		c.Assert(err, check.IsNil)
		appStruct := &app.App{
			Name:      appInstance.GetName(),
			TeamOwner: "team1",
			Pool:      "pool1",
		}
		err = s.storage.Apps().Insert(appStruct)
		c.Assert(err, check.IsNil)
	}
	buf := safe.NewBuffer(nil)
	cloneProv, err := p.rebalanceContainersByFilter(buf, []string{}, map[string]string{"pool": "pool1"}, false)
	c.Assert(err, check.IsNil)
	c.Assert(cloneProv.cluster.Healer, check.Equals, p.cluster.Healer)
	for i := range nodeHosts {
		conts, err := p.listContainersByHost(nodeHosts[i])
		c.Assert(err, check.IsNil)
		c.Logf("containers in %q: %d", nodeHosts[i], len(conts))
		c.Check(len(conts) >= 39 || len(conts) <= 41, check.Equals, true)
	}
}
<commit_msg>provision/docker/containers_linux_test: fix cluster initialization<commit_after>// Copyright 2016 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package docker

import (
	"fmt"

	dtesting "github.com/fsouza/go-dockerclient/testing"
	"github.com/tsuru/docker-cluster/cluster"
	"github.com/tsuru/tsuru/action"
	"github.com/tsuru/tsuru/app"
	"github.com/tsuru/tsuru/net"
	"github.com/tsuru/tsuru/provision"
	"github.com/tsuru/tsuru/provision/provisiontest"
	"github.com/tsuru/tsuru/safe"
	"gopkg.in/check.v1"
)

func (s *S) TestRebalanceContainersManyAppsSegStress(c *check.C) {
	var nodes []cluster.Node
	var nodeHosts []string
	for i := 0; i < 6; i++ {
		newIp := fmt.Sprintf("127.0.0.%d", i+1)
		otherServer, err := dtesting.NewServer(newIp+":0", nil, nil)
		c.Assert(err, check.IsNil)
		defer otherServer.Stop()
		nodes = append(nodes, cluster.Node{Address: otherServer.URL(), Metadata: map[string]string{"pool": "pool1"}})
		nodeHosts = append(nodeHosts, net.URLToHost(otherServer.URL()))
	}
	var err error
	p := &dockerProvisioner{}
	err = p.Initialize()
	c.Assert(err, check.IsNil)
	p.storage, err = buildClusterStorage()
	c.Assert(err, check.IsNil)
	p.scheduler = &segregatedScheduler{provisioner: p}
	p.cluster, err = cluster.New(p.scheduler, p.storage, "", nodes...)
	c.Assert(err, check.IsNil)
	opts := provision.AddPoolOptions{Name: "pool1"}
	err = provision.AddPool(opts)
	c.Assert(err, check.IsNil)
	err = provision.AddTeamsToPool("pool1", []string{"team1"})
	c.Assert(err, check.IsNil)
	variation := []int{10, 20, 30, 40, 50, 100}
	maxContainers := 40
	for i := 0; i < maxContainers; i++ {
		appName := fmt.Sprintf("myapp-%d", i)
		err = s.newFakeImage(p, "tsuru/app-"+appName, nil)
		c.Assert(err, check.IsNil)
		appInstance := provisiontest.NewFakeApp(appName, "python", 0)
		defer p.Destroy(appInstance)
		p.Provision(appInstance)
		imageId, aErr := appCurrentImageName(appInstance.GetName())
		c.Assert(aErr, check.IsNil)
		var chosenNode string
		for j := range variation {
			if i < (maxContainers*variation[j])/100 {
				chosenNode = nodeHosts[j]
				break
			}
		}
		args := changeUnitsPipelineArgs{
			app:         appInstance,
			toAdd:       map[string]*containersToAdd{"web": {Quantity: 6}},
			imageId:     imageId,
			provisioner: p,
			toHost:      chosenNode,
		}
		pipeline := action.NewPipeline(
			&provisionAddUnitsToHost,
			&bindAndHealthcheck,
			&addNewRoutes,
			&setRouterHealthcheck,
			&updateAppImage,
		)
		err = pipeline.Execute(args)
		c.Assert(err, check.IsNil)
		appStruct := &app.App{
			Name:      appInstance.GetName(),
			TeamOwner: "team1",
			Pool:      "pool1",
		}
		err = s.storage.Apps().Insert(appStruct)
		c.Assert(err, check.IsNil)
	}
	buf := safe.NewBuffer(nil)
	cloneProv, err := p.rebalanceContainersByFilter(buf, []string{}, map[string]string{"pool": "pool1"}, false)
	c.Assert(err, check.IsNil)
	c.Assert(cloneProv.cluster.Healer, check.Equals, p.cluster.Healer)
	for i := range nodeHosts {
		conts, err := p.listContainersByHost(nodeHosts[i])
		c.Assert(err, check.IsNil)
		c.Logf("containers in %q: %d", nodeHosts[i], len(conts))
		c.Check(len(conts) >= 39 || len(conts) <= 41, check.Equals, true)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"strconv"
	"strings"
	"time"

	"github.com/go-telegram-bot-api/telegram-bot-api"
)

func handleCallbackQuery(bot *tgbotapi.BotAPI, update tgbotapi.Update, st Store) error {
	if update.CallbackQuery.Data == "dummy" {
		callbackConfig := tgbotapi.NewCallback(
			update.CallbackQuery.ID,
			"")
		_, err := bot.AnswerCallbackQuery(callbackConfig)
		if err != nil {
			return fmt.Errorf("could not send answer to callback query: %v", err)
		}

		return nil
	}

	if update.CallbackQuery.Data[0] == 'e' {
		return handlePollEditQuery(bot, update, st)
	}

	if update.CallbackQuery.Data == createPollQuery {
		return sendNewQuestionMessage(bot, update, st)
	}

	if strings.Contains(update.CallbackQuery.Data, pollDoneQuery) {
		return handlePollDoneQuery(bot, update, st)
	}

	pollid, optionid, err := parseQueryPayload(update)
	if err != nil {
		return fmt.Errorf("could not parse query payload: %v", err)
	}

	if update.CallbackQuery.InlineMessageID != "" {
		if err := st.AddInlineMsgToPoll(pollid, update.CallbackQuery.InlineMessageID); err != nil {
			return fmt.Errorf("could not add inline message to poll: %v", err)
		}
	}

	p, err := st.GetPoll(pollid)
	if err != nil {
		return fmt.Errorf("could not get poll: %v", err)
	}
	if isInactive(p) {
		callbackConfig := tgbotapi.NewCallback(
			update.CallbackQuery.ID,
			"This poll is inactive.")
		_, err = bot.AnswerCallbackQuery(callbackConfig)
		if err != nil {
			return fmt.Errorf("could not send answer to callback query: %v", err)
		}
		return fmt.Errorf("poll %d is inactive", pollid)
	}

	newAnswer := answer{
		UserID:   update.CallbackQuery.From.ID,
		PollID:   pollid,
		OptionID: optionid,
	}
	unvoted, err := st.SaveAnswer(p, newAnswer)
	if err != nil {
		return fmt.Errorf("could not save answers: %v", err)
	}
	// polls were changed
	p, err = st.GetPoll(pollid)
	if err != nil {
		return fmt.Errorf("could not get poll: %v", err)
	}

	var choice option
	for _, o := range p.Options {
		if o.ID == newAnswer.OptionID {
			choice = o
		}
	}

	pollsToUpdate.enqueue(p.ID)

	popupText := fmt.Sprintf(`You voted for "%s"`, choice.Text)
	if unvoted {
		popupText = fmt.Sprintf("Seems like you deleted your vote.")
	}

	callbackConfig := tgbotapi.NewCallback(
		update.CallbackQuery.ID,
		popupText)
	_, err = bot.AnswerCallbackQuery(callbackConfig)
	if err != nil {
		return fmt.Errorf("could not send answer to callback query: %v", err)
	}

	return nil
}

func updatePollMessages(bot *tgbotapi.BotAPI, pollid int, st Store) error {
	p, err := st.GetPoll(pollid)
	if err != nil {
		return fmt.Errorf("could not get poll: %v", err)
	}

	//msgs, err := st.GetAllPollMsg(pollid)
	//if err != nil {
	//return fmt.Errorf("could not get all pollmsgs: %v", err)
	//}

	listing := buildPollListing(p, st)

	var ed tgbotapi.EditMessageTextConfig
	ed.Text = listing
	ed.ParseMode = tgbotapi.ModeHTML

	if !isInactive(p) {
		ed.ReplyMarkup = buildPollMarkup(p)
	}

	//for _, msg := range msgs {
	//ed.ChatID = msg.ChatID
	//ed.MessageID = msg.MessageID

	//_, err = bot.Send(ed)
	//if err != nil {
	//log.Printf("Could not edit message: %v \nThe message was: %s\n", err, ed.Text)
	//log.Printf("Could not edit message: %v\n", err)
	//splits := strings.Split(ed.Text, "\n")

	//ed.Text = ""
	//for _, l := range splits {
	//if !strings.HasPrefix(l, "\u251C") && !strings.HasPrefix(l, "\u2514") {
	//ed.Text += l + "\n"
	//}
	//}
	//log.Printf("try again:\n %s", ed.Text)
	//_, err = bot.Send(ed)
	//if err != nil {
	//log.Printf("could not update message: %v\n", err)
	//continue
	//}
	//}
	//}

	// reset
	ed.ChatID = 0
	ed.MessageID = 0

	msgs, err := st.GetAllPollInlineMsg(p.ID)
	if err != nil {
		return fmt.Errorf("could not get all poll inline messages: %v", err)
	}

	for _, msg := range msgs {
		ed.Text = listing
		ed.InlineMessageID = msg.InlineMessageID
		_, err := bot.Send(ed)
		if err != nil {
			if strings.Contains(err.Error(), "MESSAGE_ID_INVALID") {
				log.Printf("Remove inline message %s\n", msg.InlineMessageID)
				st.RemoveInlineMsg(msg.InlineMessageID)
			}
			// if strings.Contains(err.Error(), "chat not found") {
			// 	log.Printf("Remove inline message %s\n", msg.InlineMessageID)
			// 	st.RemoveInlineMsg(msg.InlineMessageID)
			// }
			if strings.Contains(err.Error(), "message is not modified") {
				continue
			}
			time.Sleep(20 * time.Millisecond)
			log.Printf("\n\n\nCould not edit inline message: %v \nThe message was: %s\n", err, ed.Text)
			log.Printf("Could not edit inline message: %v\n", err)
			splits := strings.Split(ed.Text, "\n")

			ed.Text = ""
			for _, l := range splits {
				if !strings.HasPrefix(l, "\u251C") && !strings.HasPrefix(l, "\u2514") {
					ed.Text += l + "\n"
				}
			}
			log.Printf("try again:\n %s", ed.Text)
			_, err = bot.Send(ed)
			if err != nil {
				log.Printf("could not update inline message: %v\n", err)
				continue
			}
		}
		time.Sleep(20 * time.Millisecond)
	}

	return nil
}

func handlePollDoneQuery(bot *tgbotapi.BotAPI, update tgbotapi.Update, st Store) error {
	splits := strings.Split(update.CallbackQuery.Data, ":")
	if len(splits) < 2 {
		return fmt.Errorf("query did not contain the pollid")
	}
	pollid, err := strconv.Atoi(splits[1])
	if err != nil {
		return fmt.Errorf("could not convert string payload to int: %v", err)
	}

	p, err := st.GetPoll(pollid)
	if err != nil {
		return fmt.Errorf("could not get poll: %v", err)
	}
	_, err = postPoll(bot, p, int64(update.CallbackQuery.From.ID))
	if err != nil {
		return fmt.Errorf("could not post finished poll: %v", err)
	}
	err = st.SaveState(update.CallbackQuery.From.ID, p.ID, pollDone)
	if err != nil {
		return fmt.Errorf("could not change state to poll done: %v", err)
	}
	return nil
}

func handlePollEditQuery(bot *tgbotapi.BotAPI, update tgbotapi.Update, st Store) error {
	splits := strings.Split(update.CallbackQuery.Data, ":")
	if len(splits) < 3 {
		log.Println(splits)
		return fmt.Errorf("query wrongly formatted")
	}
	pollid, err := strconv.Atoi(splits[1])
	if err != nil {
		return fmt.Errorf("could not convert string payload to int: %v", err)
	}

	var p *poll
	noNewer := false
	noOlder := false
	toggleInactive := false
	toggleMultipleChoice := false
	switch splits[2] {
	case "+":
		p, err = st.GetPollNewer(pollid, update.CallbackQuery.From.ID)
		if err != nil {
			log.Printf("could not get older poll: %v\n", err)
			noNewer = true
		}
	case "-":
		p, err = st.GetPollOlder(pollid, update.CallbackQuery.From.ID)
		if err != nil {
			log.Printf("could not get older poll: %v\n", err)
			noOlder = true
		}
	case "c":
		p, err = st.GetPoll(pollid)
		if err != nil {
			log.Printf("could not get poll: %v\n", err)
		}
		toggleInactive = true
	case "m":
		p, err = st.GetPoll(pollid)
		if err != nil {
			log.Printf("could not get poll: %v\n", err)
		}
		toggleMultipleChoice = true
	case "o":
		state := waitingForOption
		err = st.SaveState(update.CallbackQuery.From.ID, pollid, state)
		if err != nil {
			return fmt.Errorf("could not save state: %v", err)
		}

		msg := tgbotapi.NewMessage(update.CallbackQuery.Message.Chat.ID, locAddOption)
		_, err = bot.Send(&msg)
		if err != nil {
			return fmt.Errorf("could not send message: %v", err)
		}
		return nil
	case "q":
		state := editQuestion
		err = st.SaveState(update.CallbackQuery.From.ID, pollid, state)
		if err != nil {
			return fmt.Errorf("could not save state: %v", err)
		}

		msg := tgbotapi.NewMessage(update.CallbackQuery.Message.Chat.ID, locEditQuestion)
		_, err = bot.Send(&msg)
		if err != nil {
			return fmt.Errorf("could not send message: %v", err)
		}
		return nil
	default:
		return fmt.Errorf("query wrongly formatted")
	}
	if err != nil {
		p, err = st.GetPoll(pollid)
		if err != nil {
			return fmt.Errorf("could not get poll by poll id: %v", err)
		}
	}

	// danger! malicious client could send pollid from another user in query
	if p.UserID != update.CallbackQuery.From.ID {
		return fmt.Errorf("user does not own poll: %v", err)
	}

	if toggleInactive {
		p.Inactive = 1 - p.Inactive // states are 0 and 1
		_, err = st.SavePoll(p)
		if err != nil {
			log.Println("Could not save toggled inactive state.")
		}
	}

	if toggleMultipleChoice {
		p.Type = 1 - p.Type // states are 0 and 1
		_, err = st.SavePoll(p)
		if err != nil {
			log.Println("Could not save toggled multiple choice state.")
		}
	}

	body := "This is the poll currently selected:\n```\n"
	body += p.Question + "\n"
	for i, o := range p.Options {
		body += fmt.Sprintf("%d. %s", i+1, o.Text) + "\n"
	}
	body += "```\n\n"

	var ed tgbotapi.EditMessageTextConfig
	ed.Text = body
	ed.ParseMode = tgbotapi.ModeHTML
	ed.ReplyMarkup = buildEditMarkup(p, noOlder, noNewer)

	ed.ChatID = update.CallbackQuery.Message.Chat.ID
	ed.MessageID = update.CallbackQuery.Message.MessageID

	_, err = bot.Send(ed)
	if err != nil {
		return fmt.Errorf("could not update message: %v", err)
	}
	pollsToUpdate.enqueue(p.ID)
	return nil
}

func parseQueryPayload(update tgbotapi.Update) (pollid int, optionid int, err error) {
	dataSplit := strings.Split(update.CallbackQuery.Data, ":")
	if len(dataSplit) != 2 {
		return pollid, optionid, fmt.Errorf("could not parse response")
	}
	pollid, err = strconv.Atoi(dataSplit[0])
	if err != nil {
		return pollid, optionid, fmt.Errorf("could not convert CallbackQuery data pollid to int: %v", err)
	}

	optionid, err = strconv.Atoi(dataSplit[1])
	if err != nil {
		return pollid, optionid, fmt.Errorf("could not convert CallbackQuery data OptionID to int: %v", err)
	}
	return pollid, optionid, nil
}
<commit_msg>remove sending of messages that are deleted from the index anyway<commit_after>package main

import (
	"fmt"
	"log"
	"strconv"
	"strings"
	"time"

	"github.com/go-telegram-bot-api/telegram-bot-api"
)

func handleCallbackQuery(bot *tgbotapi.BotAPI, update tgbotapi.Update, st Store) error {
	if update.CallbackQuery.Data == "dummy" {
		callbackConfig := tgbotapi.NewCallback(
			update.CallbackQuery.ID,
			"")
		_, err := bot.AnswerCallbackQuery(callbackConfig)
		if err != nil {
			return fmt.Errorf("could not send answer to callback query: %v", err)
		}

		return nil
	}

	if update.CallbackQuery.Data[0] == 'e' {
		return handlePollEditQuery(bot, update, st)
	}

	if update.CallbackQuery.Data == createPollQuery {
		return sendNewQuestionMessage(bot, update, st)
	}

	if strings.Contains(update.CallbackQuery.Data, pollDoneQuery) {
		return handlePollDoneQuery(bot, update, st)
	}

	pollid, optionid, err := parseQueryPayload(update)
	if err != nil {
		return fmt.Errorf("could not parse query payload: %v", err)
	}

	if update.CallbackQuery.InlineMessageID != "" {
		if err := st.AddInlineMsgToPoll(pollid, update.CallbackQuery.InlineMessageID); err != nil {
			return fmt.Errorf("could not add inline message to poll: %v", err)
		}
	}

	p, err := st.GetPoll(pollid)
	if err != nil {
		return fmt.Errorf("could not get poll: %v", err)
	}
	if isInactive(p) {
		callbackConfig := tgbotapi.NewCallback(
			update.CallbackQuery.ID,
			"This poll is inactive.")
		_, err = bot.AnswerCallbackQuery(callbackConfig)
		if err != nil {
			return fmt.Errorf("could not send answer to callback query: %v", err)
		}
		return fmt.Errorf("poll %d is inactive", pollid)
	}

	newAnswer := answer{
		UserID:   update.CallbackQuery.From.ID,
		PollID:   pollid,
		OptionID: optionid,
	}
	unvoted, err := st.SaveAnswer(p, newAnswer)
	if err != nil {
		return fmt.Errorf("could not save answers: %v", err)
	}
	// polls were changed
	p, err = st.GetPoll(pollid)
	if err != nil {
		return fmt.Errorf("could not get poll: %v", err)
	}

	var choice option
	for _, o := range p.Options {
		if o.ID == newAnswer.OptionID {
			choice = o
		}
	}

	pollsToUpdate.enqueue(p.ID)

	popupText := fmt.Sprintf(`You voted for "%s"`, choice.Text)
	if unvoted {
		popupText = fmt.Sprintf("Seems like you deleted your vote.")
	}

	callbackConfig := tgbotapi.NewCallback(
		update.CallbackQuery.ID,
		popupText)
	_, err = bot.AnswerCallbackQuery(callbackConfig)
	if err != nil {
		return fmt.Errorf("could not send answer to callback query: %v", err)
	}

	return nil
}

func updatePollMessages(bot *tgbotapi.BotAPI, pollid int, st Store) error {
	p, err := st.GetPoll(pollid)
	if err != nil {
		return fmt.Errorf("could not get poll: %v", err)
	}

	//msgs, err := st.GetAllPollMsg(pollid)
	//if err != nil {
	//return fmt.Errorf("could not get all pollmsgs: %v", err)
	//}

	listing := buildPollListing(p, st)

	var ed tgbotapi.EditMessageTextConfig
	ed.Text = listing
	ed.ParseMode = tgbotapi.ModeHTML

	if !isInactive(p) {
		ed.ReplyMarkup = buildPollMarkup(p)
	}

	//for _, msg := range msgs {
	//ed.ChatID = msg.ChatID
	//ed.MessageID = msg.MessageID

	//_, err = bot.Send(ed)
	//if err != nil {
	//log.Printf("Could not edit message: %v \nThe message was: %s\n", err, ed.Text)
	//log.Printf("Could not edit message: %v\n", err)
	//splits := strings.Split(ed.Text, "\n")

	//ed.Text = ""
	//for _, l := range splits {
	//if !strings.HasPrefix(l, "\u251C") && !strings.HasPrefix(l, "\u2514") {
	//ed.Text += l + "\n"
	//}
	//}
	//log.Printf("try again:\n %s", ed.Text)
	//_, err = bot.Send(ed)
	//if err != nil {
	//log.Printf("could not update message: %v\n", err)
	//continue
	//}
	//}
	//}

	// reset
	ed.ChatID = 0
	ed.MessageID = 0

	msgs, err := st.GetAllPollInlineMsg(p.ID)
	if err != nil {
		return fmt.Errorf("could not get all poll inline messages: %v", err)
	}

	for _, msg := range msgs {
		ed.Text = listing
		ed.InlineMessageID = msg.InlineMessageID
		_, err := bot.Send(ed)
		if err != nil {
			if strings.Contains(err.Error(), "MESSAGE_ID_INVALID") {
				log.Printf("Remove inline message %s\n", msg.InlineMessageID)
				st.RemoveInlineMsg(msg.InlineMessageID)
				continue
			}
			// if strings.Contains(err.Error(), "chat not found") {
			// 	log.Printf("Remove inline message %s\n", msg.InlineMessageID)
			// 	st.RemoveInlineMsg(msg.InlineMessageID)
			// }
			if strings.Contains(err.Error(), "message is not modified") {
				continue
			}
			time.Sleep(20 * time.Millisecond)
			log.Printf("\n\n\nCould not edit inline message: %v \nThe message was: %s\n", err, ed.Text)
			log.Printf("Could not edit inline message: %v\n", err)
			splits := strings.Split(ed.Text, "\n")

			ed.Text = ""
			for _, l := range splits {
				if !strings.HasPrefix(l, "\u251C") && !strings.HasPrefix(l, "\u2514") {
					ed.Text += l + "\n"
				}
			}
			log.Printf("try again:\n %s", ed.Text)
			_, err = bot.Send(ed)
			if err != nil {
				log.Printf("could not update inline message: %v\n", err)
				continue
			}
		}
		time.Sleep(20 * time.Millisecond)
	}

	return nil
}

func handlePollDoneQuery(bot *tgbotapi.BotAPI, update tgbotapi.Update, st Store) error {
	splits := strings.Split(update.CallbackQuery.Data, ":")
	if len(splits) < 2 {
		return fmt.Errorf("query did not contain the pollid")
	}
	pollid, err := strconv.Atoi(splits[1])
	if err != nil {
		return fmt.Errorf("could not convert string payload to int: %v", err)
	}

	p, err := st.GetPoll(pollid)
	if err != nil {
		return fmt.Errorf("could not get poll: %v", err)
	}
	_, err = postPoll(bot, p, int64(update.CallbackQuery.From.ID))
	if err != nil {
		return fmt.Errorf("could not post finished poll: %v", err)
	}
	err = st.SaveState(update.CallbackQuery.From.ID, p.ID, pollDone)
	if err != nil {
		return fmt.Errorf("could not change state to poll done: %v", err)
	}
	return nil
}

func handlePollEditQuery(bot *tgbotapi.BotAPI, update tgbotapi.Update, st Store) error {
	splits := strings.Split(update.CallbackQuery.Data, ":")
	if len(splits) < 3 {
		log.Println(splits)
		return fmt.Errorf("query wrongly formatted")
	}
	pollid, err := strconv.Atoi(splits[1])
	if err != nil {
		return fmt.Errorf("could not convert string payload to int: %v", err)
	}

	var p *poll
	noNewer := false
	noOlder := false
	toggleInactive := false
	toggleMultipleChoice := false
	switch splits[2] {
	case "+":
		p, err = st.GetPollNewer(pollid, update.CallbackQuery.From.ID)
		if err != nil {
			log.Printf("could not get older poll: %v\n", err)
			noNewer = true
		}
	case "-":
		p, err = st.GetPollOlder(pollid, update.CallbackQuery.From.ID)
		if err != nil {
			log.Printf("could not get older poll: %v\n", err)
			noOlder = true
		}
	case "c":
		p, err = st.GetPoll(pollid)
		if err != nil {
			log.Printf("could not get poll: %v\n", err)
		}
		toggleInactive = true
	case "m":
		p, err = st.GetPoll(pollid)
		if err != nil {
			log.Printf("could not get poll: %v\n", err)
		}
		toggleMultipleChoice = true
	case "o":
		state := waitingForOption
		err = st.SaveState(update.CallbackQuery.From.ID, pollid, state)
		if err != nil {
			return fmt.Errorf("could not save state: %v", err)
		}

		msg := tgbotapi.NewMessage(update.CallbackQuery.Message.Chat.ID, locAddOption)
		_, err = bot.Send(&msg)
		if err != nil {
			return fmt.Errorf("could not send message: %v", err)
		}
		return nil
	case "q":
		state := editQuestion
		err = st.SaveState(update.CallbackQuery.From.ID, pollid, state)
		if err != nil {
			return fmt.Errorf("could not save state: %v", err)
		}

		msg := tgbotapi.NewMessage(update.CallbackQuery.Message.Chat.ID, locEditQuestion)
		_, err = bot.Send(&msg)
		if err != nil {
			return fmt.Errorf("could not send message: %v", err)
		}
		return nil
	default:
		return fmt.Errorf("query wrongly formatted")
	}
	if err != nil {
		p, err = st.GetPoll(pollid)
		if err != nil {
			return fmt.Errorf("could not get poll by poll id: %v", err)
		}
	}

	// danger! malicious client could send pollid from another user in query
	if p.UserID != update.CallbackQuery.From.ID {
		return fmt.Errorf("user does not own poll: %v", err)
	}

	if toggleInactive {
		p.Inactive = 1 - p.Inactive // states are 0 and 1
		_, err = st.SavePoll(p)
		if err != nil {
			log.Println("Could not save toggled inactive state.")
		}
	}

	if toggleMultipleChoice {
		p.Type = 1 - p.Type // states are 0 and 1
		_, err = st.SavePoll(p)
		if err != nil {
			log.Println("Could not save toggled multiple choice state.")
		}
	}

	body := "This is the poll currently selected:\n```\n"
	body += p.Question + "\n"
	for i, o := range p.Options {
		body += fmt.Sprintf("%d. %s", i+1, o.Text) + "\n"
	}
	body += "```\n\n"

	var ed tgbotapi.EditMessageTextConfig
	ed.Text = body
	ed.ParseMode = tgbotapi.ModeHTML
	ed.ReplyMarkup = buildEditMarkup(p, noOlder, noNewer)

	ed.ChatID = update.CallbackQuery.Message.Chat.ID
	ed.MessageID = update.CallbackQuery.Message.MessageID

	_, err = bot.Send(ed)
	if err != nil {
		return fmt.Errorf("could not update message: %v", err)
	}
	pollsToUpdate.enqueue(p.ID)
	return nil
}

func parseQueryPayload(update tgbotapi.Update) (pollid int, optionid int, err error) {
	dataSplit := strings.Split(update.CallbackQuery.Data, ":")
	if len(dataSplit) != 2 {
		return pollid, optionid, fmt.Errorf("could not parse response")
	}
	pollid, err = strconv.Atoi(dataSplit[0])
	if err != nil {
		return pollid, optionid, fmt.Errorf("could not convert CallbackQuery data pollid to int: %v", err)
	}

	optionid, err = strconv.Atoi(dataSplit[1])
	if err != nil {
		return pollid, optionid, fmt.Errorf("could not convert CallbackQuery data OptionID to int: %v", err)
	}
	return pollid, optionid, nil
}
<|endoftext|>


<commit_before>package main

import "flag"
import "fmt"
import "log"
import "os"

//import "strings"
import "encoding/hex"

import "github.com/prataprc/collatejson"

var options struct {
	encode    bool
	decode    bool
	overheads bool
	inp       string
}

var usageHelp = `Usage : codec [OPTIONS]
to encode specify -encode switch
to decode specify -decode switch and the hexdump as -inp
`

func argParse() {
	flag.StringVar(&options.inp, "inp", "", "input to encode")
	flag.BoolVar(&options.encode, "encode", false, "encode input")
	flag.BoolVar(&options.decode, "decode", false, "decode input")
	flag.BoolVar(&options.overheads, "overheads", false, "collation overhead")
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, usageHelp)
		flag.PrintDefaults()
	}
	flag.Parse()
}

func main() {
	var err error

	argParse()
	codec := collatejson.NewCodec(100)
	out := make([]byte, 0, len(options.inp)*3+collatejson.MinBufferSize)
	if options.overheads {
		computeOverheads(codec)

	} else if options.encode {
		out, err = codec.Encode([]byte(options.inp), out)
		if err != nil {
			log.Fatal(err)
		}
		hexout := make([]byte, len(out)*5)
		n := hex.Encode(hexout, out)
		fmt.Printf("in : %q\n", options.inp)
		fmt.Printf("out: %q\n", string(out))
		fmt.Printf("hex: %q\n", string(hexout[:n]))

	} else if options.decode {
		inpbs := make([]byte, len(options.inp)*5)
		n, err := hex.Decode(inpbs, []byte(options.inp))
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println(n, inpbs[:n])
		out, err = codec.Decode([]byte(inpbs[:n]), out)
		if err != nil {
			log.Fatal(err)
		}
		fmt.Printf("in : %q\n", options.inp)
		fmt.Printf("out: %q\n", string(out))
	}
}

func computeOverheads(codec *collatejson.Codec) {
	var err error

	items := []string{
		"10",
		"10000",
		"1000000000",
		"100000000000000000.0",
		"123456789123565670.0",
		"10.2",
		"10.23456789012",
		"null",
		"true",
		"false",
		`"hello world"`,
		`[ 10, 10000, 1000000000, 10.2, 10.23456789012, null, true, false, "hello world" ]`,
		`{ "a": 10000, "b": 10.23456789012, "c": null, "d": true, "e": false, "f": "hello world" }`,
	}
	for _, item := range items {
		out := make([]byte, 0, 1024)
		out, err = codec.Encode([]byte(item), out)
		if err != nil {
			log.Fatal(err)
		}
		fmt.Printf("item: %q\n", item)
		fmt.Println("  i/p len:", len(item))
		fmt.Println("  o/p len:", len(out))
	}
}
<commit_msg>tools/gson: compute memory overheads in cbor and collate.<commit_after><|endoftext|>


<commit_before>package messagebird

import (
	"encoding/json"
	"net/http"
	"reflect"
	"testing"
	"time"
)

func TestCallFlowJSONMarshal(t *testing.T) {
	refCreatedAt, _ := time.Parse(time.RFC3339, "2018-01-29T13:46:06Z")
	refUpdatedAt, _ := time.Parse(time.RFC3339, "2018-01-30T16:00:34Z")
	referenceCallflow := &CallFlow{
		ID:    "id",
		Title: "title",
		Steps: []CallFlowStep{
			&CallFlowSayStep{
				CallFlowStepBase: CallFlowStepBase{
					ID: "1",
				},
				Payload:  "Hello",
				Language: "en-US",
				Voice:    "male",
			},
			&CallFlowPauseStep{
				CallFlowStepBase: CallFlowStepBase{
					ID: "2",
				},
				Length: time.Second * 10,
			},
		},
		CreatedAt: refCreatedAt,
		UpdatedAt: refUpdatedAt,
	}

	jsonData, err := json.Marshal(referenceCallflow)
	if err != nil {
		t.Fatal(err)
	}
	var callflow CallFlow
	if err := json.Unmarshal(jsonData, &callflow); err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(*referenceCallflow, callflow) {
		t.Logf("exp: %#v", *referenceCallflow)
		t.Logf("got: %#v", callflow)
		t.Fatalf("mismatched call flows")
	}
}

func TestCallFlowJSONUnmarshal(t *testing.T) {
	referenceJSON := `{
		 "id": "id",
		 "title": "title",
		 "steps": [
			 {
				 "id": "1",
				 "action": "say",
				 "options": {
					 "payload": "Hello",
					 "language": "en-US",
					 "voice": "male"
				 }
			 },
			 {
				 "id": "2",
				 "action": "pause",
				 "options": {
					 "length": 10
				 }
			 }
		 ],
		 "createdAt": "2018-01-29T13:46:06Z",
		 "updatedAt": "2018-01-30T16:00:34Z"
	}`
	refCreatedAt, _ := time.Parse(time.RFC3339, "2018-01-29T13:46:06Z")
	refUpdatedAt, _ := time.Parse(time.RFC3339, "2018-01-30T16:00:34Z")
	referenceCallflow := &CallFlow{
		ID:    "id",
		Title: "title",
		Steps: []CallFlowStep{
			&CallFlowSayStep{
				CallFlowStepBase: CallFlowStepBase{
					ID: "1",
				},
				Payload:  "Hello",
				Language: "en-US",
				Voice:    "male",
			},
			&CallFlowPauseStep{
				CallFlowStepBase: CallFlowStepBase{
					ID: "2",
				},
				Length: time.Second * 10,
			},
		},
		CreatedAt: refCreatedAt,
		UpdatedAt: refUpdatedAt,
	}

	var callflow CallFlow
	if err := json.Unmarshal([]byte(referenceJSON), &callflow); err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(*referenceCallflow, callflow) {
		t.Logf("exp: %#v", *referenceCallflow)
		t.Logf("got: %#v", callflow)
		t.Fatalf("mismatched call flows")
	}
}

func TestCreateCallFlow(t *testing.T) {
	SetServerResponse(http.StatusOK, []byte(`{
		"data": [
			{
				"id": "the-id",
				"title": "the-title",
				"createdAt": "2018-01-29T13:46:06Z",
				"updatedAt": "2018-01-30T16:00:34Z"
			}
		]
	}`))
	newCf, err := mbClient.CreateCallFlow(&CallFlow{})
	if err != nil {
		t.Fatal(err)
	}
	if newCf.ID != "the-id" {
		t.Fatalf("Unexpected ID: %q", newCf.ID)
	}
	if newCf.Title != "the-title" {
		t.Fatalf("Unexpected Title: %q", newCf.Title)
	}
}
<commit_msg>Add an example CallFlow<commit_after>package messagebird

import (
	"encoding/json"
	"net/http"
	"reflect"
	"testing"
	"time"
)

func ExampleCallFlow() {
	callflow := CallFlow{
		Title: "My CallFlow",
		Steps: []CallFlowStep{
			&CallFlowSayStep{
				Payload:  "Hello",
				Voice:    "male",
				Language: "en-US",
			},
			&CallFlowPauseStep{
				Length: time.Second * 4,
			},
			&CallFlowTransferStep{
				Destination: "31600000000",
			},
		},
	}
	_ = callflow
}

func TestCallFlowJSONMarshal(t *testing.T) {
	refCreatedAt, _ := time.Parse(time.RFC3339, "2018-01-29T13:46:06Z")
	refUpdatedAt, _ := time.Parse(time.RFC3339, "2018-01-30T16:00:34Z")
	referenceCallflow := &CallFlow{
		ID:    "id",
		Title: "title",
		Steps: []CallFlowStep{
			&CallFlowSayStep{
				CallFlowStepBase: CallFlowStepBase{
					ID: "1",
				},
				Payload:  "Hello",
				Language: "en-US",
				Voice:    "male",
			},
			&CallFlowPauseStep{
				CallFlowStepBase: CallFlowStepBase{
					ID: "2",
				},
				Length: time.Second * 10,
			},
		},
		CreatedAt: refCreatedAt,
		UpdatedAt: refUpdatedAt,
	}

	jsonData, err := json.Marshal(referenceCallflow)
	if err != nil {
		t.Fatal(err)
	}
	var callflow CallFlow
	if err := json.Unmarshal(jsonData, &callflow); err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(*referenceCallflow, callflow) {
		t.Logf("exp: %#v", *referenceCallflow)
		t.Logf("got: %#v", callflow)
		t.Fatalf("mismatched call flows")
	}
}

func TestCallFlowJSONUnmarshal(t *testing.T) {
	referenceJSON := `{
		 "id": "id",
		 "title": "title",
		 "steps": [
			 {
				 "id": "1",
				 "action": "say",
				 "options": {
					 "payload": "Hello",
					 "language": "en-US",
					 "voice": "male"
				 }
			 },
			 {
				 "id": "2",
				 "action": "pause",
				 "options": {
					 "length": 10
				 }
			 }
		 ],
		 "createdAt": "2018-01-29T13:46:06Z",
		 "updatedAt": "2018-01-30T16:00:34Z"
	}`
	refCreatedAt, _ := time.Parse(time.RFC3339, "2018-01-29T13:46:06Z")
	refUpdatedAt, _ := time.Parse(time.RFC3339, "2018-01-30T16:00:34Z")
	referenceCallflow := &CallFlow{
		ID:    "id",
		Title: "title",
		Steps: []CallFlowStep{
			&CallFlowSayStep{
				CallFlowStepBase: CallFlowStepBase{
					ID: "1",
				},
				Payload:  "Hello",
				Language: "en-US",
				Voice:    "male",
			},
			&CallFlowPauseStep{
				CallFlowStepBase: CallFlowStepBase{
					ID: "2",
				},
				Length: time.Second * 10,
			},
		},
		CreatedAt: refCreatedAt,
		UpdatedAt: refUpdatedAt,
	}

	var callflow CallFlow
	if err := json.Unmarshal([]byte(referenceJSON), &callflow); err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(*referenceCallflow, callflow) {
		t.Logf("exp: %#v", *referenceCallflow)
		t.Logf("got: %#v", callflow)
		t.Fatalf("mismatched call flows")
	}
}

func TestCreateCallFlow(t *testing.T) {
	SetServerResponse(http.StatusOK, []byte(`{
		"data": [
			{
				"id": "the-id",
				"title": "the-title",
				"createdAt": "2018-01-29T13:46:06Z",
				"updatedAt": "2018-01-30T16:00:34Z"
			}
		]
	}`))
	newCf, err := mbClient.CreateCallFlow(&CallFlow{})
	if err != nil {
		t.Fatal(err)
	}
	if newCf.ID != "the-id" {
		t.Fatalf("Unexpected ID: %q", newCf.ID)
	}
	if newCf.Title != "the-title" {
		t.Fatalf("Unexpected Title: %q", newCf.Title)
	}
}
<|endoftext|>


<commit_before>// Copyright 2018 Google LLC
//
// 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
//
//     https://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 amppackager

import (
	"bytes"
	"crypto"
	"crypto/x509"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"path"
	"regexp"
	"time"

	"github.com/nyaxt/webpackage/go/signedexchange"
	"github.com/pkg/errors"
	"github.com/pquerna/cachecontrol"
)

// The base URL for transformed fetch URLs.
var ampCDNBase = "https://cdn.ampproject.org/c/"

// Allowed schemes for the PackagerBase URL, from which certUrls are constructed.
var acceptablePackagerSchemes = map[string]bool{"http": true, "https": true}

// The user agent to send when issuing fetches. Should look like a mobile device.
const userAgent = "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) " +
	"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.96 Mobile " +
	"Safari/537.36 (compatible; amppackager/0.0.0; +https://github.com/ampproject/amppackager)"

// Advised against, per
// https://tools.ietf.org/html/draft-yasskin-httpbis-origin-signed-exchanges-impl-00#section-4.1
// and blocked in http://crrev.com/c/958945.
var statefulResponseHeaders = map[string]bool{
	"Authentication-Control":    true,
	"Authentication-Info":       true,
	"Optional-WWW-Authenticate": true,
	"Proxy-Authenticate":        true,
	"Proxy-Authentication-Info": true,
	"Sec-WebSocket-Accept":      true,
	"Set-Cookie":                true,
	"Set-Cookie2":               true,
	"SetProfile":                true,
	"WWW-Authenticate":          true,
}

// TODO(twifkak): Remove this restriction by allowing streamed responses from the signedexchange library.
const maxBodyLength = 4 * 1 << 20

// TODO(twifkak): What value should this be?
const miRecordSize = 4096

func parseURL(rawURL string, name string) (*url.URL, *HTTPError) {
	if rawURL == "" {
		return nil, NewHTTPError(http.StatusBadRequest, name, " URL is unspecified")
	}
	ret, err := url.Parse(rawURL)
	if err != nil {
		return nil, NewHTTPError(http.StatusBadRequest, "Error parsing ", name, " url: ", err)
	}
	if !ret.IsAbs() {
		return nil, NewHTTPError(http.StatusBadRequest, name, " url is relative")
	}
	return ret, nil
}

func regexpFullMatch(pattern string, test string) bool {
	// This is how regexp/exec_test.go turns a partial pattern into a full pattern.
	fullRe := `\A(?:` + pattern + `)\z`
	matches, _ := regexp.MatchString(fullRe, test)
	return matches
}

func urlMatches(url *url.URL, pattern URLPattern) bool {
	schemeMatches := false
	for _, scheme := range pattern.Scheme {
		if url.Scheme == scheme {
			schemeMatches = true
		}
	}
	if !schemeMatches {
		return false
	}
	if url.Opaque != "" {
		return false
	}
	if url.User != nil {
		return false
	}
	if url.Host != pattern.Domain {
		return false
	}
	if !regexpFullMatch(*pattern.PathRE, url.EscapedPath()) {
		return false
	}
	for _, re := range pattern.PathExcludeRE {
		if regexpFullMatch(re, url.EscapedPath()) {
			return false
		}
	}
	if !regexpFullMatch(*pattern.QueryRE, url.RawQuery) {
		return false
	}
	return true
}

func urlsMatch(fetchURL *url.URL, signURL *url.URL, set URLSet) bool {
	return urlMatches(fetchURL, set.Fetch) && urlMatches(signURL, set.Sign) &&
		(!set.SamePath || fetchURL.RequestURI() == signURL.RequestURI())
}

// Returns parsed URLs and whether to fail on stateful headers.
func parseURLs(fetch string, sign string, urlSets []URLSet) (*url.URL, *url.URL, bool, *HTTPError) {
	fetchURL, err := parseURL(fetch, "fetch")
	if err != nil {
		return nil, nil, false, err
	}
	signURL, err := parseURL(sign, "sign")
	if err != nil {
		return nil, nil, false, err
	}
	for _, set := range urlSets {
		if urlsMatch(fetchURL, signURL, set) {
			return fetchURL, signURL, set.Fetch.ErrorOnStatefulHeaders, nil
		}
	}
	return nil, nil, false, NewHTTPError(http.StatusBadRequest, "fetch/sign URLs do not match config")
}

func validateFetch(req *http.Request, resp *http.Response) *HTTPError {
	if resp.StatusCode != http.StatusOK {
		return NewHTTPError(http.StatusBadGateway, "Non-OK fetch: ", resp.StatusCode)
	}
	// Validate response is publicly-cacheable, per
	// https://tools.ietf.org/html/draft-yasskin-http-origin-signed-responses-03#section-6.1, as referenced by
	// https://tools.ietf.org/html/draft-yasskin-httpbis-origin-signed-exchanges-impl-00#section-6.
	// TODO(twifkak): Set {PrivateCache: false} after we switch from
	// fetching through the AMP CDN to fetching directly and using the
	// transformer API. For now, the AMP CDN validates that the origin
	// response is publicly-cacheable.
	nonCachableReasons, _, err := cachecontrol.CachableResponse(req, resp, cachecontrol.Options{PrivateCache: true})
	if err != nil {
		return NewHTTPError(http.StatusBadGateway, "Error parsing cache headers: ", err)
	}
	if len(nonCachableReasons) > 0 {
		return NewHTTPError(http.StatusBadGateway, "Non-cacheable response: ", nonCachableReasons)
	}
	return nil
}

type Packager struct {
	// TODO(twifkak): Support multiple certs. This will require generating
	// a signature for each one. Note that Chrome only supports 1 signature
	// at the moment.
	cert *x509.Certificate
	// TODO(twifkak): Do we want to allow multiple keys?
	key         crypto.PrivateKey
	validityURL *url.URL
	client      *http.Client
	baseURL     *url.URL
	urlSets     []URLSet
}

func NewPackager(cert *x509.Certificate, key crypto.PrivateKey, packagerBase string, urlSets []URLSet) (*Packager, error) {
	baseURL, err := url.Parse(packagerBase)
	if err != nil {
		return nil, errors.Wrapf(err, "parsing PackagerBase %q", packagerBase)
	}
	if !baseURL.IsAbs() {
		return nil, errors.Errorf("PackagerBase %q must be an absolute URL.", packagerBase)
	}
	if !acceptablePackagerSchemes[baseURL.Scheme] {
		return nil, errors.Errorf("PackagerBase %q must be over http or https.", packagerBase)
	}
	validityURL, err := url.Parse("https://cdn.ampproject.org/null-validity")
	if err != nil {
		return nil, errors.Wrap(err, "parsing null-validity URL")
	}
	client := http.Client{
		// TODO(twifkak): Load-test and see if default transport settings are okay.
		Timeout: 60 * time.Second,
	}
	return &Packager{cert, key, validityURL, &client, baseURL, urlSets}, nil
}

func (this Packager) fetchURL(url *url.URL) (*http.Request, *http.Response, *HTTPError) {
	// Add the query parameter to enable web package transforms.
	query := url.Query()
	query.Add("usqp", "mq331AQCSAE")
	url.RawQuery = query.Encode()

	ampURL := "https://cdn.ampproject.org/c/"
	if url.Scheme == "https" {
		ampURL += "s/"
	}
	ampURL += url.Host + url.RequestURI()

	log.Printf("Fetching URL: %q\n", ampURL)
	// TODO(twifkak): Translate into AMP CDN URL, until transform API is available.
	req, err := http.NewRequest(http.MethodGet, ampURL, nil)
	req.Header.Set("User-Agent", userAgent)
	// TODO(twifkak): Should we add 'Accept-Charset: utf-8'? The AMP Transformer API requires utf-8.
	if err != nil {
		return nil, nil, NewHTTPError(http.StatusInternalServerError, "Error building request: ", err)
	}
	resp, err := this.client.Do(req)
	if err != nil {
		return nil, nil, NewHTTPError(http.StatusBadGateway, "Error fetching: ", err)
	}
	return req, resp, nil
}

func (this Packager) genCertURL(cert *x509.Certificate) (*url.URL, error) {
	urlPath := path.Join(CertURLPrefix, url.PathEscape(CertName(cert)))
	certURL, err := url.Parse(urlPath)
	if err != nil {
		return nil, errors.Wrapf(err, "parsing cert URL %q", urlPath)
	}
	ret := this.baseURL.ResolveReference(certURL)
	return ret, nil
}

func (this Packager) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
	// TODO(twifkak): See if there are any other validations or sanitizations that need adding.
	fetch := req.FormValue("fetch")
	sign := req.FormValue("sign")
	fetchURL, signURL, errorOnStatefulHeaders, httpErr := parseURLs(fetch, sign, this.urlSets)
	if httpErr != nil {
		httpErr.LogAndRespond(resp)
		return
	}

	fetchReq, fetchResp, httpErr := this.fetchURL(fetchURL)
	if httpErr != nil {
		httpErr.LogAndRespond(resp)
		return
	}
	defer func() {
		if err := fetchResp.Body.Close(); err != nil {
			log.Println("Error closing fetchResp body:", err)
		}
	}()

	if httpErr := validateFetch(fetchReq, fetchResp); httpErr != nil {
		httpErr.LogAndRespond(resp)
		return
	}

	// TODO(twifkak): Add config: either ensure Expires is + 5 days, or reject. (Or at least do one and document it in the README.)
	// TODO(twifkak): Should I be more restrictive and just whitelist some response headers?
	for header := range statefulResponseHeaders {
		if errorOnStatefulHeaders && fetchResp.Header.Get(header) != "" {
			NewHTTPError(http.StatusBadGateway, "Fetch response contains stateful header: ", header).LogAndRespond(resp)
			return
		}
		fetchResp.Header.Del(header)
	}
	// TODO(twifkak): Are there any headers that AMP CDNs sets that publishers wouldn't want
	// running on their origin? Are there any (such as CSP) that we absolutely need to run?
	// TODO(twifkak): After the Transformer API, just add whatever headers are provided by the
	// transformer plus a few extra (e.g. Content-Type).

	fetchBody, err := ioutil.ReadAll(io.LimitReader(fetchResp.Body, maxBodyLength))
	if err != nil {
		NewHTTPError(http.StatusBadGateway, "Error reading body: ", err).LogAndRespond(resp)
		return
	}

	exchange, err := signedexchange.NewExchange(signURL, http.Header{}, fetchResp.StatusCode, fetchResp.Header, fetchBody, miRecordSize)
	if err != nil {
		NewHTTPError(http.StatusInternalServerError, "Error building exchange: ", err).LogAndRespond(resp)
		return
	}
	certURL, err := this.genCertURL(this.cert)
	if err != nil {
		NewHTTPError(http.StatusInternalServerError, "Error building cert URL: ", err).LogAndRespond(resp)
		return
	}
	now := time.Now()
	signer := signedexchange.Signer{
		// Expires - Date must be <= 604800 seconds, per
		// https://tools.ietf.org/html/draft-yasskin-httpbis-origin-signed-exchanges-impl-00#section-3.5.
		Date:        now.Add(-24 * time.Hour),
		Expires:     now.Add(6 * 24 * time.Hour),
		Certs:       []*x509.Certificate{this.cert},
		CertUrl:     certURL,
		ValidityUrl: this.validityURL,
		PrivKey:     this.key,
		// TODO(twifkak): Should we make Rand user-configurable? The
		// default is to use getrandom(2) if available, else
		// /dev/urandom.
	}
	if err := exchange.AddSignatureHeader(&signer); err != nil {
		NewHTTPError(http.StatusInternalServerError, "Error signing exchange: ", err).LogAndRespond(resp)
		return
	}
	// TODO(twifkak): Make this a streaming response. How will we handle errors after part of the response has already been sent?
	var body bytes.Buffer
	if err := signedexchange.WriteExchangeFile(&body, exchange); err != nil {
		NewHTTPError(http.StatusInternalServerError, "Error serializing exchange: ", err).LogAndRespond(resp)
	}

	// TODO(twifkak): Add Cache-Control: public with expiry to match when we think the AMP Cache
	// should fetch an update (half-way between signature date & expires).
	// TODO(twifkak): Add `X-Amppkg-Version: 0.0.0`.
	resp.Header().Set("Content-Type", "application/signed-exchange;v=b0")
	resp.Header().Set("Cache-Control", "no-transform")
	if _, err := resp.Write(body.Bytes()); err != nil {
		log.Println("Error writing response:", err)
		return
	}
}
<commit_msg>Sanitize fetch and sign params.<commit_after>// Copyright 2018 Google LLC
//
// 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
//
//     https://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 amppackager

import (
	"bytes"
	"crypto"
	"crypto/x509"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"path"
	"regexp"
	"time"

	"github.com/nyaxt/webpackage/go/signedexchange"
	"github.com/pkg/errors"
	"github.com/pquerna/cachecontrol"
)

// The base URL for transformed fetch URLs.
var ampCDNBase = "https://cdn.ampproject.org/c/"

// Allowed schemes for the PackagerBase URL, from which certUrls are constructed.
var acceptablePackagerSchemes = map[string]bool{"http": true, "https": true}

// The user agent to send when issuing fetches. Should look like a mobile device.
const userAgent = "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) " +
	"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.96 Mobile " +
	"Safari/537.36 (compatible; amppackager/0.0.0; +https://github.com/ampproject/amppackager)"

// Advised against, per
// https://tools.ietf.org/html/draft-yasskin-httpbis-origin-signed-exchanges-impl-00#section-4.1
// and blocked in http://crrev.com/c/958945.
var statefulResponseHeaders = map[string]bool{
	"Authentication-Control":    true,
	"Authentication-Info":       true,
	"Optional-WWW-Authenticate": true,
	"Proxy-Authenticate":        true,
	"Proxy-Authentication-Info": true,
	"Sec-WebSocket-Accept":      true,
	"Set-Cookie":                true,
	"Set-Cookie2":               true,
	"SetProfile":                true,
	"WWW-Authenticate":          true,
}

// TODO(twifkak): Remove this restriction by allowing streamed responses from the signedexchange library.
const maxBodyLength = 4 * 1 << 20

// TODO(twifkak): What value should this be?
const miRecordSize = 4096

func parseURL(rawURL string, name string) (*url.URL, *HTTPError) {
	if rawURL == "" {
		return nil, NewHTTPError(http.StatusBadRequest, name, " URL is unspecified")
	}
	ret, err := url.Parse(rawURL)
	if err != nil {
		return nil, NewHTTPError(http.StatusBadRequest, "Error parsing ", name, " url: ", err)
	}
	if !ret.IsAbs() {
		return nil, NewHTTPError(http.StatusBadRequest, name, " url is relative")
	}
	// Evaluate "/..", by resolving the URL as a reference from itself.
	// This prevents malformed URLs from eluding the PathRE protections.
	ret = ret.ResolveReference(ret)
	return ret, nil
}

func regexpFullMatch(pattern string, test string) bool {
	// This is how regexp/exec_test.go turns a partial pattern into a full pattern.
	fullRe := `\A(?:` + pattern + `)\z`
	matches, _ := regexp.MatchString(fullRe, test)
	return matches
}

func urlMatches(url *url.URL, pattern URLPattern) bool {
	schemeMatches := false
	for _, scheme := range pattern.Scheme {
		if url.Scheme == scheme {
			schemeMatches = true
		}
	}
	if !schemeMatches {
		return false
	}
	if url.Opaque != "" {
		return false
	}
	if url.User != nil {
		return false
	}
	if url.Host != pattern.Domain {
		return false
	}
	if !regexpFullMatch(*pattern.PathRE, url.EscapedPath()) {
		return false
	}
	for _, re := range pattern.PathExcludeRE {
		if regexpFullMatch(re, url.EscapedPath()) {
			return false
		}
	}
	if !regexpFullMatch(*pattern.QueryRE, url.RawQuery) {
		return false
	}
	return true
}

func urlsMatch(fetchURL *url.URL, signURL *url.URL, set URLSet) bool {
	return urlMatches(fetchURL, set.Fetch) && urlMatches(signURL, set.Sign) &&
		(!set.SamePath || fetchURL.RequestURI() == signURL.RequestURI())
}

// Returns parsed URLs and whether to fail on stateful headers.
func parseURLs(fetch string, sign string, urlSets []URLSet) (*url.URL, *url.URL, bool, *HTTPError) {
	fetchURL, err := parseURL(fetch, "fetch")
	if err != nil {
		return nil, nil, false, err
	}
	signURL, err := parseURL(sign, "sign")
	if err != nil {
		return nil, nil, false, err
	}
	for _, set := range urlSets {
		if urlsMatch(fetchURL, signURL, set) {
			return fetchURL, signURL, set.Fetch.ErrorOnStatefulHeaders, nil
		}
	}
	return nil, nil, false, NewHTTPError(http.StatusBadRequest, "fetch/sign URLs do not match config")
}

func validateFetch(req *http.Request, resp *http.Response) *HTTPError {
	if resp.StatusCode != http.StatusOK {
		return NewHTTPError(http.StatusBadGateway, "Non-OK fetch: ", resp.StatusCode)
	}
	// Validate response is publicly-cacheable, per
	// https://tools.ietf.org/html/draft-yasskin-http-origin-signed-responses-03#section-6.1, as referenced by
	// https://tools.ietf.org/html/draft-yasskin-httpbis-origin-signed-exchanges-impl-00#section-6.
	// TODO(twifkak): Set {PrivateCache: false} after we switch from
	// fetching through the AMP CDN to fetching directly and using the
	// transformer API. For now, the AMP CDN validates that the origin
	// response is publicly-cacheable.
	nonCachableReasons, _, err := cachecontrol.CachableResponse(req, resp, cachecontrol.Options{PrivateCache: true})
	if err != nil {
		return NewHTTPError(http.StatusBadGateway, "Error parsing cache headers: ", err)
	}
	if len(nonCachableReasons) > 0 {
		return NewHTTPError(http.StatusBadGateway, "Non-cacheable response: ", nonCachableReasons)
	}
	return nil
}

type Packager struct {
	// TODO(twifkak): Support multiple certs. This will require generating
	// a signature for each one. Note that Chrome only supports 1 signature
	// at the moment.
	cert *x509.Certificate
	// TODO(twifkak): Do we want to allow multiple keys?
	key         crypto.PrivateKey
	validityURL *url.URL
	client      *http.Client
	baseURL     *url.URL
	urlSets     []URLSet
}

func NewPackager(cert *x509.Certificate, key crypto.PrivateKey, packagerBase string, urlSets []URLSet) (*Packager, error) {
	baseURL, err := url.Parse(packagerBase)
	if err != nil {
		return nil, errors.Wrapf(err, "parsing PackagerBase %q", packagerBase)
	}
	if !baseURL.IsAbs() {
		return nil, errors.Errorf("PackagerBase %q must be an absolute URL.", packagerBase)
	}
	if !acceptablePackagerSchemes[baseURL.Scheme] {
		return nil, errors.Errorf("PackagerBase %q must be over http or https.", packagerBase)
	}
	validityURL, err := url.Parse("https://cdn.ampproject.org/null-validity")
	if err != nil {
		return nil, errors.Wrap(err, "parsing null-validity URL")
	}
	client := http.Client{
		// TODO(twifkak): Load-test and see if default transport settings are okay.
		Timeout: 60 * time.Second,
	}
	return &Packager{cert, key, validityURL, &client, baseURL, urlSets}, nil
}

func (this Packager) fetchURL(url *url.URL) (*http.Request, *http.Response, *HTTPError) {
	// Add the query parameter to enable web package transforms.
	query := url.Query()
	query.Add("usqp", "mq331AQCSAE")
	url.RawQuery = query.Encode()

	ampURL := "https://cdn.ampproject.org/c/"
	if url.Scheme == "https" {
		ampURL += "s/"
	}
	ampURL += url.Host + url.RequestURI()

	log.Printf("Fetching URL: %q\n", ampURL)
	// TODO(twifkak): Translate into AMP CDN URL, until transform API is available.
	req, err := http.NewRequest(http.MethodGet, ampURL, nil)
	req.Header.Set("User-Agent", userAgent)
	// TODO(twifkak): Should we add 'Accept-Charset: utf-8'? The AMP Transformer API requires utf-8.
	if err != nil {
		return nil, nil, NewHTTPError(http.StatusInternalServerError, "Error building request: ", err)
	}
	resp, err := this.client.Do(req)
	if err != nil {
		return nil, nil, NewHTTPError(http.StatusBadGateway, "Error fetching: ", err)
	}
	return req, resp, nil
}

func (this Packager) genCertURL(cert *x509.Certificate) (*url.URL, error) {
	urlPath := path.Join(CertURLPrefix, url.PathEscape(CertName(cert)))
	certURL, err := url.Parse(urlPath)
	if err != nil {
		return nil, errors.Wrapf(err, "parsing cert URL %q", urlPath)
	}
	ret := this.baseURL.ResolveReference(certURL)
	return ret, nil
}

func (this Packager) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
	// TODO(twifkak): See if there are any other validations or sanitizations that need adding.
	if err := req.ParseForm(); err != nil {
		NewHTTPError(http.StatusBadRequest, "Form input parsing failed: ", err).LogAndRespond(resp)
		return
	}
	if len(req.Form["fetch"]) != 1 {
		NewHTTPError(http.StatusBadRequest, "Not exactly 1 fetch param").LogAndRespond(resp)
		return
	}
	if len(req.Form["sign"]) != 1 {
		NewHTTPError(http.StatusBadRequest, "Not exactly 1 sign param").LogAndRespond(resp)
		return
	}
	fetch := req.Form["fetch"][0]
	sign := req.Form["sign"][0]
	fetchURL, signURL, errorOnStatefulHeaders, httpErr := parseURLs(fetch, sign, this.urlSets)
	if httpErr != nil {
		httpErr.LogAndRespond(resp)
		return
	}

	fetchReq, fetchResp, httpErr := this.fetchURL(fetchURL)
	if httpErr != nil {
		httpErr.LogAndRespond(resp)
		return
	}
	defer func() {
		if err := fetchResp.Body.Close(); err != nil {
			log.Println("Error closing fetchResp body:", err)
		}
	}()

	if httpErr := validateFetch(fetchReq, fetchResp); httpErr != nil {
		httpErr.LogAndRespond(resp)
		return
	}

	// TODO(twifkak): Add config: either ensure Expires is + 5 days, or reject. (Or at least do one and document it in the README.)
	// TODO(twifkak): Should I be more restrictive and just whitelist some response headers?
	for header := range statefulResponseHeaders {
		if errorOnStatefulHeaders && fetchResp.Header.Get(header) != "" {
			NewHTTPError(http.StatusBadGateway, "Fetch response contains stateful header: ", header).LogAndRespond(resp)
			return
		}
		fetchResp.Header.Del(header)
	}
	// TODO(twifkak): Are there any headers that AMP CDNs sets that publishers wouldn't want
	// running on their origin? Are there any (such as CSP) that we absolutely need to run?
	// TODO(twifkak): After the Transformer API, just add whatever headers are provided by the
	// transformer plus a few extra (e.g. Content-Type).

	fetchBody, err := ioutil.ReadAll(io.LimitReader(fetchResp.Body, maxBodyLength))
	if err != nil {
		NewHTTPError(http.StatusBadGateway, "Error reading body: ", err).LogAndRespond(resp)
		return
	}

	exchange, err := signedexchange.NewExchange(signURL, http.Header{}, fetchResp.StatusCode, fetchResp.Header, fetchBody, miRecordSize)
	if err != nil {
		NewHTTPError(http.StatusInternalServerError, "Error building exchange: ", err).LogAndRespond(resp)
		return
	}
	certURL, err := this.genCertURL(this.cert)
	if err != nil {
		NewHTTPError(http.StatusInternalServerError, "Error building cert URL: ", err).LogAndRespond(resp)
		return
	}
	now := time.Now()
	signer := signedexchange.Signer{
		// Expires - Date must be <= 604800 seconds, per
		// https://tools.ietf.org/html/draft-yasskin-httpbis-origin-signed-exchanges-impl-00#section-3.5.
		Date:        now.Add(-24 * time.Hour),
		Expires:     now.Add(6 * 24 * time.Hour),
		Certs:       []*x509.Certificate{this.cert},
		CertUrl:     certURL,
		ValidityUrl: this.validityURL,
		PrivKey:     this.key,
		// TODO(twifkak): Should we make Rand user-configurable? The
		// default is to use getrandom(2) if available, else
		// /dev/urandom.
	}
	if err := exchange.AddSignatureHeader(&signer); err != nil {
		NewHTTPError(http.StatusInternalServerError, "Error signing exchange: ", err).LogAndRespond(resp)
		return
	}
	// TODO(twifkak): Make this a streaming response. How will we handle errors after part of the response has already been sent?
	var body bytes.Buffer
	if err := signedexchange.WriteExchangeFile(&body, exchange); err != nil {
		NewHTTPError(http.StatusInternalServerError, "Error serializing exchange: ", err).LogAndRespond(resp)
	}

	// TODO(twifkak): Add Cache-Control: public with expiry to match when we think the AMP Cache
	// should fetch an update (half-way between signature date & expires).
	// TODO(twifkak): Add `X-Amppkg-Version: 0.0.0`.
	resp.Header().Set("Content-Type", "application/signed-exchange;v=b0")
	resp.Header().Set("Cache-Control", "no-transform")
	if _, err := resp.Write(body.Bytes()); err != nil {
		log.Println("Error writing response:", err)
		return
	}
}
<|endoftext|>


<commit_before>/* {{{ Copyright (c) Paul R. Tagliamonte <paultag@debian.org>, 2015
 *
 * 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 archive

import (
	"fmt"
	"io"
	"os"
	"strconv"

	"crypto/md5"
	"crypto/sha1"
	"crypto/sha256"
	"hash"

	"pault.ag/go/debian/control"
	"pault.ag/go/debian/deb"
	"pault.ag/go/debian/dependency"
	"pault.ag/go/debian/version"
)

// Package {{{

// Binary .deb Package entry, as it exists in the Packages file, which
// contains the .deb Control information, as well as information on
// where the file lives, the file size, and some hashes.
type Package struct {
	control.Paragraph

	Package       string `required:"true"`
	Source        string
	Version       version.Version `required:"true"`
	Section       string
	Priority      string
	Architecture  dependency.Arch `required:"true"`
	Essential     string
	InstalledSize int    `control:"Installed-Size"`
	Maintainer    string `required:"true"`
	Description   string `required:"true"`
	Homepage      string

	Filename       string `required:"true"`
	Size           int    `required:"true"`
	MD5sum         string
	SHA1           string
	SHA256         string
	DescriptionMD5 string `control:"Description-md5"`
}

// PackageFromDeb {{{

// Create a Package entry from a deb.Deb file. This will copy the binary
// .deb Control file into the Package entry, and set information as to
// the location of the file, the size of the file, and hash the file.
func PackageFromDeb(debFile deb.Deb) (*Package, error) {
	pkg := Package{}

	paragraph := debFile.Control.Paragraph
	paragraph.Set("Filename", debFile.Path)
	/* Now, let's do some magic */

	fd, err := os.Open(debFile.Path)
	if err != nil {
		return nil, err
	}
	stat, err := fd.Stat()
	if err != nil {
		return nil, err
	}

	paragraph.Set("Size", strconv.Itoa(int(stat.Size())))
	/* Right, now, in addition, we ought to hash the crap out of the file */

	md5sum := md5.New()
	sha1 := sha1.New()
	sha256 := sha256.New()

	writer := io.MultiWriter(md5sum, sha256, sha1)

	if _, err := io.Copy(writer, fd); err != nil {
		return nil, err
	}

	for key, hasher := range map[string]hash.Hash{
		"MD5sum": md5sum,
		"SHA1":   sha1,
		"SHA256": sha256,
	} {
		paragraph.Set(key, fmt.Sprintf("%x", hasher.Sum(nil)))
	}

	return &pkg, control.UnpackFromParagraph(debFile.Control.Paragraph, &pkg)
}

// }}}

// }}}

// Packages {{{

// Iterator to access the entries contained in the Packages entry in an
// apt repo. This contians information about the binary Debian packages.
type Packages struct {
	decoder *control.Decoder
}

// Next {{{

// Get the next Package entry in the Packages list. This will return an
// io.EOF at the last entry.
func (p *Packages) Next() (*Package, error) {
	next := Package{}
	return &next, p.decoder.Decode(&next)
}

// }}}

// LoadPackagesFile {{{

// Given a path, create a Packages iterator. Note that the Packages
// file is not OpenPGP signed, so one will need to verify the integrety
// of this file from the InRelease file before trusting any output.
func LoadPackagesFile(path string) (*Packages, error) {
	fd, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	return LoadPackages(fd)
}

// }}}

// LoadPackages {{{

// Given an io.Reader, create a Packages iterator. Note that the Packages
// file is not OpenPGP signed, so one will need to verify the integrety
// of this file from the InRelease file before trusting any output.
func LoadPackages(in io.Reader) (*Packages, error) {
	decoder, err := control.NewDecoder(in, nil)
	if err != nil {
		return nil, err
	}
	return &Packages{decoder: decoder}, nil
}

// }}}

// }}}

// vim: foldmethod=marker
<commit_msg>require all the hashes<commit_after>/* {{{ Copyright (c) Paul R. Tagliamonte <paultag@debian.org>, 2015
 *
 * 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 archive

import (
	"fmt"
	"io"
	"os"
	"strconv"

	"crypto/md5"
	"crypto/sha1"
	"crypto/sha256"
	"hash"

	"pault.ag/go/debian/control"
	"pault.ag/go/debian/deb"
	"pault.ag/go/debian/dependency"
	"pault.ag/go/debian/version"
)

// Package {{{

// Binary .deb Package entry, as it exists in the Packages file, which
// contains the .deb Control information, as well as information on
// where the file lives, the file size, and some hashes.
type Package struct {
	control.Paragraph

	Package       string `required:"true"`
	Source        string
	Version       version.Version `required:"true"`
	Section       string
	Priority      string
	Architecture  dependency.Arch `required:"true"`
	Essential     string
	InstalledSize int    `control:"Installed-Size"`
	Maintainer    string `required:"true"`
	Description   string `required:"true"`
	Homepage      string

	Filename       string `required:"true"`
	Size           int    `required:"true"`
	MD5sum         string `required:"true"`
	SHA1           string `required:"true"`
	SHA256         string `required:"true"`
	DescriptionMD5 string `control:"Description-md5"`
}

// PackageFromDeb {{{

// Create a Package entry from a deb.Deb file. This will copy the binary
// .deb Control file into the Package entry, and set information as to
// the location of the file, the size of the file, and hash the file.
func PackageFromDeb(debFile deb.Deb) (*Package, error) {
	pkg := Package{}

	paragraph := debFile.Control.Paragraph
	paragraph.Set("Filename", debFile.Path)
	/* Now, let's do some magic */

	fd, err := os.Open(debFile.Path)
	if err != nil {
		return nil, err
	}
	stat, err := fd.Stat()
	if err != nil {
		return nil, err
	}

	paragraph.Set("Size", strconv.Itoa(int(stat.Size())))
	/* Right, now, in addition, we ought to hash the crap out of the file */

	md5sum := md5.New()
	sha1 := sha1.New()
	sha256 := sha256.New()

	writer := io.MultiWriter(md5sum, sha256, sha1)

	if _, err := io.Copy(writer, fd); err != nil {
		return nil, err
	}

	for key, hasher := range map[string]hash.Hash{
		"MD5sum": md5sum,
		"SHA1":   sha1,
		"SHA256": sha256,
	} {
		paragraph.Set(key, fmt.Sprintf("%x", hasher.Sum(nil)))
	}

	return &pkg, control.UnpackFromParagraph(debFile.Control.Paragraph, &pkg)
}

// }}}

// }}}

// Packages {{{

// Iterator to access the entries contained in the Packages entry in an
// apt repo. This contians information about the binary Debian packages.
type Packages struct {
	decoder *control.Decoder
}

// Next {{{

// Get the next Package entry in the Packages list. This will return an
// io.EOF at the last entry.
func (p *Packages) Next() (*Package, error) {
	next := Package{}
	return &next, p.decoder.Decode(&next)
}

// }}}

// LoadPackagesFile {{{

// Given a path, create a Packages iterator. Note that the Packages
// file is not OpenPGP signed, so one will need to verify the integrety
// of this file from the InRelease file before trusting any output.
func LoadPackagesFile(path string) (*Packages, error) {
	fd, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	return LoadPackages(fd)
}

// }}}

// LoadPackages {{{

// Given an io.Reader, create a Packages iterator. Note that the Packages
// file is not OpenPGP signed, so one will need to verify the integrety
// of this file from the InRelease file before trusting any output.
func LoadPackages(in io.Reader) (*Packages, error) {
	decoder, err := control.NewDecoder(in, nil)
	if err != nil {
		return nil, err
	}
	return &Packages{decoder: decoder}, nil
}

// }}}

// }}}

// vim: foldmethod=marker
<|endoftext|>


<commit_before>package main

import (
	"code.google.com/p/gosqlite/sqlite"
	"bufio"
	"flag"
	"fmt"
	"os"
	"os/user"
	"strconv"
	"strings"
)

var conn sqlite.Conn

// Book bundles data related to a book
type Book struct {
	ID       int
	Title    string
	Author   string
	ISBN     string
	Comments string
}

func (book Book) String() string {
	return fmt.Sprintf(
		"{#%v: '%v' by '%v', comments: %v}",
		book.ID, book.Title, book.Author, book.Comments,
	)
}

func insert(book *Book) int {
	insertSql := fmt.Sprintf(
		`INSERT INTO books(title, author, isbn, comments) VALUES('%v', '%v', '%v', '%v');`,
		book.Title,
		book.Author,
		book.ISBN,
		book.Comments,
	)

	err := conn.Exec(insertSql)
	if err != nil {
		fmt.Printf("Error while Inserting: %s", err)
	}

	selectStmt, err := conn.Prepare("select last_insert_rowid();")
	if err != nil {
		fmt.Printf("Error while getting autoincrement value: %s", err)
	}

	x := 0
	if selectStmt.Next() {
		selectStmt.Scan(&x)
	}

	return x
}

// try to parse a Book from a DB statement
func getBookFromStmt(stmt *sqlite.Stmt) *Book {
	book := new(Book)

	err := stmt.Scan(&book.ID, &book.Title, &book.Author, &book.ISBN, &book.Comments)
	if err != nil {
		fmt.Printf("Error while getting row data: %s\n", err)
		os.Exit(1)
	}

	return book
}

// get a Book slice if the title, author or comments contain the given query
func getBooks(query string) []Book {
	var books []Book
	var queryString string
	if query != "" {
		queryString = fmt.Sprintf(
		`SELECT * FROM books WHERE title LIKE "%%%v%%"
		UNION
		SELECT * FROM books WHERE author LIKE "%%%v%%"
		UNION
		SELECT * FROM books WHERE comments LIKE "%%%v%%"`,
		query,
		query,
		query)
	} else {
		queryString = "SELECT * FROM books"
	}
	selectStmt, err := conn.Prepare(queryString)
	err = selectStmt.Exec()
	if err != nil {
		fmt.Printf("Error while Selecting: %v", err)
	}

	for selectStmt.Next() {
		book := getBookFromStmt(selectStmt)
		books = append(books, *book)
	}

	return books
}

func getBookByID(id int) (*Book, error) {
	var book *Book
	var queryString = fmt.Sprintf(`SELECT * FROM books WHERE id = %v`, id)
	stmt, err := conn.Prepare(queryString)
	err = stmt.Exec()
	if err != nil {
		return book, err
	}
	stmt.Next()
	book = getBookFromStmt(stmt)
	return book, nil
}

func deleteBookByID(id int) error {
	queryString := fmt.Sprintf(`DELETE FROM books WHERE id = %v`, id)
	stmt, err := conn.Prepare(queryString)
	err = stmt.Exec()
	if err != nil {
		return err
	}
	stmt.Next()
	return nil
}

func initDb(dbPath string, dbName string) (*sqlite.Conn, error) {
	os.Mkdir(dbPath, 0700)
	var db = dbPath + dbName

	var conn, dberr = sqlite.Open(db)
	if dberr != nil {
		return nil, dberr
	}

	conn.Exec("CREATE TABLE books(id INTEGER PRIMARY KEY AUTOINCREMENT, title VARCHAR(200), author VARCHAR(200), isbn VARCHAR(20), comments TEXT);")

	return conn, nil
}

func prompt(text string) string {
	fmt.Print(text)
	bio := bufio.NewReader(os.Stdin)
	line, _, err := bio.ReadLine()
	if err != nil {
		fmt.Println("Error during input: ", err)
	}
	return string(line)
}

func printUsage() {
	fmt.Println("USAGE: books COMMAND argument1 argument2 ...")
	fmt.Println("Available commands:")
	fmt.Println("\tls - list books, pass search terms as arguments")
	fmt.Println("\tadd - add book (prompts for title, author, comments")
	fmt.Println("\tdel - delete book (prompts for id)")
	fmt.Println("\thelp - display this text")
}

func main() {
	flag.Parse()
	args := flag.Args()
	if len(args) < 1 {
		printUsage()
		os.Exit(1)
	}
	command := args[0]
	subArgs := args[1:]

	var usr, _ = user.Current()
	var conn, err = initDb(usr.HomeDir + "/.books/", "books.db")
	if err != nil {
		fmt.Println("Error initializing database ", err)
		os.Exit(1)
	}
	defer conn.Close()

	if command == "ls" {
		query := ""
		if len(subArgs) > 0 {
			query = strings.Join(subArgs, " ")
		}
		books := getBooks(query)
		for _, b := range books {
			fmt.Printf("%v\n", b)
		}
	} else if command == "add" {
		author := prompt("Author: ")
		title := prompt("Title: ")
		comments := prompt("Comments: ")
		book := Book{0, title, author, "", comments}
		insert(&book)
	} else if command == "del" {
		idString := prompt("id: ")
		id, err := strconv.ParseInt(idString, 10, 0)
		if err != nil {
			fmt.Println("Invalid id (not a number)")
			os.Exit(1)
		}
		book, err := getBookByID(int(id))
		if err != nil {
			fmt.Println("Error fetching book with id ", idString)
			os.Exit(1)
		}
		var promptString = fmt.Sprintf("Confirm deleting of %v (y/N)? ", book)
		if strings.ToUpper(prompt(promptString)) == "Y" {
			fmt.Println("Deleting ", book)
			deleteBookByID(int(id))
		}
	} else if command == "help" {
		printUsage()
	}
}
<commit_msg>API update<commit_after>package main

import (
	"code.google.com/p/gosqlite/sqlite"
	"bufio"
	"flag"
	"fmt"
	"os"
	"os/user"
	"strconv"
	"strings"
)

// Book bundles data related to a book
type Book struct {
	ID       int
	Title    string
	Author   string
	ISBN     string
	Comments string
}

func (book Book) String() string {
	return fmt.Sprintf(
		"{#%v: '%v' by '%v', comments: %v}",
		book.ID, book.Title, book.Author, book.Comments,
	)
}

func insert(book *Book, conn *sqlite.Conn) int {
	insertSql := fmt.Sprintf(
		`INSERT INTO books(title, author, isbn, comments) VALUES('%v', '%v', '%v', '%v');`,
		book.Title,
		book.Author,
		book.ISBN,
		book.Comments,
	)

	err := conn.Exec(insertSql)
	if err != nil {
		fmt.Printf("Error while Inserting: %s", err)
	}

	selectStmt, err := conn.Prepare("select last_insert_rowid();")
	if err != nil {
		fmt.Printf("Error while getting autoincrement value: %s", err)
	}

	x := 0
	if selectStmt.Next() {
		selectStmt.Scan(&x)
	}

	return x
}

// try to parse a Book from a DB statement
func getBookFromStmt(stmt *sqlite.Stmt) *Book {
	book := new(Book)

	err := stmt.Scan(&book.ID, &book.Title, &book.Author, &book.ISBN, &book.Comments)
	if err != nil {
		fmt.Printf("Error while getting row data: %s\n", err)
		os.Exit(1)
	}

	return book
}

// get a Book slice if the title, author or comments contain the given query
func getBooks(query string, conn *sqlite.Conn) []Book {
	var books []Book
	var queryString string
	if query != "" {
		queryString = fmt.Sprintf(
		`SELECT * FROM books WHERE title LIKE "%%%v%%"
		UNION
		SELECT * FROM books WHERE author LIKE "%%%v%%"
		UNION
		SELECT * FROM books WHERE comments LIKE "%%%v%%"`,
		query,
		query,
		query)
	} else {
		queryString = "SELECT * FROM books"
	}
	selectStmt, err := conn.Prepare(queryString)
	err = selectStmt.Exec()
	if err != nil {
		fmt.Printf("Error while Selecting: %v", err)
	}

	for selectStmt.Next() {
		book := getBookFromStmt(selectStmt)
		books = append(books, *book)
	}

	return books
}

func getBookByID(id int, conn *sqlite.Conn) (*Book, error) {
	var book *Book
	var queryString = fmt.Sprintf(`SELECT * FROM books WHERE id = %v`, id)
	stmt, err := conn.Prepare(queryString)
	err = stmt.Exec()
	if err != nil {
		return book, err
	}
	stmt.Next()
	book = getBookFromStmt(stmt)
	return book, nil
}

func deleteBookByID(id int, conn *sqlite.Conn) error {
	queryString := fmt.Sprintf(`DELETE FROM books WHERE id = %v`, id)
	stmt, err := conn.Prepare(queryString)
	err = stmt.Exec()
	if err != nil {
		return err
	}
	stmt.Next()
	return nil
}

func initDb(dbPath string, dbName string) (*sqlite.Conn, error) {
	os.Mkdir(dbPath, 0700)
	var db = dbPath + dbName

	var conn, dberr = sqlite.Open(db)
	if dberr != nil {
		return nil, dberr
	}

	conn.Exec("CREATE TABLE books(id INTEGER PRIMARY KEY AUTOINCREMENT, title VARCHAR(200), author VARCHAR(200), isbn VARCHAR(20), comments TEXT);")

	return conn, nil
}

func prompt(text string) string {
	fmt.Print(text)
	bio := bufio.NewReader(os.Stdin)
	line, _, err := bio.ReadLine()
	if err != nil {
		fmt.Println("Error during input: ", err)
	}
	return string(line)
}

func printUsage() {
	fmt.Println("USAGE: books COMMAND argument1 argument2 ...")
	fmt.Println("Available commands:")
	fmt.Println("\tls - list books, pass search terms as arguments")
	fmt.Println("\tadd - add book (prompts for title, author, comments")
	fmt.Println("\tdel - delete book (prompts for id)")
	fmt.Println("\thelp - display this text")
}

func main() {
	flag.Parse()
	args := flag.Args()
	if len(args) < 1 {
		printUsage()
		os.Exit(1)
	}
	command := args[0]
	subArgs := args[1:]

	var usr, _ = user.Current()
	var conn, err = initDb(usr.HomeDir + "/.books/", "books.db")
	if err != nil {
		fmt.Println("Error initializing database ", err)
		os.Exit(1)
	}
	defer conn.Close()

	if command == "ls" {
		query := ""
		if len(subArgs) > 0 {
			query = strings.Join(subArgs, " ")
		}
		books := getBooks(query, conn)
		for _, b := range books {
			fmt.Printf("%v\n", b)
		}
	} else if command == "add" {
		author := prompt("Author: ")
		title := prompt("Title: ")
		comments := prompt("Comments: ")
		book := Book{0, title, author, "", comments}
		insert(&book, conn)
	} else if command == "del" {
		idString := prompt("id: ")
		id, err := strconv.ParseInt(idString, 10, 0)
		if err != nil {
			fmt.Println("Invalid id (not a number)")
			os.Exit(1)
		}
		book, err := getBookByID(int(id), conn)
		if err != nil {
			fmt.Println("Error fetching book with id ", idString)
			os.Exit(1)
		}
		var promptString = fmt.Sprintf("Confirm deleting of %v (y/N)? ", book)
		if strings.ToUpper(prompt(promptString)) == "Y" {
			fmt.Println("Deleting ", book)
			deleteBookByID(int(id), conn)
		}
	} else if command == "help" {
		printUsage()
	}
}
<|endoftext|>


<commit_before>package survey

import (
	"fmt"

	"github.com/alecaivazis/survey/format"
)

// Password is like a normal Input but the text shows up as *'s and
// there is no default.
type Password struct {
	Message string
}

// the character to use to hide the input
var hideChar = "*"

// this function will be passed to the input handler to hide the input
func hideInput(input string) string {
	out := ""
	// fmt.Print(input, "h")
	for i := 0; i < len(input); i++ {
		out += hideChar
	}

	return out
}

// Prompt behaves like a normal int but hides the input.
func (prompt *Password) Prompt() (string, error) {
	// print the question we were given to kick off the prompt
	fmt.Print(format.Ask(fmt.Sprintf("%v ", prompt.Message), ""))

	// a running total
	value := ""

	// listen for input (this will ignore crazy characters like arrow keys)
	for val, keyCode, err := GetInput(hideInput); true; value, keyCode, err = GetInput(hideInput) {
		// if there is an error
		if err != nil {
			// bubble up
			return "", err
		}

		// add val to the running total
		value += val

		if keyCode == KeyEnter {
			fmt.Print("\n")
			return value, err
		}
	}

	return value, nil
}

// Cleanup hides the string with a fixed number of characters.
func (prompt *Password) Cleanup(val string) error {
	return nil
}
<commit_msg>nazi tweak<commit_after>package survey

import (
	"fmt"

	"github.com/alecaivazis/survey/format"
)

// Password is like a normal Input but the text shows up as *'s and
// there is no default.
type Password struct {
	Message string
}

// the character to use to hide the input
var hideChar = "*"

// this function will be passed to the input handler to hide the input
func hideInput(input string) string {
	out := ""
	// fmt.Print(input, "h")
	for i := 0; i < len(input); i++ {
		out += hideChar
	}

	return out
}

// Prompt behaves like a normal int but hides the input.
func (prompt *Password) Prompt() (string, error) {
	// print the question we were given to kick off the prompt
	fmt.Print(format.Ask(fmt.Sprintf("%v ", prompt.Message), ""))

	// a running total
	value := ""

	// listen for input (this will ignore crazy characters like arrow keys)
	for val, keyCode, err := GetInput(hideInput); true; value, keyCode, err = GetInput(hideInput) {
		// if there is an error
		if err != nil {
			// bubble up
			return "", err
		}

		// add val to the running total
		value += val

		if keyCode == KeyEnter {
			fmt.Print("\n")
			return value, nil
		}
	}

	return value, nil
}

// Cleanup hides the string with a fixed number of characters.
func (prompt *Password) Cleanup(val string) error {
	return nil
}
<|endoftext|>


<commit_before>package grester

import (
	"regexp"
	"strings"
)

func Path2Regexp(path string) (*regexp.Regexp, map[int]string){
	parts := strings.Split(path, "/")
	params := make(map[int]string)

	var j = 0
	for i, part := range parts {
		if strings.HasPrefix(part, ":") {
			params[j] = strings.TrimPrefix(part, ":")
			parts[i] = "([^/]+)"
			j++
		}
	}

	path = strings.Join(parts, "/")

	return regexp.MustCompile(path), params
}
<commit_msg>fix path regex<commit_after>package grester

import (
	"regexp"
	"strings"
)

func Path2Regexp(path string) (*regexp.Regexp, map[int]string){
	parts := strings.Split(path, "/")
	params := make(map[int]string)

	var j = 0
	for i, part := range parts {
		if strings.HasPrefix(part, ":") {
			params[j] = strings.TrimPrefix(part, ":")
			parts[i] = "([^/]+)"
			j++
		}
	}

	path = strings.Join(parts, "/") + "$"
	return regexp.MustCompile(path), params
}
<|endoftext|>


<commit_before>package server

import (
	"github.com/Comcast/webpa-common/health"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
	"net/http"
	"testing"
	"time"
)

type mockHandler struct {
	mock.Mock
}

func (m *mockHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
	m.Called(response, request)
}

func TestWebPADefaults(t *testing.T) {
	assert := assert.New(t)
	for _, webPA := range []*WebPA{nil, new(WebPA)} {
		assert.Equal(DefaultName, webPA.name())
		assert.Equal(DefaultAddress, webPA.address())
		assert.Equal(DefaultHealthAddress, webPA.healthAddress())
		assert.Equal("", webPA.pprofAddress())
		assert.Equal(DefaultHealthLogInterval, webPA.healthLogInterval())
		assert.Equal(DefaultLogConnectionState, webPA.logConnectionState())
		assert.Equal("", webPA.certificateFile())
		assert.Equal("", webPA.keyFile())
	}
}

func TestWebPAAccessors(t *testing.T) {
	const healthLogInterval time.Duration = 46 * time.Minute

	var (
		assert = assert.New(t)
		webPA  = WebPA{
			Name:               "Custom Name",
			CertificateFile:    "custom.cert",
			KeyFile:            "custom.key",
			LogConnectionState: !DefaultLogConnectionState,
			Address:            "localhost:15001",
			HealthAddress:      "localhost:55",
			HealthLogInterval:  healthLogInterval,
			PprofAddress:       "foobar:7273",
		}
	)

	assert.Equal("Custom Name", webPA.name())
	assert.Equal("custom.cert", webPA.certificateFile())
	assert.Equal("custom.key", webPA.keyFile())
	assert.Equal(!DefaultLogConnectionState, webPA.logConnectionState())
	assert.Equal("localhost:15001", webPA.address())
	assert.Equal("localhost:55", webPA.healthAddress())
	assert.Equal(healthLogInterval, webPA.healthLogInterval())
	assert.Equal("foobar:7273", webPA.pprofAddress())
}

func TestNewPrimaryServer(t *testing.T) {
	var (
		assert  = assert.New(t)
		require = require.New(t)
		handler = new(mockHandler)

		verify, logger = newTestLogger()
		webPA          = WebPA{
			Name:    "TestNewPrimaryServer",
			Address: ":6007",
		}

		primaryServer = webPA.NewPrimaryServer(logger, handler)
	)

	require.NotNil(primaryServer)
	assert.Equal(":6007", primaryServer.Addr)
	assert.Equal(handler, primaryServer.Handler)
	assert.Nil(primaryServer.ConnState)
	assertErrorLog(assert, verify, "TestNewPrimaryServer", primaryServer.ErrorLog)

	handler.AssertExpectations(t)
}

func TestNewPrimaryServerLogConnectionState(t *testing.T) {
	var (
		assert  = assert.New(t)
		require = require.New(t)
		handler = new(mockHandler)

		verify, logger = newTestLogger()
		webPA          = WebPA{
			Name:               "TestNewPrimaryServerLogConnectionState",
			Address:            ":331",
			LogConnectionState: true,
		}

		primaryServer = webPA.NewPrimaryServer(logger, handler)
	)

	require.NotNil(primaryServer)
	assert.Equal(":331", primaryServer.Addr)
	assert.Equal(handler, primaryServer.Handler)
	assertErrorLog(assert, verify, "TestNewPrimaryServerLogConnectionState", primaryServer.ErrorLog)
	assertConnState(assert, verify, primaryServer.ConnState)

	handler.AssertExpectations(t)
}

func TestNewHealthServer(t *testing.T) {
	var (
		assert  = assert.New(t)
		require = require.New(t)
		options = []health.Option{health.Stat("option1"), health.Stat("option2")}

		verify, logger = newTestLogger()
		webPA          = WebPA{
			Name:              "TestNewHealthServer",
			HealthAddress:     ":7181",
			HealthLogInterval: 15 * time.Second,
		}

		healthHandler, healthServer = webPA.NewHealthServer(logger, options...)
	)

	require.NotNil(healthHandler)
	require.NotNil(healthServer)
	assert.Equal(":7181", healthServer.Addr)
	assert.Equal(healthHandler, healthServer.Handler)
	assertErrorLog(assert, verify, "TestNewHealthServer", healthServer.ErrorLog)
	assert.Nil(healthServer.ConnState)
}

func TestNewHealthServerLogConnectionState(t *testing.T) {
	var (
		assert  = assert.New(t)
		require = require.New(t)
		options = []health.Option{health.Stat("option1"), health.Stat("option2")}

		verify, logger = newTestLogger()
		webPA          = WebPA{
			Name:               "TestNewHealthServerLogConnectionState",
			HealthAddress:      ":165",
			HealthLogInterval:  45 * time.Minute,
			LogConnectionState: true,
		}

		healthHandler, healthServer = webPA.NewHealthServer(logger, options...)
	)

	require.NotNil(healthHandler)
	require.NotNil(healthServer)
	assert.Equal(":165", healthServer.Addr)
	assert.Equal(healthHandler, healthServer.Handler)
	assertErrorLog(assert, verify, "TestNewHealthServerLogConnectionState", healthServer.ErrorLog)
	assertConnState(assert, verify, healthServer.ConnState)
}

func TestNewPprofServer(t *testing.T) {
	var (
		assert  = assert.New(t)
		require = require.New(t)
		handler = new(mockHandler)

		verify, logger = newTestLogger()
		webPA          = WebPA{
			Name:         "TestNewPprofServer",
			PprofAddress: ":996",
		}

		pprofServer = webPA.NewPprofServer(logger, handler)
	)

	require.NotNil(pprofServer)
	assert.Equal(":996", pprofServer.Addr)
	assert.Equal(handler, pprofServer.Handler)
	assert.Nil(pprofServer.ConnState)
	assertErrorLog(assert, verify, "TestNewPprofServer", pprofServer.ErrorLog)

	handler.AssertExpectations(t)
}

func TestNewPprofServerDefaultHandler(t *testing.T) {
	var (
		assert  = assert.New(t)
		require = require.New(t)

		verify, logger = newTestLogger()
		webPA          = WebPA{
			Name:         "TestNewPprofServerDefaultHandler",
			PprofAddress: ":1299",
		}

		pprofServer = webPA.NewPprofServer(logger, nil)
	)

	require.NotNil(pprofServer)
	assert.Equal(":1299", pprofServer.Addr)
	assert.Equal(http.DefaultServeMux, pprofServer.Handler)
	assert.Nil(pprofServer.ConnState)
	assertErrorLog(assert, verify, "TestNewPprofServerDefaultHandler", pprofServer.ErrorLog)
}

func TestNewPprofServerNoPprofAddress(t *testing.T) {
	var (
		assert = assert.New(t)

		verify, logger = newTestLogger()
		webPA          = WebPA{
			Name: "TestNewPprofServerNoPprofAddress",
		}

		pprofServer = webPA.NewPprofServer(logger, nil)
	)

	assert.Nil(pprofServer)
	assert.Empty(verify.String())
}

func TestNewPprofServerLogConnectionState(t *testing.T) {
	var (
		assert  = assert.New(t)
		require = require.New(t)
		handler = new(mockHandler)

		verify, logger = newTestLogger()
		webPA          = WebPA{
			Name:               "TestNewPprofServerLogConnectionState",
			PprofAddress:       ":16077",
			LogConnectionState: true,
		}

		pprofServer = webPA.NewPprofServer(logger, handler)
	)

	require.NotNil(pprofServer)
	assert.Equal(":16077", pprofServer.Addr)
	assert.Equal(handler, pprofServer.Handler)
	assertErrorLog(assert, verify, "TestNewPprofServerLogConnectionState", pprofServer.ErrorLog)
	assertConnState(assert, verify, pprofServer.ConnState)

	handler.AssertExpectations(t)
}
<commit_msg>Interim<commit_after>package server

import (
	"github.com/Comcast/webpa-common/health"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
	"net/http"
	"testing"
	"time"
)

type mockHandler struct {
	mock.Mock
}

func (m *mockHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
	m.Called(response, request)
}

type mockServerExecutor struct {
	mock.Mock
}

func (m *mockServerExecutor) ListenAndServe() error {
	return m.Called().Error(0)
}

func (m *mockServerExecutor) ListenAndServeTLS(certificateFile, keyFile string) error {
	return m.Called(certificateFile, keyFile).Error(0)
}

func TestWebPADefaults(t *testing.T) {
	assert := assert.New(t)
	for _, webPA := range []*WebPA{nil, new(WebPA)} {
		assert.Equal(DefaultName, webPA.name())
		assert.Equal(DefaultAddress, webPA.address())
		assert.Equal(DefaultHealthAddress, webPA.healthAddress())
		assert.Equal("", webPA.pprofAddress())
		assert.Equal(DefaultHealthLogInterval, webPA.healthLogInterval())
		assert.Equal(DefaultLogConnectionState, webPA.logConnectionState())
		assert.Equal("", webPA.certificateFile())
		assert.Equal("", webPA.keyFile())
	}
}

func TestWebPAAccessors(t *testing.T) {
	const healthLogInterval time.Duration = 46 * time.Minute

	var (
		assert = assert.New(t)
		webPA  = WebPA{
			Name:               "Custom Name",
			CertificateFile:    "custom.cert",
			KeyFile:            "custom.key",
			LogConnectionState: !DefaultLogConnectionState,
			Address:            "localhost:15001",
			HealthAddress:      "localhost:55",
			HealthLogInterval:  healthLogInterval,
			PprofAddress:       "foobar:7273",
		}
	)

	assert.Equal("Custom Name", webPA.name())
	assert.Equal("custom.cert", webPA.certificateFile())
	assert.Equal("custom.key", webPA.keyFile())
	assert.Equal(!DefaultLogConnectionState, webPA.logConnectionState())
	assert.Equal("localhost:15001", webPA.address())
	assert.Equal("localhost:55", webPA.healthAddress())
	assert.Equal(healthLogInterval, webPA.healthLogInterval())
	assert.Equal("foobar:7273", webPA.pprofAddress())
}

func TestNewPrimaryServer(t *testing.T) {
	var (
		assert  = assert.New(t)
		require = require.New(t)
		handler = new(mockHandler)

		verify, logger = newTestLogger()
		webPA          = WebPA{
			Name:    "TestNewPrimaryServer",
			Address: ":6007",
		}

		primaryServer = webPA.NewPrimaryServer(logger, handler)
	)

	require.NotNil(primaryServer)
	assert.Equal(":6007", primaryServer.Addr)
	assert.Equal(handler, primaryServer.Handler)
	assert.Nil(primaryServer.ConnState)
	assertErrorLog(assert, verify, "TestNewPrimaryServer", primaryServer.ErrorLog)

	handler.AssertExpectations(t)
}

func TestNewPrimaryServerLogConnectionState(t *testing.T) {
	var (
		assert  = assert.New(t)
		require = require.New(t)
		handler = new(mockHandler)

		verify, logger = newTestLogger()
		webPA          = WebPA{
			Name:               "TestNewPrimaryServerLogConnectionState",
			Address:            ":331",
			LogConnectionState: true,
		}

		primaryServer = webPA.NewPrimaryServer(logger, handler)
	)

	require.NotNil(primaryServer)
	assert.Equal(":331", primaryServer.Addr)
	assert.Equal(handler, primaryServer.Handler)
	assertErrorLog(assert, verify, "TestNewPrimaryServerLogConnectionState", primaryServer.ErrorLog)
	assertConnState(assert, verify, primaryServer.ConnState)

	handler.AssertExpectations(t)
}

func TestNewHealthServer(t *testing.T) {
	var (
		assert  = assert.New(t)
		require = require.New(t)
		options = []health.Option{health.Stat("option1"), health.Stat("option2")}

		verify, logger = newTestLogger()
		webPA          = WebPA{
			Name:              "TestNewHealthServer",
			HealthAddress:     ":7181",
			HealthLogInterval: 15 * time.Second,
		}

		healthHandler, healthServer = webPA.NewHealthServer(logger, options...)
	)

	require.NotNil(healthHandler)
	require.NotNil(healthServer)
	assert.Equal(":7181", healthServer.Addr)
	assert.Equal(healthHandler, healthServer.Handler)
	assertErrorLog(assert, verify, "TestNewHealthServer", healthServer.ErrorLog)
	assert.Nil(healthServer.ConnState)
}

func TestNewHealthServerLogConnectionState(t *testing.T) {
	var (
		assert  = assert.New(t)
		require = require.New(t)
		options = []health.Option{health.Stat("option1"), health.Stat("option2")}

		verify, logger = newTestLogger()
		webPA          = WebPA{
			Name:               "TestNewHealthServerLogConnectionState",
			HealthAddress:      ":165",
			HealthLogInterval:  45 * time.Minute,
			LogConnectionState: true,
		}

		healthHandler, healthServer = webPA.NewHealthServer(logger, options...)
	)

	require.NotNil(healthHandler)
	require.NotNil(healthServer)
	assert.Equal(":165", healthServer.Addr)
	assert.Equal(healthHandler, healthServer.Handler)
	assertErrorLog(assert, verify, "TestNewHealthServerLogConnectionState", healthServer.ErrorLog)
	assertConnState(assert, verify, healthServer.ConnState)
}

func TestNewPprofServer(t *testing.T) {
	var (
		assert  = assert.New(t)
		require = require.New(t)
		handler = new(mockHandler)

		verify, logger = newTestLogger()
		webPA          = WebPA{
			Name:         "TestNewPprofServer",
			PprofAddress: ":996",
		}

		pprofServer = webPA.NewPprofServer(logger, handler)
	)

	require.NotNil(pprofServer)
	assert.Equal(":996", pprofServer.Addr)
	assert.Equal(handler, pprofServer.Handler)
	assert.Nil(pprofServer.ConnState)
	assertErrorLog(assert, verify, "TestNewPprofServer", pprofServer.ErrorLog)

	handler.AssertExpectations(t)
}

func TestNewPprofServerDefaultHandler(t *testing.T) {
	var (
		assert  = assert.New(t)
		require = require.New(t)

		verify, logger = newTestLogger()
		webPA          = WebPA{
			Name:         "TestNewPprofServerDefaultHandler",
			PprofAddress: ":1299",
		}

		pprofServer = webPA.NewPprofServer(logger, nil)
	)

	require.NotNil(pprofServer)
	assert.Equal(":1299", pprofServer.Addr)
	assert.Equal(http.DefaultServeMux, pprofServer.Handler)
	assert.Nil(pprofServer.ConnState)
	assertErrorLog(assert, verify, "TestNewPprofServerDefaultHandler", pprofServer.ErrorLog)
}

func TestNewPprofServerNoPprofAddress(t *testing.T) {
	var (
		assert = assert.New(t)

		verify, logger = newTestLogger()
		webPA          = WebPA{
			Name: "TestNewPprofServerNoPprofAddress",
		}

		pprofServer = webPA.NewPprofServer(logger, nil)
	)

	assert.Nil(pprofServer)
	assert.Empty(verify.String())
}

func TestNewPprofServerLogConnectionState(t *testing.T) {
	var (
		assert  = assert.New(t)
		require = require.New(t)
		handler = new(mockHandler)

		verify, logger = newTestLogger()
		webPA          = WebPA{
			Name:               "TestNewPprofServerLogConnectionState",
			PprofAddress:       ":16077",
			LogConnectionState: true,
		}

		pprofServer = webPA.NewPprofServer(logger, handler)
	)

	require.NotNil(pprofServer)
	assert.Equal(":16077", pprofServer.Addr)
	assert.Equal(handler, pprofServer.Handler)
	assertErrorLog(assert, verify, "TestNewPprofServerLogConnectionState", pprofServer.ErrorLog)
	assertConnState(assert, verify, pprofServer.ConnState)

	handler.AssertExpectations(t)
}

func TestRunServer(t *testing.T) {
	var (
		verify, logger = newTestLogger()
	)
}
<|endoftext|>