<commit_before>package main

import (
	"os"
	"fmt"
	"errors"
	"strings"
	"crypto/rsa"
	"crypto/x509"
	"encoding/hex"
	"encoding/asn1"
	"encoding/pem"
	"encoding/json"
	"github.com/urfave/cli"
	"github.com/howeyc/gopass"
	//"github.com/ebfe/go.pcsclite/scard"
	"github.com/ianmcmahon/encoding_ssh"
	//reflect"
    //"github.com/vaughan0/go-ini"
)

var commonFlags = []cli.Flag {
	cli.BoolFlag {
		Name: "verbose, v",
		Usage: "詳細出力",
	},
}

func checkCard(c *cli.Context) error {
	reader := NewReader(c)
	defer reader.Finalize()
	reader.CheckCard()
	return nil
}

func showCert(c *cli.Context, efid string, pin []byte) error {
	reader := NewReader(c)
	if reader == nil {
		os.Exit(1)
	}
	defer reader.Finalize()
	card := reader.WaitForCard()
	status, _ := card.Status()
	fmt.Printf("Card Status: %s\n", status)
	aid := "D3 92 f0 00 26 01 00 00 00 01"
	apdu := "00 A4 04 0C" + " 0A " + aid
	reader.Tx(apdu)

	if pin != nil {
        //tx(card, "00 a4 02 0C 02 00 18") // VERIFY EF for AUTH
		reader.Tx("00 a4 02 0C 02 00 1B") // VERIFY EF for SIGN
		reader.Tx("00 20 00 80")
		apdu = "00 20 00 80 " + fmt.Sprintf("%02X % X", len(pin), pin)
		reader.Tx(apdu)
    }

	reader.Tx("00 A4 02 0C 02 " + efid)
	data := readBinary(reader, 4)
	if len(data) != 4 {
		fmt.Printf("エラー: Unkown\n")
		return errors.New("error")
	}
	data_size := uint16(data[2]) << 8 | uint16(data[3])
	data = readBinary(reader, 4 + data_size)

	/*
	fp, _ := os.Create("cert.der")
	defer fp.Close()
	fp.Write(data)
    */

	form := c.String("form")
	if form == "ssh" {
		cert, _ := x509.ParseCertificate(data)
		rsaPubkey := cert.PublicKey.(*rsa.PublicKey)
		sshPubkey, _ := ssh.EncodePublicKey(*rsaPubkey, "")
		fmt.Println(sshPubkey)
	}else{
		var block pem.Block
		block.Type = "CERTIFICATE"
		block.Bytes = data
		pem.Encode(os.Stdout, &block)
	}

	return nil
}

func showAuthCert(c *cli.Context) error {
	showCert(c, "00 0A", nil)
	return nil
}

func showAuthCACert(c *cli.Context) error {
	showCert(c, "00 0B", nil)
	return nil
}

func showSignCert(c *cli.Context) error {
	pin := c.String("pin")
	if len(pin) == 0 {
		fmt.Printf("署名用パスワード(6-16桁): ")
		input, _ := gopass.GetPasswdMasked()
		pin = string(input)
	}
	pass := []byte(strings.ToUpper(pin))

	if len(pin) < 6 || 16 < len(pin) {
		fmt.Printf("エラー: 署名用パスワード(6-16桁)を入力してください。\n")
		return nil
	}
	showCert(c, "00 01", pass)
	return nil
}

func showSignCACert(c *cli.Context) error {
	showCert(c, "00 02", nil)
	return nil
}

func ToHexString(b []byte) string {
	s := hex.EncodeToString(b)
	return s
}

func ToBytes(s string) []byte {
	b, _ := hex.DecodeString(strings.Replace(s, " ", "", -1))
	return b
}

func showMynumber(c *cli.Context) error {
	pin := []byte(c.String("pin"))
	if len(pin) == 0 {
		fmt.Printf("暗証番号(4桁): ")
		pin, _ = gopass.GetPasswd()
	}
	if len(pin) != 4 {
		fmt.Printf("エラー: 暗証番号(4桁)を入力してください。\n")
		return nil
	}
	reader := NewReader(c)
	if reader == nil {
		os.Exit(1)
	}
	defer reader.Finalize()
	card := reader.WaitForCard()
	if card == nil {
		os.Exit(1)
	}

	status, _ := card.Status()
	fmt.Printf("Card Status: %s\n", status)
	aid := "D3 92 10 00 31 00 01 01 04 08"
	apdu := "00 A4 04 0C" + " 0A " + aid
	reader.Tx(apdu)
	reader.Tx("00 a4 02 0C 02 00 11") // EF for VERIFY
	reader.Tx("00 20 00 80")
	apdu = "00 20 00 80 " + fmt.Sprintf("%02X % X", len(pin), pin)
	reader.Tx(apdu)
	reader.Tx("00 A4 02 0C 02 00 01")
	data := readBinary(reader, 16)
	var mynum asn1.RawValue
	asn1.Unmarshal(data, &mynum)

	reader.Tx("00 A4 02 0C 02 00 02")
	data = readBinary(reader, 5)
	if len(data) != 5 {
		fmt.Printf("エラー: Unkown\n")
		return errors.New("error")
	}
	data_size := uint16(data[3]) << 8 | uint16(data[4])
	data = readBinary(reader, 5 + data_size)
	var attr[5] asn1.RawValue
	pos := 5
	for i := 0; i < 5; i++ {
		asn1.Unmarshal(data[pos:], &attr[i])
		pos += len(attr[i].FullBytes)
	}
	if c.String("form") == "json" {
		j, _ := json.MarshalIndent(map[string]string{
			"mynumber": string(mynum.Bytes),
			"header": fmt.Sprintf("% X", attr[0].Bytes),
			"name": string(attr[1].Bytes),
			"addr": string(attr[2].Bytes),
			"birthday": string(attr[3].Bytes),
			"sex": string(attr[4].Bytes),
		}, "", "  ")
		fmt.Printf("%s", j)
	}else{
		fmt.Printf("個人番号: %s\n", mynum.Bytes)
		fmt.Printf("謎ヘッダ: % X\n", attr[0].Bytes)
		fmt.Printf("氏名:     %s\n", attr[1].Bytes)
		fmt.Printf("住所:     %s\n", attr[2].Bytes)
		fmt.Printf("生年月日: %s\n", attr[3].Bytes)
		fmt.Printf("性別:     %s\n", attr[4].Bytes)
	}
	return nil
}

func readBinary(reader *Reader, size uint16) []byte {
	var l uint8
	var apdu string
	var pos uint16
	pos = 0
	var res []byte

	for pos < size {
		if size - pos > 0xFF {
			l = 0xFF
		}else{
			l = uint8(size - pos)
		}
		apdu = fmt.Sprintf("00 B0 %02X %02X %02X",
			pos >> 8 & 0xFF, pos & 0xFF, l)
		sw1, sw2, data := reader.Tx(apdu)
		if sw1 != 0x90 || sw2 != 0x00 {
			return nil
		}
		res = append(res, data...)
		pos += uint16(len(data))
	}
	return res
}

func main() {
	cli.VersionFlag = cli.BoolFlag{
		Name: "version, V",
		Usage: "print version",
	}
	app := cli.NewApp()
	app.Name = "jinc"
	app.Usage = "個人番号カードユーティリティ"
	app.Author = "HAMANO Tsukasa"
	app.Email = "hamano@osstech.co.jp"
	app.Version = Version
	app.Commands = []cli.Command {
		{
			Name: "sign_cert",
			Usage: "署名用証明書を表示",
			Action: showSignCert,
			Flags: append(commonFlags, []cli.Flag {
				cli.StringFlag {
					Name: "pin",
					Usage: "署名用パスワード(6-16桁)",
				},
				cli.StringFlag {
					Name: "form",
					Usage: "出力形式(pem,ssh)",
				},
			}...),
		},
		{
			Name: "sign_ca_cert",
			Usage: "署名用CA証明書を表示",
			Action: showSignCACert,
		},
		{
			Name: "auth_cert",
			Usage: "利用者認証用証明書を表示",
			Action: showAuthCert,
			Flags: []cli.Flag {
				cli.StringFlag {
					Name: "form",
					Usage: "出力形式(pem,ssh)",
				},
			},
		},
		{
			Name: "auth_ca_cert",
			Usage: "利用者認証用CA証明書を表示",
			Action: showAuthCACert,
		},
		{
			Name: "mynumber",
			Usage: "券面事項入力補助AP",
			Action: showMynumber,
			Before: checkCard,
			Flags: append(commonFlags, []cli.Flag {
				cli.StringFlag {
					Name: "pin",
					Usage: "暗証番号(4桁)",
				},
				cli.StringFlag {
					Name: "form",
					Usage: "出力形式(txt,json)",
				},
			}...),
		},
		{
			Name: "tool",
			Usage: "種々様々なツール",
			Subcommands: toolCommands,
		},
	}
	app.Run(os.Args)
}
<commit_msg>add auth_change_pin subcommand<commit_after>package main

import (
	"os"
	"fmt"
	"errors"
	"strings"
	"crypto/rsa"
	"crypto/x509"
	"encoding/hex"
	"encoding/asn1"
	"encoding/pem"
	"encoding/json"
	"github.com/urfave/cli"
	"github.com/howeyc/gopass"
	//"github.com/ebfe/go.pcsclite/scard"
	"github.com/ianmcmahon/encoding_ssh"
	//reflect"
    //"github.com/vaughan0/go-ini"
)

var commonFlags = []cli.Flag {
	cli.BoolFlag {
		Name: "verbose, v",
		Usage: "詳細出力",
	},
}

func checkCard(c *cli.Context) error {
	reader := NewReader(c)
	defer reader.Finalize()
	reader.CheckCard()
	return nil
}

func showCert(c *cli.Context, efid string, pin []byte) error {
	reader := NewReader(c)
	if reader == nil {
		os.Exit(1)
	}
	defer reader.Finalize()
	card := reader.WaitForCard()
	status, _ := card.Status()
	fmt.Printf("Card Status: %s\n", status)
	aid := "D3 92 f0 00 26 01 00 00 00 01"
	apdu := "00 A4 04 0C" + " 0A " + aid
	reader.Tx(apdu)

	if pin != nil {
        //tx(card, "00 a4 02 0C 02 00 18") // VERIFY EF for AUTH
		reader.Tx("00 a4 02 0C 02 00 1B") // VERIFY EF for SIGN
		reader.Tx("00 20 00 80")
		apdu = "00 20 00 80 " + fmt.Sprintf("%02X % X", len(pin), pin)
		reader.Tx(apdu)
    }

	reader.Tx("00 A4 02 0C 02 " + efid)
	data := readBinary(reader, 4)
	if len(data) != 4 {
		fmt.Printf("エラー: Unkown\n")
		return errors.New("error")
	}
	data_size := uint16(data[2]) << 8 | uint16(data[3])
	data = readBinary(reader, 4 + data_size)

	/*
	fp, _ := os.Create("cert.der")
	defer fp.Close()
	fp.Write(data)
    */

	form := c.String("form")
	if form == "ssh" {
		cert, _ := x509.ParseCertificate(data)
		rsaPubkey := cert.PublicKey.(*rsa.PublicKey)
		sshPubkey, _ := ssh.EncodePublicKey(*rsaPubkey, "")
		fmt.Println(sshPubkey)
	}else{
		var block pem.Block
		block.Type = "CERTIFICATE"
		block.Bytes = data
		pem.Encode(os.Stdout, &block)
	}

	return nil
}

