<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"time"
)

type licensesReply struct {
	Key      string `json:"key"`
	Name     string `json:"name"`
	URL      string `json:"url"`
	Featured bool   `json:"featured"`
}

type ownerReply struct {
	URL string `json:"url"`
}

type repositoryReply struct {
	License     licensesReply `json:"license"`
	CreatedAt   string        `json:"created_at"`
	Description string        `json:"description"`
	Owner       ownerReply    `json:"owner"`
}

type licenseReply struct {
	Body string `json:"body"`
}

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

// To update, use:
// curl -s -H 'Accept: application/vnd.github.drax-preview+json' https://api.github.com/licenses | jq '.[].key'
// then compare with https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/#license-specification
var githubLicenseToDebianLicense = map[string]string{
	//"agpl-3.0" (not in debian?)
	"apache-2.0":   "Apache-2.0",
	"artistic-2.0": "Artistic-2.0",
	"bsd-2-clause": "BSD-2-clause",
	"bsd-3-clause": "BSD-3-clause",
	"cc0-1.0":      "CC0-1.0",
	//"epl-1.0" (eclipse public license)
	"gpl-2.0":  "GPL-2.0", // TODO: is this GPL-2.0+?
	"gpl-3.0":  "GPL-3.0",
	"isc":      "ISC",
	"lgpl-2.1": "LGPL-2.1",
	"lgpl-3.0": "LGPL-3.0",
	//"mit" - expat?
	//"mpl-2.0" (only 1.1 is in debian)
	//"unlicense" (not in debian)
}

var debianLicenseText = map[string]string{
	"Apache-2.0": ` 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.
 .
 On Debian systems, the complete text of the Apache version 2.0 license
 can be found in "/usr/share/common-licenses/Apache-2.0".
`,
}

func getLicenseForGopkg(gopkg string) (string, string, error) {
	if !strings.HasPrefix(gopkg, "github.com/") {
		return "", "", fmt.Errorf("%q is not on GitHub", gopkg)
	}
	// TODO: cache this reply
	req, err := http.NewRequest("GET", "https://api.github.com/repos/"+gopkg[len("github.com/"):], nil)
	if err != nil {
		return "", "", err
	}
	req.Header.Set("Accept", "application/vnd.github.drax-preview+json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return "", "", err
	}
	defer resp.Body.Close()
	var r repositoryReply
	if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
		return "", "", err
	}
	if deblicense, ok := githubLicenseToDebianLicense[r.License.Key]; ok {
		fulltext := debianLicenseText[deblicense]
		if fulltext == "" {
			fulltext = "TODO"
		}
		return deblicense, fulltext, nil
	} else {
		return "TODO", "TODO", nil
	}
}

func getAuthorAndCopyrightForGopkg(gopkg string) (string, string, error) {
	if !strings.HasPrefix(gopkg, "github.com/") {
		return "", "", fmt.Errorf("%q is not on GitHub", gopkg)
	}
	resp, err := http.Get("https://api.github.com/repos/" + gopkg[len("github.com/"):])
	if err != nil {
		return "", "", err
	}
	defer resp.Body.Close()

	var rr repositoryReply
	if err := json.NewDecoder(resp.Body).Decode(&rr); err != nil {
		return "", "", err
	}

	creation, err := time.Parse("2006-01-02T15:04:05Z", rr.CreatedAt)
	if err != nil {
		return "", "", err
	}

	if strings.TrimSpace(rr.Owner.URL) == "" {
		return "", "", fmt.Errorf("Repository owner URL not present in API response")
	}

	resp, err = http.Get(rr.Owner.URL)
	if err != nil {
		return "", "", err
	}
	defer resp.Body.Close()

	var ur usersReply
	if err := json.NewDecoder(resp.Body).Decode(&ur); err != nil {
		return "", "", err
	}

	copyright := creation.Format("2006") + " " + ur.Name
	if strings.HasPrefix(gopkg, "github.com/google/") {
		// As per https://opensource.google.com/docs/creating/, Google retains
		// the copyright for repositories underneath github.com/google/.
		copyright = creation.Format("2006") + " Google Inc."
	}

	return ur.Name, copyright, nil
}

func getDescriptionForGopkg(gopkg string) (string, error) {
	if !strings.HasPrefix(gopkg, "github.com/") {
		return "", fmt.Errorf("%q is not on GitHub", gopkg)
	}
	resp, err := http.Get("https://api.github.com/repos/" + gopkg[len("github.com/"):])
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	var rr repositoryReply
	if err := json.NewDecoder(resp.Body).Decode(&rr); err != nil {
		return "", err
	}

	return strings.TrimSpace(rr.Description), nil
}
<commit_msg>Add MPL-2.0 copyright template (#65)<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"time"
)

type licensesReply struct {
	Key      string `json:"key"`
	Name     string `json:"name"`
	URL      string `json:"url"`
	Featured bool   `json:"featured"`
}

type ownerReply struct {
	URL string `json:"url"`
}

type repositoryReply struct {
	License     licensesReply `json:"license"`
	CreatedAt   string        `json:"created_at"`
	Description string        `json:"description"`
	Owner       ownerReply    `json:"owner"`
}

type licenseReply struct {
	Body string `json:"body"`
}

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

// To update, use:
// curl -s -H 'Accept: application/vnd.github.drax-preview+json' https://api.github.com/licenses | jq '.[].key'
// then compare with https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/#license-specification
var githubLicenseToDebianLicense = map[string]string{
	//"agpl-3.0" (not in debian?)
	"apache-2.0":   "Apache-2.0",
	"artistic-2.0": "Artistic-2.0",
	"bsd-2-clause": "BSD-2-clause",
	"bsd-3-clause": "BSD-3-clause",
	"cc0-1.0":      "CC0-1.0",
	//"epl-1.0" (eclipse public license)
	"gpl-2.0":  "GPL-2.0", // TODO: is this GPL-2.0+?
	"gpl-3.0":  "GPL-3.0",
	"isc":      "ISC",
	"lgpl-2.1": "LGPL-2.1",
	"lgpl-3.0": "LGPL-3.0",
	//"mit" - expat?
	"mpl-2.0": "MPL-2.0", // include in base-files >= 9.9
	//"unlicense" (not in debian)
}

var debianLicenseText = map[string]string{
	"Apache-2.0": ` 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.
 .
 On Debian systems, the complete text of the Apache version 2.0 license
 can be found in "/usr/share/common-licenses/Apache-2.0".
`,
	"MPL-2.0": ` This Source Code Form is subject to the terms of the Mozilla Public
 License, v. 2.0. If a copy of the MPL was not distributed with this
 file, You can obtain one at http://mozilla.org/MPL/2.0/.
 .
 On Debian systems, the complete text of the MPL-2.0 license can be found
 in "/usr/share/common-licenses/MPL-2.0".
`,
}

func getLicenseForGopkg(gopkg string) (string, string, error) {
	if !strings.HasPrefix(gopkg, "github.com/") {
		return "", "", fmt.Errorf("%q is not on GitHub", gopkg)
	}
	// TODO: cache this reply
	req, err := http.NewRequest("GET", "https://api.github.com/repos/"+gopkg[len("github.com/"):], nil)
	if err != nil {
		return "", "", err
	}
	req.Header.Set("Accept", "application/vnd.github.drax-preview+json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return "", "", err
	}
	defer resp.Body.Close()
	var r repositoryReply
	if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
		return "", "", err
	}
	if deblicense, ok := githubLicenseToDebianLicense[r.License.Key]; ok {
		fulltext := debianLicenseText[deblicense]
		if fulltext == "" {
			fulltext = "TODO"
		}
		return deblicense, fulltext, nil
	} else {
		return "TODO", "TODO", nil
	}
}

func getAuthorAndCopyrightForGopkg(gopkg string) (string, string, error) {
	if !strings.HasPrefix(gopkg, "github.com/") {
		return "", "", fmt.Errorf("%q is not on GitHub", gopkg)
	}
	resp, err := http.Get("https://api.github.com/repos/" + gopkg[len("github.com/"):])
	if err != nil {
		return "", "", err
	}
	defer resp.Body.Close()

	var rr repositoryReply
	if err := json.NewDecoder(resp.Body).Decode(&rr); err != nil {
		return "", "", err
	}

	creation, err := time.Parse("2006-01-02T15:04:05Z", rr.CreatedAt)
	if err != nil {
		return "", "", err
	}

	if strings.TrimSpace(rr.Owner.URL) == "" {
		return "", "", fmt.Errorf("Repository owner URL not present in API response")
	}

	resp, err = http.Get(rr.Owner.URL)
	if err != nil {
		return "", "", err
	}
	defer resp.Body.Close()

	var ur usersReply
	if err := json.NewDecoder(resp.Body).Decode(&ur); err != nil {
		return "", "", err
	}

	copyright := creation.Format("2006") + " " + ur.Name
	if strings.HasPrefix(gopkg, "github.com/google/") {
		// As per https://opensource.google.com/docs/creating/, Google retains
		// the copyright for repositories underneath github.com/google/.
		copyright = creation.Format("2006") + " Google Inc."
	}

	return ur.Name, copyright, nil
}

func getDescriptionForGopkg(gopkg string) (string, error) {
	if !strings.HasPrefix(gopkg, "github.com/") {
		return "", fmt.Errorf("%q is not on GitHub", gopkg)
	}
	resp, err := http.Get("https://api.github.com/repos/" + gopkg[len("github.com/"):])
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	var rr repositoryReply
	if err := json.NewDecoder(resp.Body).Decode(&rr); err != nil {
		return "", err
	}

	return strings.TrimSpace(rr.Description), nil
}
<|endoftext|>


<commit_before>package collector

import (
	"strings"

	"github.com/golang/glog"
	"github.com/prometheus/client_golang/prometheus"
	mgo "gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

var (
	indexSize = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Namespace: Namespace,
		Subsystem: "db",
		Name:      "index_size_bytes",
		Help:      "The total size in bytes of all indexes created on this database",
	}, []string{"db", "shard"})
	dataSize = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Namespace: Namespace,
		Subsystem: "db",
		Name:      "data_size_bytes",
		Help:      "The total size in bytes of the uncompressed data held in this database",
	}, []string{"db", "shard"})
	collectionsTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Namespace: Namespace,
		Subsystem: "db",
		Name:      "collections_total",
		Help:      "Contains a count of the number of collections in that database",
	}, []string{"db", "shard"})
	indexesTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Namespace: Namespace,
		Subsystem: "db",
		Name:      "indexes_total",
		Help:      "Contains a count of the total number of indexes across all collections in the database",
	}, []string{"db", "shard"})
	objectsTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Namespace: Namespace,
		Subsystem: "db",
		Name:      "objects_total",
		Help:      "Contains a count of the number of objects (i.e. documents) in the database across all collections",
	}, []string{"db", "shard"})
)

// DatabaseStatus represents stats about a database
type DatabaseStatus struct {
	Name        string                `bson:"db,omitempty"`
	IndexSize   int                   `bson:"indexSize,omitempty"`
	DataSize    int                   `bson:"dataSize,omitempty"`
	Collections int                   `bson:"collections,omitempty"`
	Objects     int                   `bson:"objects,omitempty"`
	Indexes     int                   `bson:"indexes,omitempty"`
	Shards      map[string]*RawStatus `bson:"raw,omitempty"`
}

type RawStatus struct {
	Name        string `bson:"db,omitempty"`
	IndexSize   int    `bson:"indexSize,omitempty"`
	DataSize    int    `bson:"dataSize,omitempty"`
	Collections int    `bson:"collections,omitempty"`
	Objects     int    `bson:"objects,omitempty"`
	Indexes     int    `bson:"indexes,omitempty"`
}

