<commit_before>/*
Copyright 2021 The Ceph-CSI Authors.

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

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

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

package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"

	"github.com/google/go-github/github"
	"golang.org/x/oauth2"
)

var (
	totalRequiredReviews int
	retry                int
)

type retestConfig struct {
	retryLimit          string
	requiredReviewCount string
	exemptlabel         string
	requiredlabel       string
	githubToken         string
	owner               string
	repo                string
	client              *github.Client
}

// standard input env variables in action more details at
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#inputs
func getConfig() *retestConfig {
	c := &retestConfig{}
	c.retryLimit = os.Getenv("INPUT_MAX-RETRY")
	c.requiredReviewCount = os.Getenv("INPUT_REQUIRED-APPROVE-COUNT")
	c.exemptlabel = os.Getenv("INPUT_EXEMPT-LABEL")
	c.requiredlabel = os.Getenv("INPUT_REQUIRED-LABEL")
	c.githubToken = os.Getenv("GITHUB_TOKEN")
	c.owner, c.repo = func() (string, string) {
		if os.Getenv("GITHUB_REPOSITORY") != "" {
			if len(strings.Split(os.Getenv("GITHUB_REPOSITORY"), "/")) == 2 {
				return strings.Split(os.Getenv("GITHUB_REPOSITORY"), "/")[0], strings.Split(os.Getenv("GITHUB_REPOSITORY"), "/")[1]
			}
		}
		return "", ""
	}()
	return c
}

// validate validates the input parameters.
func (c retestConfig) validate() error {
	if c.requiredlabel == "" {
		return errors.New("required-label is not set")
	}

	if c.githubToken == "" {
		return errors.New("GITHUB_TOKEN is not set")
	}

	if c.owner == "" || c.repo == "" {
		return errors.New("GITHUB_REPOSITORY is not set")
	}

	return nil
}

// createClient creates a new secure client.
func (c *retestConfig) createClient() {
	ts := oauth2.StaticTokenSource(
		&oauth2.Token{AccessToken: c.githubToken},
	)
	tc := oauth2.NewClient(context.TODO(), ts)
	c.client = github.NewClient(tc)
}

func main() {
	var err error
	c := getConfig()
	if err = c.validate(); err != nil {
		log.Fatalf("%v", err)
	}

	retry, err = strconv.Atoi(c.retryLimit)
	if err != nil {
		log.Fatalf("max-retry %q is not valid", c.retryLimit)
	}

	totalRequiredReviews, err = strconv.Atoi(c.requiredReviewCount)
	if err != nil {
		log.Fatalf("required-review-count %q is not valid", c.requiredReviewCount)
	}

	c.createClient()

	opt := &github.PullRequestListOptions{}
	req, _, err := c.client.PullRequests.List(context.TODO(), c.owner, c.repo, opt)
	if err != nil {
		log.Fatalf("failed to list pull requests %v\n", err)
	}
	for _, re := range req {
		if *re.State == "open" {
			prNumber := re.GetNumber()
			log.Printf("PR with ID %d with Title %q is open\n", prNumber, re.GetTitle())
			for _, l := range re.Labels {
				// check if label is exempt
				if strings.EqualFold(c.exemptlabel, l.GetName()) {
					continue
				}
				// check if label is matching
				if !strings.EqualFold(c.requiredlabel, l.GetName()) {
					continue
				}

				// check if PR has required approvals
				if !c.checkPRRequiredApproval(prNumber) {
					continue
				}

				log.Printf("checking status for PR %d with label %s", prNumber, l.GetName())
				rs, _, err := c.client.Repositories.ListStatuses(context.TODO(), c.owner, c.repo, re.GetHead().GetSHA(), &github.ListOptions{})
				if err != nil {
					log.Printf("failed to list status %v\n", err)
					continue
				}

				statusList := filterStatusList(rs)
				failedTestFound := false
				for _, r := range statusList {
					log.Printf("found context %s with status %s\n", r.GetContext(), r.GetState())
					if contains([]string{"failed", "failure"}, r.GetState()) {
						log.Printf("found failed test %s\n", r.GetContext())
						// check if retest limit is reached
						msg := fmt.Sprintf("/retest %s", r.GetContext())
						ok, err := c.checkRetestLimitReached(prNumber, msg)
						if err != nil {
							log.Printf("failed to check retest limit %v\n", err)
							continue
						}
						if ok {
							log.Printf("Pull Request %d: %q reached  maximum attempt. skipping retest %v\n", prNumber, r.GetContext(), retry)
							continue
						}

						comment := &github.IssueComment{
							Body: github.String(msg),
						}
						_, _, err = c.client.Issues.CreateComment(context.TODO(), c.owner, c.repo, prNumber, comment)
						if err != nil {
							log.Printf("failed to create comment %v\n", err)
							continue
						}
						// Post comment with target URL for retesting
						msg = fmt.Sprintf("@%s %q test failed. Logs are available at [location](%s) for debugging", re.GetUser().GetLogin(), r.GetContext(), r.GetTargetURL())
						comment.Body = github.String(msg)
						_, _, err = c.client.Issues.CreateComment(context.TODO(), c.owner, c.repo, prNumber, comment)
						if err != nil {
							log.Printf("failed to create comment %v\n", err)
							continue
						}
						failedTestFound = true
					}
				}

				if failedTestFound {
					// comment `@Mergifyio requeue` so mergifyio adds the pr back into the queue.
					msg := "@Mergifyio requeue"
					comment := &github.IssueComment{
						Body: github.String(msg),
					}
					_, _, err = c.client.Issues.CreateComment(context.TODO(), c.owner, c.repo, prNumber, comment)
					if err != nil {
						log.Printf("failed to create comment %q: %v\n", msg, err)
						continue
					}
				}
			}
		}
	}
}

// checkPRRequiredApproval check PullRequest has required approvals.
func (c *retestConfig) checkPRRequiredApproval(prNumber int) bool {
	rev, _, err := c.client.PullRequests.ListReviews(context.TODO(), c.owner, c.repo, prNumber, &github.ListOptions{})
	if err != nil {
		log.Printf("failed to list reviews %v\n", err)
		return false
	}
	approvedReviews := 0
	for _, rv := range rev {
		if rv.GetState() == "APPROVED" {
			approvedReviews += 1
		}
	}
	if !(approvedReviews >= totalRequiredReviews) {
		log.Printf("total approved reviews for PR %d are %d but required %d", prNumber, approvedReviews, totalRequiredReviews)
		return false
	}

	return true
}

// checkRetestLimitReached check if retest limit is reached.
func (c *retestConfig) checkRetestLimitReached(prNumber int, msg string) (bool, error) {
	creq, _, err := c.client.Issues.ListComments(context.TODO(), c.owner, c.repo, prNumber, &github.IssueListCommentsOptions{})
	if err != nil {
		return false, err
	}
	retestCount := 0

	for _, pc := range creq {
		if pc.GetBody() == msg {
			retestCount += 1
		}
	}
	log.Printf("found %d retries and remaining %d retries\n", retestCount, retry-retestCount)
	if retestCount >= int(retry) {
		return true, nil
	}

	return false, nil
}

// containers check if slice contains string.
func contains(s []string, e string) bool {
	for _, a := range s {
		if a == e {
			return true
		}
	}

	return false
}

// filterStatusesList returns list of unique and recently updated github RepoStatuses.
// Raw github RepoStatus list may contain duplicate and older statuses.
func filterStatusList(rawStatusList []*github.RepoStatus) []*github.RepoStatus {
	testStatus := make(map[string]*github.RepoStatus)

	for _, r := range rawStatusList {
		status, ok := testStatus[r.GetContext()]
		if !ok || r.GetUpdatedAt().After(status.GetUpdatedAt()) {
			testStatus[r.GetContext()] = r
		}
	}

	statusList := make([]*github.RepoStatus, 0)
	for _, rs := range testStatus {
		statusList = append(statusList, rs)
	}

	return statusList
}
<commit_msg>ci: check more reviews in retest action<commit_after>/*
Copyright 2021 The Ceph-CSI Authors.

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

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

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

package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"

	"github.com/google/go-github/github"
	"golang.org/x/oauth2"
)

var (
	totalRequiredReviews int
	retry                int
)

type retestConfig struct {
	retryLimit          string
	requiredReviewCount string
	exemptlabel         string
	requiredlabel       string
	githubToken         string
	owner               string
	repo                string
	client              *github.Client
}

// standard input env variables in action more details at
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#inputs
func getConfig() *retestConfig {
	c := &retestConfig{}
	c.retryLimit = os.Getenv("INPUT_MAX-RETRY")
	c.requiredReviewCount = os.Getenv("INPUT_REQUIRED-APPROVE-COUNT")
	c.exemptlabel = os.Getenv("INPUT_EXEMPT-LABEL")
	c.requiredlabel = os.Getenv("INPUT_REQUIRED-LABEL")
	c.githubToken = os.Getenv("GITHUB_TOKEN")
	c.owner, c.repo = func() (string, string) {
		if os.Getenv("GITHUB_REPOSITORY") != "" {
			if len(strings.Split(os.Getenv("GITHUB_REPOSITORY"), "/")) == 2 {
				return strings.Split(os.Getenv("GITHUB_REPOSITORY"), "/")[0], strings.Split(os.Getenv("GITHUB_REPOSITORY"), "/")[1]
			}
		}
		return "", ""
	}()
	return c
}

// validate validates the input parameters.
func (c retestConfig) validate() error {
	if c.requiredlabel == "" {
		return errors.New("required-label is not set")
	}

	if c.githubToken == "" {
		return errors.New("GITHUB_TOKEN is not set")
	}

	if c.owner == "" || c.repo == "" {
		return errors.New("GITHUB_REPOSITORY is not set")
	}

	return nil
}

// createClient creates a new secure client.
func (c *retestConfig) createClient() {
	ts := oauth2.StaticTokenSource(
		&oauth2.Token{AccessToken: c.githubToken},
	)
	tc := oauth2.NewClient(context.TODO(), ts)
	c.client = github.NewClient(tc)
}

func main() {
	var err error
	c := getConfig()
	if err = c.validate(); err != nil {
		log.Fatalf("%v", err)
	}

	retry, err = strconv.Atoi(c.retryLimit)
	if err != nil {
		log.Fatalf("max-retry %q is not valid", c.retryLimit)
	}

	totalRequiredReviews, err = strconv.Atoi(c.requiredReviewCount)
	if err != nil {
		log.Fatalf("required-review-count %q is not valid", c.requiredReviewCount)
	}

	c.createClient()

	opt := &github.PullRequestListOptions{}
	req, _, err := c.client.PullRequests.List(context.TODO(), c.owner, c.repo, opt)
	if err != nil {
		log.Fatalf("failed to list pull requests %v\n", err)
	}
	for _, re := range req {
		if *re.State == "open" {
			prNumber := re.GetNumber()
			log.Printf("PR with ID %d with Title %q is open\n", prNumber, re.GetTitle())
			for _, l := range re.Labels {
				// check if label is exempt
				if strings.EqualFold(c.exemptlabel, l.GetName()) {
					continue
				}
				// check if label is matching
				if !strings.EqualFold(c.requiredlabel, l.GetName()) {
					continue
				}

				// check if PR has required approvals
				if !c.checkPRRequiredApproval(prNumber) {
					continue
				}

				log.Printf("checking status for PR %d with label %s", prNumber, l.GetName())
				rs, _, err := c.client.Repositories.ListStatuses(context.TODO(), c.owner, c.repo, re.GetHead().GetSHA(), &github.ListOptions{})
				if err != nil {
					log.Printf("failed to list status %v\n", err)
					continue
				}

				statusList := filterStatusList(rs)
				failedTestFound := false
				for _, r := range statusList {
					log.Printf("found context %s with status %s\n", r.GetContext(), r.GetState())
					if contains([]string{"failed", "failure"}, r.GetState()) {
						log.Printf("found failed test %s\n", r.GetContext())
						// check if retest limit is reached
						msg := fmt.Sprintf("/retest %s", r.GetContext())
						ok, err := c.checkRetestLimitReached(prNumber, msg)
						if err != nil {
							log.Printf("failed to check retest limit %v\n", err)
							continue
						}
						if ok {
							log.Printf("Pull Request %d: %q reached  maximum attempt. skipping retest %v\n", prNumber, r.GetContext(), retry)
							continue
						}

						comment := &github.IssueComment{
							Body: github.String(msg),
						}
						_, _, err = c.client.Issues.CreateComment(context.TODO(), c.owner, c.repo, prNumber, comment)
						if err != nil {
							log.Printf("failed to create comment %v\n", err)
							continue
						}
						// Post comment with target URL for retesting
						msg = fmt.Sprintf("@%s %q test failed. Logs are available at [location](%s) for debugging", re.GetUser().GetLogin(), r.GetContext(), r.GetTargetURL())
						comment.Body = github.String(msg)
						_, _, err = c.client.Issues.CreateComment(context.TODO(), c.owner, c.repo, prNumber, comment)
						if err != nil {
							log.Printf("failed to create comment %v\n", err)
							continue
						}
						failedTestFound = true
					}
				}

				if failedTestFound {
					// comment `@Mergifyio requeue` so mergifyio adds the pr back into the queue.
					msg := "@Mergifyio requeue"
					comment := &github.IssueComment{
						Body: github.String(msg),
					}
					_, _, err = c.client.Issues.CreateComment(context.TODO(), c.owner, c.repo, prNumber, comment)
					if err != nil {
						log.Printf("failed to create comment %q: %v\n", msg, err)
						continue
					}
				}
			}
		}
	}
}

// checkPRRequiredApproval check PullRequest has required approvals.
func (c *retestConfig) checkPRRequiredApproval(prNumber int) bool {
	opts := github.ListOptions{PerPage: 100} // defaults to 30 reviews, too few sometimes
	rev, _, err := c.client.PullRequests.ListReviews(context.TODO(), c.owner, c.repo, prNumber, &opts)
	if err != nil {
		log.Printf("failed to list reviews %v\n", err)
		return false
	}
	approvedReviews := 0
	for _, rv := range rev {
		if rv.GetState() == "APPROVED" {
			approvedReviews += 1
		}
	}
	if !(approvedReviews >= totalRequiredReviews) {
		log.Printf("total approved reviews for PR %d are %d but required %d", prNumber, approvedReviews, totalRequiredReviews)
		return false
	}

	return true
}

// checkRetestLimitReached check if retest limit is reached.
func (c *retestConfig) checkRetestLimitReached(prNumber int, msg string) (bool, error) {
	creq, _, err := c.client.Issues.ListComments(context.TODO(), c.owner, c.repo, prNumber, &github.IssueListCommentsOptions{})
	if err != nil {
		return false, err
	}
	retestCount := 0

	for _, pc := range creq {
		if pc.GetBody() == msg {
			retestCount += 1
		}
	}
	log.Printf("found %d retries and remaining %d retries\n", retestCount, retry-retestCount)
	if retestCount >= int(retry) {
		return true, nil
	}

	return false, nil
}

// containers check if slice contains string.
func contains(s []string, e string) bool {
	for _, a := range s {
		if a == e {
			return true
		}
	}

	return false
}

// filterStatusesList returns list of unique and recently updated github RepoStatuses.
// Raw github RepoStatus list may contain duplicate and older statuses.
func filterStatusList(rawStatusList []*github.RepoStatus) []*github.RepoStatus {
	testStatus := make(map[string]*github.RepoStatus)

	for _, r := range rawStatusList {
		status, ok := testStatus[r.GetContext()]
		if !ok || r.GetUpdatedAt().After(status.GetUpdatedAt()) {
			testStatus[r.GetContext()] = r
		}
	}

	statusList := make([]*github.RepoStatus, 0)
	for _, rs := range testStatus {
		statusList = append(statusList, rs)
	}

	return statusList
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"strconv"
)

func main() {
	name := flag.String("name", "", "host name")
	port := flag.Int("port", 0, "port")

	flag.Parse()

	useArgs := false
	var argName string
	var argPort int

	if *name == "" && *port == 0 {

		if len(os.Args) != 3 {
			log.Fatalln("incorrect number of arguments (expected 2)")
		}

		useArgs = true
		argName = os.Args[1]
		var err error
		argPort, err = strconv.Atoi(os.Args[2])

		if err != nil {
			log.Fatalln("invalid port number")
		}
	} else if *name == "" {
		log.Fatalln("no name given")
	} else if *port == 0 {
		log.Fatalln("no port given")
	}

	var nameStr string
	var portStr string

	if useArgs {
		nameStr = argName
		portStr = strconv.Itoa(argPort)
	} else {
		nameStr = *name
		portStr = strconv.Itoa(*port)
	}

	res, err := http.PostForm("http://donald.drumpf:2016/register",
		url.Values{"name": {nameStr}, "port": {portStr}})

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

	if res.StatusCode != http.StatusOK {
		log.Printf("error: response returned with code %d\n", res.StatusCode)
		defer res.Body.Close()
		body, err := ioutil.ReadAll(res.Body)
		if err != nil {
			log.Println("error reading body")
			return
		}
		strBody := string(body)
		if strBody != "" {
			log.Printf("response body: %q\n", strBody)
		}
	}
	log.Println("OK")
}
<commit_msg>2016!<commit_after>package main

import (
	"flag"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"strconv"
)

func main() {
	name := flag.String("name", "", "host name")
	port := flag.Int("port", 0, "port")

	flag.Parse()

	useArgs := false
	var argName string
	var argPort int

	if *name == "" && *port == 0 {

		if len(os.Args) != 3 {
			log.Fatalln("incorrect number of arguments (expected 2)")
		}

		useArgs = true
		argName = os.Args[1]
		var err error
		argPort, err = strconv.Atoi(os.Args[2])

		if err != nil {
			log.Fatalln("invalid port number")
		}
	} else if *name == "" {
		log.Fatalln("no name given")
	} else if *port == 0 {
		log.Fatalln("no port given")
	}

	var nameStr string
	var portStr string

	if useArgs {
		nameStr = argName
		portStr = strconv.Itoa(argPort)
	} else {
		nameStr = *name
		portStr = strconv.Itoa(*port)
	}

	res, err := http.PostForm("http://donald:2016/register",
		url.Values{"name": {nameStr}, "port": {portStr}})

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

	if res.StatusCode != http.StatusOK {
		log.Printf("error: response returned with code %d\n", res.StatusCode)
		defer res.Body.Close()
		body, err := ioutil.ReadAll(res.Body)
		if err != nil {
			log.Println("error reading body")
			return
		}
		strBody := string(body)
		if strBody != "" {
			log.Printf("response body: %q\n", strBody)
		}
	}
	log.Println("OK")
}
<|endoftext|>


<commit_before>package blocks

func SkeletonTransfer(b *Block) {

	type skeletonRule struct {
		Param int
	}
	var rule *skeletonRule

	for {
		select {
		case m := <-b.Routes["set_rule"]:
			if rule == nil {
				rule = &skeletonRule{}
			}
			unmarshal(m, rule)
		case r := <-b.Routes["get_rule"]:
			if rule == nil {
				marshal(r, &skeletonRule{})
			} else {
				marshal(r, rule)
			}
		case msg := <-b.InChan:
			if rule == nil {
				break
			}
			messageBody := msg.Msg
			broadcast(b.OutChans, messageBody)
		case msg := <-b.AddChan:
			updateOutChans(msg, b)
		case <-b.QuitChan:
			quit(b)
			return
		}
	}
}
<commit_msg>Update skeleton_transfer.go<commit_after>package blocks

func SkeletonTransfer(b *Block) {

	type skeletonRule struct {
		Param int
	}
	var rule *skeletonRule

	for {
		select {
		case m := <-b.Routes["set_rule"]:
			if rule == nil {
				rule = &skeletonRule{}
			}
			unmarshal(m, rule)
		case r := <-b.Routes["get_rule"]:
			if rule == nil {
				marshal(r, &skeletonRule{})
			} else {
				marshal(r, rule)
			}
		case msg := <-b.InChan:
			if rule == nil {
				break
			}
			messageBody := msg.Msg
			out := BMsg{
				Msg:messageBody,
			}
			broadcast(b.OutChans, out)
		case msg := <-b.AddChan:
			updateOutChans(msg, b)
		case <-b.QuitChan:
			quit(b)
			return
		}
	}
}
<|endoftext|>


<commit_before>package home

import (
  "errors"
  "github.com/keep94/appcommon/http_util"
  "github.com/keep94/finance/fin"
  "github.com/keep94/mortgage"
  "github.com/keep94/mortgage/apps/mortgage/common"
  "html/template"
  "net/http"
  "net/url"
  "strconv"
)

const (
  kRowsAtTop = 1
)

var (
kTemplateSpec = `

  <title>Mortgage</title>
  <link rel="stylesheet" type="text/css" href="/static/theme.css" />
  <link rel="shortcut icon" href="/images/favicon.ico" type="image/x-icon" />

Mortgage

{{if .Error}}
  {{.Error}}
{{end}}
<form>

      Start Date:

        <select name="month">
{{with .GetSelection .Months "month"}}
          <option value="{{.Value}}">{{.Name}}</option>
{{else}}
          <option value="">--Pick one--</option>
{{end}}
{{range .Months.Items}}
          <option value="{{.Value}}">{{.Name}}</option>
{{end}}
        </select>
        <input type="text" name="year" value="{{.Get "year"}}">

     Amount:
     <input type="text" name="amount" value="{{.Get "amount"}}">

     Rate:
     <input type="text" name="rate" value="{{.Get "rate"}}">%

     Term in months:
     <input type="text" name="term" value="{{.Get "term"}}">

  <input type="submit" name="calculate" value="Calculate">
</form>
{{if .Loan}}

    Monthly payment:
    {{FormatUSD .Loan.Payment}}

    Total Cost:
    {{FormatUSD .Totals.Payment}}

    Total Finance Charges:
    {{FormatUSD .Totals.Interest}}

    Date
    Payment
    Interest
    Principal
    Balance

  {{range $year, $ytotals := .Totals.Years}}
    {{range $ytotals.Terms}}

    {{FormatDate .Date}}
    {{FormatUSD .Payment}}
    {{FormatUSD .Interest}}
    {{FormatUSD .Principal}}
    {{FormatUSD .Balance}}

    {{end}}

    {{$year}} Totals
    {{FormatUSD $ytotals.Payment}}
    {{FormatUSD $ytotals.Interest}}
    {{FormatUSD $ytotals.Principal}}
    &nbsp

  {{end}}
{{end}}

`
)

var (
  kTemplate *template.Template
)

var (
  kMaxTerms = 1200
  kMonths = http_util.ComboBox{
      {"January", 1},
      {"February", 2},
      {"March", 3},
      {"April", 4},
      {"May", 5},
      {"June", 6},
      {"July", 7},
      {"August", 8},
      {"September", 9},
      {"October", 10},
      {"November", 11},
      {"December", 12},
  }
)

type Handler struct {
}

func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  r.ParseForm()
  if hasNoneOf(r.Form, "month", "year", "amount", "rate", "term") {
    http_util.WriteTemplate(
        w,
        kTemplate,
        &view{
            Values: http_util.Values{r.Form},
            Months: kMonths,
        })
    return
  }
  month, _ := kMonths.ToValue(r.Form.Get("month")).(int)
  year, _ := strconv.Atoi(r.Form.Get("year"))
  amount, _ := fin.ParseUSD(r.Form.Get("amount"))
  rate, _ := strconv.ParseFloat(r.Form.Get("rate"), 64)
  rate /= 100.0
  length, _ := strconv.Atoi(r.Form.Get("term"))
  var message string
  if month == 0 {
    message = "Please choose a month."
  } else if year < 1900 || year > 2200 {
    message = "Please enter a year between 1900 and 2200"
  } else if amount <= 0 {
    message = "Please enter a positive amount."
  } else if length <= 0 {
    message = "Please enter a positive term in months."
  }
  if message != "" {
    http_util.WriteTemplate(
        w,
        kTemplate,
        &view{
            Values: http_util.Values{r.Form},
            Months: kMonths,
            Error: errors.New(message),
        })
    return
  }
  loan := mortgage.NewLoan(amount, rate, length)
  terms := loan.Terms(year, month, kMaxTerms + 1)
  if len(terms) == kMaxTerms + 1 {
    http_util.WriteTemplate(
        w,
        kTemplate,
        &view{
            Values: http_util.Values{r.Form},
            Months: kMonths,
            Error: errors.New("Too many terms to display"),
        })
    return
  }
  v := &view{
      Values: http_util.Values{r.Form},
      Loan: loan,
      Totals: aggregate(terms),
      Months: kMonths,
  }
  http_util.WriteTemplate(
      w,
      kTemplate,
      v)
}

type yearTotals struct {
  Principal int64
  Interest int64
  Payment int64
  start int
  end int
  terms []*mortgage.Term
}

func newYearTotals(terms []*mortgage.Term, idx int) *yearTotals {
  return &yearTotals{
      start: idx,
      end: idx,
      terms: terms}
}

func (y *yearTotals) Terms() []*mortgage.Term {
  return y.terms[y.start:y.end]
}

func (y *yearTotals) add() {
  y.Principal += y.terms[y.end].Principal()
  y.Interest += y.terms[y.end].Interest
  y.Payment += y.terms[y.end].Payment
  y.end++
}

type loanTotals struct {
  Principal int64
  Interest int64
  Payment int64
  Years map[int]*yearTotals
}

type view struct {
  http_util.Values
  Loan *mortgage.Loan
  Totals *loanTotals
  Error error
  TotalInterest int64
  Months http_util.ComboBox
}

func aggregate(terms []*mortgage.Term) *loanTotals {
  result := &loanTotals{Years: make(map[int]*yearTotals)}
  for i := range terms {
    result.Payment += terms[i].Payment
    result.Interest += terms[i].Interest
    result.Principal += terms[i].Principal()
    year := terms[i].Date.Year()
    yTotal := result.Years[year]
    if yTotal == nil {
      yTotal = newYearTotals(terms, i)
      result.Years[year] = yTotal
    }
    yTotal.add()
  }
  return result
}

func hasNoneOf(values url.Values, paramNames... string) bool {
  for _, name := range paramNames {
    if http_util.HasParam(values, name) {
      return false
    }
  }
  return true
}

func init() {
  kTemplate = common.NewTemplate("home", kTemplateSpec)
}
<commit_msg>Remove unused field in home handler.<commit_after>package home

import (
  "errors"
  "github.com/keep94/appcommon/http_util"
  "github.com/keep94/finance/fin"
  "github.com/keep94/mortgage"
  "github.com/keep94/mortgage/apps/mortgage/common"
  "html/template"
  "net/http"
  "net/url"
  "strconv"
)

const (
  kRowsAtTop = 1
)

var (
kTemplateSpec = `

  <title>Mortgage</title>
  <link rel="stylesheet" type="text/css" href="/static/theme.css" />
  <link rel="shortcut icon" href="/images/favicon.ico" type="image/x-icon" />

Mortgage

{{if .Error}}
  {{.Error}}
{{end}}
<form>

      Start Date:

        <select name="month">
{{with .GetSelection .Months "month"}}
          <option value="{{.Value}}">{{.Name}}</option>
{{else}}
          <option value="">--Pick one--</option>
{{end}}
{{range .Months.Items}}
          <option value="{{.Value}}">{{.Name}}</option>
{{end}}
        </select>
        <input type="text" name="year" value="{{.Get "year"}}">

     Amount:
     <input type="text" name="amount" value="{{.Get "amount"}}">

     Rate:
     <input type="text" name="rate" value="{{.Get "rate"}}">%

     Term in months:
     <input type="text" name="term" value="{{.Get "term"}}">

  <input type="submit" name="calculate" value="Calculate">
</form>
{{if .Loan}}

    Monthly payment:
    {{FormatUSD .Loan.Payment}}

    Total Cost:
    {{FormatUSD .Totals.Payment}}

    Total Finance Charges:
    {{FormatUSD .Totals.Interest}}

    Date
    Payment
    Interest
    Principal
    Balance

  {{range $year, $ytotals := .Totals.Years}}
    {{range $ytotals.Terms}}

    {{FormatDate .Date}}
    {{FormatUSD .Payment}}
    {{FormatUSD .Interest}}
    {{FormatUSD .Principal}}
    {{FormatUSD .Balance}}

    {{end}}

    {{$year}} Totals
    {{FormatUSD $ytotals.Payment}}
    {{FormatUSD $ytotals.Interest}}
    {{FormatUSD $ytotals.Principal}}
    &nbsp

  {{end}}
{{end}}

`
)

var (
  kTemplate *template.Template
)

var (
  kMaxTerms = 1200
  kMonths = http_util.ComboBox{
      {"January", 1},
      {"February", 2},
      {"March", 3},
      {"April", 4},
      {"May", 5},
      {"June", 6},
      {"July", 7},
      {"August", 8},
      {"September", 9},
      {"October", 10},
      {"November", 11},
      {"December", 12},
  }
)

type Handler struct {
}

func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  r.ParseForm()
  if hasNoneOf(r.Form, "month", "year", "amount", "rate", "term") {
    http_util.WriteTemplate(
        w,
        kTemplate,
        &view{
            Values: http_util.Values{r.Form},
            Months: kMonths,
        })
    return
  }
  month, _ := kMonths.ToValue(r.Form.Get("month")).(int)
  year, _ := strconv.Atoi(r.Form.Get("year"))
  amount, _ := fin.ParseUSD(r.Form.Get("amount"))
  rate, _ := strconv.ParseFloat(r.Form.Get("rate"), 64)
  rate /= 100.0
  length, _ := strconv.Atoi(r.Form.Get("term"))
  var message string
  if month == 0 {
    message = "Please choose a month."
  } else if year < 1900 || year > 2200 {
    message = "Please enter a year between 1900 and 2200"
  } else if amount <= 0 {
    message = "Please enter a positive amount."
  } else if length <= 0 {
    message = "Please enter a positive term in months."
  }
  if message != "" {
    http_util.WriteTemplate(
        w,
        kTemplate,
        &view{
            Values: http_util.Values{r.Form},
            Months: kMonths,
            Error: errors.New(message),
        })
    return
  }
  loan := mortgage.NewLoan(amount, rate, length)
  terms := loan.Terms(year, month, kMaxTerms + 1)
  if len(terms) == kMaxTerms + 1 {
    http_util.WriteTemplate(
        w,
        kTemplate,
        &view{
            Values: http_util.Values{r.Form},
            Months: kMonths,
            Error: errors.New("Too many terms to display"),
        })
    return
  }
  http_util.WriteTemplate(
      w,
      kTemplate,
      &view{
          Values: http_util.Values{r.Form},
          Loan: loan,
          Totals: aggregate(terms),
          Months: kMonths,
      })
}

type yearTotals struct {
  Principal int64
  Interest int64
  Payment int64
  start int
  end int
  terms []*mortgage.Term
}

func newYearTotals(terms []*mortgage.Term, idx int) *yearTotals {
  return &yearTotals{
      start: idx,
      end: idx,
      terms: terms}
}

func (y *yearTotals) Terms() []*mortgage.Term {
  return y.terms[y.start:y.end]
}

func (y *yearTotals) add() {
  y.Principal += y.terms[y.end].Principal()
  y.Interest += y.terms[y.end].Interest
  y.Payment += y.terms[y.end].Payment
  y.end++
}

type loanTotals struct {
  Principal int64
  Interest int64
  Payment int64
  Years map[int]*yearTotals
}

type view struct {
  http_util.Values
  Loan *mortgage.Loan
  Totals *loanTotals
  Error error
  Months http_util.ComboBox
}

func aggregate(terms []*mortgage.Term) *loanTotals {
  result := &loanTotals{Years: make(map[int]*yearTotals)}
  for i := range terms {
    result.Payment += terms[i].Payment
    result.Interest += terms[i].Interest
    result.Principal += terms[i].Principal()
    year := terms[i].Date.Year()
    yTotal := result.Years[year]
    if yTotal == nil {
      yTotal = newYearTotals(terms, i)
      result.Years[year] = yTotal
    }
    yTotal.add()
  }
  return result
}

func hasNoneOf(values url.Values, paramNames... string) bool {
  for _, name := range paramNames {
    if http_util.HasParam(values, name) {
      return false
    }
  }
  return true
}

func init() {
  kTemplate = common.NewTemplate("home", kTemplateSpec)
}
<|endoftext|>


<commit_before>// Generated by schemesgen

package xurls

// Schemes is a sorted list of all IANA assigned schemes.
//
// Source:
//   https://www.iana.org/assignments/uri-schemes/uri-schemes-1.csv
var Schemes = []string{
	`aaa`,
	`aaas`,
	`about`,
	`acap`,
	`acct`,
	`acd`,
	`acr`,
	`adiumxtra`,
	`adt`,
	`afp`,
	`afs`,
	`aim`,
	`amss`,
	`android`,
	`appdata`,
	`apt`,
	`ar`,
	`ark`,
	`attachment`,
	`aw`,
	`barion`,
	`beshare`,
	`bitcoin`,
	`bitcoincash`,
	`blob`,
	`bolo`,
	`browserext`,
	`cabal`,
	`calculator`,
	`callto`,
	`cap`,
	`cast`,
	`casts`,
	`chrome`,
	`chrome-extension`,
	`cid`,
	`coap`,
	`coap+tcp`,
	`coap+ws`,
	`coaps`,
	`coaps+tcp`,
	`coaps+ws`,
	`com-eventbrite-attendee`,
	`content`,
	`content-type`,
	`crid`,
	`cstr`,
	`cvs`,
	`dab`,
	`dat`,
	`data`,
	`dav`,
	`diaspora`,
	`dict`,
	`did`,
	`dis`,
	`dlna-playcontainer`,
	`dlna-playsingle`,
	`dns`,
	`dntp`,
	`doi`,
	`dpp`,
	`drm`,
	`drop`,
	`dtmi`,
	`dtn`,
	`dvb`,
	`dvx`,
	`dweb`,
	`ed2k`,
	`elsi`,
	`embedded`,
	`ens`,
	`ethereum`,
	`example`,
	`facetime`,
	`fax`,
	`feed`,
	`feedready`,
	`fido`,
	`file`,
	`filesystem`,
	`finger`,
	`first-run-pen-experience`,
	`fish`,
	`fm`,
	`ftp`,
	`fuchsia-pkg`,
	`geo`,
	`gg`,
	`git`,
	`gitoid`,
	`gizmoproject`,
	`go`,
	`gopher`,
	`graph`,
	`gtalk`,
	`h323`,
	`ham`,
	`hcap`,
	`hcp`,
	`http`,
	`https`,
	`hxxp`,
	`hxxps`,
	`hydrazone`,
	`hyper`,
	`iax`,
	`icap`,
	`icon`,
	`im`,
	`imap`,
	`info`,
	`iotdisco`,
	`ipfs`,
	`ipn`,
	`ipns`,
	`ipp`,
	`ipps`,
	`irc`,
	`irc6`,
	`ircs`,
	`iris`,
	`iris.beep`,
	`iris.lwz`,
	`iris.xpc`,
	`iris.xpcs`,
	`isostore`,
	`itms`,
	`jabber`,
	`jar`,
	`jms`,
	`keyparc`,
	`lastfm`,
	`lbry`,
	`ldap`,
	`ldaps`,
	`leaptofrogans`,
	`lorawan`,
	`lvlt`,
	`magnet`,
	`mailserver`,
	`mailto`,
	`maps`,
	`market`,
	`matrix`,
	`message`,
	`microsoft.windows.camera`,
	`microsoft.windows.camera.multipicker`,
	`microsoft.windows.camera.picker`,
	`mid`,
	`mms`,
	`modem`,
	`mongodb`,
	`moz`,
	`ms-access`,
	`ms-appinstaller`,
	`ms-browser-extension`,
	`ms-calculator`,
	`ms-drive-to`,
	`ms-enrollment`,
	`ms-excel`,
	`ms-eyecontrolspeech`,
	`ms-gamebarservices`,
	`ms-gamingoverlay`,
	`ms-getoffice`,
	`ms-help`,
	`ms-infopath`,
	`ms-inputapp`,
	`ms-lockscreencomponent-config`,
	`ms-media-stream-id`,
	`ms-meetnow`,
	`ms-mixedrealitycapture`,
	`ms-mobileplans`,
	`ms-newsandinterests`,
	`ms-officeapp`,
	`ms-people`,
	`ms-project`,
	`ms-powerpoint`,
	`ms-publisher`,
	`ms-restoretabcompanion`,
	`ms-screenclip`,
	`ms-screensketch`,
	`ms-search`,
	`ms-search-repair`,
	`ms-secondary-screen-controller`,
	`ms-secondary-screen-setup`,
	`ms-settings`,
	`ms-settings-airplanemode`,
	`ms-settings-bluetooth`,
	`ms-settings-camera`,
	`ms-settings-cellular`,
	`ms-settings-cloudstorage`,
	`ms-settings-connectabledevices`,
	`ms-settings-displays-topology`,
	`ms-settings-emailandaccounts`,
	`ms-settings-language`,
	`ms-settings-location`,
	`ms-settings-lock`,
	`ms-settings-nfctransactions`,
	`ms-settings-notifications`,
	`ms-settings-power`,
	`ms-settings-privacy`,
	`ms-settings-proximity`,
	`ms-settings-screenrotation`,
	`ms-settings-wifi`,
	`ms-settings-workplace`,
	`ms-spd`,
	`ms-stickers`,
	`ms-sttoverlay`,
	`ms-transit-to`,
	`ms-useractivityset`,
	`ms-virtualtouchpad`,
	`ms-visio`,
	`ms-walk-to`,
	`ms-whiteboard`,
	`ms-whiteboard-cmd`,
	`ms-word`,
	`msnim`,
	`msrp`,
	`msrps`,
	`mss`,
	`mt`,
	`mtqp`,
	`mumble`,
	`mupdate`,
	`mvn`,
	`news`,
	`nfs`,
	`ni`,
	`nih`,
	`nntp`,
	`notes`,
	`num`,
	`ocf`,
	`oid`,
	`onenote`,
	`onenote-cmd`,
	`opaquelocktoken`,
	`openpgp4fpr`,
	`otpauth`,
	`pack`,
	`palm`,
	`paparazzi`,
	`payment`,
	`payto`,
	`pkcs11`,
	`platform`,
	`pop`,
	`pres`,
	`prospero`,
	`proxy`,
	`pwid`,
	`psyc`,
	`pttp`,
	`qb`,
	`query`,
	`quic-transport`,
	`redis`,
	`rediss`,
	`reload`,
	`res`,
	`resource`,
	`rmi`,
	`rsync`,
	`rtmfp`,
	`rtmp`,
	`rtsp`,
	`rtsps`,
	`rtspu`,
	`sarif`,
	`secondlife`,
	`secret-token`,
	`service`,
	`session`,
	`sftp`,
	`sgn`,
	`shc`,
	`sieve`,
	`simpleledger`,
	`simplex`,
	`sip`,
	`sips`,
	`skype`,
	`smb`,
	`smp`,
	`sms`,
	`smtp`,
	`snews`,
	`snmp`,
	`soap.beep`,
	`soap.beeps`,
	`soldat`,
	`spiffe`,
	`spotify`,
	`ssb`,
	`ssh`,
	`steam`,
	`stun`,
	`stuns`,
	`submit`,
	`svn`,
	`swh`,
	`swid`,
	`swidpath`,
	`tag`,
	`teamspeak`,
	`tel`,
	`teliaeid`,
	`telnet`,
	`tftp`,
	`things`,
	`thismessage`,
	`tip`,
	`tn3270`,
	`tool`,
	`turn`,
	`turns`,
	`tv`,
	`udp`,
	`unreal`,
	`urn`,
	`ut2004`,
	`uuid-in-package`,
	`v-event`,
	`vemmi`,
	`ventrilo`,
	`ves`,
	`videotex`,
	`vnc`,
	`view-source`,
	`vscode`,
	`vscode-insiders`,
	`vsls`,
	`wais`,
	`wcr`,
	`webcal`,
	`wifi`,
	`wpid`,
	`ws`,
	`wss`,
	`wtai`,
	`wyciwyg`,
	`xcon`,
	`xcon-userid`,
	`xfire`,
	`xmlrpc.beep`,
	`xmlrpc.beeps`,
	`xmpp`,
	`xri`,
	`ymsgr`,
	`z39.50`,
	`z39.50r`,
	`z39.50s`,
}
<commit_msg>update schemes<commit_after>// Generated by schemesgen

package xurls

// Schemes is a sorted list of all IANA assigned schemes.
//
// Source:
//   https://www.iana.org/assignments/uri-schemes/uri-schemes-1.csv
var Schemes = []string{
	`aaa`,
	`aaas`,
	`about`,
	`acap`,
	`acct`,
	`acd`,
	`acr`,
	`adiumxtra`,
	`adt`,
	`afp`,
	`afs`,
	`aim`,
	`amss`,
	`android`,
	`appdata`,
	`apt`,
	`ar`,
	`ark`,
	`attachment`,
	`aw`,
	`barion`,
	`beshare`,
	`bitcoin`,
	`bitcoincash`,
	`blob`,
	`bolo`,
	`browserext`,
	`cabal`,
	`calculator`,
	`callto`,
	`cap`,
	`cast`,
	`casts`,
	`chrome`,
	`chrome-extension`,
	`cid`,
	`coap`,
	`coap+tcp`,
	`coap+ws`,
	`coaps`,
	`coaps+tcp`,
	`coaps+ws`,
	`com-eventbrite-attendee`,
	`content`,
	`content-type`,
	`crid`,
	`cstr`,
	`cvs`,
	`dab`,
	`dat`,
	`data`,
	`dav`,
	`diaspora`,
	`dict`,
	`did`,
	`dis`,
	`dlna-playcontainer`,
	`dlna-playsingle`,
	`dns`,
	`dntp`,
	`doi`,
	`dpp`,
	`drm`,
	`drop`,
	`dtmi`,
	`dtn`,
	`dvb`,
	`dvx`,
	`dweb`,
	`ed2k`,
	`elsi`,
	`embedded`,
	`ens`,
	`ethereum`,
	`example`,
	`facetime`,
	`fax`,
	`feed`,
	`feedready`,
	`fido`,
	`file`,
	`filesystem`,
	`finger`,
	`first-run-pen-experience`,
	`fish`,
	`fm`,
	`ftp`,
	`fuchsia-pkg`,
	`geo`,
	`gg`,
	`git`,
	`gitoid`,
	`gizmoproject`,
	`go`,
	`gopher`,
	`graph`,
	`gtalk`,
	`h323`,
	`ham`,
	`hcap`,
	`hcp`,
	`http`,
	`https`,
	`hxxp`,
	`hxxps`,
	`hydrazone`,
	`hyper`,
	`iax`,
	`icap`,
	`icon`,
	`im`,
	`imap`,
	`info`,
	`iotdisco`,
	`ipfs`,
	`ipn`,
	`ipns`,
	`ipp`,
	`ipps`,
	`irc`,
	`irc6`,
	`ircs`,
	`iris`,
	`iris.beep`,
	`iris.lwz`,
	`iris.xpc`,
	`iris.xpcs`,
	`isostore`,
	`itms`,
	`jabber`,
	`jar`,
	`jms`,
	`keyparc`,
	`lastfm`,
	`lbry`,
	`ldap`,
	`ldaps`,
	`leaptofrogans`,
	`lorawan`,
	`lvlt`,
	`magnet`,
	`mailserver`,
	`mailto`,
	`maps`,
	`market`,
	`matrix`,
	`message`,
	`microsoft.windows.camera`,
	`microsoft.windows.camera.multipicker`,
	`microsoft.windows.camera.picker`,
	`mid`,
	`mms`,
	`modem`,
	`mongodb`,
	`moz`,
	`ms-access`,
	`ms-appinstaller`,
	`ms-browser-extension`,
	`ms-calculator`,
	`ms-drive-to`,
	`ms-enrollment`,
	`ms-excel`,
	`ms-eyecontrolspeech`,
	`ms-gamebarservices`,
	`ms-gamingoverlay`,
	`ms-getoffice`,
	`ms-help`,
	`ms-infopath`,
	`ms-inputapp`,
	`ms-lockscreencomponent-config`,
	`ms-media-stream-id`,
	`ms-meetnow`,
	`ms-mixedrealitycapture`,
	`ms-mobileplans`,
	`ms-newsandinterests`,
	`ms-officeapp`,
	`ms-people`,
	`ms-project`,
	`ms-powerpoint`,
	`ms-publisher`,
	`ms-restoretabcompanion`,
	`ms-screenclip`,
	`ms-screensketch`,
	`ms-search`,
	`ms-search-repair`,
	`ms-secondary-screen-controller`,
	`ms-secondary-screen-setup`,
	`ms-settings`,
	`ms-settings-airplanemode`,
	`ms-settings-bluetooth`,
	`ms-settings-camera`,
	`ms-settings-cellular`,
	`ms-settings-cloudstorage`,
	`ms-settings-connectabledevices`,
	`ms-settings-displays-topology`,
	`ms-settings-emailandaccounts`,
	`ms-settings-language`,
	`ms-settings-location`,
	`ms-settings-lock`,
	`ms-settings-nfctransactions`,
	`ms-settings-notifications`,
	`ms-settings-power`,
	`ms-settings-privacy`,
	`ms-settings-proximity`,
	`ms-settings-screenrotation`,
	`ms-settings-wifi`,
	`ms-settings-workplace`,
	`ms-spd`,
	`ms-stickers`,
	`ms-sttoverlay`,
	`ms-transit-to`,
	`ms-useractivityset`,
	`ms-virtualtouchpad`,
	`ms-visio`,
	`ms-walk-to`,
	`ms-whiteboard`,
	`ms-whiteboard-cmd`,
	`ms-word`,
	`msnim`,
	`msrp`,
	`msrps`,
	`mss`,
	`mt`,
	`mtqp`,
	`mumble`,
	`mupdate`,
	`mvn`,
	`news`,
	`nfs`,
	`ni`,
	`nih`,
	`nntp`,
	`notes`,
	`num`,
	`ocf`,
	`oid`,
	`onenote`,
	`onenote-cmd`,
	`opaquelocktoken`,
	`openpgp4fpr`,
	`otpauth`,
	`pack`,
	`palm`,
	`paparazzi`,
	`payment`,
	`payto`,
	`pkcs11`,
	`platform`,
	`pop`,
	`pres`,
	`prospero`,
	`proxy`,
	`pwid`,
	`psyc`,
	`pttp`,
	`qb`,
	`query`,
	`quic-transport`,
	`redis`,
	`rediss`,
	`reload`,
	`res`,
	`resource`,
	`rmi`,
	`rsync`,
	`rtmfp`,
	`rtmp`,
	`rtsp`,
	`rtsps`,
	`rtspu`,
	`sarif`,
	`secondlife`,
	`secret-token`,
	`service`,
	`session`,
	`sftp`,
	`sgn`,
	`shc`,
	`sieve`,
	`simpleledger`,
	`simplex`,
	`sip`,
	`sips`,
	`skype`,
	`smb`,
	`smp`,
	`sms`,
	`smtp`,
	`snews`,
	`snmp`,
	`soap.beep`,
	`soap.beeps`,
	`soldat`,
	`spiffe`,
	`spotify`,
	`ssb`,
	`ssh`,
	`steam`,
	`stun`,
	`stuns`,
	`submit`,
	`svn`,
	`swh`,
	`swid`,
	`swidpath`,
	`tag`,
	`teamspeak`,
	`tel`,
	`teliaeid`,
	`telnet`,
	`tftp`,
	`things`,
	`thismessage`,
	`tip`,
	`tn3270`,
	`tool`,
	`turn`,
	`turns`,
	`tv`,
	`udp`,
	`unreal`,
	`urn`,
	`ut2004`,
	`uuid-in-package`,
	`v-event`,
	`vemmi`,
	`ventrilo`,
	`ves`,
	`videotex`,
	`vnc`,
	`view-source`,
	`vscode`,
	`vscode-insiders`,
	`vsls`,
	`w3`,
	`wais`,
	`web3`,
	`wcr`,
	`webcal`,
	`wifi`,
	`wpid`,
	`ws`,
	`wss`,
	`wtai`,
	`wyciwyg`,
	`xcon`,
	`xcon-userid`,
	`xfire`,
	`xmlrpc.beep`,
	`xmlrpc.beeps`,
	`xmpp`,
	`xri`,
	`ymsgr`,
	`z39.50`,
	`z39.50r`,
	`z39.50s`,
}
<|endoftext|>


<commit_before>package govote

import (
   "errors"
)

// SchulzePoll is a Condorcet poll using the Schulze method
type SchulzePoll struct {
   candidates     []string
   ballots        [][]string
   PP             map[CPair]int // pairwise preferences
   SP             map[CPair]int // strongest path
}

// Evaluate poll; returns list of winners as slice of candidate names and
// ranking as slice of CScores
func (p *SchulzePoll) Evaluate() ([]string, []CScore, error) {
   if p.candidates == nil || p.ballots == nil {
      return []string{}, []CScore{}, errors.New("no candidates or no ballots")
   }
   winners, ranks := p.getWinners()
   return winners, ranks, nil
}

// AddBallot submits a ballot to the poll, returns true on success, false on failure
func (p *SchulzePoll) AddBallot(ballot []string) (ok bool) {
   removeDuplicates(&ballot)
   for _, bv := range ballot {
      ok = false; for _, cv := range p.candidates {
         if cv == bv { ok = true; break }
      }
      if !ok { return } // false / bad candidate
   }
   if !ok { return } // false / empty ballot
   p.ballots = append(p.ballots, ballot)
   return // true
}

// Returns the rank of a candidate on a specific ballot or -1 if unranked
func (p SchulzePoll) getBallotRank(idx, i int) int {
   ballot := p.ballots[idx]
   for k, v := range ballot {
      if v == p.candidates[i] { return k }
   }
   return -1
}

// Returns number of voters strictly prefering candidate i to j
func (p SchulzePoll) comparePref(i, j int) (ct int) {
   for k := range p.ballots {
      ri, rj := p.getBallotRank(k, i), p.getBallotRank(k, j)
      if ri == -1 || rj == -1 { continue }
      if ri < rj              { ct++ }
   }
   return
}

func (p *SchulzePoll) getWinners() (winners []string, ranks []CScore) {
   p.PP = make(map[CPair]int)                   // pairwise preferences
   p.SP = make(map[CPair]int)                   // strongest paths
   won := make([]bool, len(p.candidates))       // winners marked true by index
   ct := len(p.candidates)                      // number of candidates
   tally := make(map[string]int)                // scores keyed by candidate name

   // compute pairwise preferences
   for i := 0; i < ct; i++ {
      for j := 0; j < ct; j++ {
         if i != j {
            p.PP[CPair{i, j}] = p.comparePref(i, j)
         }
      }
   }

   // compute strongest paths
   for i := 0; i < ct; i++ {
      for j := 0; j < ct; j++ {
         if i != j {
            if p.PP[CPair{i, j}] > p.PP[CPair{j, i}] {
               p.SP[CPair{i, j}] = p.PP[CPair{i, j}]
            } else {
               p.SP[CPair{i, j}] = 0
            }
         }
      }
   }

   for i := 0; i < ct; i++ {
      for j := 0; j < ct; j++ {
         if i != j {
            for k := 0; k < ct; k++ {
               if i != k && j != k {
                  var(
                     SPjk = p.SP[CPair{j, k}]
                     SPji = p.SP[CPair{j, i}]
                     SPik = p.SP[CPair{i, k}]
                  )
                  p.SP[CPair{j, k}] = max(SPjk, min(SPji, SPik))
               }
            }
         }
      }
   }

   // mark winners
   for i := 0; i < ct; i++ {
      won[i] = true
   }

   for i := 0; i < ct; i++ {
      for j := 0; j < ct; j++ {
         if i != j {
            if p.SP[CPair{j, i}] > p.SP[CPair{i, j}] {
               won[i] = false
               tally[p.candidates[i]] += 0   // creates entry for Condorcet loser
            } else {
               tally[p.candidates[i]]++      // candidate preferred to x others
            }
         }
      }
   }
   ranks = sortScoresDesc(tally)

   // make winners list
   for i := 0; i < ct; i++ {
      if won[i] == true {
         winners = append(winners, p.candidates[i])
      }
   }
   return
}
<commit_msg>formatting<commit_after>package govote

import (
    "errors"
)

// SchulzePoll is a Condorcet poll using the Schulze method
type SchulzePoll struct {
    candidates     []string
    ballots        [][]string
    PP             map[CPair]int // pairwise preferences
    SP             map[CPair]int // strongest path
}

// Evaluate poll; returns list of winners as slice of candidate names and
// ranking as slice of CScores
func (p *SchulzePoll) Evaluate() ([]string, []CScore, error) {
    if p.candidates == nil || p.ballots == nil {
        return []string{}, []CScore{}, errors.New("no candidates or no ballots")
    }
    winners, ranks := p.getWinners()
    return winners, ranks, nil
}

// AddBallot submits a ballot to the poll, returns true on success, false on failure
func (p *SchulzePoll) AddBallot(ballot []string) (ok bool) {
   removeDuplicates(&ballot)
    for _, bv := range ballot {
      ok = false; for _, cv := range p.candidates {
        if cv == bv { ok = true; break }
      }
        if !ok { return } // false / bad candidate
    }
    if !ok { return } // false / empty ballot
    p.ballots = append(p.ballots, ballot)
    return // true
}

// Returns the rank of a candidate on a specific ballot or -1 if unranked
func (p SchulzePoll) getBallotRank(idx, i int) int {
    ballot := p.ballots[idx]
    for k, v := range ballot {
        if v == p.candidates[i] { return k }
    }
    return -1
}

// Returns number of voters strictly prefering candidate i to j
func (p SchulzePoll) comparePref(i, j int) (ct int) {
    for k := range p.ballots {
        ri, rj := p.getBallotRank(k, i), p.getBallotRank(k, j)
        if ri == -1 || rj == -1 { continue }
        if ri < rj              { ct++ }
    }
    return
}

func (p *SchulzePoll) getWinners() (winners []string, ranks []CScore) {
    p.PP = make(map[CPair]int)                   // pairwise preferences
    p.SP = make(map[CPair]int)                   // strongest paths
    won := make([]bool, len(p.candidates))       // winners marked true by index
    ct := len(p.candidates)                      // number of candidates
    tally := make(map[string]int)                // scores keyed by candidate name

    // compute pairwise preferences
    for i := 0; i < ct; i++ {
        for j := 0; j < ct; j++ {
            if i != j {
                p.PP[CPair{i, j}] = p.comparePref(i, j)
            }
        }
    }

    // compute strongest paths
    for i := 0; i < ct; i++ {
        for j := 0; j < ct; j++ {
            if i != j {
                if p.PP[CPair{i, j}] > p.PP[CPair{j, i}] {
                    p.SP[CPair{i, j}] = p.PP[CPair{i, j}]
                } else {
                    p.SP[CPair{i, j}] = 0
                }
            }
        }
    }

    for i := 0; i < ct; i++ {
        for j := 0; j < ct; j++ {
            if i != j {
                for k := 0; k < ct; k++ {
                    if i != k && j != k {
                        var(
                            SPjk = p.SP[CPair{j, k}]
                            SPji = p.SP[CPair{j, i}]
                            SPik = p.SP[CPair{i, k}]
                        )
                        p.SP[CPair{j, k}] = max(SPjk, min(SPji, SPik))
                    }
                }
            }
        }
    }

    // mark winners
    for i := 0; i < ct; i++ {
        won[i] = true
    }

    for i := 0; i < ct; i++ {
        for j := 0; j < ct; j++ {
            if i != j {
                if p.SP[CPair{j, i}] > p.SP[CPair{i, j}] {
                    won[i] = false
                    tally[p.candidates[i]] += 0   // creates entry for Condorcet loser
                } else {
                    tally[p.candidates[i]]++      // candidate preferred to x others
                }
            }
        }
    }
    ranks = sortScoresDesc(tally)

    // make winners list
    for i := 0; i < ct; i++ {
        if won[i] == true {
            winners = append(winners, p.candidates[i])
        }
    }
    return
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"os"
	"os/exec"

	"github.com/carlosbrando/lunchy/agents"
)

func (c *Command) edit() error {
	if c.pattern == "" {
		return errors.New("edit [name]")
	}

	agent, err := agents.FindOne(c.pattern)
	if err != nil {
		return err
	}

	if agent != nil {
		editor := os.Getenv("EDITOR")
		if editor == "" {
			return errors.New("EDITOR environment variable is not set")
		}

		cmd := exec.Command("sh", "-c", fmt.Sprintf("%s %s", editor, agent.Fullpath))
		err = cmd.Run()
		if err != nil {
			return err
		}
	}

	return nil
}
<commit_msg>Added license.<commit_after>// Copyright 2013 Carlos Brando. All rights reserved.
// Use of this source code is governed by a MIT license
// that can be found in the LICENSE file.

package main

import (
	"errors"
	"fmt"
	"os"
	"os/exec"

	"github.com/carlosbrando/lunchy/agents"
)

func (c *Command) edit() error {
	if c.pattern == "" {
		return errors.New("edit [name]")
	}

	agent, err := agents.FindOne(c.pattern)
	if err != nil {
		return err
	}

	if agent != nil {
		editor := os.Getenv("EDITOR")
		if editor == "" {
			return errors.New("EDITOR environment variable is not set")
		}

		cmd := exec.Command("sh", "-c", fmt.Sprintf("%s %s", editor, agent.Fullpath))
		err = cmd.Run()
		if err != nil {
			return err
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package memaccess

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

type process struct {
	pid          uint
	mapsFilepath string
	memFilepath  string
}

type mapInfo struct {
	start uintptr
	end   uintptr
}

func newProcessMemoryReaderImpl(pid uint) (process, error, []error) {
	var result process
	result.pid = pid
	result.mapsFilepath = filepath.Join("/proc", fmt.Sprintf("%d", pid), "maps")
	result.memFilepath = filepath.Join("/proc", fmt.Sprintf("%d", pid), "mem")
	// trying to open the maps file, only to see if gives an error
	f, harderror := os.Open(result.mapsFilepath)
	softerrors := make([]error, 0)
	if harderror != nil {
		return process{}, harderror, softerrors
	}
	f.Close()
	return result, nil, softerrors
}

func (p process) Close() (error, []error) {
	return nil, make([]error, 0)
}

// NextReadableMemoryRegion should return a MemoryRegion with address inside or,
// if that's impossible, the next readable MemoryRegion
func (p process) NextReadableMemoryRegion(address uintptr) (MemoryRegion, error, []error) {
	mapsFile, harderror := os.Open(p.mapsFilepath)
	softerrors := make([]error, 0)
	if harderror != nil {
		return MemoryRegion{}, harderror, softerrors
	}
	defer mapsFile.Close()

	path, harderror := pathByPID(p.pid)
	if harderror != nil {
		return MemoryRegion{}, harderror, softerrors
	}

	mappedAddresses, harderror := getMappedAddresses(mapsFile, path)
	if harderror != nil {
		return MemoryRegion{}, harderror, softerrors
	}

	mappedRegion, harderror := nextReadableMappedRegion(address, mappedAddresses)
	//TODO: ignore non readable mapped regions and add a softerror
	if harderror != nil {
		return NoRegionAvailable, harderror, softerrors
	}

	if mappedRegion.start != 0 {
		size := uint(mappedRegion.end - mappedRegion.start)
		return MemoryRegion{mappedRegion.start, size}, nil, softerrors
	}
	return NoRegionAvailable, nil, softerrors
}

func nextReadableMappedRegion(address uintptr, mappedAddresses []mapInfo) (mapInfo, error) {
	for _, mapinfo := range mappedAddresses {
		if mapinfo.start <= address && address < mapinfo.end {
			return mapinfo, nil
		}
	}
	// there's no mapped region with address inside it
	// I should return the next one
	for _, mapinfo := range mappedAddresses {
		if address < mapinfo.start {
			return mapinfo, nil
		}
	}
	// there's no mapped region with address inside it and no next region
	return mapInfo{}, nil
}

func getMappedAddresses(mapsFile *os.File, path string) ([]mapInfo, error) {
	res := make([]mapInfo, 0)
	scanner := bufio.NewScanner(mapsFile)
	goals := []string{"[heap]", "[stack]", path} // we want to look into the binary memory, its heap and its stack

	for scanner.Scan() {
		line := scanner.Text()

		items := strings.Split(line, " ")
		if len(items) <= 1 {
			continue
		}
		if stringInSlice(items[len(items)-1], goals) {
			fields := strings.Split(items[0], "-")
			start64, err := strconv.ParseUint(fields[0], 16, 64)
			if err != nil {
				return nil, err
			}
			end64, err := strconv.ParseUint(fields[1], 16, 64)
			if err != nil {
				return nil, err
			}
			start := uintptr(start64)
			end := uintptr(end64)
			info := mapInfo{start: start, end: end}
			res = append(res, info)
		}
	}
	if err := scanner.Err(); err != nil {
		return nil, err
	}
	return res, nil
}

func (p process) CopyMemory(address uintptr, buffer []byte) (error, []error) {
	mem, harderror := os.Open(p.memFilepath)
	softerrors := make([]error, 0)
	if harderror != nil {
		//TODO(laski): add address to error string
		return harderror, softerrors
	}
	defer mem.Close()
	bytes_read, harderror := mem.ReadAt(buffer, int64(address))
	if bytes_read != len(buffer) {
		return fmt.Errorf("Coul not read the entire buffer"), softerrors
	}
	if harderror != nil {
		return harderror, softerrors
	}
	return nil, softerrors
}

func nameByPID(pid uint) (string, error) {
	// inside /proc/[pid]/stat is the name of the binary between parentheses as second word
	path := filepath.Join("/proc", fmt.Sprintf("%d", pid), "stat")
	f, err := os.Open(path)
	if err != nil {
		return "", err
	}
	defer f.Close()
	scanner := bufio.NewScanner(f)
	scanner.Split(bufio.ScanWords)

	scanner.Scan() // discard first word
	scanner.Scan()

	res := scanner.Text()
	res = strings.TrimPrefix(res, "(")
	res = strings.TrimSuffix(res, ")")

	return res, nil
}

func pathByPID(pid uint) (string, error) {
	// the file /proc/[pid]/exe is a link to the binary
	path := filepath.Join("/proc", fmt.Sprintf("%d", pid), "exe")
	res, err := os.Readlink(path)
	if err != nil {
		return "", err
	}
	return res, nil
}

func stringInSlice(a string, list []string) bool {
	for _, b := range list {
		if a == b {
			return true
		}
	}
	return false
}

func areEqual(s1 []byte, s2 []byte) bool {
	for index, _ := range s1 {
		if s1[index] != s2[index] {
			return false
		}
	}
	return true
}
<commit_msg>[memaccess] Remove dead code in linux<commit_after>package memaccess

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

type process struct {
	pid          uint
	mapsFilepath string
	memFilepath  string
}

type mapInfo struct {
	start uintptr
	end   uintptr
}

func newProcessMemoryReaderImpl(pid uint) (process, error, []error) {
	var result process
	result.pid = pid
	result.mapsFilepath = filepath.Join("/proc", fmt.Sprintf("%d", pid), "maps")
	result.memFilepath = filepath.Join("/proc", fmt.Sprintf("%d", pid), "mem")
	// trying to open the maps file, only to see if gives an error
	f, harderror := os.Open(result.mapsFilepath)
	softerrors := make([]error, 0)
	if harderror != nil {
		return process{}, harderror, softerrors
	}
	f.Close()
	return result, nil, softerrors
}

func (p process) Close() (error, []error) {
	return nil, make([]error, 0)
}

// NextReadableMemoryRegion should return a MemoryRegion with address inside or,
// if that's impossible, the next readable MemoryRegion
func (p process) NextReadableMemoryRegion(address uintptr) (MemoryRegion, error, []error) {
	mapsFile, harderror := os.Open(p.mapsFilepath)
	softerrors := make([]error, 0)
	if harderror != nil {
		return MemoryRegion{}, harderror, softerrors
	}
	defer mapsFile.Close()

	path, harderror := pathByPID(p.pid)
	if harderror != nil {
		return MemoryRegion{}, harderror, softerrors
	}

	mappedAddresses, harderror := getMappedAddresses(mapsFile, path)
	if harderror != nil {
		return MemoryRegion{}, harderror, softerrors
	}

	mappedRegion, harderror := nextReadableMappedRegion(address, mappedAddresses)
	//TODO: ignore non readable mapped regions and add a softerror
	if harderror != nil {
		return NoRegionAvailable, harderror, softerrors
	}

	if mappedRegion.start != 0 {
		size := uint(mappedRegion.end - mappedRegion.start)
		return MemoryRegion{mappedRegion.start, size}, nil, softerrors
	}
	return NoRegionAvailable, nil, softerrors
}

func nextReadableMappedRegion(address uintptr, mappedAddresses []mapInfo) (mapInfo, error) {
	for _, mapinfo := range mappedAddresses {
		if mapinfo.start <= address && address < mapinfo.end {
			return mapinfo, nil
		}
	}
	// there's no mapped region with address inside it
	// I should return the next one
	for _, mapinfo := range mappedAddresses {
		if address < mapinfo.start {
			return mapinfo, nil
		}
	}
	// there's no mapped region with address inside it and no next region
	return mapInfo{}, nil
}

func getMappedAddresses(mapsFile *os.File, path string) ([]mapInfo, error) {
	res := make([]mapInfo, 0)
	scanner := bufio.NewScanner(mapsFile)
	goals := []string{"[heap]", "[stack]", path} // we want to look into the binary memory, its heap and its stack

	for scanner.Scan() {
		line := scanner.Text()

		items := strings.Split(line, " ")
		if len(items) <= 1 {
			continue
		}
		if stringInSlice(items[len(items)-1], goals) {
			fields := strings.Split(items[0], "-")
			start64, err := strconv.ParseUint(fields[0], 16, 64)
			if err != nil {
				return nil, err
			}
			end64, err := strconv.ParseUint(fields[1], 16, 64)
			if err != nil {
				return nil, err
			}
			start := uintptr(start64)
			end := uintptr(end64)
			info := mapInfo{start: start, end: end}
			res = append(res, info)
		}
	}
	if err := scanner.Err(); err != nil {
		return nil, err
	}
	return res, nil
}

func (p process) CopyMemory(address uintptr, buffer []byte) (error, []error) {
	mem, harderror := os.Open(p.memFilepath)
	softerrors := make([]error, 0)
	if harderror != nil {
		//TODO(laski): add address to error string
		return harderror, softerrors
	}
	defer mem.Close()
	bytes_read, harderror := mem.ReadAt(buffer, int64(address))
	if bytes_read != len(buffer) {
		return fmt.Errorf("Coul not read the entire buffer"), softerrors
	}
	if harderror != nil {
		return harderror, softerrors
	}
	return nil, softerrors
}

func pathByPID(pid uint) (string, error) {
	// the file /proc/[pid]/exe is a link to the binary
	path := filepath.Join("/proc", fmt.Sprintf("%d", pid), "exe")
	res, err := os.Readlink(path)
	if err != nil {
		return "", err
	}
	return res, nil
}

func stringInSlice(a string, list []string) bool {
	for _, b := range list {
		if a == b {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package scratchgo

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"net"
	"strings"
)

type RspConn struct {
	net.Conn
}

func NewDefaultConnect() (*RspConn, error) {
	return NewConnect("127.0.0.1", 42001)
}

func NewConnect(host string, port int) (*RspConn, error) {
	conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", host, port))
	if err != nil {
		return nil, err
	}
	return &RspConn{conn}, nil
}

func (self *RspConn) SensorUpdate(name string, value []string) error {
	return self.send(fmt.Sprintf(`sensor-update "%s" %q`, name, strings.Join(value, " ")))
}

func (self *RspConn) BroadCast(value []string) error {
	return self.send(fmt.Sprintf("broadcast %q", strings.Join(value, " ")))
}

func (self *RspConn) send(cmd string) error {
	var pkt []byte
	pkt = append(size_to_bytes(len(cmd)), []byte(cmd)...)
	_, err := self.Write(pkt)
	return err
}

func (self *RspConn) Recv() (*Message, error) {
	pkt := make([]byte, 4)
	_, err := self.Read(pkt)
	if err != nil {
		return nil, err
	}

	var pkt_len int32
	buf := bytes.NewBuffer(pkt)
	binary.Read(buf, binary.BigEndian, &pkt_len)

	data := make([]byte, pkt_len)
	_, err = self.Read(data)
	if err != nil {
		return nil, err
	}

	return ParseMessage(string(data)), nil
}

func size_to_bytes(size int) []byte {
	ret := make([]byte, 4)
	ret[0] = byte((size >> 24) & 0xFF)
	ret[1] = byte((size >> 16) & 0xFF)
	ret[2] = byte((size >> 8) & 0xFF)
	ret[3] = byte((size) & 0xFF)
	return ret
}
<commit_msg>changed parameters.<commit_after>package scratchgo

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"net"
)

type RspConn struct {
	net.Conn
}

func NewDefaultConnect() (*RspConn, error) {
	return NewConnect("127.0.0.1", 42001)
}

func NewConnect(host string, port int) (*RspConn, error) {
	conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", host, port))
	if err != nil {
		return nil, err
	}
	return &RspConn{conn}, nil
}

func (self *RspConn) SensorUpdate(name string, value string) error {
	return self.send(fmt.Sprintf(`sensor-update "%s" "%s"`, name, value))
}

func (self *RspConn) BroadCast(value string) error {
	return self.send(fmt.Sprintf("broadcast %s", value))
}

func (self *RspConn) send(cmd string) error {
	var pkt []byte
	pkt = append(size_to_bytes(len(cmd)), []byte(cmd)...)
	_, err := self.Write(pkt)
	return err
}

func (self *RspConn) Recv() (*Message, error) {
	pkt := make([]byte, 4)
	_, err := self.Read(pkt)
	if err != nil {
		return nil, err
	}

	var pkt_len int32
	buf := bytes.NewBuffer(pkt)
	binary.Read(buf, binary.BigEndian, &pkt_len)

	data := make([]byte, pkt_len)
	_, err = self.Read(data)
	if err != nil {
		return nil, err
	}

	return ParseMessage(string(data)), nil
}

func size_to_bytes(size int) []byte {
	ret := make([]byte, 4)
	ret[0] = byte((size >> 24) & 0xFF)
	ret[1] = byte((size >> 16) & 0xFF)
	ret[2] = byte((size >> 8) & 0xFF)
	ret[3] = byte((size) & 0xFF)
	return ret
}
<|endoftext|>


<commit_before>package pagsego

import (
	"bytes"
	"code.google.com/p/go-charset/charset"
	_ "code.google.com/p/go-charset/data"
	"crypto/tls"
	"encoding/xml"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"strconv"
	"time"
)

const (
	ShippingPAC   = 1
	ShippingSEDEX = 2
	ShippingOther = 3
	XMLHeader     = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`
	CheckoutURL   = "https://ws.pagseguro.uol.com.br/v2/checkout"
)

type PaymentRequest struct {
	XMLName         xml.Name       `xml:"checkout"`
	Email           string         `xml:"email"`
	Token           string         `xml:"token"`
	Currency        string         `xml:"currency"`
	Items           []*PaymentItem `xml:"items>item"`
	ReferenceID     string         `xml:"reference"`
	Buyer           *Buyer         `xml:"sender"`
	Shipping        *Shipping      `xml:"shipping"`
	ExtraAmount     string         `xml:"extraAmount,omitempty"` // use this for discounts or taxes
	RedirectURL     string         `xml:"redirectURL,omitempty"`
	NotificationURL string         `xml:"notificationURL,omitempty"`
	MaxUses         string         `xml:"maxUses,omitempty"`  // from 0 to 999 (the amount of tries a user can do with the same reference ID)
	MaxAge          string         `xml:"maxAge,omitempty"`   // time (in seconds) that the returned payment code is valid (30-999999999)
	Metadata        []*Metadata    `xml:"metadata,omitempty"` // https://pagseguro.uol.com.br/v2/guia-de-integracao/api-de-pagamentos.html#v2-item-api-de-pagamentos-parametros-http
	IsSandbox       bool           `xml:"-"`                  // o PagSeguro não tem um modo sandbox no momento (╯°□°）╯︵ ┻━┻
}

type PaymentItem struct {
	XMLName      xml.Name `xml:"item"`
	Id           string   `xml:"id"`
	Description  string   `xml:"description"`
	PriceAmount  string   `xml:"amount"`
	Quantity     string   `xml:"quantity"`
	ShippingCost string   `xml:"shippingCost,omitempty"`
	Weight       string   `xml:"weight,omitempty"`
}

type Buyer struct {
	Email     string           `xml:"email"`
	Name      string           `xml:"name"`
	Phone     *Phone           `xml:"phone,omitempty"`
	Documents []*BuyerDocument `xml:"documents>document,omitempty"`
	BornDate  string           `xml:"bornDate,omitempty"` //dd/MM/yyyy optional
}

type Phone struct {
	AreaCode    string `xml:"areaCode,omitempty"` // optional
	PhoneNumber string `xml:"number,omitempty"`   // optional
}

type BuyerDocument struct {
	Type  string `xml:"type"` // It's always "CPF" ¯\_(ツ)_/¯
	Value string `xml:"value"`
}

type Shipping struct {
	Type    string           `xml:"type"`
	Cost    string           `xml:"cost"`
	Address *ShippingAddress `xml:"address,omitempty"`
}

type ShippingAddress struct {
	Country    string `xml:"country"`    // It's always "BRA" ¯\_(ツ)_/¯
	State      string `xml:"state"`      // "SP"
	City       string `xml:"city"`       // max 60 min 2
	PostalCode string `xml:"postalCode"` // XXXXXXXX
	District   string `xml:"district"`   // Bairro | max chars: 60
	Street     string `xml:"street"`     // max: 80
	Number     string `xml:"number"`     // max: 20
	Complement string `xml:"complement"` // max: 40
}

type Metadata struct {
	Key   string     `xml:"key"`
	Value string     `xml:"value,omitempty"`
	Group []Metadata `xml:"group,omitempty"`
}

type ErrorResponse struct {
	Errors []XMLError `xml:"errors"`
}

type XMLError struct {
	XMLName xml.Name `xml:"error"`
	Code    int      `xml:"code"`
	Message string   `xml:"message"`
}

type PaymentPreResponse struct {
	XMLName xml.Name `xml:"checkout"`
	Code    string   `xml:"code"`
	Data    string   `xml:"data"`
}

type PaymentPreSubmitResult struct {
	CheckoutResponse *PaymentPreResponse
	Error            *ErrorResponse
	Success          bool
}

func NewPaymentRequest(sellerToken, sellerEmail, referenceID, redirectURL, notificationURL string) *PaymentRequest {
	req := &PaymentRequest{
		Email:           sellerEmail,
		Token:           sellerToken,
		Currency:        "BRL",
		ReferenceID:     referenceID,
		RedirectURL:     redirectURL,
		NotificationURL: notificationURL,
		MaxUses:         "10",
		MaxAge:          "7200",
	}
	return req
}

func (r *PaymentRequest) AddItem(id string, description string, amount float64, quantity int) *PaymentItem {
	item := &PaymentItem{
		Id:          id,
		Description: description,
		PriceAmount: toPriceAmountStr(amount),
		Quantity:    strconv.Itoa(quantity),
	}
	if r.Items == nil {
		r.Items = make([]*PaymentItem, 0)
	}
	r.Items = append(r.Items, item)

	return item
}

func (r *PaymentItem) SetWeight(grams int) *PaymentItem {
	r.Weight = strconv.Itoa(grams)
	return r
}

func (r *PaymentItem) SetAmount(amount float64) *PaymentItem {
	r.PriceAmount = toPriceAmountStr(amount)
	return r
}

func (r *PaymentItem) SetQuantity(quantity int) *PaymentItem {
	r.Quantity = strconv.Itoa(quantity)
	return r
}

func (r *PaymentItem) SetShippingCost(cost float64) *PaymentItem {
	r.ShippingCost = toPriceAmountStr(cost)
	return r
}

func (r *PaymentRequest) SetBuyer(name, email string) *Buyer {
	buyer := &Buyer{
		Name:  name,
		Email: email,
	}
	r.Buyer = buyer
	return buyer
}

func (r *Buyer) SetPhone(areaCode string, phone string) *Buyer {
	r.Phone = &Phone{
		AreaCode:    areaCode,
		PhoneNumber: phone,
	}
	return r
}

func (r *Buyer) SetCPF(cpf string) *Buyer {
	if r.Documents == nil {
		r.Documents = make([]*BuyerDocument, 0)
		r.Documents = append(r.Documents, &BuyerDocument{Type: "CPF"})
	}
	for i := 0; i < len(r.Documents); i++ {
		if r.Documents[i].Type == "CPF" {
			r.Documents[i].Value = cpf
			break
		}
	}
	return r
}

func (r *PaymentRequest) SetShipping(shippingType int, shippingCost float64) *Shipping {
	shipping := &Shipping{
		Type: strconv.Itoa(shippingType),
		Cost: toPriceAmountStr(shippingCost),
	}
	r.Shipping = shipping
	return shipping
}

func (r *Shipping) SetAddress(state, city, postalCode, district, street, number, complement string) *Shipping {
	addr := &ShippingAddress{
		Country:    "BRA",
		State:      state,
		City:       city,
		PostalCode: postalCode,
		District:   district,
		Street:     street,
		Number:     number,
		Complement: complement,
	}
	r.Address = addr
	return r
}

func (r *Shipping) SetAddressStateCity(state, city string) *Shipping {
	if r.Address == nil {
		r.SetAddress(state, city, "", "", "", "", "")
		return r
	}
	r.Address.State = state
	r.Address.City = city
	return r
}

func (r *Shipping) SetAddressCountry(country string) *Shipping {
	if r.Address == nil {
		r.SetAddress("", "", "", "", "", "", "")
	}
	r.Address.Country = country
	return r
}

func (r *PaymentRequest) Submit() (result *PaymentPreSubmitResult) {
	result = &PaymentPreSubmitResult{}

	// Conectar com timeout caso o PagSeguro esteja morgando
	functimeout := func(network, addr string) (net.Conn, error) {
		return net.DialTimeout(network, addr, time.Duration(30*time.Second))
	}

	// create a custom http client that ignores https cert validity, so we don't have to install PagSeguro CAs
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		Dial:            functimeout,
	}
	client := &http.Client{Transport: tr}

	// generate xml
	xmlb, err := xml.Marshal(r)

	if err != nil {
		log.Println("^~PAGSEGO~^ XML MARSHAL ERROR: " + err.Error())
		return
	}

	var clBuffer bytes.Buffer
	clBuffer.WriteString(XMLHeader)
	clBuffer.Write(xmlb)

	checkoutURL := fmt.Sprintf("%s?email=%s&token=%s&charset=%s", CheckoutURL, r.Email, r.Token, "UTF-8")

	// send the request (this goroutine is blocked until a response is received)
	resp, err := client.Post(checkoutURL, "application/xml", &clBuffer)

	if err != nil {
		log.Println("^~PAGSEGO~^ client.Post ERROR: " + err.Error())
		return
	}

	defer resp.Body.Close()
	clBuffer.Truncate(0)

	// io.Copy has a 32kB max buffer size, so no extra memory is consumed
	io.Copy(&clBuffer, resp.Body)
	respBytes := clBuffer.Bytes()
	log.Println(string(respBytes))
	var decoder *xml.Decoder

	errors := &ErrorResponse{}

	clBuffer.Truncate(0)
	clBuffer.Write(respBytes)
	decoder = xml.NewDecoder(&clBuffer)
	decoder.CharsetReader = charset.NewReader
	err = decoder.Decode(errors)

	if err != nil {
		// an error was not found!
		//log.Println("^~PAGSEGO~^ Unmarshal(errors)  ERROR: " + err.Error())
		//return
	} else {
		if errors.Errors != nil {
			if len(errors.Errors) > 0 {
				//log.Println("LOL ERRORS")
				//log.Println(errors.Errors[0].Message)
				result.Error = errors
				result.Success = false
				return
			}
		}
	}

	success := &PaymentPreResponse{}

	clBuffer.Truncate(0)
	clBuffer.Write(respBytes)
	decoder = xml.NewDecoder(&clBuffer)
	decoder.CharsetReader = charset.NewReader
	err = decoder.Decode(success)

	if err != nil {
		log.Println("^~PAGSEGO~^ Unmarshal(success)  ERROR: " + err.Error())
		result.Success = false
		return
	}

	result.CheckoutResponse = success
	result.Success = true
	return
}
<commit_msg>Made street number and complement optional<commit_after>package pagsego

import (
	"bytes"
	"code.google.com/p/go-charset/charset"
	_ "code.google.com/p/go-charset/data"
	"crypto/tls"
	"encoding/xml"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"strconv"
	"time"
)

const (
	ShippingPAC   = 1
	ShippingSEDEX = 2
	ShippingOther = 3
	XMLHeader     = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`
	CheckoutURL   = "https://ws.pagseguro.uol.com.br/v2/checkout"
)

type PaymentRequest struct {
	XMLName         xml.Name       `xml:"checkout"`
	Email           string         `xml:"email"`
	Token           string         `xml:"token"`
	Currency        string         `xml:"currency"`
	Items           []*PaymentItem `xml:"items>item"`
	ReferenceID     string         `xml:"reference"`
	Buyer           *Buyer         `xml:"sender"`
	Shipping        *Shipping      `xml:"shipping"`
	ExtraAmount     string         `xml:"extraAmount,omitempty"` // use this for discounts or taxes
	RedirectURL     string         `xml:"redirectURL,omitempty"`
	NotificationURL string         `xml:"notificationURL,omitempty"`
	MaxUses         string         `xml:"maxUses,omitempty"`  // from 0 to 999 (the amount of tries a user can do with the same reference ID)
	MaxAge          string         `xml:"maxAge,omitempty"`   // time (in seconds) that the returned payment code is valid (30-999999999)
	Metadata        []*Metadata    `xml:"metadata,omitempty"` // https://pagseguro.uol.com.br/v2/guia-de-integracao/api-de-pagamentos.html#v2-item-api-de-pagamentos-parametros-http
	IsSandbox       bool           `xml:"-"`                  // o PagSeguro não tem um modo sandbox no momento (╯°□°）╯︵ ┻━┻
}

type PaymentItem struct {
	XMLName      xml.Name `xml:"item"`
	Id           string   `xml:"id"`
	Description  string   `xml:"description"`
	PriceAmount  string   `xml:"amount"`
	Quantity     string   `xml:"quantity"`
	ShippingCost string   `xml:"shippingCost,omitempty"`
	Weight       string   `xml:"weight,omitempty"`
}

type Buyer struct {
	Email     string           `xml:"email"`
	Name      string           `xml:"name"`
	Phone     *Phone           `xml:"phone,omitempty"`
	Documents []*BuyerDocument `xml:"documents>document,omitempty"`
	BornDate  string           `xml:"bornDate,omitempty"` //dd/MM/yyyy optional
}

type Phone struct {
	AreaCode    string `xml:"areaCode,omitempty"` // optional
	PhoneNumber string `xml:"number,omitempty"`   // optional
}

type BuyerDocument struct {
	Type  string `xml:"type"` // It's always "CPF" ¯\_(ツ)_/¯
	Value string `xml:"value"`
}

type Shipping struct {
	Type    string           `xml:"type"`
	Cost    string           `xml:"cost"`
	Address *ShippingAddress `xml:"address,omitempty"`
}

type ShippingAddress struct {
	Country    string `xml:"country"`              // It's always "BRA" ¯\_(ツ)_/¯
	State      string `xml:"state,omitempty"`      // "SP"
	City       string `xml:"city,omitempty"`       // max 60 min 2
	PostalCode string `xml:"postalCode,omitempty"` // XXXXXXXX
	District   string `xml:"district,omitempty"`   // Bairro | max chars: 60
	Street     string `xml:"street,omitempty"`     // max: 80
	Number     string `xml:"number,omitempty"`     // max: 20
	Complement string `xml:"complement,omitempty"` // max: 40
}

type Metadata struct {
	Key   string     `xml:"key"`
	Value string     `xml:"value,omitempty"`
	Group []Metadata `xml:"group,omitempty"`
}

type ErrorResponse struct {
	Errors []XMLError `xml:"errors"`
}

type XMLError struct {
	XMLName xml.Name `xml:"error"`
	Code    int      `xml:"code"`
	Message string   `xml:"message"`
}

type PaymentPreResponse struct {
	XMLName xml.Name `xml:"checkout"`
	Code    string   `xml:"code"`
	Data    string   `xml:"data"`
}

type PaymentPreSubmitResult struct {
	CheckoutResponse *PaymentPreResponse
	Error            *ErrorResponse
	Success          bool
}

func NewPaymentRequest(sellerToken, sellerEmail, referenceID, redirectURL, notificationURL string) *PaymentRequest {
	req := &PaymentRequest{
		Email:           sellerEmail,
		Token:           sellerToken,
		Currency:        "BRL",
		ReferenceID:     referenceID,
		RedirectURL:     redirectURL,
		NotificationURL: notificationURL,
		MaxUses:         "10",
		MaxAge:          "7200",
	}
	return req
}

func (r *PaymentRequest) AddItem(id string, description string, amount float64, quantity int) *PaymentItem {
	item := &PaymentItem{
		Id:          id,
		Description: description,
		PriceAmount: toPriceAmountStr(amount),
		Quantity:    strconv.Itoa(quantity),
	}
	if r.Items == nil {
		r.Items = make([]*PaymentItem, 0)
	}
	r.Items = append(r.Items, item)

	return item
}

func (r *PaymentItem) SetWeight(grams int) *PaymentItem {
	r.Weight = strconv.Itoa(grams)
	return r
}

func (r *PaymentItem) SetAmount(amount float64) *PaymentItem {
	r.PriceAmount = toPriceAmountStr(amount)
	return r
}

func (r *PaymentItem) SetQuantity(quantity int) *PaymentItem {
	r.Quantity = strconv.Itoa(quantity)
	return r
}

func (r *PaymentItem) SetShippingCost(cost float64) *PaymentItem {
	r.ShippingCost = toPriceAmountStr(cost)
	return r
}

func (r *PaymentRequest) SetBuyer(name, email string) *Buyer {
	buyer := &Buyer{
		Name:  name,
		Email: email,
	}
	r.Buyer = buyer
	return buyer
}

func (r *Buyer) SetPhone(areaCode string, phone string) *Buyer {
	r.Phone = &Phone{
		AreaCode:    areaCode,
		PhoneNumber: phone,
	}
	return r
}

func (r *Buyer) SetCPF(cpf string) *Buyer {
	if r.Documents == nil {
		r.Documents = make([]*BuyerDocument, 0)
		r.Documents = append(r.Documents, &BuyerDocument{Type: "CPF"})
	}
	for i := 0; i < len(r.Documents); i++ {
		if r.Documents[i].Type == "CPF" {
			r.Documents[i].Value = cpf
			break
		}
	}
	return r
}

func (r *PaymentRequest) SetShipping(shippingType int, shippingCost float64) *Shipping {
	shipping := &Shipping{
		Type: strconv.Itoa(shippingType),
		Cost: toPriceAmountStr(shippingCost),
	}
	r.Shipping = shipping
	return shipping
}

func (r *Shipping) SetAddress(state, city, postalCode, district, street, number, complement string) *Shipping {
	addr := &ShippingAddress{
		Country:    "BRA",
		State:      state,
		City:       city,
		PostalCode: postalCode,
		District:   district,
		Street:     street,
		Number:     number,
		Complement: complement,
	}
	r.Address = addr
	return r
}

func (r *Shipping) SetAddressStateCity(state, city string) *Shipping {
	if r.Address == nil {
		r.SetAddress(state, city, "", "", "", "", "")
		return r
	}
	r.Address.State = state
	r.Address.City = city
	return r
}

func (r *Shipping) SetAddressCountry(country string) *Shipping {
	if r.Address == nil {
		r.SetAddress("", "", "", "", "", "", "")
	}
	r.Address.Country = country
	return r
}

func (r *PaymentRequest) Submit() (result *PaymentPreSubmitResult) {
	result = &PaymentPreSubmitResult{}

	// Conectar com timeout caso o PagSeguro esteja morgando
	functimeout := func(network, addr string) (net.Conn, error) {
		return net.DialTimeout(network, addr, time.Duration(30*time.Second))
	}

	// create a custom http client that ignores https cert validity, so we don't have to install PagSeguro CAs
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		Dial:            functimeout,
	}
	client := &http.Client{Transport: tr}

	// generate xml
	xmlb, err := xml.Marshal(r)

	if err != nil {
		log.Println("^~PAGSEGO~^ XML MARSHAL ERROR: " + err.Error())
		return
	}

	var clBuffer bytes.Buffer
	clBuffer.WriteString(XMLHeader)
	clBuffer.Write(xmlb)

	checkoutURL := fmt.Sprintf("%s?email=%s&token=%s&charset=%s", CheckoutURL, r.Email, r.Token, "UTF-8")

	// send the request (this goroutine is blocked until a response is received)
	resp, err := client.Post(checkoutURL, "application/xml", &clBuffer)

	if err != nil {
		log.Println("^~PAGSEGO~^ client.Post ERROR: " + err.Error())
		return
	}

	defer resp.Body.Close()
	clBuffer.Truncate(0)

	// io.Copy has a 32kB max buffer size, so no extra memory is consumed
	io.Copy(&clBuffer, resp.Body)
	respBytes := clBuffer.Bytes()
	log.Println(string(respBytes))
	var decoder *xml.Decoder

	errors := &ErrorResponse{}

	clBuffer.Truncate(0)
	clBuffer.Write(respBytes)
	decoder = xml.NewDecoder(&clBuffer)
	decoder.CharsetReader = charset.NewReader
	err = decoder.Decode(errors)

	if err != nil {
		// an error was not found!
		//log.Println("^~PAGSEGO~^ Unmarshal(errors)  ERROR: " + err.Error())
		//return
	} else {
		if errors.Errors != nil {
			if len(errors.Errors) > 0 {
				//log.Println("LOL ERRORS")
				//log.Println(errors.Errors[0].Message)
				result.Error = errors
				result.Success = false
				return
			}
		}
	}

	success := &PaymentPreResponse{}

	clBuffer.Truncate(0)
	clBuffer.Write(respBytes)
	decoder = xml.NewDecoder(&clBuffer)
	decoder.CharsetReader = charset.NewReader
	err = decoder.Decode(success)

	if err != nil {
		log.Println("^~PAGSEGO~^ Unmarshal(success)  ERROR: " + err.Error())
		result.Success = false
		return
	}

	result.CheckoutResponse = success
	result.Success = true
	return
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/rand"
	"crypto/rsa"
	"encoding/base64"
	"github.com/realglobe-Inc/edo/util"
	"github.com/realglobe-Inc/go-lib-rg/erro"
	"io"
	"net"
	"net/http"
	"net/url"
	"time"
)

const (
	headerTaId       = "X-Edo-Ta-Id"
	headerTaToken    = "X-Edo-Ta-Token"
	headerTaTokenSig = "X-Edo-Ta-Token-Sign"
	headerHashFunc   = "X-Edo-Hash-Function"

	headerTaAuthErr = "X-Edo-Ta-Auth-Error"

	cookieTaSess = "X-Edo-Ta-Session"
)

func uriBase(url *url.URL) string {
	return url.Scheme + "://" + url.Host + url.Path
}

// Web プロキシ。
func proxyApi(sys *system, w http.ResponseWriter, r *http.Request) error {

	taId := r.Header.Get(headerTaId)
	if taId == "" {
		taId = sys.taId
	}

	sess, _, err := sys.session(uriBase(r.URL), taId, nil)
	if err != nil {
		return erro.Wrap(err)
	}

	if sess != nil {
		// セッション確立済み。
		return forward(sys, w, r, taId, sess)
	} else {
		// セッション未確立。
		return startSession(sys, w, r, taId)
	}
}

// 転送する。
func forward(sys *system, w http.ResponseWriter, r *http.Request, taId string, sess *session) error {
	r.AddCookie(&http.Cookie{Name: cookieTaSess, Value: sess.id})
	r.RequestURI = ""

	resp, err := sess.cli.Do(r)
	if err != nil {
		err = erro.Wrap(err)
		switch erro.Unwrap(err).(type) {
		case *net.OpError:
			return erro.Wrap(util.NewHttpStatusError(http.StatusNotFound, "cannot connect "+uriBase(r.URL), err))
		default:
			return err
		}
	}
	defer resp.Body.Close()

	if resp.StatusCode == http.StatusUnauthorized && resp.Header.Get(headerTaAuthErr) != "" {
		// edo-auth で 401 Unauthorized なら、タイミングの問題なので startSession からやり直す。
		// 古いセッションは上書きされるので消す必要無し。
		return startSession(sys, w, r, taId)
	}

	return copyResponse(resp, w)
}

// セッション開始。
func startSession(sys *system, w http.ResponseWriter, r *http.Request, taId string) error {

	cli := &http.Client{}

	resp, err := cli.Get(uriBase(r.URL))
	if err != nil {
		err = erro.Wrap(err)
		switch erro.Unwrap(err).(type) {
		case *net.OpError:
			return erro.Wrap(util.NewHttpStatusError(http.StatusNotFound, "cannot connect "+uriBase(r.URL), err))
		default:
			return err
		}
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusUnauthorized {
		return copyResponse(resp, w)
	}

	// 相手側 TA も認証始めた。

	sess, sessToken := parseSession(resp)
	if sess == nil {
		return erro.Wrap(util.NewHttpStatusError(http.StatusForbidden, "no cookie "+cookieTaSess, nil))
	} else if sessToken == "" {
		return erro.Wrap(util.NewHttpStatusError(http.StatusForbidden, "no header field "+headerTaToken, nil))
	}

	expiDate := getExpirationDate(sess)

	// 認証用データが揃ってた。

	priKey, _, err := sys.privateKey(taId, nil)
	if err != nil {
		return erro.Wrap(err)
	} else if priKey == nil {
		return erro.Wrap(util.NewHttpStatusError(http.StatusForbidden, "no private key of "+taId, nil))
	}

	// 秘密鍵を用意できた。

	hashName := r.Header.Get(headerHashFunc)
	if hashName == "" {
		hashName = sys.hashName
	}

	sign, err := sign(priKey, hashName, sessToken)
	if err != nil {
		return erro.Wrap(err)
	}

	// 署名できた。

	r.AddCookie(&http.Cookie{Name: cookieTaSess, Value: sess.Value})
	r.Header.Set(headerTaId, taId)
	r.Header.Set(headerTaTokenSig, sign)
	r.Header.Set(headerHashFunc, hashName)
	r.RequestURI = ""

	resp, err = cli.Do(r)
	if err != nil {
		err = erro.Wrap(err)
		switch erro.Unwrap(err).(type) {
		case *net.OpError:
			return erro.Wrap(util.NewHttpStatusError(http.StatusNotFound, "cannot connect "+uriBase(r.URL), err))
		default:
			return err
		}
	}
	defer resp.Body.Close()

	if resp.Header.Get(headerTaAuthErr) != "" {
		// セッションを保存。
		if _, err := sys.addSession(&session{id: sess.Value, uri: uriBase(r.URL), taId: taId, cli: cli}, expiDate); err != nil {
			return erro.Wrap(err)
		}
	}

	return copyResponse(resp, w)
}

// 相手側 TA の認証開始レスポンスから必要情報を抜き出す。
func parseSession(resp *http.Response) (sess *http.Cookie, sessToken string) {
	for _, cookie := range resp.Cookies() {
		if cookie.Name == cookieTaSess {
			sess = cookie
			break
		}
	}

	return sess, resp.Header.Get(headerTaToken)
}

// 相手側 TA からのレスポンスをリクエスト元へのレスポンスに写す。
func copyResponse(resp *http.Response, w http.ResponseWriter) error {
	// ヘッダフィールドのコピー。
	for key, values := range resp.Header {
		for _, value := range values {
			w.Header().Add(key, value)
		}
	}

	// ステータスのコピー。
	w.WriteHeader(resp.StatusCode)

	// ボディのコピー。
	if _, err := io.Copy(w, resp.Body); err != nil {
		return erro.Wrap(err)
	}

	return nil
}

// 相手側 TA からのお題に署名する。
func sign(priKey *rsa.PrivateKey, hashName, token string) (string, error) {
	hash, err := util.ParseHashFunction(hashName)
	if err != nil {
		return "", erro.Wrap(err)
	}

	h := hash.New()
	h.Write([]byte(token))
	buff, err := rsa.SignPKCS1v15(rand.Reader, priKey, hash, h.Sum(nil))
	if err != nil {
		return "", erro.Wrap(err)
	}

	return base64.StdEncoding.EncodeToString(buff), nil
}

// 相手側 TA が提示したセッションの有効期限を読み取る。
func getExpirationDate(sess *http.Cookie) (expiDate time.Time) {
	if sess.MaxAge != 0 {
		return time.Now().Add(time.Duration(sess.MaxAge))
	} else {
		return sess.Expires
	}
}
<commit_msg>最初からリクエストをそのまま横流しするように変更<commit_after>package main

import (
	"crypto/rand"
	"crypto/rsa"
	"encoding/base64"
	"github.com/realglobe-Inc/edo/util"
	"github.com/realglobe-Inc/go-lib-rg/erro"
	"io"
	"net"
	"net/http"
	"net/url"
	"time"
)

const (
	headerTaId       = "X-Edo-Ta-Id"
	headerTaToken    = "X-Edo-Ta-Token"
	headerTaTokenSig = "X-Edo-Ta-Token-Sign"
	headerHashFunc   = "X-Edo-Hash-Function"

	headerTaAuthErr = "X-Edo-Ta-Auth-Error"

	cookieTaSess = "X-Edo-Ta-Session"
)

func uriBase(url *url.URL) string {
	return url.Scheme + "://" + url.Host + url.Path
}

// Web プロキシ。
func proxyApi(sys *system, w http.ResponseWriter, r *http.Request) error {

	taId := r.Header.Get(headerTaId)
	if taId == "" {
		taId = sys.taId
	}

	sess, _, err := sys.session(uriBase(r.URL), taId, nil)
	if err != nil {
		return erro.Wrap(err)
	}

	if sess != nil {
		// セッション確立済み。
		return forward(sys, w, r, taId, sess)
	} else {
		// セッション未確立。
		return startSession(sys, w, r, taId)
	}
}

// 転送する。
func forward(sys *system, w http.ResponseWriter, r *http.Request, taId string, sess *session) error {
	r.AddCookie(&http.Cookie{Name: cookieTaSess, Value: sess.id})
	r.RequestURI = ""

	resp, err := sess.cli.Do(r)
	if err != nil {
		err = erro.Wrap(err)
		switch erro.Unwrap(err).(type) {
		case *net.OpError:
			return erro.Wrap(util.NewHttpStatusError(http.StatusNotFound, "cannot connect "+uriBase(r.URL), err))
		default:
			return err
		}
	}
	defer resp.Body.Close()

	if resp.StatusCode == http.StatusUnauthorized && resp.Header.Get(headerTaAuthErr) != "" {
		// edo-auth で 401 Unauthorized なら、タイミングの問題なので startSession からやり直す。
		// 古いセッションは上書きされるので消す必要無し。
		return startSession(sys, w, r, taId)
	}

	return copyResponse(resp, w)
}

// セッション開始。
func startSession(sys *system, w http.ResponseWriter, r *http.Request, taId string) error {

	cli := &http.Client{}

	r.RequestURI = ""
	resp, err := cli.Do(r)
	if err != nil {
		err = erro.Wrap(err)
		switch erro.Unwrap(err).(type) {
		case *net.OpError:
			return erro.Wrap(util.NewHttpStatusError(http.StatusNotFound, "cannot connect "+uriBase(r.URL), err))
		default:
			return err
		}
	}
	defer resp.Body.Close()

	if resp.Header.Get(headerTaAuthErr) == "" || resp.StatusCode != http.StatusUnauthorized {
		// 相手側が TA 認証を必要としていなかったのかもしれない。
		return copyResponse(resp, w)
	}

	// 相手側 TA も認証始めた。

	sess, sessToken := parseSession(resp)
	if sess == nil {
		return erro.Wrap(util.NewHttpStatusError(http.StatusForbidden, "no cookie "+cookieTaSess, nil))
	} else if sessToken == "" {
		return erro.Wrap(util.NewHttpStatusError(http.StatusForbidden, "no header field "+headerTaToken, nil))
	}

	expiDate := getExpirationDate(sess)

	// 認証用データが揃ってた。

	priKey, _, err := sys.privateKey(taId, nil)
	if err != nil {
		return erro.Wrap(err)
	} else if priKey == nil {
		return erro.Wrap(util.NewHttpStatusError(http.StatusForbidden, "no private key of "+taId, nil))
	}

	// 秘密鍵を用意できた。

	hashName := r.Header.Get(headerHashFunc)
	if hashName == "" {
		hashName = sys.hashName
	}

	sign, err := sign(priKey, hashName, sessToken)
	if err != nil {
		return erro.Wrap(err)
	}

	// 署名できた。

	r.AddCookie(&http.Cookie{Name: cookieTaSess, Value: sess.Value})
	r.Header.Set(headerTaId, taId)
	r.Header.Set(headerTaTokenSig, sign)
	r.Header.Set(headerHashFunc, hashName)
	r.RequestURI = ""

	resp, err = cli.Do(r)
	if err != nil {
		err = erro.Wrap(err)
		switch erro.Unwrap(err).(type) {
		case *net.OpError:
			return erro.Wrap(util.NewHttpStatusError(http.StatusNotFound, "cannot connect "+uriBase(r.URL), err))
		default:
			return err
		}
	}
	defer resp.Body.Close()

	if resp.Header.Get(headerTaAuthErr) != "" {
		// セッションを保存。
		if _, err := sys.addSession(&session{id: sess.Value, uri: uriBase(r.URL), taId: taId, cli: cli}, expiDate); err != nil {
			return erro.Wrap(err)
		}
	}

	return copyResponse(resp, w)
}

// 相手側 TA の認証開始レスポンスから必要情報を抜き出す。
func parseSession(resp *http.Response) (sess *http.Cookie, sessToken string) {
	for _, cookie := range resp.Cookies() {
		if cookie.Name == cookieTaSess {
			sess = cookie
			break
		}
	}

	return sess, resp.Header.Get(headerTaToken)
}

// 相手側 TA からのレスポンスをリクエスト元へのレスポンスに写す。
func copyResponse(resp *http.Response, w http.ResponseWriter) error {
	// ヘッダフィールドのコピー。
	for key, values := range resp.Header {
		for _, value := range values {
			w.Header().Add(key, value)
		}
	}

	// ステータスのコピー。
	w.WriteHeader(resp.StatusCode)

	// ボディのコピー。
	if _, err := io.Copy(w, resp.Body); err != nil {
		return erro.Wrap(err)
	}

	return nil
}

// 相手側 TA からのお題に署名する。
func sign(priKey *rsa.PrivateKey, hashName, token string) (string, error) {
	hash, err := util.ParseHashFunction(hashName)
	if err != nil {
		return "", erro.Wrap(err)
	}

	h := hash.New()
	h.Write([]byte(token))
	buff, err := rsa.SignPKCS1v15(rand.Reader, priKey, hash, h.Sum(nil))
	if err != nil {
		return "", erro.Wrap(err)
	}

	return base64.StdEncoding.EncodeToString(buff), nil
}

// 相手側 TA が提示したセッションの有効期限を読み取る。
func getExpirationDate(sess *http.Cookie) (expiDate time.Time) {
	if sess.MaxAge != 0 {
		return time.Now().Add(time.Duration(sess.MaxAge))
	} else {
		return sess.Expires
	}
}
<|endoftext|>


<commit_before>package libkb

import (
	"encoding/json"
	"fmt"
	"github.com/okcupid/jsonw"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
)

type ApiArg struct {
	Endpoint    string
	uArgs       url.Values
	Args        HttpArgs
	NeedSession bool
	HttpStatus  []int
	AppStatus   []string
}

type ApiRes struct {
	Status     *jsonw.Wrapper
	Body       *jsonw.Wrapper
	HttpStatus int
	AppStatus  string
}

type ApiAccess struct {
	config              *ClientConfig
	cookied, notCookied *Client
}

func NewApiAccess(e Env) (*ApiAccess, error) {
	config, err := e.GenClientConfig()
	if err != nil {
		return nil, err
	}
	return &ApiAccess{config, nil, nil}, nil
}

func (api *ApiAccess) getCli(cookied bool) (ret *Client) {
	if cookied {
		if api.cookied == nil {
			api.cookied = NewClient(api.config, true)
		}
		ret = api.cookied
	} else {
		if api.notCookied == nil {
			api.notCookied = NewClient(api.config, false)
		}
		ret = api.notCookied
	}
	return ret
}

func (a ApiAccess) getUrl(arg ApiArg) url.URL {
	u := *a.config.Url
	var path string
	if len(a.config.Prefix) > 0 {
		path = a.config.Prefix
	} else {
		path = API_URI_PATH_PREFIX
	}
	u.Path = path + "/" + arg.Endpoint + ".json"
	return u
}

func (a ApiAccess) fixHeaders(req *http.Request, arg ApiArg) {
	if arg.NeedSession && G.Session != nil {
		if tok := G.Session.token; len(tok) > 0 {
			req.Header.Add("X-Keybase-Session", tok)
		}
		if csrf := G.Session.csrf; len(csrf) > 0 {
			req.Header.Add("X-CSRF-Token", csrf)
		}
	}
	req.Header.Set("User-Agent", USER_AGENT)
	req.Header.Set("X-Keybase-Client", IDENTIFY_AS)
}

func checkAppStatus(arg ApiArg, jw *jsonw.Wrapper) (string, error) {
	var set []string

	res_name, err := jw.AtKey("name").GetString()
	if err != nil {
		err = fmt.Errorf("Cannot find status name in reply")
		return "", err
	}

	if arg.AppStatus == nil || len(arg.AppStatus) == 0 {
		set = []string{"OK"}
	} else {
		set = arg.AppStatus
	}
	for _, status := range set {
		if res_name == status {
			return res_name, nil
		}
	}
	desc, _ := jw.AtKey("desc").GetString()
	code, _ := jw.AtKey("code").GetInt64()
	return "", fmt.Errorf("%s (error %d)", desc, int(code))
}

func checkHttpStatus(arg ApiArg, resp *http.Response) error {
	var set []int
	if arg.HttpStatus == nil || len(arg.HttpStatus) == 0 {
		set = []int{200}
	} else {
		set = arg.HttpStatus
	}
	for _, status := range set {
		if resp.StatusCode == status {
			return nil
		}
	}
	return fmt.Errorf("Bad HTTP response code: %s", resp.Status)
}

func (arg ApiArg) getHttpArgs() url.Values {
	if arg.Args != nil {
		return arg.Args.ToValues()
	} else {
		return arg.uArgs
	}
}

func (api *ApiAccess) Get(arg ApiArg) (*ApiRes, error) {
	url := api.getUrl(arg)
	url.RawQuery = arg.getHttpArgs().Encode()
	ruri := url.String()
	G.Log.Debug(fmt.Sprintf("+ API GET request to %s", ruri))
	req, err := http.NewRequest("GET", ruri, nil)
	if err != nil {
		return nil, err
	}
	return api.DoRequest(arg, req)
}

func (api *ApiAccess) Post(arg ApiArg) (*ApiRes, error) {
	url := api.getUrl(arg)
	ruri := url.String()
	G.Log.Debug(fmt.Sprintf("+ API Post request to %s", ruri))
	body := ioutil.NopCloser(strings.NewReader(arg.getHttpArgs().Encode()))
	req, err := http.NewRequest("POST", ruri, body)
	typ := "application/x-www-form-urlencoded; charset=utf-8"
	req.Header.Add("Content-Type", typ)
	if err != nil {
		return nil, err
	}
	return api.DoRequest(arg, req)
}

func (api *ApiAccess) DoRequest(arg ApiArg,
	req *http.Request) (*ApiRes, error) {

	api.fixHeaders(req, arg)
	cli := api.getCli(arg.NeedSession)

	// Actually send the request via Go's libraries
	resp, err := cli.cli.Do(req)
	if err != nil {
		return nil, err
	}
	G.Log.Debug(fmt.Sprintf("| Result is: %s", resp.Status))

	// Check for a code 200 or rather which codes were allowed in arg.HttpStatus
	err = checkHttpStatus(arg, resp)
	if err != nil {
		return nil, err
	}

	decoder := json.NewDecoder(resp.Body)
	obj := make(map[string]interface{})
	err = decoder.Decode(&obj)
	resp.Body.Close()
	if err != nil {
		err = fmt.Errorf("Error in parsing JSON reply from server: %s", err.Error())
		return nil, err
	}

	jw := jsonw.NewWrapper(obj)
	G.Log.Debug(fmt.Sprintf("| full reply: %v", obj))
	status, err := jw.AtKey("status").ToDictionary()
	if err != nil {
		err = fmt.Errorf("Cannot parse server's 'status' field: %s", err.Error())
		return nil, err
	}

	// Check for an "OK" or whichever app-level replies were allowed by
	// http.AppStatus
	appStatus, err := checkAppStatus(arg, status)
	if err != nil {
		err = fmt.Errorf("Got failure from Keybase server: %s", err.Error())
		return nil, err
	}

	body := jw
	G.Log.Debug(fmt.Sprintf("- succesful API call"))
	return &ApiRes{status, body, resp.StatusCode, appStatus}, err
}
<commit_msg>change formatting a big<commit_after>package libkb

import (
	"encoding/json"
	"fmt"
	"github.com/okcupid/jsonw"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
)

type ApiArg struct {
	Endpoint    string
	uArgs       url.Values
	Args        HttpArgs
	NeedSession bool
	HttpStatus  []int
	AppStatus   []string
}

type ApiRes struct {
	Status     *jsonw.Wrapper
	Body       *jsonw.Wrapper
	HttpStatus int
	AppStatus  string
}

type ApiAccess struct {
	config              *ClientConfig
	cookied, notCookied *Client
}

func NewApiAccess(e Env) (*ApiAccess, error) {
	config, err := e.GenClientConfig()
	if err != nil {
		return nil, err
	}
	return &ApiAccess{config, nil, nil}, nil
}

func (api *ApiAccess) getCli(cookied bool) (ret *Client) {
	if cookied {
		if api.cookied == nil {
			api.cookied = NewClient(api.config, true)
		}
		ret = api.cookied
	} else {
		if api.notCookied == nil {
			api.notCookied = NewClient(api.config, false)
		}
		ret = api.notCookied
	}
	return ret
}

func (a ApiAccess) getUrl(arg ApiArg) url.URL {
	u := *a.config.Url
	var path string
	if len(a.config.Prefix) > 0 {
		path = a.config.Prefix
	} else {
		path = API_URI_PATH_PREFIX
	}
	u.Path = path + "/" + arg.Endpoint + ".json"
	return u
}

func (a ApiAccess) fixHeaders(req *http.Request, arg ApiArg) {
	if arg.NeedSession && G.Session != nil {
		if tok := G.Session.token; len(tok) > 0 {
			req.Header.Add("X-Keybase-Session", tok)
		}
		if csrf := G.Session.csrf; len(csrf) > 0 {
			req.Header.Add("X-CSRF-Token", csrf)
		}
	}
	req.Header.Set("User-Agent", USER_AGENT)
	req.Header.Set("X-Keybase-Client", IDENTIFY_AS)
}

func checkAppStatus(arg ApiArg, jw *jsonw.Wrapper) (string, error) {
	var set []string

	res_name, err := jw.AtKey("name").GetString()
	if err != nil {
		err = fmt.Errorf("Cannot find status name in reply")
		return "", err
	}

	if arg.AppStatus == nil || len(arg.AppStatus) == 0 {
		set = []string{"OK"}
	} else {
		set = arg.AppStatus
	}
	for _, status := range set {
		if res_name == status {
			return res_name, nil
		}
	}
	desc, _ := jw.AtKey("desc").GetString()
	code, _ := jw.AtKey("code").GetInt64()
	return "", fmt.Errorf("%s (error %d)", desc, int(code))
}

func checkHttpStatus(arg ApiArg, resp *http.Response) error {
	var set []int
	if arg.HttpStatus == nil || len(arg.HttpStatus) == 0 {
		set = []int{200}
	} else {
		set = arg.HttpStatus
	}
	for _, status := range set {
		if resp.StatusCode == status {
			return nil
		}
	}
	return fmt.Errorf("Bad HTTP response code: %s", resp.Status)
}

func (arg ApiArg) getHttpArgs() url.Values {
	if arg.Args != nil {
		return arg.Args.ToValues()
	} else {
		return arg.uArgs
	}
}

func (api *ApiAccess) Get(arg ApiArg) (*ApiRes, error) {
	url := api.getUrl(arg)
	url.RawQuery = arg.getHttpArgs().Encode()
	ruri := url.String()
	G.Log.Debug(fmt.Sprintf("+ API GET request to %s", ruri))
	req, err := http.NewRequest("GET", ruri, nil)
	if err != nil {
		return nil, err
	}
	return api.DoRequest(arg, req)
}

func (api *ApiAccess) Post(arg ApiArg) (*ApiRes, error) {
	url := api.getUrl(arg)
	ruri := url.String()
	G.Log.Debug(fmt.Sprintf("+ API Post request to %s", ruri))
	body := ioutil.NopCloser(strings.NewReader(arg.getHttpArgs().Encode()))
	req, err := http.NewRequest("POST", ruri, body)
	typ := "application/x-www-form-urlencoded; charset=utf-8"
	req.Header.Add("Content-Type", typ)
	if err != nil {
		return nil, err
	}
	return api.DoRequest(arg, req)
}

func (api *ApiAccess) DoRequest(
	arg ApiArg, req *http.Request) (*ApiRes, error) {

	api.fixHeaders(req, arg)
	cli := api.getCli(arg.NeedSession)

	// Actually send the request via Go's libraries
	resp, err := cli.cli.Do(req)
	if err != nil {
		return nil, err
	}
	G.Log.Debug(fmt.Sprintf("| Result is: %s", resp.Status))

	// Check for a code 200 or rather which codes were allowed in arg.HttpStatus
	err = checkHttpStatus(arg, resp)
	if err != nil {
		return nil, err
	}

	decoder := json.NewDecoder(resp.Body)
	obj := make(map[string]interface{})
	err = decoder.Decode(&obj)
	resp.Body.Close()
	if err != nil {
		err = fmt.Errorf("Error in parsing JSON reply from server: %s", err.Error())
		return nil, err
	}

	jw := jsonw.NewWrapper(obj)
	G.Log.Debug(fmt.Sprintf("| full reply: %v", obj))
	status, err := jw.AtKey("status").ToDictionary()
	if err != nil {
		err = fmt.Errorf("Cannot parse server's 'status' field: %s", err.Error())
		return nil, err
	}

	// Check for an "OK" or whichever app-level replies were allowed by
	// http.AppStatus
	appStatus, err := checkAppStatus(arg, status)
	if err != nil {
		err = fmt.Errorf("Got failure from Keybase server: %s", err.Error())
		return nil, err
	}

	body := jw
	G.Log.Debug(fmt.Sprintf("- succesful API call"))
	return &ApiRes{status, body, resp.StatusCode, appStatus}, err
}
<|endoftext|>