func showAuthCert(c *cli.Context) error {
	showCert(c, "00 0A", nil)
	return nil
}

func showAuthCACert(c *cli.Context) error {
	showCert(c, "00 0B", nil)
	return nil
}

func changeAuthPIN(c *cli.Context) error {
	pin := c.String("pin")
	if len(pin) == 0 {
		fmt.Printf("認証用PIN(4桁): ")
		input, _ := gopass.GetPasswdMasked()
		pin = string(input)
	}
	bpin := []byte(strings.ToUpper(pin))

	newpin := c.String("newpin")
	if len(newpin) == 0 {
		fmt.Printf("新しい認証用PIN(4桁): ")
		input, _ := gopass.GetPasswdMasked()
		newpin = string(input)
	}

	newbpin := []byte(strings.ToUpper(newpin))

	/*
	fmt.Printf("pin: % V\n", bpin)
	fmt.Printf("newpin: % V\n", newbpin)
*/
	return nil
}

func showSignCert(c *cli.Context) error {
	pin := c.String("pin")
	if len(pin) == 0 {
		fmt.Printf("署名用パスワード(6-16桁): ")
		input, _ := gopass.GetPasswdMasked()
		pin = string(input)
	}
	pass := []byte(strings.ToUpper(pin))

	if len(pin) < 6 || 16 < len(pin) {
		fmt.Printf("エラー: 署名用パスワード(6-16桁)を入力してください。\n")
		return nil
	}
	showCert(c, "00 01", pass)
	return nil
}

func showSignCACert(c *cli.Context) error {
	showCert(c, "00 02", nil)
	return nil
}

func ToHexString(b []byte) string {
	s := hex.EncodeToString(b)
	return s
}

func ToBytes(s string) []byte {
	b, _ := hex.DecodeString(strings.Replace(s, " ", "", -1))
	return b
}

func showMynumber(c *cli.Context) error {
	pin := []byte(c.String("pin"))
	if len(pin) == 0 {
		fmt.Printf("暗証番号(4桁): ")
		pin, _ = gopass.GetPasswd()
	}
	if len(pin) != 4 {
		fmt.Printf("エラー: 暗証番号(4桁)を入力してください。\n")
		return nil
	}
	reader := NewReader(c)
	if reader == nil {
		os.Exit(1)
	}
	defer reader.Finalize()
	card := reader.WaitForCard()
	if card == nil {
		os.Exit(1)
	}

	status, _ := card.Status()
	fmt.Printf("Card Status: %s\n", status)
	aid := "D3 92 10 00 31 00 01 01 04 08"
	apdu := "00 A4 04 0C" + " 0A " + aid
	reader.Tx(apdu)
	reader.Tx("00 a4 02 0C 02 00 11") // EF for VERIFY
	reader.Tx("00 20 00 80")
	apdu = "00 20 00 80 " + fmt.Sprintf("%02X % X", len(pin), pin)
	reader.Tx(apdu)
	reader.Tx("00 A4 02 0C 02 00 01")
	data := readBinary(reader, 16)
	var mynum asn1.RawValue
	asn1.Unmarshal(data, &mynum)

	reader.Tx("00 A4 02 0C 02 00 02")
	data = readBinary(reader, 5)
	if len(data) != 5 {
		fmt.Printf("エラー: Unkown\n")
		return errors.New("error")
	}
	data_size := uint16(data[3]) << 8 | uint16(data[4])
	data = readBinary(reader, 5 + data_size)
	var attr[5] asn1.RawValue
	pos := 5
	for i := 0; i < 5; i++ {
		asn1.Unmarshal(data[pos:], &attr[i])
		pos += len(attr[i].FullBytes)
	}
	if c.String("form") == "json" {
		j, _ := json.MarshalIndent(map[string]string{
			"mynumber": string(mynum.Bytes),
			"header": fmt.Sprintf("% X", attr[0].Bytes),
			"name": string(attr[1].Bytes),
			"addr": string(attr[2].Bytes),
			"birthday": string(attr[3].Bytes),
			"sex": string(attr[4].Bytes),
		}, "", "  ")
		fmt.Printf("%s", j)
	}else{
		fmt.Printf("個人番号: %s\n", mynum.Bytes)
		fmt.Printf("謎ヘッダ: % X\n", attr[0].Bytes)
		fmt.Printf("氏名:     %s\n", attr[1].Bytes)
		fmt.Printf("住所:     %s\n", attr[2].Bytes)
		fmt.Printf("生年月日: %s\n", attr[3].Bytes)
		fmt.Printf("性別:     %s\n", attr[4].Bytes)
	}
	return nil
}

func readBinary(reader *Reader, size uint16) []byte {
	var l uint8
	var apdu string
	var pos uint16
	pos = 0
	var res []byte

	for pos < size {
		if size - pos > 0xFF {
			l = 0xFF
		}else{
			l = uint8(size - pos)
		}
		apdu = fmt.Sprintf("00 B0 %02X %02X %02X",
			pos >> 8 & 0xFF, pos & 0xFF, l)
		sw1, sw2, data := reader.Tx(apdu)
		if sw1 != 0x90 || sw2 != 0x00 {
			return nil
		}
		res = append(res, data...)
		pos += uint16(len(data))
	}
	return res
}

func main() {
	cli.VersionFlag = cli.BoolFlag{
		Name: "version, V",
		Usage: "print version",
	}
	app := cli.NewApp()
	app.Name = "jinc"
	app.Usage = "個人番号カードユーティリティ"
	app.Author = "HAMANO Tsukasa"
	app.Email = "hamano@osstech.co.jp"
	app.Version = Version
	app.Commands = []cli.Command {
		{
			Name: "sign_cert",
			Usage: "署名用証明書を表示",
			Action: showSignCert,
			Flags: append(commonFlags, []cli.Flag {
				cli.StringFlag {
					Name: "pin",
					Usage: "署名用パスワード(6-16桁)",
				},
				cli.StringFlag {
					Name: "form",
					Usage: "出力形式(pem,ssh)",
				},
			}...),
		},
		{
			Name: "sign_ca_cert",
			Usage: "署名用CA証明書を表示",
			Action: showSignCACert,
		},
		{
			Name: "auth_cert",
			Usage: "利用者認証用証明書を表示",
			Action: showAuthCert,
			Flags: []cli.Flag {
				cli.StringFlag {
					Name: "form",
					Usage: "出力形式(pem,ssh)",
				},
			},
		},
		{
			Name: "auth_ca_cert",
			Usage: "利用者認証用CA証明書を表示",
			Action: showAuthCACert,
		},
		{
			Name: "auth_change_pin",
			Usage: "利用者認証用PINを変更",
			Flags: append(commonFlags, []cli.Flag {
				cli.StringFlag {
					Name: "pin",
					Usage: "暗証番号(4桁)",
				},
				cli.StringFlag {
					Name: "newpin",
					Usage: "新しい暗証番号(4桁)",
				},
			}...),
			Action: changeAuthPIN,
		},
		{
			Name: "mynumber",
			Usage: "券面事項入力補助AP",
			Action: showMynumber,
			Before: checkCard,
			Flags: append(commonFlags, []cli.Flag {
				cli.StringFlag {
					Name: "pin",
					Usage: "暗証番号(4桁)",
				},
				cli.StringFlag {
					Name: "form",
					Usage: "出力形式(txt,json)",
				},
			}...),
		},
		{
			Name: "tool",
			Usage: "種々様々なツール",
			Subcommands: toolCommands,
		},
	}
	app.Run(os.Args)
}
<|endoftext|>


<commit_before>package jirachat

import (
	"encoding/json"
	"io/ioutil"
	"net/http"
)

// This is a json response for a JIRA webhook (more or less) according to
// https://developer.atlassian.com/jiradev/jira-architecture/webhooks
type JIRAWebevent struct {
	// Internal ID of the event
	Id int `json:"id,omitempty"`

	// Unix timestamp
	Timestamp int `json:"timestamp,omitempty"`

	// Object describing issue event relates to
	Issue JIRAIssue `json:"issue"`

	// User who triggered event
	User JIRAUser `json:"user"`

	// An array of change items, with one entry for each field that has
	// been changed. The changelog is only provided for the issue_updated event.
	Changelog JIRAChangelog `json:"changelog"`

	// Set if this event is a jira_updated event and a comment was made
	Comment JIRAComment `json:"comment"`

	// The type of event
	WebhookEvent string `json:"webhookEvent"`
}

// Decribes the JIRAIssue object defined in the JIRA 5.1 REST docs
//
// https://docs.atlassian.com/jira/REST/5.1/#id204637
type JIRAIssue struct {
	Expand string `json:"expand"`

	// Internal ID of the issue
	Id string `json:"id"`

	Self string `json:"self"`

	// This is the common issue key, e.g. JIRA-100
	Key string `json:"key"`

	// There are quite a few fields and that ones provided in this library
	// are nowhere near exhaustive.
	Fields IssueFieldData `json:"fields"`
}

// Describes the JIRAUser object defined in the JIRA 5.1 REST docs
//
// https://docs.atlassian.com/jira/REST/5.1/#id202197
type JIRAUser struct {
	Self string `json:"self"`

	// The user's system name, e.g. mmcfly
	Name         string            `json:"name"`
	EmailAddress string            `json:"emailAddress"`
	AvatarUrls   map[string]string `json:"avatarUrls"`

	// Pretty name E.g. Mart McFly
	DisplayName string `json:"displayName"`
	Active      bool   `json:"active"`
}

// Some of the ChangeLogItems may through unmarshal errors but they don't seem
// to cause any major issues
type JIRAChangelog struct {
	Items []ChangleLogItems `json:"items,omitempty"`
	Id    string            `json:"id,omitempty"`
}

type ChangleLogItems struct {
	ToString   string `json:"toString"`
	To         string `json:"to"`
	FromString string `json:"fromString"`
	From       string `json:"from"`
	FieldType  string `json:"fieldtype"`
	Field      string `json:"field"`
}