// Export exports database stats to prometheus
func (dbStatus *DatabaseStatus) Export(ch chan<- prometheus.Metric) {
	if len(dbStatus.Shards) > 0 {
		for shard, stats := range dbStatus.Shards {
			shard = strings.Split(shard, "/")[0]
			indexSize.WithLabelValues(stats.Name, shard).Set(float64(stats.IndexSize))
			dataSize.WithLabelValues(stats.Name, shard).Set(float64(stats.DataSize))
			collectionsTotal.WithLabelValues(stats.Name, shard).Set(float64(stats.Collections))
			indexesTotal.WithLabelValues(stats.Name, shard).Set(float64(stats.Indexes))
			objectsTotal.WithLabelValues(stats.Name, shard).Set(float64(stats.Objects))
		}
	} else {
		indexSize.WithLabelValues(dbStatus.Name, "").Set(float64(dbStatus.IndexSize))
		dataSize.WithLabelValues(dbStatus.Name, "").Set(float64(dbStatus.DataSize))
		collectionsTotal.WithLabelValues(dbStatus.Name, "").Set(float64(dbStatus.Collections))
		indexesTotal.WithLabelValues(dbStatus.Name, "").Set(float64(dbStatus.Indexes))
		objectsTotal.WithLabelValues(dbStatus.Name, "").Set(float64(dbStatus.Objects))
	}

	indexSize.Collect(ch)
	dataSize.Collect(ch)
	collectionsTotal.Collect(ch)
	indexesTotal.Collect(ch)
	objectsTotal.Collect(ch)

	indexSize.Reset()
	dataSize.Reset()
	collectionsTotal.Reset()
	indexesTotal.Reset()
	objectsTotal.Reset()
}

// Describe describes database stats for prometheus
func (dbStatus *DatabaseStatus) Describe(ch chan<- *prometheus.Desc) {
	indexSize.Describe(ch)
	dataSize.Describe(ch)
	collectionsTotal.Describe(ch)
	indexesTotal.Describe(ch)
	objectsTotal.Describe(ch)
}

// GetDatabaseStatus returns stats for a given database
func GetDatabaseStatus(session *mgo.Session, db string) *DatabaseStatus {
	var dbStatus DatabaseStatus
	err := session.DB(db).Run(bson.D{{"dbStats", 1}, {"scale", 1}}, &dbStatus)
	if err != nil {
		glog.Error(err)
		return nil
	}

	return &dbStatus
}
<commit_msg>add accidentally deleted comment<commit_after>package collector

import (
	"strings"

	"github.com/golang/glog"
	"github.com/prometheus/client_golang/prometheus"
	mgo "gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

var (
	indexSize = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Namespace: Namespace,
		Subsystem: "db",
		Name:      "index_size_bytes",
		Help:      "The total size in bytes of all indexes created on this database",
	}, []string{"db", "shard"})
	dataSize = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Namespace: Namespace,
		Subsystem: "db",
		Name:      "data_size_bytes",
		Help:      "The total size in bytes of the uncompressed data held in this database",
	}, []string{"db", "shard"})
	collectionsTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Namespace: Namespace,
		Subsystem: "db",
		Name:      "collections_total",
		Help:      "Contains a count of the number of collections in that database",
	}, []string{"db", "shard"})
	indexesTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Namespace: Namespace,
		Subsystem: "db",
		Name:      "indexes_total",
		Help:      "Contains a count of the total number of indexes across all collections in the database",
	}, []string{"db", "shard"})
	objectsTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Namespace: Namespace,
		Subsystem: "db",
		Name:      "objects_total",
		Help:      "Contains a count of the number of objects (i.e. documents) in the database across all collections",
	}, []string{"db", "shard"})
)

// DatabaseStatus represents stats about a database
type DatabaseStatus struct {
	Name        string                `bson:"db,omitempty"`
	IndexSize   int                   `bson:"indexSize,omitempty"`
	DataSize    int                   `bson:"dataSize,omitempty"`
	Collections int                   `bson:"collections,omitempty"`
	Objects     int                   `bson:"objects,omitempty"`
	Indexes     int                   `bson:"indexes,omitempty"`
	Shards      map[string]*RawStatus `bson:"raw,omitempty"`
}

// RawStatus represents stats about a database shard
type RawStatus struct {
	Name        string `bson:"db,omitempty"`
	IndexSize   int    `bson:"indexSize,omitempty"`
	DataSize    int    `bson:"dataSize,omitempty"`
	Collections int    `bson:"collections,omitempty"`
	Objects     int    `bson:"objects,omitempty"`
	Indexes     int    `bson:"indexes,omitempty"`
}

// Export exports database stats to prometheus
func (dbStatus *DatabaseStatus) Export(ch chan<- prometheus.Metric) {
	if len(dbStatus.Shards) > 0 {
		for shard, stats := range dbStatus.Shards {
			shard = strings.Split(shard, "/")[0]
			indexSize.WithLabelValues(stats.Name, shard).Set(float64(stats.IndexSize))
			dataSize.WithLabelValues(stats.Name, shard).Set(float64(stats.DataSize))
			collectionsTotal.WithLabelValues(stats.Name, shard).Set(float64(stats.Collections))
			indexesTotal.WithLabelValues(stats.Name, shard).Set(float64(stats.Indexes))
			objectsTotal.WithLabelValues(stats.Name, shard).Set(float64(stats.Objects))
		}
	} else {
		indexSize.WithLabelValues(dbStatus.Name, "").Set(float64(dbStatus.IndexSize))
		dataSize.WithLabelValues(dbStatus.Name, "").Set(float64(dbStatus.DataSize))
		collectionsTotal.WithLabelValues(dbStatus.Name, "").Set(float64(dbStatus.Collections))
		indexesTotal.WithLabelValues(dbStatus.Name, "").Set(float64(dbStatus.Indexes))
		objectsTotal.WithLabelValues(dbStatus.Name, "").Set(float64(dbStatus.Objects))
	}

	indexSize.Collect(ch)
	dataSize.Collect(ch)
	collectionsTotal.Collect(ch)
	indexesTotal.Collect(ch)
	objectsTotal.Collect(ch)

	indexSize.Reset()
	dataSize.Reset()
	collectionsTotal.Reset()
	indexesTotal.Reset()
	objectsTotal.Reset()
}

// Describe describes database stats for prometheus
func (dbStatus *DatabaseStatus) Describe(ch chan<- *prometheus.Desc) {
	indexSize.Describe(ch)
	dataSize.Describe(ch)
	collectionsTotal.Describe(ch)
	indexesTotal.Describe(ch)
	objectsTotal.Describe(ch)
}

// GetDatabaseStatus returns stats for a given database
func GetDatabaseStatus(session *mgo.Session, db string) *DatabaseStatus {
	var dbStatus DatabaseStatus
	err := session.DB(db).Run(bson.D{{"dbStats", 1}, {"scale", 1}}, &dbStatus)
	if err != nil {
		glog.Error(err)
		return nil
	}

	return &dbStatus
}
<|endoftext|>


<commit_before>package http

import (
	"bytes"
	"context"
	"encoding/gob"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io"
	"io/ioutil"
	"mime"
	"net/http"
	"strings"
)

const (
	// AcceptTypeKey is the context key used to store the value of the HTTP
	// request Accept-Type header. The value may be used by encoders and
	// decoders to implement a content type negotiation algorithm.
	AcceptTypeKey contextKey = iota + 1
	// ContentTypeKey is the context key used to store the value of the HTTP
	// response Content-Type header when explicitly set in the DSL. The value
	// may be used by encoders to set the header appropriately.
	ContentTypeKey
)

type (
	// Decoder provides the actual decoding algorithm used to load HTTP
	// request and response bodies.
	Decoder interface {
		// Decode decodes into v.
		Decode(v interface{}) error
	}

	// Encoder provides the actual encoding algorithm used to write HTTP
	// request and response bodies.
	Encoder interface {
		// Encode encodes v.
		Encode(v interface{}) error
	}

	// EncodingFunc allows a function with appropriate signature to act as a
	// Decoder/Encoder.
	EncodingFunc func(v interface{}) error

	// private type used to define context keys.
	contextKey int
)

// RequestDecoder returns a HTTP request body decoder suitable for the given
// request. The decoder handles the following mime types:
//
//     * application/json using package encoding/json
//     * application/xml using package encoding/xml
//     * application/gob using package encoding/gob
//
// RequestDecoder defaults to the JSON decoder if the request "Content-Type"
// header does not match any of the supported mime type or is missing
// altogether.
func RequestDecoder(r *http.Request) Decoder {
	contentType := r.Header.Get("Content-Type")
	if contentType == "" {
		// default to JSON
		contentType = "application/json"
	} else {
		// sanitize
		if mediaType, _, err := mime.ParseMediaType(contentType); err == nil {
			contentType = mediaType
		}
	}
	switch contentType {
	case "application/json":
		return json.NewDecoder(r.Body)
	case "application/gob":
		return gob.NewDecoder(r.Body)
	case "application/xml":
		return xml.NewDecoder(r.Body)
	default:
		return json.NewDecoder(r.Body)
	}
}

// ResponseEncoder returns a HTTP response encoder leveraging the mime type
// set in the context under the AcceptTypeKey or the ContentTypeKey if any.
// The encoder supports the following mime types:
//
//     * application/json using package encoding/json
//     * application/xml using package encoding/xml
//     * application/gob using package encoding/gob
//     * text/html for strings
//
// ResponseEncoder defaults to the JSON encoder if the context AcceptTypeKey or
// ContentTypeKey value does not match any of the supported mime types or is
// missing altogether.
func ResponseEncoder(ctx context.Context, w http.ResponseWriter) Encoder {
	negotiate := func(a string) (Encoder, string) {
		switch a {
		case "", "application/json":
			// default to JSON
			return json.NewEncoder(w), "application/json"
		case "application/xml":
			return xml.NewEncoder(w), "application/xml"
		case "application/gob":
			return gob.NewEncoder(w), "application/gob"
		case "text/html":
			return newTextHTMLEncoder(w), "text/html"
		}
		return nil, ""
	}
	var accept string
	{
		if a := ctx.Value(AcceptTypeKey); a != nil {
			accept = a.(string)
		}
	}
	var ct string
	{
		if a := ctx.Value(ContentTypeKey); a != nil {
			ct = a.(string)
		}
	}
	var (
		enc Encoder
		mt  string
		err error
	)
	{
		if ct != "" {
			// If content type explicitly set in the DSL, infer the response encoder
			// from the content type context key.
			if mt, _, err = mime.ParseMediaType(ct); err == nil {
				switch {
				case ct == "application/json" || strings.HasSuffix(ct, "+json"):
					enc = json.NewEncoder(w)
				case ct == "application/xml" || strings.HasSuffix(ct, "+xml"):
					enc = xml.NewEncoder(w)
				case ct == "application/gob" || strings.HasSuffix(ct, "+gob"):
					enc = gob.NewEncoder(w)
				case ct == "text/html" || strings.HasSuffix(ct, "+html"):
					enc = newTextHTMLEncoder(w)
				default:
					enc = json.NewEncoder(w)
				}
			}
			SetContentType(w, mt)
			return enc
		}
		// If Accept header exists in the request, infer the response encoder
		// from the header value.
		if enc, mt = negotiate(accept); enc == nil {
			// attempt to normalize
			if mt, _, err = mime.ParseMediaType(accept); err == nil {
				enc, mt = negotiate(mt)
			}
		}
		if enc == nil {
			enc, mt = negotiate("")
		}
	}
	SetContentType(w, mt)
	return enc
}

// RequestEncoder returns a HTTP request encoder.
// The encoder uses package encoding/json.
func RequestEncoder(r *http.Request) Encoder {
	var buf bytes.Buffer
	r.Body = ioutil.NopCloser(&buf)
	return json.NewEncoder(&buf)
}

// ResponseDecoder returns a HTTP response decoder.
// The decoder handles the following content types:
//
//   * application/json using package encoding/json (default)
//   * application/xml using package encoding/xml
//   * application/gob using package encoding/gob
//   * text/html for strings
//
func ResponseDecoder(resp *http.Response) Decoder {
	ct := resp.Header.Get("Content-Type")
	if ct == "" {
		return json.NewDecoder(resp.Body)
	}
	if mediaType, _, err := mime.ParseMediaType(ct); err == nil {
		ct = mediaType
	}
	switch {
	case ct == "application/json" || strings.HasSuffix(ct, "+json"):
		return json.NewDecoder(resp.Body)
	case ct == "application/xml" || strings.HasSuffix(ct, "+xml"):
		return xml.NewDecoder(resp.Body)
	case ct == "application/gob" || strings.HasSuffix(ct, "+gob"):
		return gob.NewDecoder(resp.Body)
	case ct == "text/html" || strings.HasSuffix(ct, "+html"):
		return newTextHTMLDecoder(resp.Body)
	default:
		return json.NewDecoder(resp.Body)
	}
}