// Describes the JIRAComment object defined in the JIRA 5.1 REST docs
//
// https://docs.atlassian.com/jira/REST/5.1/#id204337
type JIRAComment struct {
	Self         string   `json:"self"`
	Id           string   `json:"id"`
	Author       JIRAUser `json:"author"`
	Body         string   `json:"body"`
	UpdateAuthor JIRAUser `json:"updateAuthor"`
	Created      string   `json:"created"`
	Updated      string   `json:"updated"`
}

type IssueFieldData struct {
	Summary     string            `json:"summary"`
	Created     string            `json:"created"`
	Description string            `json:"description"`
	Priority    JIRAIssuePriority `json:"priority"`
	Assignee    JIRAIssueAssignee `json:"assignee"`
	Labels      []string          `json:"labels"`
	Status      JIRAIssueStatus   `json:"status"`
	Comment     InnerComment      `json:"comment"`
	IssueType   JIRAIssueType     `json:"issuetype"`
}

type JIRAIssueAssignee struct {
	Self        string            `json:"self"`
	Name        string            `json:"name"`
	Key         string            `json:"key"`
	Email       string            `json:"emailAddress"`
	AvatarUrls  map[string]string `json:"avatarUrls"`
	DisplayName string            `json:"displayName"`
	Active      bool              `json:"active"`
	Timezone    string            `json:"timeZone"`
}

type JIRAIssuePriority struct {
	Name string `json:"name"`
	Id   string `json:"id"`
}

type JIRAIssueStatus struct {
	Name string `json:"name"`
}

type JIRAIssueType struct {
	Self        string `json:"self"`
	Id          string `json:"id"`
	Description string `json:"description"`
	IconUrl     string `json:"iconUrl"`
	Name        string `json:"name"`
	Subtask     bool   `json:"subtask"`
}

type InnerComment struct {
	StartAt    int           `json:"startAt"`
	MaxResults int           `json:"maxResults"`
	Total      int           `json:"total"`
	Comments   []JIRAComment `json:"comments"`
}

// Returns the 16x16 user Avatar
// Note: If this is not a Gravatar, it will not render
// in Slack message because uploaded images are private to your JIRA instance.
func (j *JIRAUser) SmallAvatar() string {
	return j.AvatarUrls["16x16"]
}

// Returns the 48x48 user Avatar
// Note: If this is not a Gravatar, it will not render
// in Slack message because uploaded images are private to your JIRA instance.
func (j *JIRAUser) LargeAvatar() string {
	return j.AvatarUrls["48x48"]
}

//TODO add JIRAIssue.Fields.labels function(s)

// Parse the request body as a JIRA webhook event
// Returns a new JiraWebEvent object or error
func Parse(r *http.Request) (JIRAWebevent, error) {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		panic(err)
	}

	var event JIRAWebevent

	// This will generate a error unmarshaling some of the data but
	// is is safe to ignore. We return the error so you at least know
	// that there is some oddly formed data.
	err = json.Unmarshal(body, &event)
	return event, err
}

// Convenience interface for printing anonymous JSON objects
type Response map[string]interface{}

func (r Response) String() (s string) {
	b, err := json.Marshal(r)
	if err != nil {
		s = ""
		return
	}
	s = string(b)
	return
}
<commit_msg>Add support for custom fields<commit_after>package jirachat

import (
	"encoding/json"
	"github.com/buger/jsonparser"
	"io/ioutil"
	"net/http"
	"strings"
)

// This is a json response for a JIRA webhook (more or less) according to
// https://developer.atlassian.com/jiradev/jira-architecture/webhooks
type JIRAWebevent struct {
	// Internal ID of the event
	Id int `json:"id,omitempty"`

	// Unix timestamp
	Timestamp int `json:"timestamp,omitempty"`

	// Object describing issue event relates to
	Issue JIRAIssue `json:"issue"`

	// User who triggered event
	User JIRAUser `json:"user"`

	// An array of change items, with one entry for each field that has
	// been changed. The changelog is only provided for the issue_updated event.
	Changelog JIRAChangelog `json:"changelog"`

	// Set if this event is a jira_updated event and a comment was made
	Comment JIRAComment `json:"comment"`

	// The type of event
	WebhookEvent string `json:"webhookEvent"`
}

// Decribes the JIRAIssue object defined in the JIRA 5.1 REST docs
//
// https://docs.atlassian.com/jira/REST/5.1/#id204637
type JIRAIssue struct {
	Expand string `json:"expand"`

	// Internal ID of the issue
	Id string `json:"id"`

	Self string `json:"self"`

	// This is the common issue key, e.g. JIRA-100
	Key string `json:"key"`

	// There are quite a few fields and that ones provided in this library
	// are nowhere near exhaustive.
	Fields IssueFieldData `json:"fields"`
}

// Describes the JIRAUser object defined in the JIRA 5.1 REST docs
//
// https://docs.atlassian.com/jira/REST/5.1/#id202197
type JIRAUser struct {
	Self string `json:"self"`

	// The user's system name, e.g. mmcfly
	Name         string            `json:"name"`
	EmailAddress string            `json:"emailAddress"`
	AvatarUrls   map[string]string `json:"avatarUrls"`

	// Pretty name E.g. Mart McFly
	DisplayName string `json:"displayName"`
	Active      bool   `json:"active"`
}

// Some of the ChangeLogItems may through unmarshal errors but they don't seem
// to cause any major issues
type JIRAChangelog struct {
	Items []ChangleLogItems `json:"items,omitempty"`
	Id    string            `json:"id,omitempty"`
}

type ChangleLogItems struct {
	ToString   string `json:"toString"`
	To         string `json:"to"`
	FromString string `json:"fromString"`
	From       string `json:"from"`
	FieldType  string `json:"fieldtype"`
	Field      string `json:"field"`
}

// Describes the JIRAComment object defined in the JIRA 5.1 REST docs
//
// https://docs.atlassian.com/jira/REST/5.1/#id204337
type JIRAComment struct {
	Self         string   `json:"self"`
	Id           string   `json:"id"`
	Author       JIRAUser `json:"author"`
	Body         string   `json:"body"`
	UpdateAuthor JIRAUser `json:"updateAuthor"`
	Created      string   `json:"created"`
	Updated      string   `json:"updated"`
}

type IssueFieldData struct {
	Summary     string            `json:"summary"`
	Created     string            `json:"created"`
	Description string            `json:"description"`
	Priority    JIRAIssuePriority `json:"priority"`
	Assignee    JIRAIssueAssignee `json:"assignee"`
	Labels      []string          `json:"labels"`
	Status      JIRAIssueStatus   `json:"status"`
	Comment     InnerComment      `json:"comment"`
	IssueType   JIRAIssueType     `json:"issuetype"`
	Project     JIRAProject       `json:"project"`
	// CustomFields is a map of customfield_xxx from your JIRA instance. The key will match whichever
	// custom fields you have created. The contents obviously depend on what you have created. The value
	// the raw string value of whatever your field contains.
	CustomFields map[string]string
}

type JIRAIssueAssignee struct {
	Self        string            `json:"self"`
	Name        string            `json:"name"`
	Key         string            `json:"key"`
	Email       string            `json:"emailAddress"`
	AvatarUrls  map[string]string `json:"avatarUrls"`
	DisplayName string            `json:"displayName"`
	Active      bool              `json:"active"`
	Timezone    string            `json:"timeZone"`
}

type JIRAIssuePriority struct {
	Name string `json:"name"`
	Id   string `json:"id"`
}

type JIRAIssueStatus struct {
	Name string `json:"name"`
}

type JIRAIssueType struct {
	Self        string `json:"self"`
	Id          string `json:"id"`
	Description string `json:"description"`
	IconUrl     string `json:"iconUrl"`
	Name        string `json:"name"`
	Subtask     bool   `json:"subtask"`
}

type InnerComment struct {
	StartAt    int           `json:"startAt"`
	MaxResults int           `json:"maxResults"`
	Total      int           `json:"total"`
	Comments   []JIRAComment `json:"comments"`
}

type JIRAProject struct {
	Self       string            `json:"self"`
	Id         string            `json:"id"`
	Key        string            `json:"key"`
	Name       string            `json:"name"`
	AvatarUrls map[string]string `json:"avatarUrls"`
}

// Returns the 16x16 user Avatar
// Note: If this is not a Gravatar, it will not render
// in Slack message because uploaded images are private to your JIRA instance.
func (j *JIRAUser) SmallAvatar() string {
	return j.AvatarUrls["16x16"]
}

// Returns the 48x48 user Avatar
// Note: If this is not a Gravatar, it will not render
// in Slack message because uploaded images are private to your JIRA instance.
func (j *JIRAUser) LargeAvatar() string {
	return j.AvatarUrls["48x48"]
}

//TODO add JIRAIssue.Fields.labels function(s)

// Parse the request body as a JIRA webhook event
// Returns a new JiraWebEvent object or error
func Parse(r *http.Request) (JIRAWebevent, error) {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		panic(err)
	}

	var event JIRAWebevent

	// This will generate a error unmarshaling some of the data but
	// is is safe to ignore. We return the error so you at least know
	// that there is some oddly formed data.
	err = json.Unmarshal(body, &event)

	event.Issue.Fields.CustomFields = make(map[string]string, 0)

	if len(event.Issue.Id) != 0 {
		jsonparser.ObjectEach(body, func(key []byte, value []byte, dataType jsonparser.ValueType, offset int) error {
			k := string(key)
			if strings.HasPrefix(k, "customfield_") {
				event.Issue.Fields.CustomFields[string(key)] = string(value)
			}
			return nil
		}, "issue", "fields")
	}

	return event, err
}

// Convenience interface for printing anonymous JSON objects
type Response map[string]interface{}

func (r Response) String() (s string) {
	b, err := json.Marshal(r)
	if err != nil {
		s = ""
		return
	}
	s = string(b)
	return
}
<|endoftext|>


<commit_before>package sprig

import (
	"bytes"
	"encoding/json"
	"reflect"
	"strings"
)

// dfault checks whether `given` is set, and returns default if not set.
//
// This returns `d` if `given` appears not to be set, and `given` otherwise.
//
// For numeric types 0 is unset.
// For strings, maps, arrays, and slices, len() = 0 is considered unset.
// For bool, false is unset.
// Structs are never considered unset.
//
// For everything else, including pointers, a nil value is unset.
func dfault(d interface{}, given ...interface{}) interface{} {

	if empty(given) || empty(given[0]) {
		return d
	}
	return given[0]
}

// empty returns true if the given value has the zero value for its type.
func empty(given interface{}) bool {
	g := reflect.ValueOf(given)
	if !g.IsValid() {
		return true
	}

	// Basically adapted from text/template.isTrue
	switch g.Kind() {
	default:
		return g.IsNil()
	case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
		return g.Len() == 0
	case reflect.Bool:
		return !g.Bool()
	case reflect.Complex64, reflect.Complex128:
		return g.Complex() == 0
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return g.Int() == 0
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		return g.Uint() == 0
	case reflect.Float32, reflect.Float64:
		return g.Float() == 0
	case reflect.Struct:
		return false
	}
}