// ErrorEncoder returns an encoder that encodes errors returned by service
// methods. The encoder checks whether the error is a goa ServiceError struct
// and if so uses the error temporary and timeout fields to infer a proper HTTP
// status code and marshals the error struct to the body using the provided
// encoder. If the error is not a goa ServiceError struct then it is encoded
// as a permanent internal server error.
func ErrorEncoder(encoder func(context.Context, http.ResponseWriter) Encoder) func(context.Context, http.ResponseWriter, error) error {
	return func(ctx context.Context, w http.ResponseWriter, err error) error {
		enc := encoder(ctx, w)
		resp := NewErrorResponse(err)
		w.WriteHeader(resp.StatusCode())
		return enc.Encode(resp)
	}
}

// Decode implements the Decoder interface. It simply calls f(v).
func (f EncodingFunc) Decode(v interface{}) error { return f(v) }

// Encode implements the Encoder interface. It simply calls f(v).
func (f EncodingFunc) Encode(v interface{}) error { return f(v) }

// SetContentType initializes the response Content-Type header given a MIME
// type. If the Content-Type header is already set and the MIME type is
// "application/json" or "application/xml" then SetContentType appends a suffix
// to the header ("+json" or "+xml" respectively).
func SetContentType(w http.ResponseWriter, ct string) {
	h := w.Header().Get("Content-Type")
	if h == "" {
		w.Header().Set("Content-Type", ct)
		return
	}
	// RFC6839 only defines suffixes for a few mime types, we only concern
	// ourselves with JSON and XML.
	if ct != "application/json" && ct != "application/xml" {
		w.Header().Set("Content-Type", ct)
		return
	}
	if strings.Contains(h, "+") {
		return
	}
	suffix := "+json"
	if ct == "application/xml" {
		suffix = "+xml"
	}
	w.Header().Set("Content-Type", h+suffix)
}

func newTextHTMLEncoder(w io.Writer) Encoder {
	return &textHTMLEncoder{w}
}

type textHTMLEncoder struct {
	w io.Writer
}

func (e *textHTMLEncoder) Encode(v interface{}) error {
	var err error

	switch c := v.(type) {
	case string:
		_, err = e.w.Write([]byte(c))
	case *string:
		_, err = e.w.Write([]byte(*c))
	case []byte:
		_, err = e.w.Write(c)
	case *[]byte:
		_, err = e.w.Write(*c)
	default:
		err = fmt.Errorf("can't encode %T as text/html", c)
	}

	return err
}

func newTextHTMLDecoder(r io.Reader) Decoder {
	return &textHTMLDecoder{r}
}

type textHTMLDecoder struct {
	r io.Reader
}

func (e *textHTMLDecoder) Decode(v interface{}) error {
	b, err := ioutil.ReadAll(e.r)
	if err != nil {
		return err
	}

	switch c := v.(type) {
	case *string:
		*c = string(b)
	case *[]byte:
		*c = []byte(string(b))
	default:
		err = fmt.Errorf("can't decode text/html to %T", c)
	}

	return err
}
<commit_msg>Explain why we need to support encoding *string<commit_after>package http

import (
	"bytes"
	"context"
	"encoding/gob"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io"
	"io/ioutil"
	"mime"
	"net/http"
	"strings"
)

const (
	// AcceptTypeKey is the context key used to store the value of the HTTP
	// request Accept-Type header. The value may be used by encoders and
	// decoders to implement a content type negotiation algorithm.
	AcceptTypeKey contextKey = iota + 1
	// ContentTypeKey is the context key used to store the value of the HTTP
	// response Content-Type header when explicitly set in the DSL. The value
	// may be used by encoders to set the header appropriately.
	ContentTypeKey
)

type (
	// Decoder provides the actual decoding algorithm used to load HTTP
	// request and response bodies.
	Decoder interface {
		// Decode decodes into v.
		Decode(v interface{}) error
	}

	// Encoder provides the actual encoding algorithm used to write HTTP
	// request and response bodies.
	Encoder interface {
		// Encode encodes v.
		Encode(v interface{}) error
	}

	// EncodingFunc allows a function with appropriate signature to act as a
	// Decoder/Encoder.
	EncodingFunc func(v interface{}) error

	// private type used to define context keys.
	contextKey int
)

// RequestDecoder returns a HTTP request body decoder suitable for the given
// request. The decoder handles the following mime types:
//
//     * application/json using package encoding/json
//     * application/xml using package encoding/xml
//     * application/gob using package encoding/gob
//
// RequestDecoder defaults to the JSON decoder if the request "Content-Type"
// header does not match any of the supported mime type or is missing
// altogether.
func RequestDecoder(r *http.Request) Decoder {
	contentType := r.Header.Get("Content-Type")
	if contentType == "" {
		// default to JSON
		contentType = "application/json"
	} else {
		// sanitize
		if mediaType, _, err := mime.ParseMediaType(contentType); err == nil {
			contentType = mediaType
		}
	}
	switch contentType {
	case "application/json":
		return json.NewDecoder(r.Body)
	case "application/gob":
		return gob.NewDecoder(r.Body)
	case "application/xml":
		return xml.NewDecoder(r.Body)
	default:
		return json.NewDecoder(r.Body)
	}
}

// ResponseEncoder returns a HTTP response encoder leveraging the mime type
// set in the context under the AcceptTypeKey or the ContentTypeKey if any.
// The encoder supports the following mime types:
//
//     * application/json using package encoding/json
//     * application/xml using package encoding/xml
//     * application/gob using package encoding/gob
//     * text/html for strings
//
// ResponseEncoder defaults to the JSON encoder if the context AcceptTypeKey or
// ContentTypeKey value does not match any of the supported mime types or is
// missing altogether.
func ResponseEncoder(ctx context.Context, w http.ResponseWriter) Encoder {
	negotiate := func(a string) (Encoder, string) {
		switch a {
		case "", "application/json":
			// default to JSON
			return json.NewEncoder(w), "application/json"
		case "application/xml":
			return xml.NewEncoder(w), "application/xml"
		case "application/gob":
			return gob.NewEncoder(w), "application/gob"
		case "text/html":
			return newTextHTMLEncoder(w), "text/html"
		}
		return nil, ""
	}
	var accept string
	{
		if a := ctx.Value(AcceptTypeKey); a != nil {
			accept = a.(string)
		}
	}
	var ct string
	{
		if a := ctx.Value(ContentTypeKey); a != nil {
			ct = a.(string)
		}
	}
	var (
		enc Encoder
		mt  string
		err error
	)
	{
		if ct != "" {
			// If content type explicitly set in the DSL, infer the response encoder
			// from the content type context key.
			if mt, _, err = mime.ParseMediaType(ct); err == nil {
				switch {
				case ct == "application/json" || strings.HasSuffix(ct, "+json"):
					enc = json.NewEncoder(w)
				case ct == "application/xml" || strings.HasSuffix(ct, "+xml"):
					enc = xml.NewEncoder(w)
				case ct == "application/gob" || strings.HasSuffix(ct, "+gob"):
					enc = gob.NewEncoder(w)
				case ct == "text/html" || strings.HasSuffix(ct, "+html"):
					enc = newTextHTMLEncoder(w)
				default:
					enc = json.NewEncoder(w)
				}
			}
			SetContentType(w, mt)
			return enc
		}
		// If Accept header exists in the request, infer the response encoder
		// from the header value.
		if enc, mt = negotiate(accept); enc == nil {
			// attempt to normalize
			if mt, _, err = mime.ParseMediaType(accept); err == nil {
				enc, mt = negotiate(mt)
			}
		}
		if enc == nil {
			enc, mt = negotiate("")
		}
	}
	SetContentType(w, mt)
	return enc
}

// RequestEncoder returns a HTTP request encoder.
// The encoder uses package encoding/json.
func RequestEncoder(r *http.Request) Encoder {
	var buf bytes.Buffer
	r.Body = ioutil.NopCloser(&buf)
	return json.NewEncoder(&buf)
}

// ResponseDecoder returns a HTTP response decoder.
// The decoder handles the following content types:
//
//   * application/json using package encoding/json (default)
//   * application/xml using package encoding/xml
//   * application/gob using package encoding/gob
//   * text/html for strings
//
func ResponseDecoder(resp *http.Response) Decoder {
	ct := resp.Header.Get("Content-Type")
	if ct == "" {
		return json.NewDecoder(resp.Body)
	}
	if mediaType, _, err := mime.ParseMediaType(ct); err == nil {
		ct = mediaType
	}
	switch {
	case ct == "application/json" || strings.HasSuffix(ct, "+json"):
		return json.NewDecoder(resp.Body)
	case ct == "application/xml" || strings.HasSuffix(ct, "+xml"):
		return xml.NewDecoder(resp.Body)
	case ct == "application/gob" || strings.HasSuffix(ct, "+gob"):
		return gob.NewDecoder(resp.Body)
	case ct == "text/html" || strings.HasSuffix(ct, "+html"):
		return newTextHTMLDecoder(resp.Body)
	default:
		return json.NewDecoder(resp.Body)
	}
}

// ErrorEncoder returns an encoder that encodes errors returned by service
// methods. The encoder checks whether the error is a goa ServiceError struct
// and if so uses the error temporary and timeout fields to infer a proper HTTP
// status code and marshals the error struct to the body using the provided
// encoder. If the error is not a goa ServiceError struct then it is encoded
// as a permanent internal server error.
func ErrorEncoder(encoder func(context.Context, http.ResponseWriter) Encoder) func(context.Context, http.ResponseWriter, error) error {
	return func(ctx context.Context, w http.ResponseWriter, err error) error {
		enc := encoder(ctx, w)
		resp := NewErrorResponse(err)
		w.WriteHeader(resp.StatusCode())
		return enc.Encode(resp)
	}
}

// Decode implements the Decoder interface. It simply calls f(v).
func (f EncodingFunc) Decode(v interface{}) error { return f(v) }

// Encode implements the Encoder interface. It simply calls f(v).
func (f EncodingFunc) Encode(v interface{}) error { return f(v) }

// SetContentType initializes the response Content-Type header given a MIME
// type. If the Content-Type header is already set and the MIME type is
// "application/json" or "application/xml" then SetContentType appends a suffix
// to the header ("+json" or "+xml" respectively).
func SetContentType(w http.ResponseWriter, ct string) {
	h := w.Header().Get("Content-Type")
	if h == "" {
		w.Header().Set("Content-Type", ct)
		return
	}
	// RFC6839 only defines suffixes for a few mime types, we only concern
	// ourselves with JSON and XML.
	if ct != "application/json" && ct != "application/xml" {
		w.Header().Set("Content-Type", ct)
		return
	}
	if strings.Contains(h, "+") {
		return
	}
	suffix := "+json"
	if ct == "application/xml" {
		suffix = "+xml"
	}
	w.Header().Set("Content-Type", h+suffix)
}

func newTextHTMLEncoder(w io.Writer) Encoder {
	return &textHTMLEncoder{w}
}

type textHTMLEncoder struct {
	w io.Writer
}

func (e *textHTMLEncoder) Encode(v interface{}) error {
	var err error

	switch c := v.(type) {
	case string:
		_, err = e.w.Write([]byte(c))
	case *string: // v may be a string pointer when the Response Body is set to the field of a custom response type.
		_, err = e.w.Write([]byte(*c))
	case []byte:
		_, err = e.w.Write(c)
	default:
		err = fmt.Errorf("can't encode %T as text/html", c)
	}

	return err
}

func newTextHTMLDecoder(r io.Reader) Decoder {
	return &textHTMLDecoder{r}
}

type textHTMLDecoder struct {
	r io.Reader
}

func (e *textHTMLDecoder) Decode(v interface{}) error {
	b, err := ioutil.ReadAll(e.r)
	if err != nil {
		return err
	}

	switch c := v.(type) {
	case *string:
		*c = string(b)
	case *[]byte:
		*c = []byte(string(b))
	default:
		err = fmt.Errorf("can't decode text/html to %T", c)
	}

	return err
}
<|endoftext|>


<commit_before>package victor

import (
	"fmt"
	"github.com/brettbuddin/victor/pkg/httpserver"
	"github.com/gorilla/mux"
	"io/ioutil"
	"net/http"
)