// coalesce returns the first non-empty value.
func coalesce(v ...interface{}) interface{} {
	for _, val := range v {
		if !empty(val) {
			return val
		}
	}
	return nil
}

// toJson encodes an item into a JSON string
func toJson(v interface{}) string {
	output, _ := json.Marshal(v)
	return string(output)
}

func mustToJson(v interface{}) (string, error) {
	output, err := json.Marshal(v)
	if err != nil {
		return "", err
	}
	return string(output), nil
}

// toPrettyJson encodes an item into a pretty (indented) JSON string
func toPrettyJson(v interface{}) string {
	output, _ := json.MarshalIndent(v, "", "  ")
	return string(output)
}

func mustToPrettyJson(v interface{}) (string, error) {
	output, err := json.MarshalIndent(v, "", "  ")
	if err != nil {
		return "", err
	}
	return string(output), nil
}

// toRawJson encodes an item into a JSON string with no escaping of HTML characters.
func toRawJson(v interface{}) string {
	output, err := mustToRawJson(v)
	if err != nil {
		panic(err)
	}
	return string(output)
}

// mustToRawJson encodes an item into a JSON string with no escaping of HTML characters.
func mustToRawJson(v interface{}) (string, error) {
	buf := new(bytes.Buffer)
	enc := json.NewEncoder(buf)
	enc.SetEscapeHTML(false)
	err := enc.Encode(&v)
	if err != nil {
		return "", err
	}
	return strings.TrimSuffix(buf.String(), "\n"), nil
}

// ternary returns the first value if the last value is true, otherwise returns the second value.
func ternary(vt interface{}, vf interface{}, v bool) interface{} {
	if v {
		return vt
	}

	return vf
}
<commit_msg>rand.Seed was added<commit_after>package sprig

import (
	"bytes"
	"encoding/json"
	"math/rand"
	"reflect"
	"strings"
	"time"
)

func init() {
	rand.Seed(time.Now().UnixNano())
}

// dfault checks whether `given` is set, and returns default if not set.
//
// This returns `d` if `given` appears not to be set, and `given` otherwise.
//
// For numeric types 0 is unset.
// For strings, maps, arrays, and slices, len() = 0 is considered unset.
// For bool, false is unset.
// Structs are never considered unset.
//
// For everything else, including pointers, a nil value is unset.
func dfault(d interface{}, given ...interface{}) interface{} {

	if empty(given) || empty(given[0]) {
		return d
	}
	return given[0]
}

// empty returns true if the given value has the zero value for its type.
func empty(given interface{}) bool {
	g := reflect.ValueOf(given)
	if !g.IsValid() {
		return true
	}

	// Basically adapted from text/template.isTrue
	switch g.Kind() {
	default:
		return g.IsNil()
	case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
		return g.Len() == 0
	case reflect.Bool:
		return !g.Bool()
	case reflect.Complex64, reflect.Complex128:
		return g.Complex() == 0
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return g.Int() == 0
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		return g.Uint() == 0
	case reflect.Float32, reflect.Float64:
		return g.Float() == 0
	case reflect.Struct:
		return false
	}
}

// coalesce returns the first non-empty value.
func coalesce(v ...interface{}) interface{} {
	for _, val := range v {
		if !empty(val) {
			return val
		}
	}
	return nil
}

// toJson encodes an item into a JSON string
func toJson(v interface{}) string {
	output, _ := json.Marshal(v)
	return string(output)
}

func mustToJson(v interface{}) (string, error) {
	output, err := json.Marshal(v)
	if err != nil {
		return "", err
	}
	return string(output), nil
}

// toPrettyJson encodes an item into a pretty (indented) JSON string
func toPrettyJson(v interface{}) string {
	output, _ := json.MarshalIndent(v, "", "  ")
	return string(output)
}

func mustToPrettyJson(v interface{}) (string, error) {
	output, err := json.MarshalIndent(v, "", "  ")
	if err != nil {
		return "", err
	}
	return string(output), nil
}

// toRawJson encodes an item into a JSON string with no escaping of HTML characters.
func toRawJson(v interface{}) string {
	output, err := mustToRawJson(v)
	if err != nil {
		panic(err)
	}
	return string(output)
}

// mustToRawJson encodes an item into a JSON string with no escaping of HTML characters.
func mustToRawJson(v interface{}) (string, error) {
	buf := new(bytes.Buffer)
	enc := json.NewEncoder(buf)
	enc.SetEscapeHTML(false)
	err := enc.Encode(&v)
	if err != nil {
		return "", err
	}
	return strings.TrimSuffix(buf.String(), "\n"), nil
}

// ternary returns the first value if the last value is true, otherwise returns the second value.
func ternary(vt interface{}, vf interface{}, v bool) interface{} {
	if v {
		return vt
	}

	return vf
}
<|endoftext|>


<commit_before>/*  lMDQ is a MDQ server that caches metadata locally so it's local clients can lookup
    pre-checked metadata and not depend on a working connection to a remote MDQ server.

    It uses SQLite as it's datastore and allows lookup by either entityID or Location.
    The latter is used by WAYF for it's mass hosting services BIRK and KRIB.

    It can also be used as a library for just looking up metadata inside a go program.

    Or a client can use the SQLite database directly using the following query:

		"select e.md, e.hash from entity e, lookup l where l.hash = $1 and l.entity_id_fk = e.id and e.validuntil >= $2"

    where $1 is the lowercase hex sha1 of the entityID or location without the {sha1} prefix
    $2 is the current epoch.

    to-do:
        √ caching interface
          invalidate cache ???
*/

package lMDQ

import (
	"crypto"
	"crypto/tls"
	"database/sql"
	"encoding/hex"
	"errors"
	"fmt"
    "github.com/aryann/difflib"
	_ "github.com/mattn/go-sqlite3"
	"github.com/wayf-dk/gosaml"
	"io/ioutil"
	"log"
	"net/http"
	//"runtime/debug"
	"strings"
	"sync"
	"time"
)

const (
	// pragma wal ???
	lMDQSchema = `
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS entity
(
    id INTEGER PRIMARY KEY,
    entityid text not null unique,
    md text NOT NULL,
    hash text NOT NULL
);

CREATE TABLE IF NOT EXISTS lookup
(
    id INTEGER PRIMARY KEY,
    hash text NOT NULL,
    entity_id_fk INTEGER,
    unique(hash, entity_id_fk),
    FOREIGN KEY(entity_id_fk) REFERENCES entity(id) on delete cascade
);

CREATE TABLE IF NOT EXISTS validuntil
(
    id integer primary key default 1,
    validuntil integer not null default 0
);

CREATE INDEX if not exists lookupbyhash ON lookup(hash);
insert or ignore into validuntil (id) values (1);
`
)

type (
	EntityRec struct {
		id       int64
		entityid string
		hash     string
	}

	MDQ struct {
		db              *sql.DB
		stmt            *sql.Stmt
		Url, Hash, Path, MetadataSchemaPath string
		Cache           map[string]*MdXp
	    Lock            sync.Mutex
	    Silent          bool
	}

	MdXp struct {
		*gosaml.Xp
		created time.Time
	}
)

var (
	cacheduration = time.Minute * 1

	indextargets = []string{
		"./md:IDPSSODescriptor/md:SingleSignOnService[@Binding='urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect']/@Location",
		"./md:SPSSODescriptor/md:AssertionConsumerService[@Binding='urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST']/@Location",
	}
)

func init() {

}

func Cmp(feed1, feed2 MDQ) {
    ents1, _ := feed1.getEntityList()
    ents2, _ := feed2.getEntityList()
//    len1 := len(ents1)
    len2 := len(ents2)
    seen := 0
    for id, _ := range ents1 {
        x1, _ := feed1.MDQ(id)
        x2, _ := feed2.MDQ(id)
        str1 := x1.Pp()
        str2 := x2.Pp()
        hash1 := hex.EncodeToString(gosaml.Hash(crypto.SHA1, str1))
        hash2 := hex.EncodeToString(gosaml.Hash(crypto.SHA1, str2))
        if hash1 == hash2 {
            log.Println("OK ", id)
        } else {
            log.Println("NOT", id)
            diffs := difflib.Diff(strings.Split(str1, "\n"), strings.Split(str2, "\n"))
            for _, diff := range diffs {
                if diff.Delta != difflib.Common {
                    log.Println(diff)
                }
            }
            fmt.Println()
        }
        seen++
    }
    log.Println("Number of entityies: ", seen, len2)
}

func (xp *MdXp) Valid(duration time.Duration) bool {
	since := time.Since(xp.created)
	//log.Println(since, duration, since  < duration)
	return since < duration
}

func Open(path string) (mdq *MDQ, err error) {
    mdq = new(MDQ)
    return mdq, mdq.Open(path)
}

func (mdq *MDQ) Open(path string) (err error) {
    mdq.Cache = make(map[string]*MdXp)
	mdq.Path = path
	mdq.db, err = sql.Open("sqlite3", path)
	if err != nil {
		return
	}

	return
}

// MDQ looks up an entity using the supplied feed and key.
// The key can be an entityID or a location, optionally in {sha1} format
// It returns a non nil err if the entity is not found
// and the metadata and a hash/etag over the content if it is.
// The hash can be used to decide if a cached dom object is still valid,
// This might be an optimization as the database lookup is much faster that the parsing.
func (mdq *MDQ) MDQ(key string) (xp *gosaml.Xp, err error) {
    return mdq.dbget(key, true)
}

func (mdq *MDQ) dbget(key string, cache bool) (xp *gosaml.Xp, err error) {
    if mdq.stmt == nil {
        mdq.stmt, err = mdq.db.Prepare(`select e.md from entity e, lookup l, validuntil v
        where l.hash = $1 and l.entity_id_fk = e.id and v.validuntil >= $2`)
        if err != nil {
            return
        }
    }

	k := key
	const prefix = "{sha1}"
	if strings.HasPrefix(key, prefix) {
		key = key[6:]
	} else {
		key = hex.EncodeToString(gosaml.Hash(crypto.SHA1, key))
	}

	mdq.Lock.Lock()
	defer mdq.Lock.Unlock()
	cachedxp := mdq.Cache[key]
	if cachedxp != nil && cachedxp.Valid(cacheduration) {
		xp = cachedxp.Xp.CpXp()
		return
	}

	var xml []byte
	err = mdq.stmt.QueryRow(key, time.Now().Unix()).Scan(&xml)
	if err != nil {
		//log.Println("query", mdq.path, k, key, err, string(xml))
		err = fmt.Errorf("Metadata not found for entity: %s", k)
		//debug.PrintStack()
		return
	}
	xp = gosaml.NewXp(xml)
    if cache {
	    mdxp := new(MdXp)
	    mdxp.Xp = xp
	    mdxp.created = time.Now()
	    mdq.Cache[key] = mdxp
	}
	return
}

/**
    One at a time - not that fast - only use for testing
    Filtered by xpath for testing purposes
*/
func (mdq *MDQ) MDQFilter(xpathfilter string) (xp *gosaml.Xp, err error) {
	recs, err := mdq.getEntityList()
	if err != nil {
		return
	}
	log.Println(xpathfilter)
	xp = gosaml.NewXp([]byte(`<md:EntitiesDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" />`))

	for entityID, _ := range recs {
	    ent, _ := mdq.dbget(entityID, false)

	    if xpathfilter == "" || len(ent.Query(nil, xpathfilter)) > 0 {
            xp.DocGetRootElement().AddChild(xp.CopyNode(ent.DocGetRootElement(), 1))
        }
	}
	return
}

func (mdq *MDQ) Update() (err error) {
	start := time.Now()
	log.Println("lMDQ updating", mdq.Url)

	_, err = mdq.db.Exec(lMDQSchema)
	if err != nil {
		return
	}

	recs, err := mdq.getEntityList()
	if err != nil {
		return err
	}
	var md []byte
	if md, err = get(mdq.Url); err != nil {
		return
	}

	dom := gosaml.NewXp(md)

	if _, err := dom.SchemaValidate(mdq.MetadataSchemaPath); err != nil {
		log.Println("feed", "SchemaError")
	}

	certificate := dom.Query(nil, "/md:EntitiesDescriptor/ds:Signature/ds:KeyInfo/ds:X509Data/ds:X509Certificate")
	if len(certificate) != 1 {
		err = errors.New("Metadata not signed")
		return
	}
	keyname, key, err := gosaml.PublicKeyInfo(dom.NodeGetContent(certificate[0]))

	if err != nil {
		return
	}

	ok := dom.VerifySignature(nil, key)
	if ok != nil || keyname != mdq.Hash {
		return fmt.Errorf("Signature check failed. Signature %s, %s = %s", ok, keyname, mdq.Hash)
	}

	tx, err := mdq.db.Begin()
	if err != nil {
		return
	}
	defer func() {
		if err != nil {
			tx.Rollback()
			return
		}
		err = tx.Commit()
	}()

	entityInsertStmt, err := tx.Prepare("insert into entity (entityid, md, hash) values ($1, $2, $3)")
	if err != nil {
		return
	}
	defer entityInsertStmt.Close()

	lookupInsertStmt, err := tx.Prepare("insert or ignore into lookup (hash, entity_id_fk) values (?, ?)")
	if err != nil {
		return err
	}
	defer lookupInsertStmt.Close()

	entityDeleteStmt, err := tx.Prepare("delete from entity where id = $1")
	if err != nil {
		return err
	}
	defer entityDeleteStmt.Close()

	vu, err := time.Parse(time.RFC3339Nano, dom.Query1(nil, "@validUntil"))
	if err != nil {
		return err
	}
	validUntil := vu.Unix()

	var new, updated, nochange, deleted int
	seen := map[string]bool{}

	entities := dom.Query(nil, "./md:EntityDescriptor")
	for _, entity := range entities {
		entityID := dom.Query1(entity, "@entityID")
		if seen[entityID] {
			log.Printf("lMDQ duplicate entityID: %s", entityID)
			continue
		}
        seen[entityID] = true
		md := gosaml.NewXpFromNode(entity).X2s()
		rec := recs[entityID]
		id := rec.id
		hash := hex.EncodeToString(gosaml.Hash(crypto.SHA1, md))
		oldhash := rec.hash
		if rec.hash == hash { // no changes
			delete(recs, entityID) // remove so it won't be deleted
			nochange++
			continue
		} else if oldhash != "" { // update is delete + insert - then the cascading delete will also delete the potential stale lookup entries
			_, err = entityDeleteStmt.Exec(rec.id)
			if err != nil {
				return
			}
			updated++
			log.Printf("lMDQ updated entityID: %s", entityID)
			delete(recs, entityID) // updated - remove so it won't be deleted
		} else {
			new++
			if !mdq.Silent {
			    log.Printf("lMDQ new entityID: %s", entityID)
			}
		}
		var res sql.Result
		res, err = entityInsertStmt.Exec(entityID, md, hash)
		if err != nil {
			return err
		}

		id, _ = res.LastInsertId()

		_, err = lookupInsertStmt.Exec(hex.EncodeToString(gosaml.Hash(crypto.SHA1, entityID)), id)
		if err != nil {
			return
		}

		for _, target := range indextargets {
			locations := dom.Query(entity, target)
			for i, location := range locations {
			    if !mdq.Silent {
				    log.Println(i, dom.NodeGetContent(location))
				}
				_, err = lookupInsertStmt.Exec(hex.EncodeToString(gosaml.Hash(crypto.SHA1, dom.NodeGetContent(location))), id)
				if err != nil {
					return
				}
			}
		}
	}
	for entid, ent := range recs { // delete entities no longer in feed
		_, err = entityDeleteStmt.Exec(ent.id)
		if err != nil {
			return
		}
		deleted++
		log.Printf("lMDQ deleted entityID: %s", entid)
	}

	_, err = tx.Exec("update validuntil set validuntil = $1 where id = 1", validUntil)
	if err != nil {
		return
	}

	log.Printf("lMDQ finished %d new, %d updated, %d unchanged, %d deleted validUntil: %s duration: %.1f",
		new, updated, nochange, deleted, time.Unix(validUntil, 0).Format(time.RFC3339), time.Since(start).Seconds())
	return
}

// getEntityList returns a map keyed by entityIDs for the
// current entities in the database
func (mdq *MDQ) getEntityList() (entities map[string]EntityRec, err error) {

	entities = make(map[string]EntityRec)
	var rows *sql.Rows
	rows, err = mdq.db.Query("select id, entityid, hash from entity")
	if err != nil {
		return
	}
	defer rows.Close()
	for rows.Next() {
		var rec EntityRec
		if err = rows.Scan(&rec.id, &rec.entityid, &rec.hash); err != nil {
			return
		}
		entities[rec.entityid] = rec
	}
	if err = rows.Err(); err != nil { // no reason to actually check err, but if we later forget ...
		return
	}
	return
}

// Get - insecure Get if https is used, doesn't matter for metadata as we check the signature anyway
func get(url string) (body []byte, err error) {
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client := &http.Client{
		Transport: tr,
	}
	var resp *http.Response
	if resp, err = client.Get(url); err != nil {
		return
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return
	}
	body, err = ioutil.ReadAll(resp.Body)
	return
}

<commit_msg>MDQFilter now delivers a stable DOM - ie. the order of the entities is stable - given a stable metadata feed.<commit_after>/*  lMDQ is a MDQ server that caches metadata locally so it's local clients can lookup
    pre-checked metadata and not depend on a working connection to a remote MDQ server.

    It uses SQLite as it's datastore and allows lookup by either entityID or Location.
    The latter is used by WAYF for it's mass hosting services BIRK and KRIB.

    It can also be used as a library for just looking up metadata inside a go program.

    Or a client can use the SQLite database directly using the following query:

		"select e.md, e.hash from entity e, lookup l where l.hash = $1 and l.entity_id_fk = e.id and e.validuntil >= $2"

    where $1 is the lowercase hex sha1 of the entityID or location without the {sha1} prefix
    $2 is the current epoch.

    to-do:
        √ caching interface
          invalidate cache ???
*/

package lMDQ

import (
	"crypto"
	"crypto/tls"
	"database/sql"
	"encoding/hex"
	"errors"
	"fmt"
    "github.com/aryann/difflib"
	_ "github.com/mattn/go-sqlite3"
	"github.com/wayf-dk/gosaml"
	"io/ioutil"
	"log"
	"net/http"
	//"runtime/debug"
	"sort"
	"strings"
	"sync"
	"time"
)

const (
	// pragma wal ???
	lMDQSchema = `
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS entity
(
    id INTEGER PRIMARY KEY,
    entityid text not null unique,
    md text NOT NULL,
    hash text NOT NULL
);

CREATE TABLE IF NOT EXISTS lookup
(
    id INTEGER PRIMARY KEY,
    hash text NOT NULL,
    entity_id_fk INTEGER,
    unique(hash, entity_id_fk),
    FOREIGN KEY(entity_id_fk) REFERENCES entity(id) on delete cascade
);

CREATE TABLE IF NOT EXISTS validuntil
(
    id integer primary key default 1,
    validuntil integer not null default 0
);

CREATE INDEX if not exists lookupbyhash ON lookup(hash);
insert or ignore into validuntil (id) values (1);
`
)

type (
	EntityRec struct {
		id       int64
		entityid string
		hash     string
	}

	MDQ struct {
		db              *sql.DB
		stmt            *sql.Stmt
		Url, Hash, Path, MetadataSchemaPath string
		Cache           map[string]*MdXp
	    Lock            sync.Mutex
	    Silent          bool
	}

	MdXp struct {
		*gosaml.Xp
		created time.Time
	}
)

var (
	cacheduration = time.Minute * 1

	indextargets = []string{
		"./md:IDPSSODescriptor/md:SingleSignOnService[@Binding='urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect']/@Location",
		"./md:SPSSODescriptor/md:AssertionConsumerService[@Binding='urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST']/@Location",
	}
)

func init() {

}

func Cmp(feed1, feed2 MDQ) {
    ents1, _ := feed1.getEntityList()
    ents2, _ := feed2.getEntityList()
//    len1 := len(ents1)
    len2 := len(ents2)
    seen := 0
    for id, _ := range ents1 {
        x1, _ := feed1.MDQ(id)
        x2, _ := feed2.MDQ(id)
        str1 := x1.Pp()
        str2 := x2.Pp()
        hash1 := hex.EncodeToString(gosaml.Hash(crypto.SHA1, str1))
        hash2 := hex.EncodeToString(gosaml.Hash(crypto.SHA1, str2))
        if hash1 == hash2 {
            fmt.Println("OK ", id)
        } else {
            fmt.Println("NOT", id)
            diffs := difflib.Diff(strings.Split(str1, "\n"), strings.Split(str2, "\n"))
            for _, diff := range diffs {
                if diff.Delta != difflib.Common {
                    fmt.Println(diff)
                }
            }
            fmt.Println()
        }
        seen++
    }
    fmt.Println("Number of entityies: ", seen, len2)
}

func (xp *MdXp) Valid(duration time.Duration) bool {
	since := time.Since(xp.created)
	//log.Println(since, duration, since  < duration)
	return since < duration
}

func Open(path string) (mdq *MDQ, err error) {
    mdq = new(MDQ)
    return mdq, mdq.Open(path)
}