func handlers(bot *Robot) *mux.Router {
	router := mux.NewRouter()

	router.HandleFunc("/action", func(w http.ResponseWriter, r *http.Request) {
		text, _ := ioutil.ReadAll(r.Body)
		bot.Receive(&textMessage{string(text)})
	}).Methods("POST")

	router.HandleFunc("/data", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprintf(w, httpserver.Message("ok", bot.Store().All()))
	}).Methods("GET")

	router.HandleFunc("/data/{key}", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		vars := mux.Vars(r)
		if val, ok := bot.Store().Get(vars["key"]); ok {
			fmt.Fprintf(w, httpserver.Message("ok", val))
		} else {
			fmt.Fprintf(w, httpserver.Message("error", "key doesn't exist"))
		}
	}).Methods("GET")

	router.HandleFunc("/data/{key}", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		vars := mux.Vars(r)
		body, _ := ioutil.ReadAll(r.Body)
		bot.Store().Set(vars["key"], string(body))
		fmt.Fprintf(w, httpserver.Message("ok", "key set"))
	}).Methods("POST", "PUT")

	router.HandleFunc("/data/{key}", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		vars := mux.Vars(r)
		bot.Store().Delete(vars["key"])
		fmt.Fprintf(w, httpserver.Message("ok", "key deleted"))
	}).Methods("DELETE")

    return router
}

type textMessage struct {
	text string
}

func (m *textMessage) UserId() string {
	return ""
}

func (m *textMessage) UserName() string {
	return ""
}

func (m *textMessage) ChannelId() string {
	return ""
}

func (m *textMessage) ChannelName() string {
	return ""
}

func (m *textMessage) Text() string {
	return m.text
}
<commit_msg>Taking this action thing out<commit_after>package victor

import (
	"fmt"
	"github.com/brettbuddin/victor/pkg/httpserver"
	"github.com/gorilla/mux"
	"io/ioutil"
	"net/http"
)

func handlers(bot *Robot) *mux.Router {
	router := mux.NewRouter()

	router.HandleFunc("/data", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprintf(w, httpserver.Message("ok", bot.Store().All()))
	}).Methods("GET")

	router.HandleFunc("/data/{key}", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		vars := mux.Vars(r)
		if val, ok := bot.Store().Get(vars["key"]); ok {
			fmt.Fprintf(w, httpserver.Message("ok", val))
		} else {
			fmt.Fprintf(w, httpserver.Message("error", "key doesn't exist"))
		}
	}).Methods("GET")

	router.HandleFunc("/data/{key}", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		vars := mux.Vars(r)
		body, _ := ioutil.ReadAll(r.Body)
		bot.Store().Set(vars["key"], string(body))
		fmt.Fprintf(w, httpserver.Message("ok", "key set"))
	}).Methods("POST", "PUT")

	router.HandleFunc("/data/{key}", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		vars := mux.Vars(r)
		bot.Store().Delete(vars["key"])
		fmt.Fprintf(w, httpserver.Message("ok", "key deleted"))
	}).Methods("DELETE")

    return router
}

type textMessage struct {
	text string
}

func (m *textMessage) UserId() string {
	return ""
}

func (m *textMessage) UserName() string {
	return ""
}

func (m *textMessage) ChannelId() string {
	return ""
}

func (m *textMessage) ChannelName() string {
	return ""
}

func (m *textMessage) Text() string {
	return m.text
}
<|endoftext|>


<commit_before>// Copyright 2015 CoreOS, Inc.
//
// 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.

// The vmware provider fetches a configuration from the VMware Guest Info
// interface.

package vmware

import (
	"github.com/coreos/ignition/v2/config/v3_4_experimental/types"
	"github.com/coreos/ignition/v2/internal/providers"
	"github.com/coreos/ignition/v2/internal/providers/util"
	"github.com/coreos/ignition/v2/internal/resource"

	"github.com/coreos/vcontext/report"
	"github.com/vmware/vmw-guestinfo/rpcvmx"
	"github.com/vmware/vmw-guestinfo/vmcheck"
	"github.com/vmware/vmw-ovflib"
)

func FetchConfig(f *resource.Fetcher) (types.Config, report.Report, error) {
	if isVM, err := vmcheck.IsVirtualWorld(true); err != nil {
		return types.Config{}, report.Report{}, err
	} else if !isVM {
		return types.Config{}, report.Report{}, providers.ErrNoProvider
	}

	config, err := fetchRawConfig(f)
	if err != nil {
		return types.Config{}, report.Report{}, err
	}

	decodedData, err := decodeConfig(config)
	if err != nil {
		f.Logger.Debug("failed to decode config: %v", err)
		return types.Config{}, report.Report{}, err
	}

	f.Logger.Debug("config successfully fetched")
	return util.ParseConfig(f.Logger, decodedData)
}

func fetchRawConfig(f *resource.Fetcher) (config, error) {
	info := rpcvmx.NewConfig()

	var ovfData string
	var ovfEncoding string

	ovfEnv, err := info.String("ovfenv", "")
	if err != nil {
		f.Logger.Warning("failed to fetch ovfenv: %v. Continuing...", err)
	} else if ovfEnv != "" {
		f.Logger.Debug("using OVF environment from guestinfo")
		env, err := ovf.ReadEnvironment([]byte(ovfEnv))
		if err != nil {
			f.Logger.Warning("failed to parse OVF environment: %v. Continuing...", err)
		}

		ovfData = env.Properties["guestinfo.ignition.config.data"]
		ovfEncoding = env.Properties["guestinfo.ignition.config.data.encoding"]
	}

	data, err := info.String("ignition.config.data", ovfData)
	if err != nil {
		f.Logger.Debug("failed to fetch config: %v", err)
		return config{}, err
	}

	encoding, err := info.String("ignition.config.data.encoding", ovfEncoding)
	if err != nil {
		f.Logger.Debug("failed to fetch config encoding: %v", err)
		return config{}, err
	}

	return config{
		data:     data,
		encoding: encoding,
	}, nil
}
<commit_msg>providers/vmware: add constants for guestinfo and OVF property names<commit_after>// Copyright 2015 CoreOS, Inc.
//
// 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.

// The vmware provider fetches a configuration from the VMware Guest Info
// interface.

package vmware

import (
	"github.com/coreos/ignition/v2/config/v3_4_experimental/types"
	"github.com/coreos/ignition/v2/internal/providers"
	"github.com/coreos/ignition/v2/internal/providers/util"
	"github.com/coreos/ignition/v2/internal/resource"

	"github.com/coreos/vcontext/report"
	"github.com/vmware/vmw-guestinfo/rpcvmx"
	"github.com/vmware/vmw-guestinfo/vmcheck"
	"github.com/vmware/vmw-ovflib"
)

const (
	GUESTINFO_OVF               = "ovfenv"
	GUESTINFO_USERDATA          = "ignition.config.data"
	GUESTINFO_USERDATA_ENCODING = "ignition.config.data.encoding"

	OVF_PREFIX            = "guestinfo."
	OVF_USERDATA          = OVF_PREFIX + GUESTINFO_USERDATA
	OVF_USERDATA_ENCODING = OVF_PREFIX + GUESTINFO_USERDATA_ENCODING
)

func FetchConfig(f *resource.Fetcher) (types.Config, report.Report, error) {
	if isVM, err := vmcheck.IsVirtualWorld(true); err != nil {
		return types.Config{}, report.Report{}, err
	} else if !isVM {
		return types.Config{}, report.Report{}, providers.ErrNoProvider
	}

	config, err := fetchRawConfig(f)
	if err != nil {
		return types.Config{}, report.Report{}, err
	}

	decodedData, err := decodeConfig(config)
	if err != nil {
		f.Logger.Debug("failed to decode config: %v", err)
		return types.Config{}, report.Report{}, err
	}

	f.Logger.Debug("config successfully fetched")
	return util.ParseConfig(f.Logger, decodedData)
}

func fetchRawConfig(f *resource.Fetcher) (config, error) {
	info := rpcvmx.NewConfig()

	var ovfData string
	var ovfEncoding string

	ovfEnv, err := info.String(GUESTINFO_OVF, "")
	if err != nil {
		f.Logger.Warning("failed to fetch ovfenv: %v. Continuing...", err)
	} else if ovfEnv != "" {
		f.Logger.Debug("using OVF environment from guestinfo")
		env, err := ovf.ReadEnvironment([]byte(ovfEnv))
		if err != nil {
			f.Logger.Warning("failed to parse OVF environment: %v. Continuing...", err)
		}

		ovfData = env.Properties[OVF_USERDATA]
		ovfEncoding = env.Properties[OVF_USERDATA_ENCODING]
	}

	data, err := info.String(GUESTINFO_USERDATA, ovfData)
	if err != nil {
		f.Logger.Debug("failed to fetch config: %v", err)
		return config{}, err
	}

	encoding, err := info.String(GUESTINFO_USERDATA_ENCODING, ovfEncoding)
	if err != nil {
		f.Logger.Debug("failed to fetch config encoding: %v", err)
		return config{}, err
	}

	return config{
		data:     data,
		encoding: encoding,
	}, nil
}
<|endoftext|>


<commit_before>package gmgmap

import "math/rand"

// NewVillage - create a village, made up of multiple buildings
func NewVillage(width, height, buildingPadding int) *Map {
	m := NewMap(width, height)

	// Grass
	g := m.Layer("Ground")
	g.fill(grass)

	// Buildings
	s := m.Layer("Structures")
	buildings := make([]rect, 0)
	// Keep placing buildings for a while
	for i := 0; i < 500; i++ {
		w := rand.Intn(3) + 5
		h := rand.Intn(3) + 5
		x := rand.Intn(width - w)
		y := rand.Intn(height - h)
		if x < 0 || y < 0 {
			continue
		}
		// Check if it overlaps with any existing buildings
		overlaps := false
		for _, r := range buildings {
			// Add a bit of padding between the buildings
			if rectOverlaps(
				r,
				rect{
					x - buildingPadding,
					y - buildingPadding,
					w + buildingPadding*2,
					h + buildingPadding*2}) {
				overlaps = true
				break
			}
		}
		if overlaps {
			continue
		}
		addBuilding(s, x, y, w, h)
		buildings = append(buildings, rect{x, y, w, h})
	}

	return m
}

func addBuilding(s *Layer, x, y, w, h int) {
	// Perimeter
	s.rectangle(rect{x, y, w, h}, wall, false)
}
<commit_msg>Building floor<commit_after>package gmgmap

import "math/rand"

// NewVillage - create a village, made up of multiple buildings
func NewVillage(width, height, buildingPadding int) *Map {
	m := NewMap(width, height)
	g := m.Layer("Ground")
	s := m.Layer("Structures")

	// Grass
	g.fill(grass)

	// Buildings
	buildings := make([]rect, 0)
	// Keep placing buildings for a while
	for i := 0; i < 500; i++ {
		w := rand.Intn(3) + 5
		h := rand.Intn(3) + 5
		x := rand.Intn(width - w)
		y := rand.Intn(height - h)
		if x < 0 || y < 0 {
			continue
		}
		// Check if it overlaps with any existing buildings
		overlaps := false
		for _, r := range buildings {
			// Add a bit of padding between the buildings
			if rectOverlaps(
				r,
				rect{
					x - buildingPadding,
					y - buildingPadding,
					w + buildingPadding*2,
					h + buildingPadding*2}) {
				overlaps = true
				break
			}
		}
		if overlaps {
			continue
		}
		addBuilding(g, s, x, y, w, h)
		buildings = append(buildings, rect{x, y, w, h})
	}

	return m
}

func addBuilding(g, s *Layer, x, y, w, h int) {
	// Perimeter
	s.rectangle(rect{x, y, w, h}, wall, false)
	// Floor
	g.rectangle(rect{x + 1, y + 1, w - 2, h - 2}, room, true)
}
<|endoftext|>


<commit_before>package nv

import (
  "encoding/json"
  "time"
)

type NV_Event struct {
    ServerID    string
    Timestamp   time.Time
    ARI_Event   string
}

func Init(in chan string, out chan *NV_Event) {
  go func(in chan string, out chan *NV_Event) {
    for instring := range in {
      var e *NV_Event
      json.Unmarshal([]byte(instring), e)
      out <- e
    }
  }(in, out)
}<commit_msg>Inbound channel needs to be []byte not string<commit_after>package nv

import (
  "encoding/json"
  "time"
)

type NV_Event struct {
    ServerID    string
    Timestamp   time.Time
    ARI_Event   string
}

func Init(in chan []byte, out chan *NV_Event) {
  go func(in chan []byte, out chan *NV_Event) {
    for instring := range in {
      var e *NV_Event
      json.Unmarshal(instring, e)
      out <- e
    }
  }(in, out)
}<|endoftext|>