func (mdq *MDQ) Open(path string) (err error) {
    mdq.Cache = make(map[string]*MdXp)
	mdq.Path = path
	mdq.db, err = sql.Open("sqlite3", path)
	if err != nil {
		return
	}

	return
}

// MDQ looks up an entity using the supplied feed and key.
// The key can be an entityID or a location, optionally in {sha1} format
// It returns a non nil err if the entity is not found
// and the metadata and a hash/etag over the content if it is.
// The hash can be used to decide if a cached dom object is still valid,
// This might be an optimization as the database lookup is much faster that the parsing.
func (mdq *MDQ) MDQ(key string) (xp *gosaml.Xp, err error) {
    return mdq.dbget(key, true)
}

func (mdq *MDQ) dbget(key string, cache bool) (xp *gosaml.Xp, err error) {
    if mdq.stmt == nil {
        mdq.stmt, err = mdq.db.Prepare(`select e.md from entity e, lookup l, validuntil v
        where l.hash = $1 and l.entity_id_fk = e.id and v.validuntil >= $2`)
        if err != nil {
            return
        }
    }

	k := key
	const prefix = "{sha1}"
	if strings.HasPrefix(key, prefix) {
		key = key[6:]
	} else {
		key = hex.EncodeToString(gosaml.Hash(crypto.SHA1, key))
	}

	mdq.Lock.Lock()
	defer mdq.Lock.Unlock()
	cachedxp := mdq.Cache[key]
	if cachedxp != nil && cachedxp.Valid(cacheduration) {
		xp = cachedxp.Xp.CpXp()
		return
	}

	var xml []byte
	err = mdq.stmt.QueryRow(key, time.Now().Unix()).Scan(&xml)
	if err != nil {
		//log.Println("query", mdq.path, k, key, err, string(xml))
		err = fmt.Errorf("Metadata not found for entity: %s", k)
		//debug.PrintStack()
		return
	}
	xp = gosaml.NewXp(xml)
    if cache {
	    mdxp := new(MdXp)
	    mdxp.Xp = xp
	    mdxp.created = time.Now()
	    mdq.Cache[key] = mdxp
	}
	return
}

/**
    One at a time - not that fast - only use for testing
    Filtered by xpath for testing purposes
*/
func (mdq *MDQ) MDQFilter(xpathfilter string) (xp *gosaml.Xp, numberOfEntities int, err error) {
	recs, err := mdq.getEntityList()
	if err != nil {
		return
	}

    // get the entities into an ordered slice
    index := make([]string, len(recs))
    i := 0
    for k, _ := range recs {
        index[i] = k
        i++
    }
    sort.Strings(index)

	//log.Println(xpathfilter)
	xp = gosaml.NewXp([]byte(`<md:EntitiesDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" />`))

	for _, entityID := range index {
	    ent, _ := mdq.dbget(entityID, false)

	    if xpathfilter == "" || len(ent.Query(nil, xpathfilter)) > 0 {
            xp.DocGetRootElement().AddChild(xp.CopyNode(ent.DocGetRootElement(), 1))
            numberOfEntities++
        }
	}
	return
}

func (mdq *MDQ) Update() (err error) {
	start := time.Now()
	log.Println("lMDQ updating", mdq.Url, mdq.Path)

	_, err = mdq.db.Exec(lMDQSchema)
	if err != nil {
		return
	}

	recs, err := mdq.getEntityList()
	if err != nil {
		return err
	}
	var md []byte
	if md, err = get(mdq.Url); err != nil {
		return
	}

	dom := gosaml.NewXp(md)

	if _, err := dom.SchemaValidate(mdq.MetadataSchemaPath); err != nil {
		log.Println("feed", "SchemaError")
	}

	certificate := dom.Query(nil, "/md:EntitiesDescriptor/ds:Signature/ds:KeyInfo/ds:X509Data/ds:X509Certificate")
	if len(certificate) != 1 {
		err = errors.New("Metadata not signed")
		return
	}
	keyname, key, err := gosaml.PublicKeyInfo(dom.NodeGetContent(certificate[0]))

	if err != nil {
		return
	}

	ok := dom.VerifySignature(nil, key)
	if ok != nil || keyname != mdq.Hash {
		return fmt.Errorf("Signature check failed. Signature %s, %s = %s", ok, keyname, mdq.Hash)
	}

	tx, err := mdq.db.Begin()
	if err != nil {
		return
	}
	defer func() {
		if err != nil {
			tx.Rollback()
			return
		}
		err = tx.Commit()
	}()

	entityInsertStmt, err := tx.Prepare("insert into entity (entityid, md, hash) values ($1, $2, $3)")
	if err != nil {
		return
	}
	defer entityInsertStmt.Close()

	lookupInsertStmt, err := tx.Prepare("insert or ignore into lookup (hash, entity_id_fk) values (?, ?)")
	if err != nil {
		return err
	}
	defer lookupInsertStmt.Close()

	entityDeleteStmt, err := tx.Prepare("delete from entity where id = $1")
	if err != nil {
		return err
	}
	defer entityDeleteStmt.Close()

	vu, err := time.Parse(time.RFC3339Nano, dom.Query1(nil, "@validUntil"))
	if err != nil {
		return err
	}
	validUntil := vu.Unix()

	var new, updated, nochange, deleted int
	seen := map[string]bool{}

	entities := dom.Query(nil, "./md:EntityDescriptor")
	for _, entity := range entities {
		entityID := dom.Query1(entity, "@entityID")
		if seen[entityID] {
			log.Printf("lMDQ duplicate entityID: %s", entityID)
			continue
		}
        seen[entityID] = true
		md := gosaml.NewXpFromNode(entity).X2s()
		rec := recs[entityID]
		id := rec.id
		hash := hex.EncodeToString(gosaml.Hash(crypto.SHA1, md))
		oldhash := rec.hash
		if rec.hash == hash { // no changes
			delete(recs, entityID) // remove so it won't be deleted
			nochange++
			continue
		} else if oldhash != "" { // update is delete + insert - then the cascading delete will also delete the potential stale lookup entries
			_, err = entityDeleteStmt.Exec(rec.id)
			if err != nil {
				return
			}
			updated++
			log.Printf("lMDQ updated entityID: %s", entityID)
			delete(recs, entityID) // updated - remove so it won't be deleted
		} else {
			new++
			if !mdq.Silent {
			    log.Printf("lMDQ new entityID: %s", entityID)
			}
		}
		var res sql.Result
		res, err = entityInsertStmt.Exec(entityID, md, hash)
		if err != nil {
			return err
		}

		id, _ = res.LastInsertId()

		_, err = lookupInsertStmt.Exec(hex.EncodeToString(gosaml.Hash(crypto.SHA1, entityID)), id)
		if err != nil {
			return
		}

		for _, target := range indextargets {
			locations := dom.Query(entity, target)
			for i, location := range locations {
			    if !mdq.Silent {
				    log.Println(i, dom.NodeGetContent(location))
				}
				_, err = lookupInsertStmt.Exec(hex.EncodeToString(gosaml.Hash(crypto.SHA1, dom.NodeGetContent(location))), id)
				if err != nil {
					return
				}
			}
		}
	}
	for entid, ent := range recs { // delete entities no longer in feed
		_, err = entityDeleteStmt.Exec(ent.id)
		if err != nil {
			return
		}
		deleted++
		log.Printf("lMDQ deleted entityID: %s", entid)
	}

	_, err = tx.Exec("update validuntil set validuntil = $1 where id = 1", validUntil)
	if err != nil {
		return
	}

	log.Printf("lMDQ finished %d new, %d updated, %d unchanged, %d deleted validUntil: %s duration: %.1f",
		new, updated, nochange, deleted, time.Unix(validUntil, 0).Format(time.RFC3339), time.Since(start).Seconds())
	return
}

// getEntityList returns a map keyed by entityIDs for the
// current entities in the database
func (mdq *MDQ) getEntityList() (entities map[string]EntityRec, err error) {

	entities = make(map[string]EntityRec)
	var rows *sql.Rows
	rows, err = mdq.db.Query("select id, entityid, hash from entity")
	if err != nil {
		return
	}
	defer rows.Close()
	for rows.Next() {
		var rec EntityRec
		if err = rows.Scan(&rec.id, &rec.entityid, &rec.hash); err != nil {
			return
		}
		entities[rec.entityid] = rec
	}
	if err = rows.Err(); err != nil { // no reason to actually check err, but if we later forget ...
		return
	}
	return
}

// Get - insecure Get if https is used, doesn't matter for metadata as we check the signature anyway
func get(url string) (body []byte, err error) {
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client := &http.Client{
		Transport: tr,
	}
	var resp *http.Response
	if resp, err = client.Get(url); err != nil {
		return
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return
	}
	body, err = ioutil.ReadAll(resp.Body)
	return
}

<|endoftext|>


<commit_before>package devquest

import (
    "fmt"
    "strconv"
    "net/http"
    "log"
    "encoding/json"
    "io/ioutil"
    "os"
    "appengine"
    "appengine/urlfetch"
    "appengine/datastore"
    "appengine/memcache"
)

type Resp struct {
    Email string
    Pseudo string
    Score int8
    Time int64
}

type jsonobject struct {
    SPREADSHEET_KEY string
}

type Value struct{
    T string `json:"$t"`
}

type Entry struct{
    Title Value `json:"title"`
    Content Value `json:"content"`
}

type Feed struct{
    Entry []Entry `json:"entry"`
}

type SpreadSheet struct {
    Feed Feed `json:"feed"`
    Version string `json:"version"`
}

type Question struct{
    Title string `json:"title"`
    RepA string `json:"repA"`
    RepB string `json:"repB"`
    RepC string `json:"repC"`
    RepD string `json:"repD"`
}

type Questions struct{
    Questions []Question `json:"questions"`
}

var jsontype jsonobject

func init() {
    http.HandleFunc("/api/v1/questions", questions)
    http.HandleFunc("/api/v1/", anwser)

    file, e := ioutil.ReadFile("./credentials.json")
    if e != nil {
        log.Printf("File error: %v\n", e)
        os.Exit(1)
    }
    log.Print(string(file))

    e= json.Unmarshal(file, &jsontype)
    if e!=nil{
        log.Print("Error:",e)
    }
    log.Print(jsontype)
}

func questions(w http.ResponseWriter, r *http.Request) {
    //w.Header().Set("SUPER-HACK", "@GDGNANTES")
    //w.WriteHeader(http.StatusFound)
    c := appengine.NewContext(r)
    client := urlfetch.Client(c)

    // Get the item from the memcache
    keyMemCache := "questions"+jsontype.SPREADSHEET_KEY;
    item, err := memcache.Get(c, keyMemCache);
    if  err == memcache.ErrCacheMiss || err != nil{
        // No Cache info
        url := "https://spreadsheets.google.com/feeds/cells/"+jsontype.SPREADSHEET_KEY+"/od6/public/basic?alt=json";
        resp, err := client.Get(url)

         if err != nil {
            fmt.Fprintf(w, "%s\n", err)
            os.Exit(1)
        } else {
            body, err := ioutil.ReadAll(resp.Body)
            if err != nil {
                fmt.Fprintf(w, "%s\n", err)
                os.Exit(1)
            }
            data := SpreadSheet{}
            json.Unmarshal([]byte(string(body)), &data)

            questions := Questions{}
            questions.Questions = make([]Question,(len(data.Feed.Entry)/6));
            indexQuestion :=0
            indexRow := 1;
            for _,element := range data.Feed.Entry{
                keyA := "A"+strconv.Itoa(indexRow)
                if element.Title.T == keyA{
                    question := Question{}
                    questions.Questions[indexQuestion] = question
                }else{
                    question := &questions.Questions[indexQuestion]

                    keyB := "B"+strconv.Itoa(indexRow)
                    keyC := "C"+strconv.Itoa(indexRow)
                    keyD := "D"+strconv.Itoa(indexRow)
                    keyE := "E"+strconv.Itoa(indexRow)
                    keyF := "F"+strconv.Itoa(indexRow)
                    if element.Title.T == keyB{
                        question.Title = element.Content.T;
                    }else if element.Title.T == keyC{
                        question.RepA = element.Content.T;
                    }else if element.Title.T == keyD{
                        question.RepB = element.Content.T;
                    }else if element.Title.T == keyE{
                        question.RepC = element.Content.T;
                    }else if element.Title.T == keyF{
                        question.RepD = element.Content.T;
                        indexRow++
                        indexQuestion++
                    }
                }

            }
            strJson, _ := json.Marshal(questions);
            itemValue := &memcache.Item{
                Key: keyMemCache,
                Value: strJson,
            }
            if err := memcache.Add(c, itemValue); err == memcache.ErrNotStored {
                c.Infof("itemValue with key %q already exists", itemValue.Key)
            } else if err != nil {
                c.Errorf("error adding itemValue: %v", err)
            }
            fmt.Fprintf(w, "%s\n", strJson)
        }
        //c.Infof("item not in the cache")
        //c.Errorf("error getting item: %v", err)
    } else {
        fmt.Fprintf(w, "%s\n", item.Value)
        // Data in Cache
        //c.Infof("the lyric is %q", item.Value)
    }

}

func anwser(w http.ResponseWriter, r *http.Request) {
    //w.Header().Set("SUPER-HACK", "@GDGNANTES")
    //w.WriteHeader(http.StatusFound)
    fmt.Fprint(w, "Hello World\n")
    fmt.Fprint(w, jsontype.SPREADSHEET_KEY);
    fmt.Fprint(w, "\n")
    email := r.FormValue("email")
    indexQuestion := r.FormValue("indexQuestion")
    resp := r.FormValue("resp")
    code := r.FormValue("code")
    time := r.FormValue("time")
    if len(email) != 0{
        fmt.Fprint(w, email+"\n")
    }
    if len(indexQuestion) != 0{
        fmt.Fprint(w, indexQuestion+"\n")
    }
    if len(resp) != 0{
        fmt.Fprint(w, resp+"\n")
    }
    if len(code) != 0{
        fmt.Fprint(w, code+"\n")
    }
    if len(time) != 0{
        fmt.Fprint(w, time+"\n")
    }

    c := appengine.NewContext(r)
    respDataStore := Resp{
        Email: email,
        Score: 1,
        Time: 1,
    }
    key := datastore.NewIncompleteKey(c,"Score", nil)
    _, err := datastore.Put(c, key, &respDataStore)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
}
<commit_msg>fix(#24)<commit_after>package devquest

import (
    "fmt"
    "strconv"
    "net/http"
    "log"
    "encoding/json"
    "io/ioutil"
    "os"
    "appengine"
    "appengine/urlfetch"
    "appengine/datastore"
    "appengine/memcache"
)

type Resp struct {
    Email string
    Pseudo string
    Score int8
    Time int64
}

type jsonobject struct {
    SPREADSHEET_KEY string
}

type Value struct{
    T string `json:"$t"`
}

type Entry struct{
    Title Value `json:"title"`
    Content Value `json:"content"`
}

type Feed struct{
    Entry []Entry `json:"entry"`
}

type SpreadSheet struct {
    Feed Feed `json:"feed"`
    Version string `json:"version"`
}

type Question struct{
    Id int `json:"id"`
    Title string `json:"title"`
    RepA string `json:"repA"`
    RepB string `json:"repB"`
    RepC string `json:"repC"`
    RepD string `json:"repD"`
    Code string `json:"-"`
    Resp string `json:"-"`
}

type Questions struct{
    Questions []Question `json:"questions"`
}

var jsontype jsonobject

func init() {
    http.HandleFunc("/api/v1/questions", questions)
    http.HandleFunc("/api/v1/", anwser)

    file, e := ioutil.ReadFile("./credentials.json")
    if e != nil {
        log.Printf("File error: %v\n", e)
        os.Exit(1)
    }
    log.Print(string(file))

    e= json.Unmarshal(file, &jsontype)
    if e!=nil{
        log.Print("Error:",e)
    }
    log.Print(jsontype)

}

func questions(w http.ResponseWriter, r *http.Request) {
    //w.Header().Set("SUPER-HACK", "@GDGNANTES")
    //w.WriteHeader(http.StatusFound)
    c := appengine.NewContext(r)
    client := urlfetch.Client(c)

    // Mise en cache direct du spreadsheet
    keyMemCache := "questions"+jsontype.SPREADSHEET_KEY;
    item, err := memcache.Get(c, keyMemCache);
    if  err == memcache.ErrCacheMiss || err != nil{
        // No Cache info
        url := "https://spreadsheets.google.com/feeds/cells/"+jsontype.SPREADSHEET_KEY+"/od6/public/basic?alt=json";
        resp, err := client.Get(url)

        if err != nil {
            fmt.Fprintf(w, "%s\n", err)
            os.Exit(1)
        } else {
            body, err := ioutil.ReadAll(resp.Body)
            if err != nil {
                fmt.Fprintf(w, "%s\n", err)
                os.Exit(1)
            }
            data := SpreadSheet{}
            json.Unmarshal([]byte(string(body)), &data)

            questions := Questions{}
            questions.Questions = make([]Question,(len(data.Feed.Entry)/6));
            indexQuestion :=0
            indexRow := 1;
            for _,element := range data.Feed.Entry{
                keyId := "A"+strconv.Itoa(indexRow)
                if element.Title.T == keyId{
                    idTmp, _ := strconv.Atoi(element.Content.T)
                    question := Question{
                        Id: idTmp,
                    }
                    questions.Questions[indexQuestion] = question
                }else{
                    question := &questions.Questions[indexQuestion]

                    keyQuestionTitle := "B"+strconv.Itoa(indexRow)
                    keyRepA := "C"+strconv.Itoa(indexRow)
                    keyRepB := "D"+strconv.Itoa(indexRow)
                    keyRepC := "E"+strconv.Itoa(indexRow)
                    keyRepD := "F"+strconv.Itoa(indexRow)
                    keyCode := "G"+strconv.Itoa(indexRow)
                    keyResp := "H"+strconv.Itoa(indexRow)
                    if element.Title.T == keyQuestionTitle{
                        question.Title = element.Content.T;
                    }else if element.Title.T == keyRepA{
                        question.RepA = element.Content.T;
                    }else if element.Title.T == keyRepB{
                        question.RepB = element.Content.T;
                    }else if element.Title.T == keyRepC{
                        question.RepC = element.Content.T;
                    }else if element.Title.T == keyRepD{
                        question.RepD = element.Content.T;
                    }else if element.Title.T == keyCode{
                        question.Code = element.Content.T;
                    }else if element.Title.T == keyResp{
                        question.Resp = element.Content.T;
                        indexRow++
                        indexQuestion++
                    }
                }

            }
            strJson, _ := json.Marshal(questions);
            itemValue := &memcache.Item{
                Key: keyMemCache,
                Value: strJson,
            }
            if err := memcache.Add(c, itemValue); err == memcache.ErrNotStored {
                c.Infof("itemValue with key %q already exists", itemValue.Key)
            } else if err != nil {
                c.Errorf("error adding itemValue: %v", err)
            }

            // On ajout au datastore les questions pour plus de facilité d'intérogation
            for _,question := range questions.Questions{
                keyQuestion := datastore.NewKey(c,"Question", strconv.Itoa(question.Id), 0, nil)
                _, err := datastore.Put(c, keyQuestion, &question)
                if  err != nil{
                    // Print error
                    fmt.Fprintf(w, "%s\n", err)
                    // TODO jeter une erreur
                    return
                }
            }
            fmt.Fprintf(w, "%s\n", strJson)
        }
    } else{

        fmt.Fprintf(w, "%s\n", item.Value)
    }

}

func anwser(w http.ResponseWriter, r *http.Request) {
    //w.Header().Set("SUPER-HACK", "@GDGNANTES")
    //w.WriteHeader(http.StatusFound)
    //fmt.Fprint(w, "Hello World\n")
    //fmt.Fprint(w, jsontype.SPREADSHEET_KEY);
    //fmt.Fprint(w, "\n")
    email := r.FormValue("email")
    indexQuestion := r.FormValue("indexQuestion")
    resp := r.FormValue("resp")
    code := r.FormValue("code")
    time := r.FormValue("time")
    if len(email) == 0 || len(indexQuestion) == 0 || len(resp) == 0 || len(code) == 0 || len(time) == 0{
        fmt.Fprint(w, "Parametre manquant\n")
        // TODO jetter une erreur
        return
    }
    fmt.Fprint(w, email+"\n")
    fmt.Fprint(w, time+"\n")
    fmt.Fprint(w, code+"\n")
    fmt.Fprint(w, resp+"\n")
    fmt.Fprint(w, indexQuestion+"\n")

    c := appengine.NewContext(r)
    var scoreQuestion int8
    scoreQuestion = 0
    // Vérification du code de réponse
    // Get the item from the memcache
    var question Question
    keyQuestion := datastore.NewKey(c, "Question", indexQuestion, 0, nil)
    if err := datastore.Get(c, keyQuestion, &question); err != nil{
        fmt.Fprintf(w, "%s\n", err)
        // TODO jeter une erreur
        return
    }
    if resp == question.Resp{
        scoreQuestion += 10
    }
    if code == question.Code{
        scoreQuestion += 5
    }

    // Préparation du Data Store
    key := datastore.NewKey(c,"Score", email, 0, nil)

    var respDataStore Resp
    if err := datastore.Get(c, key, &respDataStore); err != nil{
        // Data pas déjà présente => on la créé
        respDataStore = Resp{
            Email: email,
            Time: 0,
            Score: 0,
        }
    }
    timeTmp, _ :=strconv.ParseInt(time, 10, 64)
    respDataStore.Time += timeTmp
    respDataStore.Score +=scoreQuestion
    _, err := datastore.Put(c, key, &respDataStore)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
}
<|endoftext|>


<commit_before>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2016 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package netext

import (
	"context"
	"net"
	"strings"
	"sync/atomic"

	"github.com/viki-org/dnscache"
)

type Dialer struct {
	net.Dialer

	Resolver     *dnscache.Resolver
	BytesRead    *int64
	BytesWritten *int64
}

func NewDialer(dialer net.Dialer) *Dialer {
	return &Dialer{
		Dialer:   dialer,
		Resolver: dnscache.New(0),
	}
}

func (d Dialer) DialContext(ctx context.Context, proto, addr string) (net.Conn, error) {
	delimiter := strings.LastIndex(addr, ":")
	ip, err := d.Resolver.FetchOne(addr[:delimiter])
	if err != nil {
		return nil, err
	}
	ipStr := ip.String()
	if strings.ContainsRune(ipStr, ':') {
		ipStr = "[" + ipStr + "]"
	}
	conn, err := d.Dialer.DialContext(ctx, proto, ipStr+":"+addr[delimiter+1:])
	if err != nil {
		return nil, err
	}
	if d.BytesRead != nil && d.BytesWritten != nil {
		conn = &Conn{conn, d.BytesRead, d.BytesWritten}
	}
	return conn, err
}

type Conn struct {
	net.Conn

	BytesRead, BytesWritten *int64
}

func (c *Conn) Read(b []byte) (int, error) {
	n, err := c.Conn.Read(b)
	atomic.AddInt64(c.BytesRead, int64(n))
	return n, err
}

func (c *Conn) Write(b []byte) (int, error) {
	n, err := c.Conn.Write(b)
	atomic.AddInt64(c.BytesWritten, int64(n))
	return n, err
}
<commit_msg>Fix data received/sent metrics for HTTP<commit_after>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2016 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package netext

import (
	"context"
	"net"
	"strings"
	"sync/atomic"

	"github.com/viki-org/dnscache"
)

type Dialer struct {
	net.Dialer

	Resolver     *dnscache.Resolver
	BytesRead    *int64
	BytesWritten *int64
}

func NewDialer(dialer net.Dialer) *Dialer {
	return &Dialer{
		Dialer:   dialer,
		Resolver: dnscache.New(0),
	}
}

func (d *Dialer) DialContext(ctx context.Context, proto, addr string) (net.Conn, error) {
	delimiter := strings.LastIndex(addr, ":")
	ip, err := d.Resolver.FetchOne(addr[:delimiter])
	if err != nil {
		return nil, err
	}
	ipStr := ip.String()
	if strings.ContainsRune(ipStr, ':') {
		ipStr = "[" + ipStr + "]"
	}
	conn, err := d.Dialer.DialContext(ctx, proto, ipStr+":"+addr[delimiter+1:])
	if err != nil {
		return nil, err
	}
	if d.BytesRead != nil && d.BytesWritten != nil {
		conn = &Conn{conn, d.BytesRead, d.BytesWritten}
	}
	return conn, err
}

type Conn struct {
	net.Conn

	BytesRead, BytesWritten *int64
}

func (c *Conn) Read(b []byte) (int, error) {
	n, err := c.Conn.Read(b)
	atomic.AddInt64(c.BytesRead, int64(n))
	return n, err
}

func (c *Conn) Write(b []byte) (int, error) {
	n, err := c.Conn.Write(b)
	atomic.AddInt64(c.BytesWritten, int64(n))
	return n, err
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"flag"
	"fmt"
	"log"
	"math/rand"
	"net"
	"strings"
	"time"
)

func init() {
	rand.Seed(time.Now().UnixNano())
	flag.Parse()
}

func main() {
	name := flag.Arg(0)
	resolver := flag.Arg(1)

	if !strings.HasPrefix(name, ".") {
		name += "."
	}

	raddr, err := net.ResolveUDPAddr("udp", resolver+":53")
	if err != nil {
		log.Fatal(err)
	}

	conn, err := net.DialUDP("udp", nil, raddr)
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	query, err := pack(name)
	if err != nil {
		log.Fatal(err)
	}

	if _, err := conn.Write(query.Bytes()); err != nil {
		log.Fatal(err)
	}

	answer := make([]byte, 512)
	_, err = bufio.NewReader(conn).Read(answer)
	if err != nil {
		log.Fatal(err)
	}

	out := new(bytes.Buffer)

	fmt.Fprintf(out, "%s has address ", name)
	// Skip Question Section and pick the last four octets
	offset := len(query.Bytes()) + 12
	for i := offset; i < offset+4; i++ {
		if i > offset {
			fmt.Fprint(out, ".")
		}
		fmt.Fprint(out, answer[i])
	}

	fmt.Println(out.String())
}

type msgHeader struct {
	ID      uint16
	Bits    [2]byte
	QDcount uint16
	ANcount uint16
	NScount uint16
	ARcount uint16
}

type msgQuestionFooter struct {
	Qtype  uint16
	Qclass uint16
}

func pack(name string) (*bytes.Buffer, error) {
	b := new(bytes.Buffer)

	h := msgHeader{
		ID:      uint16(rand.Intn(1 << 16)),
		Bits:    [2]byte{1, 0},
		QDcount: uint16(1),
		ANcount: uint16(0),
		NScount: uint16(0),
		ARcount: uint16(0),
	}
	if err := binary.Write(b, binary.BigEndian, &h); err != nil {
		return nil, err
	}

	// QNAME
	for _, label := range strings.Split(name, ".") {
		// byte is just an alias for uint8
		l := uint8(len(label))
		b.WriteByte(l)
		if l > 0 {
			b.WriteString(label)
		}
	}

	f := msgQuestionFooter{
		Qtype:  uint16(1), // A
		Qclass: uint16(1), // IN
	}
	if err := binary.Write(b, binary.BigEndian, &f); err != nil {
		return nil, err
	}

	return b, nil
}

// decompName decompress RFC 1035 4.1.4. Message compression and returns name
// as string and next offset as int
func decompName(b []byte, off int) (string, int) {
	buf := bytes.NewBuffer(nil)
	off0 := off
	for {
		if off-off0 > 256 {
			break
		}
		c := b[off]
		if c >= 0xc0 {
			// TODO: handle 01 and 10 bits cases
			// technically offset is uint14 value
			off += 1
			p := binary.BigEndian.Uint16([]byte{c ^ 0xc0, b[off]})
			buf.WriteString(labels(b, int(p)))
			break
		} else {
			if c == 0x00 {
				break
			}
			l := int(b[off])
			off += 1
			buf.Write(b[off : off+l])
			buf.WriteString(".")
			off += l
		}
	}
	return buf.String(), off + 1
}

func labels(b []byte, off int) string {
	buf := bytes.NewBuffer(nil)
	for {
		if b[off] == 0x00 {
			break
		}
		l := int(b[off])
		off += 1
		buf.Write(b[off : off+l])
		buf.WriteString(".")
		off += l
	}
	return buf.String()
}
<commit_msg>remove non-realistic condition<commit_after>package main

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"flag"
	"fmt"
	"log"
	"math/rand"
	"net"
	"strings"
	"time"
)