<commit_before>package kubernetes

import (
	"context"
	"time"

	"github.com/pkg/errors"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/pkg/watch"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
)

// Client represents the wrapper of Kubernetes API client
type Client struct {
	clientConfig clientcmd.ClientConfig
	clientset    *kubernetes.Clientset
}

// PodEvent represents Pod termination event
type PodEvent struct {
	Namespace  string
	PodName    string
	StartedAt  time.Time
	FinishedAt time.Time
	ExitCode   int
	Reason     string
}

// NotifyFunc represents callback function for Pod event
type NotifyFunc func(event *PodEvent) error

// NewClient creates Client object using local kubecfg
func NewClient(kubeconfig, context string) (*Client, error) {
	clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
		&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig},
		&clientcmd.ConfigOverrides{CurrentContext: context})

	config, err := clientConfig.ClientConfig()
	if err != nil {
		return nil, errors.Wrap(err, "falied to load local kubeconfig")
	}

	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, errors.Wrap(err, "failed to load clientset")
	}

	return &Client{
		clientConfig: clientConfig,
		clientset:    clientset,
	}, nil
}

// NewClientInCluster creates Client object in Kubernetes cluster
func NewClientInCluster() (*Client, error) {
	config, err := rest.InClusterConfig()
	if err != nil {
		return nil, errors.Wrap(err, "failed to load kubeconfig in cluster")
	}

	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, errors.Wrap(err, "falied to load clientset")
	}

	return &Client{
		clientset: clientset,
	}, nil
}

// NamespaceInConfig returns namespace set in kubeconfig
func (c *Client) NamespaceInConfig() (string, error) {
	if c.clientConfig == nil {
		return "", errors.New("clientConfig is not set")
	}

	rawConfig, err := c.clientConfig.RawConfig()
	if err != nil {
		return "", errors.Wrap(err, "failed to load rawConfig")
	}

	return rawConfig.Contexts[rawConfig.CurrentContext].Namespace, nil
}

// WatchPodEvents watches Pod events
func (c *Client) WatchPodEvents(ctx context.Context, namespace, labels string, succeededFunc, failedFunc NotifyFunc) error {
	watcher, err := c.clientset.Core().Pods(namespace).Watch(v1.ListOptions{
		LabelSelector: labels,
	})
	if err != nil {
		return errors.Wrap(err, "cannot create Pod event watcher")
	}

	go func() {
		for {
			select {
			case e := <-watcher.ResultChan():
				if e.Object == nil {
					return
				}

				pod, ok := e.Object.(*v1.Pod)
				if !ok {
					continue
				}

				switch e.Type {
				case watch.Modified:
					if pod.DeletionTimestamp != nil {
						continue
					}

					startedAt := pod.CreationTimestamp.Time

					switch pod.Status.Phase {
					case v1.PodSucceeded:
						for _, cst := range pod.Status.ContainerStatuses {
							if cst.State.Terminated == nil {
								continue
							}

							finishedAt := cst.State.Terminated.FinishedAt.Time

							succeededFunc(&PodEvent{
								Namespace:  pod.Namespace,
								PodName:    pod.Name,
								StartedAt:  startedAt,
								FinishedAt: finishedAt,
								ExitCode:   0,
								Reason:     "",
							})
						}
					case v1.PodFailed:
						for _, cst := range pod.Status.ContainerStatuses {
							if cst.State.Terminated == nil {
								continue
							}

							finishedAt := cst.State.Terminated.FinishedAt.Time

							failedFunc(&PodEvent{
								Namespace:  pod.Namespace,
								PodName:    pod.Name,
								StartedAt:  startedAt,
								FinishedAt: finishedAt,
								ExitCode:   int(cst.State.Terminated.ExitCode),
								Reason:     cst.State.Terminated.Reason,
							})
						}
					}
				}
			case <-ctx.Done():
				watcher.Stop()
				return
			}
		}
	}()

	return nil
}
<commit_msg>Accept 1 container only<commit_after>package kubernetes

import (
	"context"
	"time"

	"github.com/pkg/errors"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/pkg/watch"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
)

// Client represents the wrapper of Kubernetes API client
type Client struct {
	clientConfig clientcmd.ClientConfig
	clientset    *kubernetes.Clientset
}

// PodEvent represents Pod termination event
type PodEvent struct {
	Namespace  string
	PodName    string
	StartedAt  time.Time
	FinishedAt time.Time
	ExitCode   int
	Reason     string
}

// NotifyFunc represents callback function for Pod event
type NotifyFunc func(event *PodEvent) error

// NewClient creates Client object using local kubecfg
func NewClient(kubeconfig, context string) (*Client, error) {
	clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
		&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig},
		&clientcmd.ConfigOverrides{CurrentContext: context})

	config, err := clientConfig.ClientConfig()
	if err != nil {
		return nil, errors.Wrap(err, "falied to load local kubeconfig")
	}

	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, errors.Wrap(err, "failed to load clientset")
	}

	return &Client{
		clientConfig: clientConfig,
		clientset:    clientset,
	}, nil
}

// NewClientInCluster creates Client object in Kubernetes cluster
func NewClientInCluster() (*Client, error) {
	config, err := rest.InClusterConfig()
	if err != nil {
		return nil, errors.Wrap(err, "failed to load kubeconfig in cluster")
	}

	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, errors.Wrap(err, "falied to load clientset")
	}

	return &Client{
		clientset: clientset,
	}, nil
}

// NamespaceInConfig returns namespace set in kubeconfig
func (c *Client) NamespaceInConfig() (string, error) {
	if c.clientConfig == nil {
		return "", errors.New("clientConfig is not set")
	}

	rawConfig, err := c.clientConfig.RawConfig()
	if err != nil {
		return "", errors.Wrap(err, "failed to load rawConfig")
	}

	return rawConfig.Contexts[rawConfig.CurrentContext].Namespace, nil
}

// WatchPodEvents watches Pod events
func (c *Client) WatchPodEvents(ctx context.Context, namespace, labels string, succeededFunc, failedFunc NotifyFunc) error {
	watcher, err := c.clientset.Core().Pods(namespace).Watch(v1.ListOptions{
		LabelSelector: labels,
	})
	if err != nil {
		return errors.Wrap(err, "cannot create Pod event watcher")
	}

	go func() {
		for {
			select {
			case e := <-watcher.ResultChan():
				if e.Object == nil {
					return
				}

				pod, ok := e.Object.(*v1.Pod)
				if !ok {
					continue
				}

				switch e.Type {
				case watch.Modified:
					if pod.DeletionTimestamp != nil {
						continue
					}

					startedAt := pod.CreationTimestamp.Time

					switch pod.Status.Phase {
					case v1.PodSucceeded:
						for _, cst := range pod.Status.ContainerStatuses {
							if cst.State.Terminated == nil {
								continue
							}

							finishedAt := cst.State.Terminated.FinishedAt.Time

							succeededFunc(&PodEvent{
								Namespace:  pod.Namespace,
								PodName:    pod.Name,
								StartedAt:  startedAt,
								FinishedAt: finishedAt,
								ExitCode:   0,
								Reason:     "",
							})

							break
						}
					case v1.PodFailed:
						for _, cst := range pod.Status.ContainerStatuses {
							if cst.State.Terminated == nil {
								continue
							}

							finishedAt := cst.State.Terminated.FinishedAt.Time

							failedFunc(&PodEvent{
								Namespace:  pod.Namespace,
								PodName:    pod.Name,
								StartedAt:  startedAt,
								FinishedAt: finishedAt,
								ExitCode:   int(cst.State.Terminated.ExitCode),
								Reason:     cst.State.Terminated.Reason,
							})

							break
						}
					}
				}
			case <-ctx.Done():
				watcher.Stop()
				return
			}
		}
	}()

	return nil
}
<|endoftext|>


<commit_before>// Copyright (C) 2014 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.

// Package events provides event subscription and polling functionality.
package events

import (
	"errors"
	stdsync "sync"
	"time"

	"github.com/syncthing/syncthing/lib/sync"
)

type EventType int

const (
	Ping EventType = 1 << iota
	Starting
	StartupComplete
	DeviceDiscovered
	DeviceConnected
	DeviceDisconnected
	DeviceRejected
	DevicePaused
	DeviceResumed
	LocalIndexUpdated
	RemoteIndexUpdated
	ItemStarted
	ItemFinished
	StateChanged
	FolderRejected
	ConfigSaved
	DownloadProgress
	FolderSummary
	FolderCompletion
	FolderErrors
	FolderScanProgress
	ExternalPortMappingChanged
	RelayStateChanged
	LoginAttempt

	AllEvents = (1 << iota) - 1
)

func (t EventType) String() string {
	switch t {
	case Ping:
		return "Ping"
	case Starting:
		return "Starting"
	case StartupComplete:
		return "StartupComplete"
	case DeviceDiscovered:
		return "DeviceDiscovered"
	case DeviceConnected:
		return "DeviceConnected"
	case DeviceDisconnected:
		return "DeviceDisconnected"
	case DeviceRejected:
		return "DeviceRejected"
	case LocalIndexUpdated:
		return "LocalIndexUpdated"
	case RemoteIndexUpdated:
		return "RemoteIndexUpdated"
	case ItemStarted:
		return "ItemStarted"
	case ItemFinished:
		return "ItemFinished"
	case StateChanged:
		return "StateChanged"
	case FolderRejected:
		return "FolderRejected"
	case ConfigSaved:
		return "ConfigSaved"
	case DownloadProgress:
		return "DownloadProgress"
	case FolderSummary:
		return "FolderSummary"
	case FolderCompletion:
		return "FolderCompletion"
	case FolderErrors:
		return "FolderErrors"
	case DevicePaused:
		return "DevicePaused"
	case DeviceResumed:
		return "DeviceResumed"
	case FolderScanProgress:
		return "FolderScanProgress"
	case ExternalPortMappingChanged:
		return "ExternalPortMappingChanged"
	case RelayStateChanged:
		return "RelayStateChanged"
	case LoginAttempt:
		return "LoginAttempt"
	default:
		return "Unknown"
	}
}

func (t EventType) MarshalText() ([]byte, error) {
	return []byte(t.String()), nil
}

const BufferSize = 64

type Logger struct {
	subs   []*Subscription
	nextID int
	mutex  sync.Mutex
}

type Event struct {
	ID   int         `json:"id"`
	Time time.Time   `json:"time"`
	Type EventType   `json:"type"`
	Data interface{} `json:"data"`
}

type Subscription struct {
	mask    EventType
	events  chan Event
	timeout *time.Timer
}

var Default = NewLogger()

var (
	ErrTimeout = errors.New("timeout")
	ErrClosed  = errors.New("closed")
)

func NewLogger() *Logger {
	return &Logger{
		mutex: sync.NewMutex(),
	}
}

func (l *Logger) Log(t EventType, data interface{}) {
	l.mutex.Lock()
	dl.Debugln("log", l.nextID, t, data)
	l.nextID++
	e := Event{
		ID:   l.nextID,
		Time: time.Now(),
		Type: t,
		Data: data,
	}
	for _, s := range l.subs {
		if s.mask&t != 0 {
			select {
			case s.events <- e:
			default:
				// if s.events is not ready, drop the event
			}
		}
	}
	l.mutex.Unlock()
}

func (l *Logger) Subscribe(mask EventType) *Subscription {
	l.mutex.Lock()
	dl.Debugln("subscribe", mask)
	s := &Subscription{
		mask:    mask,
		events:  make(chan Event, BufferSize),
		timeout: time.NewTimer(0),
	}
	l.subs = append(l.subs, s)
	l.mutex.Unlock()
	return s
}

func (l *Logger) Unsubscribe(s *Subscription) {
	l.mutex.Lock()
	dl.Debugln("unsubscribe")
	for i, ss := range l.subs {
		if s == ss {
			last := len(l.subs) - 1
			l.subs[i] = l.subs[last]
			l.subs[last] = nil
			l.subs = l.subs[:last]
			break
		}
	}
	close(s.events)
	l.mutex.Unlock()
}

// Poll returns an event from the subscription or an error if the poll times
// out of the event channel is closed. Poll should not be called concurrently
// from multiple goroutines for a single subscription.
func (s *Subscription) Poll(timeout time.Duration) (Event, error) {
	dl.Debugln("poll", timeout)

	if !s.timeout.Reset(timeout) {
		select {
		case <-s.timeout.C:
		default:
		}
	}

	select {
	case e, ok := <-s.events:
		if !ok {
			return e, ErrClosed
		}
		s.timeout.Stop()
		return e, nil
	case <-s.timeout.C:
		return Event{}, ErrTimeout
	}
}