func init() {
	rand.Seed(time.Now().UnixNano())
	flag.Parse()
}

func main() {
	name := flag.Arg(0)
	resolver := flag.Arg(1)

	if !strings.HasPrefix(name, ".") {
		name += "."
	}

	raddr, err := net.ResolveUDPAddr("udp", resolver+":53")
	if err != nil {
		log.Fatal(err)
	}

	conn, err := net.DialUDP("udp", nil, raddr)
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	query, err := pack(name)
	if err != nil {
		log.Fatal(err)
	}

	if _, err := conn.Write(query.Bytes()); err != nil {
		log.Fatal(err)
	}

	answer := make([]byte, 512)
	_, err = bufio.NewReader(conn).Read(answer)
	if err != nil {
		log.Fatal(err)
	}

	out := new(bytes.Buffer)

	fmt.Fprintf(out, "%s has address ", name)
	// Skip Question Section and pick the last four octets
	offset := len(query.Bytes()) + 12
	for i := offset; i < offset+4; i++ {
		if i > offset {
			fmt.Fprint(out, ".")
		}
		fmt.Fprint(out, answer[i])
	}

	fmt.Println(out.String())
}

type msgHeader struct {
	ID      uint16
	Bits    [2]byte
	QDcount uint16
	ANcount uint16
	NScount uint16
	ARcount uint16
}

type msgQuestionFooter struct {
	Qtype  uint16
	Qclass uint16
}

func pack(name string) (*bytes.Buffer, error) {
	b := new(bytes.Buffer)

	h := msgHeader{
		ID:      uint16(rand.Intn(1 << 16)),
		Bits:    [2]byte{1, 0},
		QDcount: uint16(1),
		ANcount: uint16(0),
		NScount: uint16(0),
		ARcount: uint16(0),
	}
	if err := binary.Write(b, binary.BigEndian, &h); err != nil {
		return nil, err
	}

	// QNAME
	for _, label := range strings.Split(name, ".") {
		// byte is just an alias for uint8
		l := uint8(len(label))
		b.WriteByte(l)
		if l > 0 {
			b.WriteString(label)
		}
	}

	f := msgQuestionFooter{
		Qtype:  uint16(1), // A
		Qclass: uint16(1), // IN
	}
	if err := binary.Write(b, binary.BigEndian, &f); err != nil {
		return nil, err
	}

	return b, nil
}

// decompName decompress RFC 1035 4.1.4. Message compression and returns name
// as string and next offset as int
func decompName(b []byte, off int) (string, int) {
	buf := bytes.NewBuffer(nil)
	for {
		c := b[off]
		if c >= 0xc0 {
			// TODO: handle 01 and 10 bits cases
			// technically offset is uint14 value
			off += 1
			p := binary.BigEndian.Uint16([]byte{c ^ 0xc0, b[off]})
			buf.WriteString(labels(b, int(p)))
			break
		} else {
			if c == 0x00 {
				break
			}
			l := int(b[off])
			off += 1
			buf.Write(b[off : off+l])
			buf.WriteString(".")
			off += l
		}
	}
	return buf.String(), off + 1
}

func labels(b []byte, off int) string {
	buf := bytes.NewBuffer(nil)
	for {
		if b[off] == 0x00 {
			break
		}
		l := int(b[off])
		off += 1
		buf.Write(b[off : off+l])
		buf.WriteString(".")
		off += l
	}
	return buf.String()
}
<|endoftext|>