func (s *Subscription) C() <-chan Event {
	return s.events
}

type BufferedSubscription struct {
	sub  *Subscription
	buf  []Event
	next int
	cur  int
	mut  sync.Mutex
	cond *stdsync.Cond
}

func NewBufferedSubscription(s *Subscription, size int) *BufferedSubscription {
	bs := &BufferedSubscription{
		sub: s,
		buf: make([]Event, size),
		mut: sync.NewMutex(),
	}
	bs.cond = stdsync.NewCond(bs.mut)
	go bs.pollingLoop()
	return bs
}

func (s *BufferedSubscription) pollingLoop() {
	for {
		ev, err := s.sub.Poll(60 * time.Second)
		if err == ErrTimeout {
			continue
		}
		if err == ErrClosed {
			return
		}
		if err != nil {
			panic("unexpected error: " + err.Error())
		}

		s.mut.Lock()
		s.buf[s.next] = ev
		s.next = (s.next + 1) % len(s.buf)
		s.cur = ev.ID
		s.cond.Broadcast()
		s.mut.Unlock()
	}
}

func (s *BufferedSubscription) Since(id int, into []Event) []Event {
	s.mut.Lock()
	defer s.mut.Unlock()

	for id >= s.cur {
		s.cond.Wait()
	}

	for i := s.next; i < len(s.buf); i++ {
		if s.buf[i].ID > id {
			into = append(into, s.buf[i])
		}
	}
	for i := 0; i < s.next; i++ {
		if s.buf[i].ID > id {
			into = append(into, s.buf[i])
		}
	}

	return into
}

// Error returns a string pointer suitable for JSON marshalling errors. It
// retains the "null on success" semantics, but ensures the error result is a
// string regardless of the underlying concrete error type.
func Error(err error) *string {
	if err == nil {
		return nil
	}
	str := err.Error()
	return &str
}
<commit_msg>Don't cause rare spurious event timeout<commit_after>// Copyright (C) 2014 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.

// Package events provides event subscription and polling functionality.
package events

import (
	"errors"
	stdsync "sync"
	"time"

	"github.com/syncthing/syncthing/lib/sync"
)

type EventType int

const (
	Ping EventType = 1 << iota
	Starting
	StartupComplete
	DeviceDiscovered
	DeviceConnected
	DeviceDisconnected
	DeviceRejected
	DevicePaused
	DeviceResumed
	LocalIndexUpdated
	RemoteIndexUpdated
	ItemStarted
	ItemFinished
	StateChanged
	FolderRejected
	ConfigSaved
	DownloadProgress
	FolderSummary
	FolderCompletion
	FolderErrors
	FolderScanProgress
	ExternalPortMappingChanged
	RelayStateChanged
	LoginAttempt

	AllEvents = (1 << iota) - 1
)

func (t EventType) String() string {
	switch t {
	case Ping:
		return "Ping"
	case Starting:
		return "Starting"
	case StartupComplete:
		return "StartupComplete"
	case DeviceDiscovered:
		return "DeviceDiscovered"
	case DeviceConnected:
		return "DeviceConnected"
	case DeviceDisconnected:
		return "DeviceDisconnected"
	case DeviceRejected:
		return "DeviceRejected"
	case LocalIndexUpdated:
		return "LocalIndexUpdated"
	case RemoteIndexUpdated:
		return "RemoteIndexUpdated"
	case ItemStarted:
		return "ItemStarted"
	case ItemFinished:
		return "ItemFinished"
	case StateChanged:
		return "StateChanged"
	case FolderRejected:
		return "FolderRejected"
	case ConfigSaved:
		return "ConfigSaved"
	case DownloadProgress:
		return "DownloadProgress"
	case FolderSummary:
		return "FolderSummary"
	case FolderCompletion:
		return "FolderCompletion"
	case FolderErrors:
		return "FolderErrors"
	case DevicePaused:
		return "DevicePaused"
	case DeviceResumed:
		return "DeviceResumed"
	case FolderScanProgress:
		return "FolderScanProgress"
	case ExternalPortMappingChanged:
		return "ExternalPortMappingChanged"
	case RelayStateChanged:
		return "RelayStateChanged"
	case LoginAttempt:
		return "LoginAttempt"
	default:
		return "Unknown"
	}
}

func (t EventType) MarshalText() ([]byte, error) {
	return []byte(t.String()), nil
}

const BufferSize = 64

type Logger struct {
	subs   []*Subscription
	nextID int
	mutex  sync.Mutex
}

type Event struct {
	ID   int         `json:"id"`
	Time time.Time   `json:"time"`
	Type EventType   `json:"type"`
	Data interface{} `json:"data"`
}

type Subscription struct {
	mask    EventType
	events  chan Event
	timeout *time.Timer
}

var Default = NewLogger()

var (
	ErrTimeout = errors.New("timeout")
	ErrClosed  = errors.New("closed")
)

func NewLogger() *Logger {
	return &Logger{
		mutex: sync.NewMutex(),
	}
}

func (l *Logger) Log(t EventType, data interface{}) {
	l.mutex.Lock()
	dl.Debugln("log", l.nextID, t, data)
	l.nextID++
	e := Event{
		ID:   l.nextID,
		Time: time.Now(),
		Type: t,
		Data: data,
	}
	for _, s := range l.subs {
		if s.mask&t != 0 {
			select {
			case s.events <- e:
			default:
				// if s.events is not ready, drop the event
			}
		}
	}
	l.mutex.Unlock()
}

func (l *Logger) Subscribe(mask EventType) *Subscription {
	l.mutex.Lock()
	dl.Debugln("subscribe", mask)

	s := &Subscription{
		mask:    mask,
		events:  make(chan Event, BufferSize),
		timeout: time.NewTimer(0),
	}

	// We need to create the timeout timer in the stopped, non-fired state so
	// that Subscription.Poll() can safely reset it and select on the timeout
	// channel. This ensures the timer is stopped and the channel drained.
	if !s.timeout.Stop() {
		<-s.timeout.C
	}

	l.subs = append(l.subs, s)
	l.mutex.Unlock()
	return s
}

func (l *Logger) Unsubscribe(s *Subscription) {
	l.mutex.Lock()
	dl.Debugln("unsubscribe")
	for i, ss := range l.subs {
		if s == ss {
			last := len(l.subs) - 1
			l.subs[i] = l.subs[last]
			l.subs[last] = nil
			l.subs = l.subs[:last]
			break
		}
	}
	close(s.events)
	l.mutex.Unlock()
}

// Poll returns an event from the subscription or an error if the poll times
// out of the event channel is closed. Poll should not be called concurrently
// from multiple goroutines for a single subscription.
func (s *Subscription) Poll(timeout time.Duration) (Event, error) {
	dl.Debugln("poll", timeout)

	s.timeout.Reset(timeout)

	select {
	case e, ok := <-s.events:
		if !ok {
			return e, ErrClosed
		}
		if !s.timeout.Stop() {
			// The timeout must be stopped and possibly drained to be ready
			// for reuse in the next call.
			<-s.timeout.C
		}
		return e, nil
	case <-s.timeout.C:
		return Event{}, ErrTimeout
	}
}

func (s *Subscription) C() <-chan Event {
	return s.events
}

type BufferedSubscription struct {
	sub  *Subscription
	buf  []Event
	next int
	cur  int
	mut  sync.Mutex
	cond *stdsync.Cond
}

func NewBufferedSubscription(s *Subscription, size int) *BufferedSubscription {
	bs := &BufferedSubscription{
		sub: s,
		buf: make([]Event, size),
		mut: sync.NewMutex(),
	}
	bs.cond = stdsync.NewCond(bs.mut)
	go bs.pollingLoop()
	return bs
}

func (s *BufferedSubscription) pollingLoop() {
	for {
		ev, err := s.sub.Poll(60 * time.Second)
		if err == ErrTimeout {
			continue
		}
		if err == ErrClosed {
			return
		}
		if err != nil {
			panic("unexpected error: " + err.Error())
		}

		s.mut.Lock()
		s.buf[s.next] = ev
		s.next = (s.next + 1) % len(s.buf)
		s.cur = ev.ID
		s.cond.Broadcast()
		s.mut.Unlock()
	}
}

func (s *BufferedSubscription) Since(id int, into []Event) []Event {
	s.mut.Lock()
	defer s.mut.Unlock()

	for id >= s.cur {
		s.cond.Wait()
	}

	for i := s.next; i < len(s.buf); i++ {
		if s.buf[i].ID > id {
			into = append(into, s.buf[i])
		}
	}
	for i := 0; i < s.next; i++ {
		if s.buf[i].ID > id {
			into = append(into, s.buf[i])
		}
	}

	return into
}

// Error returns a string pointer suitable for JSON marshalling errors. It
// retains the "null on success" semantics, but ensures the error result is a
// string regardless of the underlying concrete error type.
func Error(err error) *string {
	if err == nil {
		return nil
	}
	str := err.Error()
	return &str
}
<|endoftext|>


<commit_before>// login handles logging in users.
package login

// Theory of operation.
//
// We use OAuth 2.0 handle authentication. We are essentially doing OpenID
// Connect, but vastly simplified since we are hardcoded to Google's endpoints.
//
// We do a simple OAuth 2.0 flow where the user is asked to grant permission to
// the 'email' scope. See https://developers.google.com/+/api/oauth#email for
// details on that scope. Note that you need to have the Google Plus API turned
// on in your project for this to work, but note that the 'email' scope will
// still work for people w/o Google Plus accounts.
//
// Now in theory once we are authorized and have an Access Token we could call
// https://developers.google.com/+/api/openidconnect/getOpenIdConnect and get the
// users email address. But here we can cheat, as we know it's Google and that for
// the 'email' scope an ID Token will be returned along with the Access Token.
// If we decode the ID Token we can get the email address out of that w/o the
// need for the second round trip. This is all clearly *ahem* explained here:
//
//   https://developers.google.com/accounts/docs/OAuth2Login#exchangecode
//
// Once we get the users email address we put it in a cookie for later
// retrieval. The cookie value is validated using HMAC to stop spoofing.
//
// N.B. The cookiesaltkey metadata value must be set on the GCE instance.

import (
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"time"

	"code.google.com/p/goauth2/oauth"
	"github.com/golang/glog"
	"github.com/gorilla/securecookie"
	"skia.googlesource.com/buildbot.git/go/util"
)

const (
	COOKIE_NAME         = "skid"
	SESSION_COOKIE_NAME = "sksession"
)

var (
	// cookieSalt is some entropy for our encoders.
	cookieSalt = ""

	secureCookie *securecookie.SecureCookie = nil

	// oauthConfig is the OAuth 2.0 client configuration.
	oauthConfig = &oauth.Config{
		ClientId:     "not-a-valid-client-id",
		ClientSecret: "not-a-valid-client-secret",
		Scope:        "email",
		AuthURL:      "https://accounts.google.com/o/oauth2/auth",
		TokenURL:     "https://accounts.google.com/o/oauth2/token",
		RedirectURL:  "http://localhost:8000/oauth2callback/",

		// We don't need a refresh token, we'll just go through the approval flow again.
		AccessType: "online",

		// And when we go through the approval flow again don't stop if they've already approved once.
		ApprovalPrompt: "auto",
	}

	// domainWhitelist is the list of domains that are allowed to log in to our site.
	domainWhitelist = []string{"google.com", "chromium.org", "skia.org"}
)

// Init must be called before any other methods.
//
// The Client ID, Client Secret, and Redirect URL are listed in the Google
// Developers Console.
func Init(clientId, clientSecret, redirectURL, cookieSalt string) {
	secureCookie = securecookie.New([]byte(cookieSalt), nil)
	oauthConfig.ClientId = clientId
	oauthConfig.ClientSecret = clientSecret
	oauthConfig.RedirectURL = redirectURL
}

// LoginURL returns a URL that the user is to be directed to for login.
func LoginURL(w http.ResponseWriter, r *http.Request) string {
	// Check for a session id, if not there then assign one, and add it to the redirect URL.
	session, err := r.Cookie(SESSION_COOKIE_NAME)
	state := ""
	if err != nil || session.Value == "" {
		b := make([]byte, 16)
		_, err := rand.Read(b)
		if err != nil {
			glog.Errorf("Failed to create a session token: %s", err)
			return ""
		}
		state = fmt.Sprintf("%X", b)
		cookie := &http.Cookie{
			Name:     SESSION_COOKIE_NAME,
			Value:    state,
			Path:     "/",
			HttpOnly: true,
			Expires:  time.Now().Add(365 * 24 * time.Hour),
		}
		http.SetCookie(w, cookie)
	} else {
		state = session.Value
	}

	return oauthConfig.AuthCodeURL(state)
}

// LoggedInAs returns the user's ID, i.e. their email address, if they are
// logged in, and "" if they are not logged in.
func LoggedInAs(r *http.Request) string {
	cookie, err := r.Cookie(COOKIE_NAME)
	if err != nil {
		return ""
	}
	var email string
	if err := secureCookie.Decode(COOKIE_NAME, cookie.Value, &email); err != nil {
		return ""
	}
	return email
}

// A JSON Web Token can contain much info, such as 'iss' and 'sub'. We don't care about
// that, we only want one field which is 'email'.
//
// {
//   "iss":"accounts.google.com",
//   "sub":"110642259984599645813",
//   "email":"jcgregorio@google.com",
//   ...
// }
type decodedIDToken struct {
	Email string `json:"email"`
}

// CookieFor creates an encoded Cookie for the given user id.
func CookieFor(value string) (*http.Cookie, error) {
	encoded, err := secureCookie.Encode(COOKIE_NAME, value)
	if err != nil {
		return nil, fmt.Errorf("Failed to encode cookie")
	}
	return &http.Cookie{
		Name:     COOKIE_NAME,
		Value:    encoded,
		Path:     "/",
		HttpOnly: true,
		Expires:  time.Now().Add(365 * 24 * time.Hour),
	}, nil
}

func setSkIDCookieValue(w http.ResponseWriter, value string) {
	cookie, err := CookieFor(value)
	if err != nil {
		http.Error(w, fmt.Sprintf("%s", err), 500)
		return
	}
	http.SetCookie(w, cookie)
}

// LogoutHandler logs the user out by overwriting the cookie with a blank email
// address.
//
// Note that this doesn't revoke the 'email' grant, so logging in later will
// still be fast. Users can always visit
//
//   https://security.google.com/settings/security/permissions
//
// to revoke any grants they make.
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
	glog.Infof("LogoutHandler\n")
	setSkIDCookieValue(w, "")
	http.Redirect(w, r, r.FormValue("redirect"), 302)
}

// OAuth2CallbackHandler must be attached at a handler that matches
// the callback URL registered in the APIs Console. In this case
// "/oauth2callback".
func OAuth2CallbackHandler(w http.ResponseWriter, r *http.Request) {
	glog.Infof("OAuth2CallbackHandler\n")
	session, err := r.Cookie(SESSION_COOKIE_NAME)
	if err != nil || session.Value == "" {
		http.Error(w, "Invalid session state.", 500)
		return
	}
	state := r.FormValue("state")
	if state != session.Value {
		http.Error(w, "Session state doesn't match callback state.", 500)
		return
	}

	code := r.FormValue("code")
	glog.Infof("Code: %s ", code[:5])
	transport := &oauth.Transport{
		Config: oauthConfig,
		Transport: &http.Transport{
			Dial: util.DialTimeout,
		},
	}
	token, err := transport.Exchange(code)
	if err != nil {
		glog.Errorf("Failed to authenticate: %s", err)
		http.Error(w, "Failed to authenticate.", 500)
		return
	}
	// idToken is a JSON Web Token. We only need to decode the token, we do not
	// need to validate the token because it came to us over HTTPS directly from
	// Google's servers.
	idToken := token.Extra["id_token"]
	// The id token is actually three base64 encoded parts that are "." separated.
	segments := strings.Split(idToken, ".")
	if len(segments) != 3 {
		http.Error(w, "Invalid id_token.", 500)
		return
	}
	// Now base64 decode the middle segment, which decodes to JSON.
	padding := 4 - (len(segments[1]) % 4)
	b, err := base64.StdEncoding.DecodeString(segments[1] + strings.Repeat("=", padding))
	if err != nil {
		http.Error(w, "Failed to base64 decode id_token.", 500)
		return
	}
	// Finally decode the JSON.
	decoded := &decodedIDToken{}
	if err := json.Unmarshal(b, decoded); err != nil {
		http.Error(w, "Failed to JSON decode id_token.", 500)
		return
	}
	parts := strings.Split(decoded.Email, "@")
	if len(parts) != 2 {
		http.Error(w, "Invalid email address received.", 500)
		return
	}
	if !util.In(parts[1], domainWhitelist) {
		http.Error(w, "Accounts from your domain are not allowed.", 500)
		return
	}
	setSkIDCookieValue(w, decoded.Email)
	http.Redirect(w, r, "/", 302)
}

// StatusHandler returns the login status of the user as JSON that looks like:
//
// {
//   "Email": "fred@example.com",
//   "LoginURL": "https://..."
// }
//
func StatusHandler(w http.ResponseWriter, r *http.Request) {
	glog.Infof("StatusHandler\n")
	w.Header().Set("Content-Type", "application/json")
	enc := json.NewEncoder(w)
	body := map[string]string{
		"Email":    LoggedInAs(r),
		"LoginURL": LoginURL(w, r),
	}
	if err := enc.Encode(body); err != nil {
		glog.Errorf("Failed to encode Login status to JSON", err)
	}
}
<commit_msg>Fix base64 decoding of id tokens.<commit_after>// login handles logging in users.
package login

// Theory of operation.
//
// We use OAuth 2.0 handle authentication. We are essentially doing OpenID
// Connect, but vastly simplified since we are hardcoded to Google's endpoints.
//
// We do a simple OAuth 2.0 flow where the user is asked to grant permission to
// the 'email' scope. See https://developers.google.com/+/api/oauth#email for
// details on that scope. Note that you need to have the Google Plus API turned
// on in your project for this to work, but note that the 'email' scope will
// still work for people w/o Google Plus accounts.
//
// Now in theory once we are authorized and have an Access Token we could call
// https://developers.google.com/+/api/openidconnect/getOpenIdConnect and get the
// users email address. But here we can cheat, as we know it's Google and that for
// the 'email' scope an ID Token will be returned along with the Access Token.
// If we decode the ID Token we can get the email address out of that w/o the
// need for the second round trip. This is all clearly *ahem* explained here:
//
//   https://developers.google.com/accounts/docs/OAuth2Login#exchangecode
//
// Once we get the users email address we put it in a cookie for later
// retrieval. The cookie value is validated using HMAC to stop spoofing.
//
// N.B. The cookiesaltkey metadata value must be set on the GCE instance.

import (
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"time"

	"code.google.com/p/goauth2/oauth"
	"github.com/golang/glog"
	"github.com/gorilla/securecookie"
	"skia.googlesource.com/buildbot.git/go/util"
)

const (
	COOKIE_NAME         = "skid"
	SESSION_COOKIE_NAME = "sksession"
)

var (
	// cookieSalt is some entropy for our encoders.
	cookieSalt = ""

	secureCookie *securecookie.SecureCookie = nil

	// oauthConfig is the OAuth 2.0 client configuration.
	oauthConfig = &oauth.Config{
		ClientId:     "not-a-valid-client-id",
		ClientSecret: "not-a-valid-client-secret",
		Scope:        "email",
		AuthURL:      "https://accounts.google.com/o/oauth2/auth",
		TokenURL:     "https://accounts.google.com/o/oauth2/token",
		RedirectURL:  "http://localhost:8000/oauth2callback/",

		// We don't need a refresh token, we'll just go through the approval flow again.
		AccessType: "online",

		// And when we go through the approval flow again don't stop if they've already approved once.
		ApprovalPrompt: "auto",
	}

	// domainWhitelist is the list of domains that are allowed to log in to our site.
	domainWhitelist = []string{"google.com", "chromium.org", "skia.org"}
)

// Init must be called before any other methods.
//
// The Client ID, Client Secret, and Redirect URL are listed in the Google
// Developers Console.
func Init(clientId, clientSecret, redirectURL, cookieSalt string) {
	secureCookie = securecookie.New([]byte(cookieSalt), nil)
	oauthConfig.ClientId = clientId
	oauthConfig.ClientSecret = clientSecret
	oauthConfig.RedirectURL = redirectURL
}

// LoginURL returns a URL that the user is to be directed to for login.
func LoginURL(w http.ResponseWriter, r *http.Request) string {
	// Check for a session id, if not there then assign one, and add it to the redirect URL.
	session, err := r.Cookie(SESSION_COOKIE_NAME)
	state := ""
	if err != nil || session.Value == "" {
		b := make([]byte, 16)
		_, err := rand.Read(b)
		if err != nil {
			glog.Errorf("Failed to create a session token: %s", err)
			return ""
		}
		state = fmt.Sprintf("%X", b)
		cookie := &http.Cookie{
			Name:     SESSION_COOKIE_NAME,
			Value:    state,
			Path:     "/",
			HttpOnly: true,
			Expires:  time.Now().Add(365 * 24 * time.Hour),
		}
		http.SetCookie(w, cookie)
	} else {
		state = session.Value
	}

	return oauthConfig.AuthCodeURL(state)
}

// LoggedInAs returns the user's ID, i.e. their email address, if they are
// logged in, and "" if they are not logged in.
func LoggedInAs(r *http.Request) string {
	cookie, err := r.Cookie(COOKIE_NAME)
	if err != nil {
		return ""
	}
	var email string
	if err := secureCookie.Decode(COOKIE_NAME, cookie.Value, &email); err != nil {
		return ""
	}
	return email
}

// A JSON Web Token can contain much info, such as 'iss' and 'sub'. We don't care about
// that, we only want one field which is 'email'.
//
// {
//   "iss":"accounts.google.com",
//   "sub":"110642259984599645813",
//   "email":"jcgregorio@google.com",
//   ...
// }
type decodedIDToken struct {
	Email string `json:"email"`
}

// CookieFor creates an encoded Cookie for the given user id.
func CookieFor(value string) (*http.Cookie, error) {
	encoded, err := secureCookie.Encode(COOKIE_NAME, value)
	if err != nil {
		return nil, fmt.Errorf("Failed to encode cookie")
	}
	return &http.Cookie{
		Name:     COOKIE_NAME,
		Value:    encoded,
		Path:     "/",
		HttpOnly: true,
		Expires:  time.Now().Add(365 * 24 * time.Hour),
	}, nil
}

func setSkIDCookieValue(w http.ResponseWriter, value string) {
	cookie, err := CookieFor(value)
	if err != nil {
		http.Error(w, fmt.Sprintf("%s", err), 500)
		return
	}
	http.SetCookie(w, cookie)
}

// LogoutHandler logs the user out by overwriting the cookie with a blank email
// address.
//
// Note that this doesn't revoke the 'email' grant, so logging in later will
// still be fast. Users can always visit
//
//   https://security.google.com/settings/security/permissions
//
// to revoke any grants they make.
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
	glog.Infof("LogoutHandler\n")
	setSkIDCookieValue(w, "")
	http.Redirect(w, r, r.FormValue("redirect"), 302)
}

// OAuth2CallbackHandler must be attached at a handler that matches
// the callback URL registered in the APIs Console. In this case
// "/oauth2callback".
func OAuth2CallbackHandler(w http.ResponseWriter, r *http.Request) {
	glog.Infof("OAuth2CallbackHandler\n")
	session, err := r.Cookie(SESSION_COOKIE_NAME)
	if err != nil || session.Value == "" {
		http.Error(w, "Invalid session state.", 500)
		return
	}
	state := r.FormValue("state")
	if state != session.Value {
		http.Error(w, "Session state doesn't match callback state.", 500)
		return
	}

	code := r.FormValue("code")
	glog.Infof("Code: %s ", code[:5])
	transport := &oauth.Transport{
		Config: oauthConfig,
		Transport: &http.Transport{
			Dial: util.DialTimeout,
		},
	}
	token, err := transport.Exchange(code)
	if err != nil {
		glog.Errorf("Failed to authenticate: %s", err)
		http.Error(w, "Failed to authenticate.", 500)
		return
	}
	// idToken is a JSON Web Token. We only need to decode the token, we do not
	// need to validate the token because it came to us over HTTPS directly from
	// Google's servers.
	idToken := token.Extra["id_token"]
	// The id token is actually three base64 encoded parts that are "." separated.
	segments := strings.Split(idToken, ".")
	if len(segments) != 3 {
		http.Error(w, "Invalid id_token.", 500)
		return
	}
	// Now base64 decode the middle segment, which decodes to JSON.
	padding := 4 - (len(segments[1]) % 4)
	b, err := base64.URLEncoding.DecodeString(segments[1] + strings.Repeat("=", padding))
	if err != nil {
		http.Error(w, "Failed to base64 decode id_token.", 500)
		return
	}
	// Finally decode the JSON.
	decoded := &decodedIDToken{}
	if err := json.Unmarshal(b, decoded); err != nil {
		http.Error(w, "Failed to JSON decode id_token.", 500)
		return
	}
	parts := strings.Split(decoded.Email, "@")
	if len(parts) != 2 {
		http.Error(w, "Invalid email address received.", 500)
		return
	}
	if !util.In(parts[1], domainWhitelist) {
		http.Error(w, "Accounts from your domain are not allowed.", 500)
		return
	}
	setSkIDCookieValue(w, decoded.Email)
	http.Redirect(w, r, "/", 302)
}

// StatusHandler returns the login status of the user as JSON that looks like:
//
// {
//   "Email": "fred@example.com",
//   "LoginURL": "https://..."
// }
//
func StatusHandler(w http.ResponseWriter, r *http.Request) {
	glog.Infof("StatusHandler\n")
	w.Header().Set("Content-Type", "application/json")
	enc := json.NewEncoder(w)
	body := map[string]string{
		"Email":    LoggedInAs(r),
		"LoginURL": LoginURL(w, r),
	}
	if err := enc.Encode(body); err != nil {
		glog.Errorf("Failed to encode Login status to JSON", err)
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package pvl

import (
	b64 "encoding/base64"
	"errors"
	"fmt"
	"regexp"
	"strconv"
	"strings"

	"github.com/PuerkitoBio/goquery"
	libkb "github.com/keybase/client/go/libkb"
	keybase1 "github.com/keybase/client/go/protocol/keybase1"
	jsonw "github.com/keybase/go-jsonw"
)

// Substitute vars for %{name} in the string.
// Only substitutes whitelisted variables.
// It is an error to refer to an unknown variable or undefined numbered group.
// Match is an optional slice which is a regex match.
func pvlSubstitute(template string, state PvlScriptState, match []string) (string, libkb.ProofError) {
	vars := state.Vars
	webish := (state.Service == keybase1.ProofType_DNS || state.Service == keybase1.ProofType_GENERIC_WEB_SITE)

	var outerr libkb.ProofError
	// Regex to find %{name} occurrences.
	re := regexp.MustCompile("%\\{[\\w]*\\}")
	pvlSubstituteOne := func(vartag string) string {
		// Strip off the %, {, and }
		varname := vartag[2 : len(vartag)-1]
		var value string
		switch varname {
		case "username_service":
			if !webish {
				value = vars.UsernameService
			} else {
				outerr = libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL,
					"Cannot use username_service in proof type %v", state.Service)
			}
		case "username_keybase":
			value = vars.UsernameKeybase
		case "sig":
			value = b64.StdEncoding.EncodeToString(vars.Sig)
		case "sig_id_medium":
			value = vars.SigIDMedium
		case "sig_id_short":
			value = vars.SigIDShort
		case "hostname":
			if webish {
				value = vars.Hostname
			} else {
				outerr = libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL,
					"Cannot use username_service in proof type %v", state.Service)
			}
		default:
			var i int
			i, err := strconv.Atoi(varname)
			if err == nil {
				if i >= 0 && i < len(match) {
					value = match[i]
				} else {
					outerr = libkb.NewProofError(keybase1.ProofStatus_BAD_API_URL,
						"Substitution argument %v out of range of match", i)
				}
			} else {
				outerr = libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL,
					"Unrecognized variable: %v", varname)
			}
		}
		return regexp.QuoteMeta(value)
	}
	res := re.ReplaceAllStringFunc(template, pvlSubstituteOne)
	if outerr != nil {
		return template, outerr
	}
	return res, nil
}

func pvlServiceToString(service keybase1.ProofType) (string, libkb.ProofError) {
	for name, stat := range keybase1.ProofTypeMap {
		if service == stat {
			return strings.ToLower(name), nil
		}
	}

	return "", libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, "Unsupported service %v", service)
}

func pvlJSONHasKey(w *jsonw.Wrapper, key string) bool {
	return !w.AtKey(key).IsNil()
}

func pvlJSONHasKeyCommand(w *jsonw.Wrapper, key PvlCommandName) bool {
	return !w.AtKey(string(key)).IsNil()
}

// Return the elements of an array.
func pvlJSONUnpackArray(w *jsonw.Wrapper) ([]*jsonw.Wrapper, error) {
	w, err := w.ToArray()
	if err != nil {
		return nil, err
	}
	length, err := w.Len()
	if err != nil {
		return nil, err
	}
	res := make([]*jsonw.Wrapper, length)
	for i := 0; i < length; i++ {
		res[i] = w.AtIndex(i)
	}
	return res, nil
}

// Return the elements of an array or values of a map.
func pvlJSONGetChildren(w *jsonw.Wrapper) ([]*jsonw.Wrapper, error) {
	dict, err := w.ToDictionary()
	isDict := err == nil
	array, err := w.ToArray()
	isArray := err == nil

	switch {
	case isDict:
		keys, err := dict.Keys()
		if err != nil {
			return nil, err
		}
		var res = make([]*jsonw.Wrapper, len(keys))
		for i, key := range keys {
			res[i] = dict.AtKey(key)
		}
		return res, nil
	case isArray:
		return pvlJSONUnpackArray(array)
	default:
		return nil, errors.New("got children of non-container")
	}
}

// pvlJSONStringSimple converts a simple json object into a string.
// Simple objects are those that are not arrays or objects.
// Non-simple objects result in an error.
func pvlJSONStringSimple(object *jsonw.Wrapper) (string, error) {
	x, err := object.GetInt()
	if err == nil {
		return fmt.Sprintf("%d", x), nil
	}
	y, err := object.GetString()
	if err == nil {
		return y, nil
	}
	z, err := object.GetBool()
	if err == nil {
		if z {
			return "true", nil
		}
		return "false", nil
	}
	isnil := object.IsNil()
	if isnil {
		return "null", nil
	}

	return "", fmt.Errorf("Non-simple object: %v", object)
}

// pvlSelectionContents gets the HTML contents of all elements in a selection, concatenated by a space.
// If getting the contents/attr value of any elements fails, that does not cause an error.
// The result can be an empty string.
func pvlSelectionContents(selection *goquery.Selection, useAttr bool, attr string) string {
	var results []string
	selection.Each(func(i int, element *goquery.Selection) {
		if useAttr {
			res, ok := element.Attr(attr)
			if ok {
				results = append(results, res)
			}
		} else {
			results = append(results, element.Text())
		}
	})

	return strings.Join(results, " ")
}
<commit_msg>add comment<commit_after>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package pvl

import (
	b64 "encoding/base64"
	"errors"
	"fmt"
	"regexp"
	"strconv"
	"strings"

	"github.com/PuerkitoBio/goquery"
	libkb "github.com/keybase/client/go/libkb"
	keybase1 "github.com/keybase/client/go/protocol/keybase1"
	jsonw "github.com/keybase/go-jsonw"
)

// Substitute vars for %{name} in the string.
// Only substitutes whitelisted variables.
// It is an error to refer to an unknown variable or undefined numbered group.
// Match is an optional slice which is a regex match.
func pvlSubstitute(template string, state PvlScriptState, match []string) (string, libkb.ProofError) {
	vars := state.Vars
	webish := (state.Service == keybase1.ProofType_DNS || state.Service == keybase1.ProofType_GENERIC_WEB_SITE)

	var outerr libkb.ProofError
	// Regex to find %{name} occurrences.
	// Match broadly here so that even %{} is sent to the default case and reported as invalid.
	re := regexp.MustCompile("%\\{[\\w]*\\}")
	pvlSubstituteOne := func(vartag string) string {
		// Strip off the %, {, and }
		varname := vartag[2 : len(vartag)-1]
		var value string
		switch varname {
		case "username_service":
			if !webish {
				value = vars.UsernameService
			} else {
				outerr = libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL,
					"Cannot use username_service in proof type %v", state.Service)
			}
		case "username_keybase":
			value = vars.UsernameKeybase
		case "sig":
			value = b64.StdEncoding.EncodeToString(vars.Sig)
		case "sig_id_medium":
			value = vars.SigIDMedium
		case "sig_id_short":
			value = vars.SigIDShort
		case "hostname":
			if webish {
				value = vars.Hostname
			} else {
				outerr = libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL,
					"Cannot use username_service in proof type %v", state.Service)
			}
		default:
			var i int
			i, err := strconv.Atoi(varname)
			if err == nil {
				if i >= 0 && i < len(match) {
					value = match[i]
				} else {
					outerr = libkb.NewProofError(keybase1.ProofStatus_BAD_API_URL,
						"Substitution argument %v out of range of match", i)
				}
			} else {
				outerr = libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL,
					"Unrecognized variable: %v", varname)
			}
		}
		return regexp.QuoteMeta(value)
	}
	res := re.ReplaceAllStringFunc(template, pvlSubstituteOne)
	if outerr != nil {
		return template, outerr
	}
	return res, nil
}

func pvlServiceToString(service keybase1.ProofType) (string, libkb.ProofError) {
	for name, stat := range keybase1.ProofTypeMap {
		if service == stat {
			return strings.ToLower(name), nil
		}
	}

	return "", libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, "Unsupported service %v", service)
}

func pvlJSONHasKey(w *jsonw.Wrapper, key string) bool {
	return !w.AtKey(key).IsNil()
}

func pvlJSONHasKeyCommand(w *jsonw.Wrapper, key PvlCommandName) bool {
	return !w.AtKey(string(key)).IsNil()
}

// Return the elements of an array.
func pvlJSONUnpackArray(w *jsonw.Wrapper) ([]*jsonw.Wrapper, error) {
	w, err := w.ToArray()
	if err != nil {
		return nil, err
	}
	length, err := w.Len()
	if err != nil {
		return nil, err
	}
	res := make([]*jsonw.Wrapper, length)
	for i := 0; i < length; i++ {
		res[i] = w.AtIndex(i)
	}
	return res, nil
}

// Return the elements of an array or values of a map.
func pvlJSONGetChildren(w *jsonw.Wrapper) ([]*jsonw.Wrapper, error) {
	dict, err := w.ToDictionary()
	isDict := err == nil
	array, err := w.ToArray()
	isArray := err == nil

	switch {
	case isDict:
		keys, err := dict.Keys()
		if err != nil {
			return nil, err
		}
		var res = make([]*jsonw.Wrapper, len(keys))
		for i, key := range keys {
			res[i] = dict.AtKey(key)
		}
		return res, nil
	case isArray:
		return pvlJSONUnpackArray(array)
	default:
		return nil, errors.New("got children of non-container")
	}
}

// pvlJSONStringSimple converts a simple json object into a string.
// Simple objects are those that are not arrays or objects.
// Non-simple objects result in an error.
func pvlJSONStringSimple(object *jsonw.Wrapper) (string, error) {
	x, err := object.GetInt()
	if err == nil {
		return fmt.Sprintf("%d", x), nil
	}
	y, err := object.GetString()
	if err == nil {
		return y, nil
	}
	z, err := object.GetBool()
	if err == nil {
		if z {
			return "true", nil
		}
		return "false", nil
	}
	isnil := object.IsNil()
	if isnil {
		return "null", nil
	}

	return "", fmt.Errorf("Non-simple object: %v", object)
}

// pvlSelectionContents gets the HTML contents of all elements in a selection, concatenated by a space.
// If getting the contents/attr value of any elements fails, that does not cause an error.
// The result can be an empty string.
func pvlSelectionContents(selection *goquery.Selection, useAttr bool, attr string) string {
	var results []string
	selection.Each(func(i int, element *goquery.Selection) {
		if useAttr {
			res, ok := element.Attr(attr)
			if ok {
				results = append(results, res)
			}
		} else {
			results = append(results, element.Text())
		}
	})

	return strings.Join(results, " ")
}
<|endoftext|>