<commit_before>package permissions

import (
	"github.com/xyproto/pinterface"
	"testing"
	"time"
)

func TestPerm(t *testing.T) {
	userstate := NewUserStateSimple()

	userstate.AddUser("bob", "hunter1", "bob@zombo.com")

	if !userstate.HasUser("bob") {
		t.Error("Error, user bob should exist")
	}

	if userstate.IsConfirmed("bob") {
		t.Error("Error, user bob should not be confirmed right now.")
	}

	userstate.MarkConfirmed("bob")

	if !userstate.IsConfirmed("bob") {
		t.Error("Error, user bob should be marked as confirmed right now.")
	}

	if userstate.IsAdmin("bob") {
		t.Error("Error, user bob should not have admin rights")
	}

	userstate.SetAdminStatus("bob")

	if !userstate.IsAdmin("bob") {
		t.Error("Error, user bob should have admin rights")
	}

	userstate.RemoveUser("bob")

	if userstate.HasUser("bob") {
		t.Error("Error, user bob should not exist")
	}
}

func TestPasswordBasic(t *testing.T) {
	userstate := NewUserStateSimple()

	// Assert that the default password algorithm is "bcrypt+"
	if userstate.PasswordAlgo() != "bcrypt+" {
		t.Error("Error, bcrypt+ should be the default password algorithm")
	}

	// Set password algorithm
	userstate.SetPasswordAlgo("sha256")

	// Assert that the algorithm is now sha256
	if userstate.PasswordAlgo() != "sha256" {
		t.Error("Error, setting password algorithm failed")
	}

}

func TestPasswordBasic2(t *testing.T) {
	// Test the other method for connecting to Redis
	userstate, err := NewUserStateSimple2()
	if err != nil {
		t.Error("Error, " + err.Error())
	}

	// Assert that the default password algorithm is "bcrypt+"
	if userstate.PasswordAlgo() != "bcrypt+" {
		t.Error("Error, bcrypt+ should be the default password algorithm")
	}

	// Set password algorithm
	userstate.SetPasswordAlgo("sha256")

	// Assert that the algorithm is now sha256
	if userstate.PasswordAlgo() != "sha256" {
		t.Error("Error, setting password algorithm failed")
	}

}

// Check if the functionality for backwards compatible hashing works
func TestPasswordBackward(t *testing.T) {
	userstate := NewUserStateSimple()
	userstate.SetPasswordAlgo("sha256")
	userstate.AddUser("bob", "hunter1", "bob@zombo.com")
	if !userstate.HasUser("bob") {
		t.Error("Error, user bob should exist")
	}
	defer userstate.RemoveUser("bob")

	userstate.SetPasswordAlgo("sha256")
	if !userstate.CorrectPassword("bob", "hunter1") {
		t.Error("Error, the sha256 password really is correct")
	}

	userstate.SetPasswordAlgo("bcrypt")
	if userstate.CorrectPassword("bob", "hunter1") {
		t.Error("Error, the password as stored as sha256, not bcrypt")
	}

	userstate.SetPasswordAlgo("bcrypt+")
	if !userstate.CorrectPassword("bob", "hunter1") {
		t.Error("Error, the sha256 password is not correct when checking with bcrypt+")
	}
}

// Check if the functionality for backwards compatible hashing works
func TestPasswordNotBackward(t *testing.T) {
	userstate := NewUserStateSimple()
	userstate.SetPasswordAlgo("bcrypt")
	userstate.AddUser("bob", "hunter1", "bob@zombo.com")
	if !userstate.HasUser("bob") {
		t.Error("Error, user bob should exist")
	}
	defer userstate.RemoveUser("bob")

	userstate.SetPasswordAlgo("sha256")
	if userstate.CorrectPassword("bob", "hunter1") {
		t.Error("Error, the password is stored as bcrypt, should not be okay with sha256")
	}

	userstate.SetPasswordAlgo("bcrypt")
	if !userstate.CorrectPassword("bob", "hunter1") {
		t.Error("Error, the password should be correct when checking with bcrypt")
	}
}

func TestPasswordAlgoMatching(t *testing.T) {
	userstate := NewUserStateSimple()
	// generate two different password using the same credentials but different algos
	userstate.SetPasswordAlgo("sha256")
	sha256Hash := userstate.HashPassword("testuser@example.com", "textpassword")
	userstate.SetPasswordAlgo("bcrypt")
	bcryptHash := userstate.HashPassword("testuser@example.com", "textpassword")

	// they shouldn't match
	if sha256Hash == bcryptHash {
		t.Error("Error, different algorithms should not have a password match")
	}
}

func TestUserStateKeeper(t *testing.T) {
	userstate := NewUserStateSimple()

	// Check that the userstate qualifies for the IUserState interface
	var _ pinterface.IUserState = userstate
}

func TestHostPassword(t *testing.T) {
	//userstate := NewUserStateWithPassword("localhost", "foobared")
	userstate := NewUserStateWithPassword("localhost", "")

	userstate.AddUser("bob", "hunter1", "bob@zombo.com")
	if !userstate.HasUser("bob") {
		t.Error("Error, user bob should exist")
	}

	// Remove bob
	userstate.RemoveUser("bob")
	if userstate.HasUser("bob") {
		t.Error("Error, user bob should not exist")
	}
}

func TestChangePassword(t *testing.T) {
	userstate := NewUserStateSimple()

	userstate.AddUser("bob", "hunter1", "bob@zombo.com")
	if !userstate.HasUser("bob") {
		t.Error("Error, user bob should exist")
	}
	defer userstate.RemoveUser("bob")

	// Check that the password is "hunter1"
	if !userstate.CorrectPassword("bob", "hunter1") {
		t.Error("Error, password is incorrect: should be hunter1!")
	}
	// Check that the password is not "hunter2"
	if userstate.CorrectPassword("bob", "hunter2") {
		t.Error("Error, password is incorrect: should not be hunter2!")
	}

	// Change the password for user "bob" to "hunter2"
	username := "bob"
	password := "hunter2"
	passwordHash := userstate.HashPassword(username, password)
	userstate.Users().Set(username, "password", passwordHash)

	// Check that the password is "hunter2"
	if !userstate.CorrectPassword("bob", "hunter2") {
		t.Error("Error, password is incorrect: should be hunter2!")
	}
	// Check that the password is not "hunter1"
	if userstate.CorrectPassword("bob", "hunter1") {
		t.Error("Error, password is incorrect: should not be hunter1!")
	}

	// Change the password back to "hunter1"
	userstate.SetPassword("bob", "hunter1")

	// Check that the password is "hunter1"
	if !userstate.CorrectPassword("bob", "hunter1") {
		t.Error("Error, password is incorrect: should be hunter1!")
	}
	// Check that the password is not "hunter2"
	if userstate.CorrectPassword("bob", "hunter2") {
		t.Error("Error, password is incorrect: should not be hunter2!")
	}

	if len(userstate.Properties("bob")) != 5 {
		t.Error("Error, there should be 5 properties on bob now!")
	}
}

func TestTokens(t *testing.T) {
	userstate := NewUserStateSimple()

	// Add bob
	userstate.AddUser("bob", "hunter1", "bob@zombo.com")
	if !userstate.HasUser("bob") {
		t.Error("Error, user bob should exist")
	}
	defer userstate.RemoveUser("bob")

	// Set a token that will expire in 200 milliseconds
	userstate.SetToken("bob", "asdf123", time.Millisecond*200)

	// Check that the token is "asdf123"
	retval, err := userstate.GetToken("bob")
	if err != nil {
		t.Error("Error, could not get token")
	}
	if retval != "asdf123" {
		t.Error("Error, token is incorrect: should be asdf123!")
	}

	// Wait 400 milliseconds
	time.Sleep(time.Millisecond * 400)

	// Check that the token is now gone
	retval, err = userstate.GetToken("bob")
	if err == nil || retval != "" {
		t.Error("Error, token is incorrect: should be gone!")
	}
}

func TestEmail(t *testing.T) {
	userstate := NewUserStateSimple()
	userstate.AddUser("bob", "hunter1", "bob@zombo.com")
	username, err := userstate.HasEmail("bob@zombo.com")
	if err != nil {
		t.Error("Error, the e-mail should exist:", err)
	}
	if username != "bob" {
		t.Error("Error, the e-mail address should belong to bob, but belongs to:", username)
	}
	username, err = userstate.HasEmail("rob@zombo.com")
	if err != ErrNotFound {
		t.Error("Error, the e-mail should not exist: " + username)
	}
}

func TestTiming(t *testing.T) {
	userstate := NewUserStateSimple()

	userstate.SetPasswordAlgo("bcrypt")
	userstate.AddUser("bob", "hunter1", "bob@zombo.com")
	if !userstate.HasUser("bob") {
		t.Error("Error, user bob should exist")
	}
	defer userstate.RemoveUser("bob")

	// First run takes a bit longer
	_ = userstate.CorrectPassword("bob", "asdf")

	start1 := time.Now()
	if !userstate.CorrectPassword("bob", "hunter1") {
		t.Error("Error, the password IS correct")
	}
	elapsed1 := time.Since(start1)

	start2 := time.Now()
	if userstate.CorrectPassword("bob", "_") {
		t.Error("Error, the password is NOT correct")
	}
	elapsed2 := time.Since(start2)

	start3 := time.Now()
	if userstate.CorrectPassword("bob", "abc123asfdasdfasfasdfqwertyqwertyqwerty") {
		t.Error("Error, the password is NOT correct")
	}
	elapsed3 := time.Since(start3)

	start4 := time.Now()
	if userstate.CorrectPassword("bob", "abc123asfdasdfasfasdfqwertyqwertyqwertyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy") {
		t.Error("Error, the password is NOT correct")
	}
	elapsed4 := time.Since(start4)

	// The tolerance is +- 1/10th of the first elapsed time. Hard to find a good value here, since the values differ from machine to machine.
	baseMin := elapsed1 - time.Duration(elapsed1/10)
	baseMax := elapsed1 + time.Duration(elapsed1/10)

	if elapsed2 < baseMin || elapsed2 > baseMax {
		t.Error("Checking passwords of different lengths should not take much longer or shorter")
	}

	if elapsed3 < baseMin || elapsed3 > baseMax {
		t.Error("Checking passwords of different lengths should not take much longer or shorter")
	}

	if elapsed4 < baseMin || elapsed4 > baseMax {
		t.Error("Checking passwords of different lengths should not take much longer or shorter")
	}
	//fmt.Println(baseMin, base_max, elapsed1, elapsed2, elapsed3, elapsed4)
}
<commit_msg>Update timing values in the userstate test<commit_after>package permissions

import (
	"github.com/xyproto/pinterface"
	"testing"
	"time"
)

func TestPerm(t *testing.T) {
	userstate := NewUserStateSimple()

	userstate.AddUser("bob", "hunter1", "bob@zombo.com")

	if !userstate.HasUser("bob") {
		t.Error("Error, user bob should exist")
	}

	if userstate.IsConfirmed("bob") {
		t.Error("Error, user bob should not be confirmed right now.")
	}

	userstate.MarkConfirmed("bob")

	if !userstate.IsConfirmed("bob") {
		t.Error("Error, user bob should be marked as confirmed right now.")
	}

	if userstate.IsAdmin("bob") {
		t.Error("Error, user bob should not have admin rights")
	}

	userstate.SetAdminStatus("bob")

	if !userstate.IsAdmin("bob") {
		t.Error("Error, user bob should have admin rights")
	}

	userstate.RemoveUser("bob")

	if userstate.HasUser("bob") {
		t.Error("Error, user bob should not exist")
	}
}

func TestPasswordBasic(t *testing.T) {
	userstate := NewUserStateSimple()

	// Assert that the default password algorithm is "bcrypt+"
	if userstate.PasswordAlgo() != "bcrypt+" {
		t.Error("Error, bcrypt+ should be the default password algorithm")
	}

	// Set password algorithm
	userstate.SetPasswordAlgo("sha256")

	// Assert that the algorithm is now sha256
	if userstate.PasswordAlgo() != "sha256" {
		t.Error("Error, setting password algorithm failed")
	}

}

func TestPasswordBasic2(t *testing.T) {
	// Test the other method for connecting to Redis
	userstate, err := NewUserStateSimple2()
	if err != nil {
		t.Error("Error, " + err.Error())
	}

	// Assert that the default password algorithm is "bcrypt+"
	if userstate.PasswordAlgo() != "bcrypt+" {
		t.Error("Error, bcrypt+ should be the default password algorithm")
	}

	// Set password algorithm
	userstate.SetPasswordAlgo("sha256")

	// Assert that the algorithm is now sha256
	if userstate.PasswordAlgo() != "sha256" {
		t.Error("Error, setting password algorithm failed")
	}

}

// Check if the functionality for backwards compatible hashing works
func TestPasswordBackward(t *testing.T) {
	userstate := NewUserStateSimple()
	userstate.SetPasswordAlgo("sha256")
	userstate.AddUser("bob", "hunter1", "bob@zombo.com")
	if !userstate.HasUser("bob") {
		t.Error("Error, user bob should exist")
	}
	defer userstate.RemoveUser("bob")

	userstate.SetPasswordAlgo("sha256")
	if !userstate.CorrectPassword("bob", "hunter1") {
		t.Error("Error, the sha256 password really is correct")
	}

	userstate.SetPasswordAlgo("bcrypt")
	if userstate.CorrectPassword("bob", "hunter1") {
		t.Error("Error, the password as stored as sha256, not bcrypt")
	}

	userstate.SetPasswordAlgo("bcrypt+")
	if !userstate.CorrectPassword("bob", "hunter1") {
		t.Error("Error, the sha256 password is not correct when checking with bcrypt+")
	}
}

// Check if the functionality for backwards compatible hashing works
func TestPasswordNotBackward(t *testing.T) {
	userstate := NewUserStateSimple()
	userstate.SetPasswordAlgo("bcrypt")
	userstate.AddUser("bob", "hunter1", "bob@zombo.com")
	if !userstate.HasUser("bob") {
		t.Error("Error, user bob should exist")
	}
	defer userstate.RemoveUser("bob")

	userstate.SetPasswordAlgo("sha256")
	if userstate.CorrectPassword("bob", "hunter1") {
		t.Error("Error, the password is stored as bcrypt, should not be okay with sha256")
	}

	userstate.SetPasswordAlgo("bcrypt")
	if !userstate.CorrectPassword("bob", "hunter1") {
		t.Error("Error, the password should be correct when checking with bcrypt")
	}
}

func TestPasswordAlgoMatching(t *testing.T) {
	userstate := NewUserStateSimple()
	// generate two different password using the same credentials but different algos
	userstate.SetPasswordAlgo("sha256")
	sha256Hash := userstate.HashPassword("testuser@example.com", "textpassword")
	userstate.SetPasswordAlgo("bcrypt")
	bcryptHash := userstate.HashPassword("testuser@example.com", "textpassword")

	// they shouldn't match
	if sha256Hash == bcryptHash {
		t.Error("Error, different algorithms should not have a password match")
	}
}

func TestUserStateKeeper(t *testing.T) {
	userstate := NewUserStateSimple()

	// Check that the userstate qualifies for the IUserState interface
	var _ pinterface.IUserState = userstate
}

func TestHostPassword(t *testing.T) {
	//userstate := NewUserStateWithPassword("localhost", "foobared")
	userstate := NewUserStateWithPassword("localhost", "")

	userstate.AddUser("bob", "hunter1", "bob@zombo.com")
	if !userstate.HasUser("bob") {
		t.Error("Error, user bob should exist")
	}

	// Remove bob
	userstate.RemoveUser("bob")
	if userstate.HasUser("bob") {
		t.Error("Error, user bob should not exist")
	}
}

func TestChangePassword(t *testing.T) {
	userstate := NewUserStateSimple()

	userstate.AddUser("bob", "hunter1", "bob@zombo.com")
	if !userstate.HasUser("bob") {
		t.Error("Error, user bob should exist")
	}
	defer userstate.RemoveUser("bob")

	// Check that the password is "hunter1"
	if !userstate.CorrectPassword("bob", "hunter1") {
		t.Error("Error, password is incorrect: should be hunter1!")
	}
	// Check that the password is not "hunter2"
	if userstate.CorrectPassword("bob", "hunter2") {
		t.Error("Error, password is incorrect: should not be hunter2!")
	}

	// Change the password for user "bob" to "hunter2"
	username := "bob"
	password := "hunter2"
	passwordHash := userstate.HashPassword(username, password)
	userstate.Users().Set(username, "password", passwordHash)

	// Check that the password is "hunter2"
	if !userstate.CorrectPassword("bob", "hunter2") {
		t.Error("Error, password is incorrect: should be hunter2!")
	}
	// Check that the password is not "hunter1"
	if userstate.CorrectPassword("bob", "hunter1") {
		t.Error("Error, password is incorrect: should not be hunter1!")
	}

	// Change the password back to "hunter1"
	userstate.SetPassword("bob", "hunter1")

	// Check that the password is "hunter1"
	if !userstate.CorrectPassword("bob", "hunter1") {
		t.Error("Error, password is incorrect: should be hunter1!")
	}
	// Check that the password is not "hunter2"
	if userstate.CorrectPassword("bob", "hunter2") {
		t.Error("Error, password is incorrect: should not be hunter2!")
	}

	if len(userstate.Properties("bob")) != 5 {
		t.Error("Error, there should be 5 properties on bob now!")
	}
}

func TestTokens(t *testing.T) {
	userstate := NewUserStateSimple()

	// Add bob
	userstate.AddUser("bob", "hunter1", "bob@zombo.com")
	if !userstate.HasUser("bob") {
		t.Error("Error, user bob should exist")
	}
	defer userstate.RemoveUser("bob")

	// Set a token that will expire in 200 milliseconds
	userstate.SetToken("bob", "asdf123", time.Millisecond*200)

	// Check that the token is "asdf123"
	retval, err := userstate.GetToken("bob")
	if err != nil {
		t.Error("Error, could not get token")
	}
	if retval != "asdf123" {
		t.Error("Error, token is incorrect: should be asdf123!")
	}

	// Wait 400 milliseconds
	time.Sleep(time.Millisecond * 400)

	// Check that the token is now gone
	retval, err = userstate.GetToken("bob")
	if err == nil || retval != "" {
		t.Error("Error, token is incorrect: should be gone!")
	}
}

func TestEmail(t *testing.T) {
	userstate := NewUserStateSimple()
	userstate.AddUser("bob", "hunter1", "bob@zombo.com")
	username, err := userstate.HasEmail("bob@zombo.com")
	if err != nil {
		t.Error("Error, the e-mail should exist:", err)
	}
	if username != "bob" {
		t.Error("Error, the e-mail address should belong to bob, but belongs to:", username)
	}
	username, err = userstate.HasEmail("rob@zombo.com")
	if err != ErrNotFound {
		t.Error("Error, the e-mail should not exist: " + username)
	}
}

func TestTiming(t *testing.T) {
	userstate := NewUserStateSimple()

	userstate.SetPasswordAlgo("bcrypt")
	userstate.AddUser("bob", "hunter1", "bob@zombo.com")
	if !userstate.HasUser("bob") {
		t.Error("Error, user bob should exist")
	}
	defer userstate.RemoveUser("bob")

	// First run takes a bit longer
	_ = userstate.CorrectPassword("bob", "asdf")

	start1 := time.Now()
	if !userstate.CorrectPassword("bob", "hunter1") {
		t.Error("Error, the password IS correct")
	}
	elapsed1 := time.Since(start1)

	start2 := time.Now()
	if userstate.CorrectPassword("bob", "_") {
		t.Error("Error, the password is NOT correct")
	}
	elapsed2 := time.Since(start2)

	start3 := time.Now()
	if userstate.CorrectPassword("bob", "abc123asfdasdfasfasdfqwertyqwertyqwerty") {
		t.Error("Error, the password is NOT correct")
	}
	elapsed3 := time.Since(start3)

	start4 := time.Now()
	if userstate.CorrectPassword("bob", "abc123asfdasdfasfasdfqwertyqwertyqwertyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy") {
		t.Error("Error, the password is NOT correct")
	}
	elapsed4 := time.Since(start4)

	// The tolerance is +- 1/10th of the first elapsed time. Hard to find a good value here, since the values differ from machine to machine.
	baseMin := elapsed1 - time.Duration(elapsed1/5)
	baseMax := elapsed1 + time.Duration(elapsed1/5)

	if elapsed2 < baseMin || elapsed2 > baseMax {
		t.Error("Checking passwords of different lengths should not take much longer or shorter")
	}

	if elapsed3 < baseMin || elapsed3 > baseMax {
		t.Error("Checking passwords of different lengths should not take much longer or shorter")
	}

	if elapsed4 < baseMin || elapsed4 > baseMax {
		t.Error("Checking passwords of different lengths should not take much longer or shorter")
	}
	//fmt.Println(baseMin, base_max, elapsed1, elapsed2, elapsed3, elapsed4)
}
<|endoftext|>


<commit_before>package diff

import (
	"encoding/json"
	"errors"
	"fmt"

	log "github.com/sirupsen/logrus"
	"github.com/yudai/gojsondiff"
	"github.com/yudai/gojsondiff/formatter"
	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/util/strategicpatch"
	"k8s.io/kubernetes/pkg/apis/core"
	"k8s.io/kubernetes/pkg/kubectl/scheme"
)

type DiffResult struct {
	Diff     gojsondiff.Diff
	Modified bool
}

type DiffResultList struct {
	Diffs    []DiffResult
	Modified bool
}

// Diff performs a diff on two unstructured objects. If the live object happens to have a
// "kubectl.kubernetes.io/last-applied-configuration", then perform a three way diff.
func Diff(config, live *unstructured.Unstructured) *DiffResult {
	orig := getLastAppliedConfigAnnotation(live)
	if orig != nil {
		return ThreeWayDiff(orig, config, live)
	}
	return TwoWayDiff(config, live)
}

// TwoWayDiff performs a normal two-way diff between two unstructured objects. Ignores extra fields
// in the live object.
func TwoWayDiff(config, live *unstructured.Unstructured) *DiffResult {
	var configObj, liveObj map[string]interface{}
	if config != nil {
		configObj = config.Object
	}
	if live != nil {
		liveObj = RemoveMapFields(configObj, live.Object)
	}
	gjDiff := gojsondiff.New().CompareObjects(configObj, liveObj)
	dr := DiffResult{
		Diff:     gjDiff,
		Modified: gjDiff.Modified(),
	}
	return &dr
}

// ThreeWayDiff performs a diff with the understanding of how to incorporate the
// last-applied-configuration annotation in the diff.
func ThreeWayDiff(orig, config, live *unstructured.Unstructured) *DiffResult {
	orig = removeNamespaceAnnotation(orig)
	// remove extra fields in the live, that were not in the original object
	liveObj := RemoveMapFields(orig.Object, live.Object)
	// now we have a pruned live object
	gjDiff := gojsondiff.New().CompareObjects(config.Object, liveObj)
	dr := DiffResult{
		Diff:     gjDiff,
		Modified: gjDiff.Modified(),
	}
	// Theoretically, we should be able to return the diff result a this point. Just to be safe,
	// calculate a kubernetes 3-way merge patch to see if kubernetes will also agree with what we
	// just calculated.
	patch, err := threeWayMergePatch(orig, config, live)
	if err != nil {
		log.Warnf("Failed to calculate three way merge patch: %v", err)
		return &dr
	}
	patchStr := string(patch)
	modified := bool(patchStr != "{}")
	if dr.Modified != modified {
		// We theoretically should not get here. If we do, it is a issue with our diff calculation
		// We should honor what kubernetes thinks. If we *do* get here, it means what we will be
		// reporting OutOfSync, but do not have a good way to visualize the diff to the user.
		log.Warnf("Disagreement in three way diff calculation: %s", patchStr)
		dr.Modified = modified
	}
	return &dr
}

func removeNamespaceAnnotation(orig *unstructured.Unstructured) *unstructured.Unstructured {
	orig = orig.DeepCopy()
	// remove the namespace an annotation from the
	if metadataIf, ok := orig.Object["metadata"]; ok {
		metadata := metadataIf.(map[string]interface{})
		delete(metadata, "namespace")
		if annotationsIf, ok := metadata["annotations"]; ok {
			annotation := annotationsIf.(map[string]interface{})
			if len(annotation) == 0 {
				delete(metadata, "annotations")
			}
		}
	}
	return orig
}

func threeWayMergePatch(orig, config, live *unstructured.Unstructured) ([]byte, error) {
	origBytes, err := json.Marshal(orig.Object)
	if err != nil {
		return nil, err
	}
	configBytes, err := json.Marshal(config.Object)
	if err != nil {
		return nil, err
	}
	liveBytes, err := json.Marshal(live.Object)
	if err != nil {
		return nil, err
	}
	gvk := orig.GroupVersionKind()
	versionedObject, err := scheme.Scheme.New(gvk)
	if err != nil {
		return nil, err
	}
	lookupPatchMeta, err := strategicpatch.NewPatchMetaFromStruct(versionedObject)
	if err != nil {
		return nil, err
	}
	return strategicpatch.CreateThreeWayMergePatch(origBytes, configBytes, liveBytes, lookupPatchMeta, true)
}

func getLastAppliedConfigAnnotation(live *unstructured.Unstructured) *unstructured.Unstructured {
	if live == nil {
		return nil
	}
	annots := live.GetAnnotations()
	if annots == nil {
		return nil
	}
	lastAppliedStr, ok := annots[v1.LastAppliedConfigAnnotation]
	if !ok {
		return nil
	}
	var obj unstructured.Unstructured
	err := json.Unmarshal([]byte(lastAppliedStr), &obj)
	if err != nil {
		log.Warnf("Failed to unmarshal %s in %s", core.LastAppliedConfigAnnotation, live.GetName())
		return nil
	}
	return &obj
}

// DiffArray performs a diff on a list of unstructured objects. Objects are expected to match
// environments
func DiffArray(configArray, liveArray []*unstructured.Unstructured) (*DiffResultList, error) {
	numItems := len(configArray)
	if len(liveArray) != numItems {
		return nil, fmt.Errorf("left and right arrays have mismatched lengths")
	}

	diffResultList := DiffResultList{
		Diffs: make([]DiffResult, numItems),
	}
	for i := 0; i < numItems; i++ {
		config := configArray[i]
		live := liveArray[i]
		diffRes := Diff(config, live)
		diffResultList.Diffs[i] = *diffRes
		if diffRes.Modified {
			diffResultList.Modified = true
		}
	}
	return &diffResultList, nil
}

// ASCIIFormat returns the ASCII format of the diff
func (d *DiffResult) ASCIIFormat(left *unstructured.Unstructured, formatOpts formatter.AsciiFormatterConfig) (string, error) {
	if !d.Diff.Modified() {
		return "", nil
	}
	if left == nil {
		return "", errors.New("Supplied nil left object")
	}
	asciiFmt := formatter.NewAsciiFormatter(left.Object, formatOpts)
	return asciiFmt.Format(d.Diff)
}

// https://github.com/ksonnet/ksonnet/blob/master/pkg/kubecfg/diff.go
func removeFields(config, live interface{}) interface{} {
	switch c := config.(type) {
	case map[string]interface{}:
		return RemoveMapFields(c, live.(map[string]interface{}))
	case []interface{}:
		return removeListFields(c, live.([]interface{}))
	default:
		return live
	}
}

// RemoveMapFields remove all non-existent fields in the live that don't exist in the config
func RemoveMapFields(config, live map[string]interface{}) map[string]interface{} {
	result := map[string]interface{}{}
	for k, v1 := range config {
		v2, ok := live[k]
		if !ok {
			continue
		}
		result[k] = removeFields(v1, v2)
	}
	return result
}

func removeListFields(config, live []interface{}) []interface{} {
	// If live is longer than config, then the extra elements at the end of the
	// list will be returned as-is so they appear in the diff.
	result := make([]interface{}, 0, len(live))
	for i, v2 := range live {
		if len(config) > i {
			result = append(result, removeFields(config[i], v2))
		} else {
			result = append(result, v2)
		}
	}
	return result
}
<commit_msg>Issue #271 - perform three way diff only if resource has expected state and live state with last-applied-configuration annotation (#274)<commit_after>package diff

import (
	"encoding/json"
	"errors"
	"fmt"

	log "github.com/sirupsen/logrus"
	"github.com/yudai/gojsondiff"
	"github.com/yudai/gojsondiff/formatter"
	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/util/strategicpatch"
	"k8s.io/kubernetes/pkg/apis/core"
	"k8s.io/kubernetes/pkg/kubectl/scheme"
)

type DiffResult struct {
	Diff     gojsondiff.Diff
	Modified bool
}

type DiffResultList struct {
	Diffs    []DiffResult
	Modified bool
}

// Diff performs a diff on two unstructured objects. If the live object happens to have a
// "kubectl.kubernetes.io/last-applied-configuration", then perform a three way diff.
func Diff(config, live *unstructured.Unstructured) *DiffResult {
	orig := getLastAppliedConfigAnnotation(live)
	if orig != nil && config != nil {
		return ThreeWayDiff(orig, config, live)
	}
	return TwoWayDiff(config, live)
}

// TwoWayDiff performs a normal two-way diff between two unstructured objects. Ignores extra fields
// in the live object.
func TwoWayDiff(config, live *unstructured.Unstructured) *DiffResult {
	var configObj, liveObj map[string]interface{}
	if config != nil {
		configObj = config.Object
	}
	if live != nil {
		liveObj = RemoveMapFields(configObj, live.Object)
	}
	gjDiff := gojsondiff.New().CompareObjects(configObj, liveObj)
	dr := DiffResult{
		Diff:     gjDiff,
		Modified: gjDiff.Modified(),
	}
	return &dr
}

// ThreeWayDiff performs a diff with the understanding of how to incorporate the
// last-applied-configuration annotation in the diff.
func ThreeWayDiff(orig, config, live *unstructured.Unstructured) *DiffResult {
	orig = removeNamespaceAnnotation(orig)
	// remove extra fields in the live, that were not in the original object
	liveObj := RemoveMapFields(orig.Object, live.Object)
	// now we have a pruned live object
	gjDiff := gojsondiff.New().CompareObjects(config.Object, liveObj)
	dr := DiffResult{
		Diff:     gjDiff,
		Modified: gjDiff.Modified(),
	}
	// Theoretically, we should be able to return the diff result a this point. Just to be safe,
	// calculate a kubernetes 3-way merge patch to see if kubernetes will also agree with what we
	// just calculated.
	patch, err := threeWayMergePatch(orig, config, live)
	if err != nil {
		log.Warnf("Failed to calculate three way merge patch: %v", err)
		return &dr
	}
	patchStr := string(patch)
	modified := bool(patchStr != "{}")
	if dr.Modified != modified {
		// We theoretically should not get here. If we do, it is a issue with our diff calculation
		// We should honor what kubernetes thinks. If we *do* get here, it means what we will be
		// reporting OutOfSync, but do not have a good way to visualize the diff to the user.
		log.Warnf("Disagreement in three way diff calculation: %s", patchStr)
		dr.Modified = modified
	}
	return &dr
}

func removeNamespaceAnnotation(orig *unstructured.Unstructured) *unstructured.Unstructured {
	orig = orig.DeepCopy()
	// remove the namespace an annotation from the
	if metadataIf, ok := orig.Object["metadata"]; ok {
		metadata := metadataIf.(map[string]interface{})
		delete(metadata, "namespace")
		if annotationsIf, ok := metadata["annotations"]; ok {
			annotation := annotationsIf.(map[string]interface{})
			if len(annotation) == 0 {
				delete(metadata, "annotations")
			}
		}
	}
	return orig
}

func threeWayMergePatch(orig, config, live *unstructured.Unstructured) ([]byte, error) {
	origBytes, err := json.Marshal(orig.Object)
	if err != nil {
		return nil, err
	}
	configBytes, err := json.Marshal(config.Object)
	if err != nil {
		return nil, err
	}
	liveBytes, err := json.Marshal(live.Object)
	if err != nil {
		return nil, err
	}
	gvk := orig.GroupVersionKind()
	versionedObject, err := scheme.Scheme.New(gvk)
	if err != nil {
		return nil, err
	}
	lookupPatchMeta, err := strategicpatch.NewPatchMetaFromStruct(versionedObject)
	if err != nil {
		return nil, err
	}
	return strategicpatch.CreateThreeWayMergePatch(origBytes, configBytes, liveBytes, lookupPatchMeta, true)
}

func getLastAppliedConfigAnnotation(live *unstructured.Unstructured) *unstructured.Unstructured {
	if live == nil {
		return nil
	}
	annots := live.GetAnnotations()
	if annots == nil {
		return nil
	}
	lastAppliedStr, ok := annots[v1.LastAppliedConfigAnnotation]
	if !ok {
		return nil
	}
	var obj unstructured.Unstructured
	err := json.Unmarshal([]byte(lastAppliedStr), &obj)
	if err != nil {
		log.Warnf("Failed to unmarshal %s in %s", core.LastAppliedConfigAnnotation, live.GetName())
		return nil
	}
	return &obj
}

// DiffArray performs a diff on a list of unstructured objects. Objects are expected to match
// environments
func DiffArray(configArray, liveArray []*unstructured.Unstructured) (*DiffResultList, error) {
	numItems := len(configArray)
	if len(liveArray) != numItems {
		return nil, fmt.Errorf("left and right arrays have mismatched lengths")
	}

	diffResultList := DiffResultList{
		Diffs: make([]DiffResult, numItems),
	}
	for i := 0; i < numItems; i++ {
		config := configArray[i]
		live := liveArray[i]
		diffRes := Diff(config, live)
		diffResultList.Diffs[i] = *diffRes
		if diffRes.Modified {
			diffResultList.Modified = true
		}
	}
	return &diffResultList, nil
}

// ASCIIFormat returns the ASCII format of the diff
func (d *DiffResult) ASCIIFormat(left *unstructured.Unstructured, formatOpts formatter.AsciiFormatterConfig) (string, error) {
	if !d.Diff.Modified() {
		return "", nil
	}
	if left == nil {
		return "", errors.New("Supplied nil left object")
	}
	asciiFmt := formatter.NewAsciiFormatter(left.Object, formatOpts)
	return asciiFmt.Format(d.Diff)
}

// https://github.com/ksonnet/ksonnet/blob/master/pkg/kubecfg/diff.go
func removeFields(config, live interface{}) interface{} {
	switch c := config.(type) {
	case map[string]interface{}:
		return RemoveMapFields(c, live.(map[string]interface{}))
	case []interface{}:
		return removeListFields(c, live.([]interface{}))
	default:
		return live
	}
}

// RemoveMapFields remove all non-existent fields in the live that don't exist in the config
func RemoveMapFields(config, live map[string]interface{}) map[string]interface{} {
	result := map[string]interface{}{}
	for k, v1 := range config {
		v2, ok := live[k]
		if !ok {
			continue
		}
		result[k] = removeFields(v1, v2)
	}
	return result
}

func removeListFields(config, live []interface{}) []interface{} {
	// If live is longer than config, then the extra elements at the end of the
	// list will be returned as-is so they appear in the diff.
	result := make([]interface{}, 0, len(live))
	for i, v2 := range live {
		if len(config) > i {
			result = append(result, removeFields(config[i], v2))
		} else {
			result = append(result, v2)
		}
	}
	return result
}
<|endoftext|>


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

package generate

import (
	"fmt"
	"log"
	"reflect"
)

// Return the string that should be used to refer to the supplied type within
// the given package.
//
// For example, a pointer to an io.Reader may be rendered as "*Reader" or
// "*io.Reader" depending on whether the package path is "io" or not.
func typeString(
	t reflect.Type,
	pkgPath string) (s string) {
	// Is this type named? If so we use its name, possibly with a package prefix.
	//
	// Examples:
	//
	//     int
	//     string
	//     error
	//     gcs.Bucket
	//
	if t.Name() != "" {
		if t.PkgPath() == pkgPath {
			s = t.Name()
		} else {
			s = t.String()
		}

		return
	}

	// This type is unnamed. Recurse.
	switch t.Kind() {
	case reflect.Array:
		s = fmt.Sprintf("[%d]%s", t.Len(), typeString(t.Elem(), pkgPath))

	default:
		log.Panicf("Unhandled kind: %v", t.Kind())
	}

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

package generate

import (
	"fmt"
	"log"
	"reflect"
)

// Return the string that should be used to refer to the supplied type within
// the given package.
//
// For example, a pointer to an io.Reader may be rendered as "*Reader" or
// "*io.Reader" depending on whether the package path is "io" or not.
func typeString(
	t reflect.Type,
	pkgPath string) (s string) {
	// Is this type named? If so we use its name, possibly with a package prefix.
	//
	// Examples:
	//
	//     int
	//     string
	//     error
	//     gcs.Bucket
	//
	if t.Name() != "" {
		if t.PkgPath() == pkgPath {
			s = t.Name()
		} else {
			s = t.String()
		}

		return
	}

	// This type is unnamed. Recurse.
	switch t.Kind() {
	case reflect.Array:
		s = fmt.Sprintf("[%d]%s", t.Len(), typeString(t.Elem(), pkgPath))

	case reflect.Chan:
		s = fmt.Sprintf("%s %s", t.ChanDir(), typeString(t.Elem(), pkgPath))

	case reflect.Func:
		s = funcTypeString(t)

	case reflect.Map:
		s = fmt.Sprintf(
			"map[%s]%s",
			typeString(t.Key(), pkgPath),
			typeString(t.Elem(), pkgPath))

	case reflect.Ptr:
		s = fmt.Sprintf("*%s", typeString(t.Elem(), pkgPath))

	case reflect.Slice:
		s = fmt.Sprintf("[]%s", typeString(t.Elem(), pkgPath))

	default:
		log.Panicf("Unhandled kind: %v", t.Kind())
	}

	return
}

func funcTypeString(t reflect.Type) (s string) {
	s = "TODO"
	return
}
<|endoftext|>


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

package auth

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

/*
   When establishing a connection to the mux, in addition to the address of the receiver,
   the sender sends an authentication token and signs the whole message. The token determines
   if the sender is authorized to send data to the receiver or not

   --------------------------------------------------------------------------------------------------------
   | Auth Token length (4 bytes)  |     Auth Token (N bytes)  | Address (6 bytes) |  Signature (32 bytes) |
   --------------------------------------------------------------------------------------------------------
*/

var (
	endian           = binary.BigEndian
	ErrBadMuxAddress = errors.New("Bad mux address")
	ErrBadMuxHeader  = errors.New("Bad mux header")
	ErrBadToken      = errors.New("Could not extract token")
)

const (
	ADDRESS_BYTES   = 6
	TOKEN_LEN_BYTES = 4
	SIGNATURE_BYTES = 32
)

func BuildMuxHeader(address []byte) ([]byte, error) {
	// get current host token
	return BuildAuthMuxHeader(address, AuthToken())
}

func BuildAuthMuxHeader(address []byte, token string) ([]byte, error) {
	if len(address) != ADDRESS_BYTES {
		return nil, ErrBadMuxAddress
	}
	headerBuf := new(bytes.Buffer)

	// add token length
	var tokenLen uint32 = uint32(len(token))
	tokenLenBuf := make([]byte, 4)
	endian.PutUint32(tokenLenBuf, tokenLen)
	headerBuf.Write(tokenLenBuf)

	// add token
	headerBuf.Write([]byte(token))

	// add address
	headerBuf.Write([]byte(address))

	// Sign what we have so far
	signature, err := SignAsDelegate(headerBuf.Bytes())
	if err != nil {
		return nil, err
	}
	// add signature to header
	headerBuf.Write(signature)

	return headerBuf.Bytes(), nil
}

func errorExtractingHeader(err error) ([]byte, Identity, error) {
	return nil, nil, err
}

func ExtractMuxHeader(rawHeader []byte) ([]byte, Identity, error) {
	if len(rawHeader) <= TOKEN_LEN_BYTES+ADDRESS_BYTES {
		return errorExtractingHeader(ErrBadMuxHeader)
	}

	var offset uint32 = 0

	// First four bytes represents the token length
	tokenLen := endian.Uint32(rawHeader[offset : offset+TOKEN_LEN_BYTES])
	offset += TOKEN_LEN_BYTES
	if len(rawHeader) <= TOKEN_LEN_BYTES+int(tokenLen)+ADDRESS_BYTES {
		return errorExtractingHeader(ErrBadMuxHeader)
	}

	// Next tokeLen bytes contain the token
	token := string(rawHeader[offset : offset+tokenLen])
	offset += tokenLen

	// Validate the token can be parsed
	senderIdentity, err := ParseJWTIdentity(token)
	if err != nil || senderIdentity == nil {
		if err == nil || senderIdentity == nil {
			err = ErrBadToken
		}
		return errorExtractingHeader(err)
	}

	// Next six bytes is going to be the address
	address := rawHeader[offset : offset+ADDRESS_BYTES]
	offset += ADDRESS_BYTES

	// get the part of the header that has been signed
	signed_message := rawHeader[:offset]

	// Whatever is left is the signature
	signature := rawHeader[offset:]

	// Verify the identity of the signed message
	senderVerifier, err := senderIdentity.Verifier()
	if err != nil {
		return errorExtractingHeader(err)
	}
	err = senderVerifier.Verify(signed_message, signature)
	if err != nil {
		return errorExtractingHeader(err)
	}

	return address, senderIdentity, nil
}

func ReadMuxHeader(conn net.Conn) ([]byte, error) {
	// Read token Length
	tokenLenBuff := make([]byte, TOKEN_LEN_BYTES)
	_, err := conn.Read(tokenLenBuff)
	if err != nil {
		return nil, err
	}
	tokenLen := endian.Uint32(tokenLenBuff)
	// Read rest of the header
	remainderBuff := make([]byte, tokenLen+ADDRESS_BYTES+SIGNATURE_BYTES)
	_, err = conn.Read(remainderBuff)
	if err != nil {
		return nil, err
	}
	return append(tokenLenBuff, remainderBuff...), nil
}
<commit_msg>Make signature 256 bytes, which it is<commit_after>// Copyright 2016 The Serviced Authors.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package auth

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

/*
   When establishing a connection to the mux, in addition to the address of the receiver,
   the sender sends an authentication token and signs the whole message. The token determines
   if the sender is authorized to send data to the receiver or not

   ---------------------------------------------------------------------------------------------------------
   | Auth Token length (4 bytes)  |     Auth Token (N bytes)  | Address (6 bytes) |  Signature (256 bytes) |
   ---------------------------------------------------------------------------------------------------------
*/

var (
	endian           = binary.BigEndian
	ErrBadMuxAddress = errors.New("Bad mux address")
	ErrBadMuxHeader  = errors.New("Bad mux header")
	ErrBadToken      = errors.New("Could not extract token")
)

const (
	ADDRESS_BYTES   = 6
	TOKEN_LEN_BYTES = 4
	SIGNATURE_BYTES = 256
)

func BuildMuxHeader(address []byte) ([]byte, error) {
	// get current host token
	return BuildAuthMuxHeader(address, AuthToken())
}

func BuildAuthMuxHeader(address []byte, token string) ([]byte, error) {
	if len(address) != ADDRESS_BYTES {
		return nil, ErrBadMuxAddress
	}
	headerBuf := new(bytes.Buffer)

	// add token length
	var tokenLen uint32 = uint32(len(token))
	tokenLenBuf := make([]byte, 4)
	endian.PutUint32(tokenLenBuf, tokenLen)
	headerBuf.Write(tokenLenBuf)

	// add token
	headerBuf.Write([]byte(token))

	// add address
	headerBuf.Write([]byte(address))

	// Sign what we have so far
	signature, err := SignAsDelegate(headerBuf.Bytes())
	if err != nil {
		return nil, err
	}
	// add signature to header
	headerBuf.Write(signature)

	return headerBuf.Bytes(), nil
}

func errorExtractingHeader(err error) ([]byte, Identity, error) {
	return nil, nil, err
}

func ExtractMuxHeader(rawHeader []byte) ([]byte, Identity, error) {
	if len(rawHeader) <= TOKEN_LEN_BYTES+ADDRESS_BYTES {
		return errorExtractingHeader(ErrBadMuxHeader)
	}

	var offset uint32 = 0

	// First four bytes represents the token length
	tokenLen := endian.Uint32(rawHeader[offset : offset+TOKEN_LEN_BYTES])
	offset += TOKEN_LEN_BYTES
	if len(rawHeader) <= TOKEN_LEN_BYTES+int(tokenLen)+ADDRESS_BYTES {
		return errorExtractingHeader(ErrBadMuxHeader)
	}

	// Next tokeLen bytes contain the token
	token := string(rawHeader[offset : offset+tokenLen])
	offset += tokenLen

	// Validate the token can be parsed
	senderIdentity, err := ParseJWTIdentity(token)
	if err != nil || senderIdentity == nil {
		if err == nil || senderIdentity == nil {
			err = ErrBadToken
		}
		return errorExtractingHeader(err)
	}

	// Next six bytes is going to be the address
	address := rawHeader[offset : offset+ADDRESS_BYTES]
	offset += ADDRESS_BYTES

	// get the part of the header that has been signed
	signed_message := rawHeader[:offset]

	// Whatever is left is the signature
	signature := rawHeader[offset:]

	// Verify the identity of the signed message
	senderVerifier, err := senderIdentity.Verifier()
	if err != nil {
		return errorExtractingHeader(err)
	}
	err = senderVerifier.Verify(signed_message, signature)
	if err != nil {
		return errorExtractingHeader(err)
	}

	return address, senderIdentity, nil
}

func ReadMuxHeader(conn net.Conn) ([]byte, error) {
	// Read token Length
	tokenLenBuff := make([]byte, TOKEN_LEN_BYTES)
	_, err := conn.Read(tokenLenBuff)
	if err != nil {
		return nil, err
	}
	tokenLen := endian.Uint32(tokenLenBuff)
	// Read rest of the header
	remainderBuff := make([]byte, tokenLen+ADDRESS_BYTES+SIGNATURE_BYTES)
	_, err = conn.Read(remainderBuff)
	if err != nil {
		return nil, err
	}
	return append(tokenLenBuff, remainderBuff...), nil
}
<|endoftext|>


<commit_before>package docker

import (
	"bufio"
	"context"
	"github.com/docker/docker/api/types"
	"github.com/gorilla/websocket"
	"log"
	"net/http"
	"regexp"
)

const ignoredByteLogSize = 8

var logWebsocketRequest = regexp.MustCompile(`/containers/([^/]+)/logs`)
var hostCheck = regexp.MustCompile(`vibioh\.fr$`)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin: func(r *http.Request) bool {
		return hostCheck.MatchString(r.Host)
	},
}

func logsContainerWebsocketHandler(w http.ResponseWriter, r *http.Request, containerID []byte) {
	ws, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Print(err)
		return
	}

	defer ws.Close()

	_, basicAuth, err := ws.ReadMessage()
	if err != nil {
		log.Print(err)
		return
	}

	if _, err := isAuthenticatedByBasicAuth(string(basicAuth)); err != nil {
		ws.WriteMessage(websocket.TextMessage, []byte(err.Error()))
		return
	}

	logs, err := docker.ContainerLogs(context.Background(), string(containerID), types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: true})
	if err != nil {
		log.Print(err)
		return
	}

	defer logs.Close()

	go func() {
		scanner := bufio.NewScanner(logs)
		for scanner.Scan() {
			log := scanner.Bytes()
			if len(log) > ignoredByteLogSize {
				if err = ws.WriteMessage(websocket.TextMessage, log[ignoredByteLogSize:]); err != nil {
					log.Print(err)
					return
				}
			}
		}
	}()

	for {
		if messageType, _, err := ws.NextReader(); messageType == websocket.CloseMessage || err != nil {
			if messageType != websocket.CloseMessage {
				log.Print(err)
			}
			return
		}
	}
}

func handleWebsocket(w http.ResponseWriter, r *http.Request) {
	urlPath := []byte(r.URL.Path)

	if logWebsocketRequest.Match(urlPath) {
		logsContainerWebsocketHandler(w, r, logWebsocketRequest.FindSubmatch(urlPath)[1])
	}
}
<commit_msg>Renaming var<commit_after>package docker

import (
	"bufio"
	"context"
	"github.com/docker/docker/api/types"
	"github.com/gorilla/websocket"
	"log"
	"net/http"
	"regexp"
)

const ignoredByteLogSize = 8

var logWebsocketRequest = regexp.MustCompile(`/containers/([^/]+)/logs`)
var hostCheck = regexp.MustCompile(`vibioh\.fr$`)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin: func(r *http.Request) bool {
		return hostCheck.MatchString(r.Host)
	},
}

func logsContainerWebsocketHandler(w http.ResponseWriter, r *http.Request, containerID []byte) {
	ws, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Print(err)
		return
	}

	defer ws.Close()

	_, basicAuth, err := ws.ReadMessage()
	if err != nil {
		log.Print(err)
		return
	}

	if _, err := isAuthenticatedByBasicAuth(string(basicAuth)); err != nil {
		ws.WriteMessage(websocket.TextMessage, []byte(err.Error()))
		return
	}

	logs, err := docker.ContainerLogs(context.Background(), string(containerID), types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: true})
	if err != nil {
		log.Print(err)
		return
	}

	defer logs.Close()

	go func() {
		scanner := bufio.NewScanner(logs)
		for scanner.Scan() {
			logLine := scanner.Bytes()
			if len(logLine) > ignoredByteLogSize {
				if err = ws.WriteMessage(websocket.TextMessage, logLine[ignoredByteLogSize:]); err != nil {
					log.Print(err)
					return
				}
			}
		}
	}()

	for {
		if messageType, _, err := ws.NextReader(); messageType == websocket.CloseMessage || err != nil {
			if messageType != websocket.CloseMessage {
				log.Print(err)
			}
			return
		}
	}
}

func handleWebsocket(w http.ResponseWriter, r *http.Request) {
	urlPath := []byte(r.URL.Path)

	if logWebsocketRequest.Match(urlPath) {
		logsContainerWebsocketHandler(w, r, logWebsocketRequest.FindSubmatch(urlPath)[1])
	}
}
<|endoftext|>


<commit_before>/* Local file storage stuff, for when we just want to upload files locally and
 * not send them to s3 or somesuch. A building block of sandbox and cookbook
 * functionality. */

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

// Package filestore provides local file uploads and downloads for cookbook
// uploading and downloading. All access to the files is through the checksum,
// rather than the file name.
//
// If config.Config.LocalFstoreDir is != "", the content of the files will be
// stored in that directory.
package filestore

import (
	"crypto/md5"
	"database/sql"
	"fmt"
	"github.com/ctdk/goas/v2/logger"
	"github.com/ctdk/goiardi/config"
	"github.com/ctdk/goiardi/datastore"
	"io"
	"os"
	"path"
)

/* Local filestorage struct. Add fields as needed. */

// FileStore is an individual file in the filestore. Note that there is no
// actual name for the file used, but it is identified by the file's checksum.
// The file's data is stored as a pointer to an array of bytes.
type FileStore struct {
	Chksum string
	Data   *[]byte
}

/* New, for this, includes giving it the file data */

// New creates a new filestore item with the given checksum, io.ReadCloser
// holding the file's data, and the length of the file. If the file data's
// checksum does not match the provided checksum an error will be trhown.
func New(chksum string, data io.ReadCloser, dataLength int64) (*FileStore, error) {
	if _, err := Get(chksum); err == nil {
		err := fmt.Errorf("File with checksum %s already exists.", chksum)
		return nil, err
	}
	/* Read the data in */
	fileData := make([]byte, dataLength)
	if n, err := io.ReadFull(data, fileData); err != nil {
		/* Something went wrong reading the data! */
		readErr := fmt.Errorf("Only read %d bytes (out of %d, supposedly) from io.ReadCloser: %s", n, dataLength, err.Error())
		return nil, readErr
	}
	/* Verify checksum. May move to a different function later. */
	verChk := md5.New()
	/* try writestring first */
	verChk.Write(fileData)
	verChksum := fmt.Sprintf("%x", verChk.Sum(nil))
	if verChksum != chksum {
		chkErr := fmt.Errorf("Checksum %s did not match original %s!", verChksum, chksum)
		return nil, chkErr
	}
	filestore := &FileStore{
		Chksum: chksum,
		Data:   &fileData,
	}
	return filestore, nil
}

// Get the file with this checksum.
func Get(chksum string) (*FileStore, error) {
	var filestore *FileStore
	var found bool
	if config.UsingDB() {
		var err error
		filestore, err = getSQL(chksum)
		if err != nil {
			if err == sql.ErrNoRows {
				found = false
			} else {
				return nil, err
			}
		} else {
			found = true
		}
	} else {
		ds := datastore.New()
		var f interface{}
		f, found = ds.Get("filestore", chksum)
		if f != nil {
			filestore = f.(*FileStore)
		}
	}
	if !found {
		err := fmt.Errorf("File with checksum %s not found", chksum)
		return nil, err
	}
	if config.Config.LocalFstoreDir != "" {
		if err := filestore.loadData(); err != nil {
			return nil, err
		}
	}
	return filestore, nil
}

func (f *FileStore) loadData() error {
	/* If this is called, file data is stored on disk */
	chkPath := path.Join(config.Config.LocalFstoreDir, f.Chksum)

	fp, err := os.Open(chkPath)
	if err != nil {
		return err
	}
	defer fp.Close()
	stat, sterr := fp.Stat()
	if sterr != nil {
		return sterr
	}
	fdata := make([]byte, stat.Size())
	n, fperr := fp.Read(fdata)
	if fperr != nil {
		return fperr
	} else if int64(n) != stat.Size() {
		err = fmt.Errorf("only %d bytes were read from the expected %d", n, stat.Size())
		return err
	}
	f.Data = &fdata
	return nil
}

// Save a file store item.
func (f *FileStore) Save() error {
	if config.Config.UseMySQL {
		err := f.saveMySQL()
		if err != nil {
			return err
		}
	} else if config.Config.UsePostgreSQL {
		err := f.savePostgreSQL()
		if err != nil {
			return nil
		}
	} else {
		ds := datastore.New()
		ds.Set("filestore", f.Chksum, f)
	}
	if config.Config.LocalFstoreDir != "" {
		fp, err := os.Create(path.Join(config.Config.LocalFstoreDir, f.Chksum))
		if err != nil {
			return err
		}
		defer fp.Close()
		_, err = fp.Write(*f.Data)
		if err != nil {
			return err
		}
		return fp.Close()
	}
	return nil
}

// Delete a file store item.
func (f *FileStore) Delete() error {
	if config.UsingDB() {
		err := f.deleteSQL()
		if err != nil {
			return err
		}
	} else {
		ds := datastore.New()
		ds.Delete("filestore", f.Chksum)
	}

	if config.Config.LocalFstoreDir != "" {
		err := os.Remove(path.Join(config.Config.LocalFstoreDir, f.Chksum))
		if err != nil {
			return err
		}
	}
	return nil
}

// GetList gets a list of files that have been uploaded.
func GetList() []string {
	var fileList []string
	if config.UsingDB() {
		fileList = getListSQL()
	} else {
		ds := datastore.New()
		fileList = ds.GetList("filestore")
	}
	return fileList
}

// DeleteHashes deletes all the checksum hashes given from the filestore.
func DeleteHashes(fileHashes []string) {
	if config.Config.UseMySQL {
		deleteHashesMySQL(fileHashes)
	} else if config.Config.UsePostgreSQL {
		deleteHashesPostgreSQL(fileHashes)
	} else {
		for _, ff := range fileHashes {
			delFile, err := Get(ff)
			if err != nil {
				logger.Debugf("Strange, we got an error trying to get %s to delete it.\n", ff)
				logger.Debugf(err.Error())
			} else {
				_ = delFile.Delete()
			}
			// May be able to remove this. Check that it actually deleted
			d, _ := Get(ff)
			if d != nil {
				logger.Debugf("Stranger and stranger, %s is still in the file store.\n", ff)
			}
		}
	}
	if config.Config.LocalFstoreDir != "" {
		for _, fh := range fileHashes {
			err := os.Remove(path.Join(config.Config.LocalFstoreDir, fh))
			if err != nil {
				logger.Errorf(err.Error())
			}
		}
	}
}

// AllFilestores returns all file checksums and their contents, for exporting.
func AllFilestores() []*FileStore {
	var filestores []*FileStore
	if config.UsingDB() {
		filestores = allFilestoresSQL()
	} else {
		fileList := GetList()
		for _, f := range fileList {
			fl, err := Get(f)
			if err != nil {
				logger.Debugf("File checksum %s was in the list of files, but wasn't found when fetched. Continuing.", f)
				continue
			}
			filestores = append(filestores, fl)
		}
	}
	return filestores
}
<commit_msg>Make uploading files a little more forgiving<commit_after>/* Local file storage stuff, for when we just want to upload files locally and
 * not send them to s3 or somesuch. A building block of sandbox and cookbook
 * functionality. */

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

// Package filestore provides local file uploads and downloads for cookbook
// uploading and downloading. All access to the files is through the checksum,
// rather than the file name.
//
// If config.Config.LocalFstoreDir is != "", the content of the files will be
// stored in that directory.
package filestore

import (
	"bytes"
	"crypto/md5"
	"database/sql"
	"fmt"
	"github.com/ctdk/goas/v2/logger"
	"github.com/ctdk/goiardi/config"
	"github.com/ctdk/goiardi/datastore"
	"io"
	"os"
	"path"
)

/* Local filestorage struct. Add fields as needed. */

// FileStore is an individual file in the filestore. Note that there is no
// actual name for the file used, but it is identified by the file's checksum.
// The file's data is stored as a pointer to an array of bytes.
type FileStore struct {
	Chksum string
	Data   *[]byte
}

/* New, for this, includes giving it the file data */

// New creates a new filestore item with the given checksum, io.ReadCloser
// holding the file's data, and the length of the file. If the file data's
// checksum does not match the provided checksum an error will be trhown.
func New(chksum string, data io.ReadCloser, dataLength int64) (*FileStore, error) {
	f, err := Get(chksum);
	if err == nil {
		// if err is nil, wait until checking the uploaded content to
		// see if it's the same as what we have already
		err = fmt.Errorf("File with checksum %s already exists.", chksum)
	}
	/* Read the data in */
	fileData := make([]byte, dataLength)
	if n, err := io.ReadFull(data, fileData); err != nil {
		/* Something went wrong reading the data! */
		readErr := fmt.Errorf("Only read %d bytes (out of %d, supposedly) from io.ReadCloser: %s", n, dataLength, err.Error())
		return nil, readErr
	}
	if f != nil {
		if !bytes.Equal(fileData, *f.Data) {
			return nil, err
		}
	}
	/* Verify checksum. May move to a different function later. */
	verChk := md5.New()
	/* try writestring first */
	verChk.Write(fileData)
	verChksum := fmt.Sprintf("%x", verChk.Sum(nil))
	if verChksum != chksum {
		chkErr := fmt.Errorf("Checksum %s did not match original %s!", verChksum, chksum)
		return nil, chkErr
	}
	filestore := &FileStore{
		Chksum: chksum,
		Data:   &fileData,
	}
	return filestore, nil
}

// Get the file with this checksum.
func Get(chksum string) (*FileStore, error) {
	var filestore *FileStore
	var found bool
	if config.UsingDB() {
		var err error
		filestore, err = getSQL(chksum)
		if err != nil {
			if err == sql.ErrNoRows {
				found = false
			} else {
				return nil, err
			}
		} else {
			found = true
		}
	} else {
		ds := datastore.New()
		var f interface{}
		f, found = ds.Get("filestore", chksum)
		if f != nil {
			filestore = f.(*FileStore)
		}
	}
	if !found {
		err := fmt.Errorf("File with checksum %s not found", chksum)
		return nil, err
	}
	if config.Config.LocalFstoreDir != "" {
		if err := filestore.loadData(); err != nil {
			return nil, err
		}
	}
	return filestore, nil
}

func (f *FileStore) loadData() error {
	/* If this is called, file data is stored on disk */
	chkPath := path.Join(config.Config.LocalFstoreDir, f.Chksum)

	fp, err := os.Open(chkPath)
	if err != nil {
		return err
	}
	defer fp.Close()
	stat, sterr := fp.Stat()
	if sterr != nil {
		return sterr
	}
	fdata := make([]byte, stat.Size())
	n, fperr := fp.Read(fdata)
	if fperr != nil {
		return fperr
	} else if int64(n) != stat.Size() {
		err = fmt.Errorf("only %d bytes were read from the expected %d", n, stat.Size())
		return err
	}
	f.Data = &fdata
	return nil
}

// Save a file store item.
func (f *FileStore) Save() error {
	if config.Config.UseMySQL {
		err := f.saveMySQL()
		if err != nil {
			return err
		}
	} else if config.Config.UsePostgreSQL {
		err := f.savePostgreSQL()
		if err != nil {
			return nil
		}
	} else {
		ds := datastore.New()
		ds.Set("filestore", f.Chksum, f)
	}
	if config.Config.LocalFstoreDir != "" {
		fp, err := os.Create(path.Join(config.Config.LocalFstoreDir, f.Chksum))
		if err != nil {
			return err
		}
		defer fp.Close()
		_, err = fp.Write(*f.Data)
		if err != nil {
			return err
		}
		return fp.Close()
	}
	return nil
}

// Delete a file store item.
func (f *FileStore) Delete() error {
	if config.UsingDB() {
		err := f.deleteSQL()
		if err != nil {
			return err
		}
	} else {
		ds := datastore.New()
		ds.Delete("filestore", f.Chksum)
	}

	if config.Config.LocalFstoreDir != "" {
		err := os.Remove(path.Join(config.Config.LocalFstoreDir, f.Chksum))
		if err != nil {
			return err
		}
	}
	return nil
}

// GetList gets a list of files that have been uploaded.
func GetList() []string {
	var fileList []string
	if config.UsingDB() {
		fileList = getListSQL()
	} else {
		ds := datastore.New()
		fileList = ds.GetList("filestore")
	}
	return fileList
}

// DeleteHashes deletes all the checksum hashes given from the filestore.
func DeleteHashes(fileHashes []string) {
	if config.Config.UseMySQL {
		deleteHashesMySQL(fileHashes)
	} else if config.Config.UsePostgreSQL {
		deleteHashesPostgreSQL(fileHashes)
	} else {
		for _, ff := range fileHashes {
			delFile, err := Get(ff)
			if err != nil {
				logger.Debugf("Strange, we got an error trying to get %s to delete it.\n", ff)
				logger.Debugf(err.Error())
			} else {
				_ = delFile.Delete()
			}
			// May be able to remove this. Check that it actually deleted
			d, _ := Get(ff)
			if d != nil {
				logger.Debugf("Stranger and stranger, %s is still in the file store.\n", ff)
			}
		}
	}
	if config.Config.LocalFstoreDir != "" {
		for _, fh := range fileHashes {
			err := os.Remove(path.Join(config.Config.LocalFstoreDir, fh))
			if err != nil {
				logger.Errorf(err.Error())
			}
		}
	}
}

// AllFilestores returns all file checksums and their contents, for exporting.
func AllFilestores() []*FileStore {
	var filestores []*FileStore
	if config.UsingDB() {
		filestores = allFilestoresSQL()
	} else {
		fileList := GetList()
		for _, f := range fileList {
			fl, err := Get(f)
			if err != nil {
				logger.Debugf("File checksum %s was in the list of files, but wasn't found when fetched. Continuing.", f)
				continue
			}
			filestores = append(filestores, fl)
		}
	}
	return filestores
}
<|endoftext|>


<commit_before>// Copyright 2012, Brian Hetro <whee@smaertness.net>
// Use of this source code is governed by the ISC license
// that can be found in the LICENSE file.

// DuckDuckGo Zero-click API client. See https://duckduckgo.com/api.html
// for information about the API.
//
// Example command-line program to show abstracts:
//
//	package main
//
//	import (
//		"fmt"
//		"github.com/whee/ddg"
//		"os"
//	)
//
//	func main() {
//		for _, s := range os.Args[1:] {
//			if r, err := ddg.ZeroClick(s); err == nil {
//				fmt.Printf("%s: %s\n", s, r.Abstract)
//			} else {
//				fmt.Printf("Error looking up %s: %v\n", s, err)
//			}
//		}
//	}
package ddg

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

// Response represents the response from a zero-click API request via
// the ZeroClick function.
type Response struct {
	Abstract       string // topic summary (can contain HTML, e.g. italics)
	AbstractText   string // topic summary (with no HTML)
	AbstractSource string // name of Abstract source
	AbstractURL    string // deep link to expanded topic page in AbstractSource
	Image          string // link to image that goes with Abstract
	Heading        string // name of topic that goes with Abstract

	Answer     string // instant answer
	AnswerType string // type of Answer, e.g. calc, color, digest, info, ip, iploc, phone, pw, rand, regexp, unicode, upc, or zip (see goodies & tech pages for examples).

	Definition       string // dictionary definition (may differ from Abstract)
	DefinitionSource string // name of Definition source
	DefinitionURL    string // deep link to expanded definition page in DefinitionSource

	RelatedTopics []Result // array of internal links to related topics associated with Abstract

	Results []Result // array of external links associated with Abstract

	Type CategoryType // response category, i.e. A (article), D (disambiguation), C (category), N (name), E (exclusive), or nothing.

	Redirect string // !bang redirect URL
}

type Result struct {
	Result   string // HTML link(s) to external site(s)
	FirstURL string // first URL in Result
	Icon     Icon   // icon associated with FirstURL
	Text     string // text from FirstURL
}

type Icon struct {
	URL    string      // URL of icon
	Height interface{} // height of icon (px)
	Width  interface{} // width of icon (px)
}

type CategoryType string

const (
	Article        = "A"
	Disambiguation = "D"
	Category       = "C"
	Name           = "N"
	Exclusive      = "E"
	None           = ""
)

// A Client is a DDG Zero-click client.
type Client struct {
	// Secure specifies whether HTTPS is used.
	Secure bool
}

// ZeroClick queries DuckDuckGo's zero-click API for the specified query
// and returns the Response.
func (c *Client) ZeroClick(query string) (res Response, err error) {
	// TODO: Support some of the available configuration (e.g., no html)
	v := url.Values{}
	v.Set("q", query)
	v.Set("format", "json")
	// TODO: support the disambiguation category type
	v.Set("skip_disambig", "1")

	var scheme string
	if c.Secure {
		scheme = "https"
	} else {
		scheme = "http"
	}

	req, err := http.NewRequest("GET", scheme+"://api.duckduckgo.com/?"+v.Encode(), nil)
	if err != nil {
		return
	}
	req.Header.Set("User-Agent", "ddg.go/0.1")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	err = json.NewDecoder(resp.Body).Decode(&res)
	if err == io.EOF {
		err = nil
	}

	return
}

// ZeroClick queries DuckDuckGo's zero-click API for the specified query
// and returns the Response. This helper function uses a zero-value Client.
func ZeroClick(query string) (res Response, err error) {
	c := &Client{}
	return c.ZeroClick(query)
}
<commit_msg>Handle Icon width/height as interface{}<commit_after>// Copyright 2012, Brian Hetro <whee@smaertness.net>
// Use of this source code is governed by the ISC license
// that can be found in the LICENSE file.

// DuckDuckGo Zero-click API client. See https://duckduckgo.com/api.html
// for information about the API.
//
// Example command-line program to show abstracts:
//
//	package main
//
//	import (
//		"fmt"
//		"github.com/whee/ddg"
//		"os"
//	)
//
//	func main() {
//		for _, s := range os.Args[1:] {
//			if r, err := ddg.ZeroClick(s); err == nil {
//				fmt.Printf("%s: %s\n", s, r.Abstract)
//			} else {
//				fmt.Printf("Error looking up %s: %v\n", s, err)
//			}
//		}
//	}
package ddg

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

// Response represents the response from a zero-click API request via
// the ZeroClick function.
type Response struct {
	Abstract       string // topic summary (can contain HTML, e.g. italics)
	AbstractText   string // topic summary (with no HTML)
	AbstractSource string // name of Abstract source
	AbstractURL    string // deep link to expanded topic page in AbstractSource
	Image          string // link to image that goes with Abstract
	Heading        string // name of topic that goes with Abstract

	Answer     string // instant answer
	AnswerType string // type of Answer, e.g. calc, color, digest, info, ip, iploc, phone, pw, rand, regexp, unicode, upc, or zip (see goodies & tech pages for examples).

	Definition       string // dictionary definition (may differ from Abstract)
	DefinitionSource string // name of Definition source
	DefinitionURL    string // deep link to expanded definition page in DefinitionSource

	RelatedTopics []Result // array of internal links to related topics associated with Abstract

	Results []Result // array of external links associated with Abstract

	Type CategoryType // response category, i.e. A (article), D (disambiguation), C (category), N (name), E (exclusive), or nothing.

	Redirect string // !bang redirect URL
}

type Result struct {
	Result   string // HTML link(s) to external site(s)
	FirstURL string // first URL in Result
	Icon     Icon   // icon associated with FirstURL
	Text     string // text from FirstURL
}

type Icon struct {
	URL    string      // URL of icon
	Height int        `json:"-"` // height of icon (px)
	Width  int        `json:"-"` // width of icon (px)

	// The height and width can be "" (string; we treat as 0) or an int. Unmarshal here, then populate the above two.
	RawHeight interface{} `json:"Height"`
	RawWidth interface{} `json:"Width"`
}

type CategoryType string

const (
	Article        = "A"
	Disambiguation = "D"
	Category       = "C"
	Name           = "N"
	Exclusive      = "E"
	None           = ""
)

// A Client is a DDG Zero-click client.
type Client struct {
	// Secure specifies whether HTTPS is used.
	Secure bool
}

// ZeroClick queries DuckDuckGo's zero-click API for the specified query
// and returns the Response.
func (c *Client) ZeroClick(query string) (res Response, err error) {
	// TODO: Support some of the available configuration (e.g., no html)
	v := url.Values{}
	v.Set("q", query)
	v.Set("format", "json")
	// TODO: support the disambiguation category type
	v.Set("skip_disambig", "1")

	var scheme string
	if c.Secure {
		scheme = "https"
	} else {
		scheme = "http"
	}

	req, err := http.NewRequest("GET", scheme+"://api.duckduckgo.com/?"+v.Encode(), nil)
	if err != nil {
		return
	}
	req.Header.Set("User-Agent", "ddg.go/0.1")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	err = json.NewDecoder(resp.Body).Decode(&res)
	if err == io.EOF {
		err = nil
	}
	handleInterfaces(&res)

	return
}

// handleInterfaces cleans up incoming data that can be of multiple types; for example, the
// icon width and height are either a float64 or string, but we want to treat them as int.
func handleInterfaces(response *Response) {
	for _, result := range response.Results {
		if height, ok := result.Icon.RawHeight.(float64); ok {
			result.Icon.Height = int(height)
		}
		if width, ok := result.Icon.RawWidth.(float64); ok {
			result.Icon.Width = int(width)
		}
	}
}

// ZeroClick queries DuckDuckGo's zero-click API for the specified query
// and returns the Response. This helper function uses a zero-value Client.
func ZeroClick(query string) (res Response, err error) {
	c := &Client{}
	return c.ZeroClick(query)
}
<|endoftext|>


<commit_before>/*
 Copyright Digital Asset Holdings, LLC 2016 All Rights Reserved.

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

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

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

package main

// Component codes - should be set to the same key in the errorCodes json below
const (
	Utility ComponentCode = "Utility"
	Logging ComponentCode = "Logging"
	Peer    ComponentCode = "Peer"
)

// Reason codes - should be set to the same key in the errorCodes json below
const (
	// Utility errors - placeholders
	UtilityUnknownError ReasonCode = "UtilityUnknownError"
	UtilityErrorWithArg ReasonCode = "UtilityErrorWithArg"

	// Logging errors
	LoggingUnknownError        ReasonCode = "LoggingUnknownError"
	LoggingErrorWithArg        ReasonCode = "LoggingErrorWithArg"
	LoggingNoParameters        ReasonCode = "LoggingNoParameters"
	LoggingNoLogLevelParameter ReasonCode = "LoggingNoLogLevelParameter"
	LoggingInvalidLogLevel     ReasonCode = "LoggingInvalidLogLevel"

	// Peer errors
	PeerConnectionError ReasonCode = "PeerConnectionError"
)

// To use this file to define a new component code or reason code, please follow
// these steps:
// 1. Add the new component code below the last component code or add the
// 		new reason code below the last reason code for the applicable component
//	  in the json formatted string below
// 2. Define the message in English using "en" as the key
// 3. Add a constant above for each new component/reason code making sure it
// 		matches the key used in the json formatted string below
// 4. Import "github.com/hyperledger/fabric/core/errors" wherever the error
//		will be created.
// 5. Reference the component and reason codes in the call to
// 		errors.ErrorWithCallstack as follows:
//			err = errors.ErrorWithCallstack(errors.Logging, errors.LoggingNoParameters)
// 6. Any code that receives this error will automatically have the callstack
//		appended if CORE_LOGGING_ERROR is set to DEBUG in peer/core.yaml, at the
//		command line when the peer is started, or if the error module is set
//		dynamically using the CLI call "peer logging setlevel error debug"
// 6. For an example in context, see "peer/clilogging/common.go", which creates
//		a number of callstack errors, and "peer/clilogging/setlevel.go", which
//		receives the errors
const errorMapping string = `
{"Utility" :
  {"UtilityUnknownError" :
    {"en": "An unknown error occurred."},
   "UtilityErrorWithArg":
    {"en": "An error occurred: %s"}
  },
 "Logging":
  {"LoggingUnknownError" :
    {"en": "A logging error occurred."},
   "LoggingErrorWithArg":
    {"en": "A logging error occurred: %s"},
   "LoggingNoParameters":
    {"en": "No parameters provided."},
   "LoggingNoLogLevelParameter":
    {"en": "No log level provided."},
   "LoggingInvalidLogLevel":
    {"en": "Invalid log level provided - %s"}
  },
 "Peer" :
  {"PeerConnectionError" :
    {"en": "Error trying to connect to local peer: %s"}
  }
}`
<commit_msg>Delete error_json.go<commit_after><|endoftext|>


<commit_before>// Copyright 2012 The Plotinum Authors. All rights reserved.
// Use of this source code is governed by an MIT-style license
// that can be found in the LICENSE file.

// The vecsvg package uses svgo as a backend for vg.
package vecsvg

import (
	"bufio"
	"bytes"
	"code.google.com/p/plotinum/vg"
	"fmt"
	svgo "github.com/ajstarks/svgo"
	"image/color"
	"io"
	"math"
	"os"
)

const (
	// inkscape, Chrome, FireFox, and gpicview all seem
	// to use 90 dots-per-inch.  I can't find anywhere that
	// this is actually specified, however.
	dpi = 90

	// pr is the amount of precision to use when outputting float64s.
	pr = 5
)

type Canvas struct {
	svg *svgo.SVG
	buf *bytes.Buffer
	ht  float64
	stk []context
}

type context struct {
	color      color.Color
	dashArray  []vg.Length
	dashOffset vg.Length
	lineWidth  vg.Length
	gEnds      int
}

func New(w, h vg.Length) *Canvas {
	buf := new(bytes.Buffer)
	c := &Canvas{
		svg: svgo.New(buf),
		buf: buf,
		ht:  w.Points(),
		stk: []context{context{}},
	}

	// This is like svg.Start, except it uses floats
	// and specifies the units.
	fmt.Fprintf(buf, `<?xml version="1.0"?>
<!-- Generated by SVGo and Plotinum VG -->
<svg width="%.*gin" height="%.*gin"
	xmlns="http://www.w3.org/2000/svg"
	xmlns:xlink="http://www.w3.org/1999/xlink">`+"\n",
		pr, w.Inches(), pr, h.Inches())

	// Swap the origin to the bottom left.
	// This must be matched with a </g> when saving,
	// before the closing </svg>.
	c.svg.Gtransform(fmt.Sprintf("scale(1, -1) translate(0, -%.*g)", pr, h.Dots(c)))

	vg.Initialize(c)
	return c
}

func (c *Canvas) cur() *context {
	return &c.stk[len(c.stk)-1]
}

func (c *Canvas) SetLineWidth(w vg.Length) {
	c.cur().lineWidth = w
}

func (c *Canvas) SetLineDash(dashes []vg.Length, offs vg.Length) {
	c.cur().dashArray = dashes
	c.cur().dashOffset = offs
}

func (c *Canvas) SetColor(clr color.Color) {
	c.cur().color = clr
}

func (c *Canvas) Rotate(rot float64) {
	rot = rot * 180 / math.Pi
	c.svg.Rotate(rot)
	c.cur().gEnds++
}

func (c *Canvas) Translate(x, y vg.Length) {
	c.svg.Gtransform(fmt.Sprintf("translate(%.*g, %.*g)", pr, x.Points(), pr, y.Points()))
	c.cur().gEnds++
}

func (c *Canvas) Scale(x, y float64) {
	c.svg.ScaleXY(x, y)
	c.cur().gEnds++
}

func (c *Canvas) Push() {
	top := *c.cur()
	top.gEnds = 0
	c.stk = append(c.stk, top)
}

func (c *Canvas) Pop() {
	for i := 0; i < c.cur().gEnds; i++ {
		c.svg.Gend()
	}
	c.stk = c.stk[:len(c.stk)-1]
}

func (c *Canvas) Stroke(path vg.Path) {
	c.svg.Path(c.pathData(path),
		style(elm("fill", "#000000", "none"),
			elm("stroke", "none", colorString(c.cur().color)),
			elm("stroke-opacity", "1", opacityString(c.cur().color)),
			elm("stroke-width", "1", "%.*g", pr, c.cur().lineWidth.Dots(c)),
			elm("stroke-dasharray", "none", dashArrayString(c)),
			elm("stroke-dashoffset", "0", "%.*g", pr, c.cur().dashOffset.Dots(c))))
}

func (c *Canvas) Fill(path vg.Path) {
	c.svg.Path(c.pathData(path),
		style(elm("fill", "#000000", colorString(c.cur().color))))
}

func (c *Canvas) pathData(path vg.Path) string {
	buf := new(bytes.Buffer)
	var x, y float64
	for _, comp := range path {
		switch comp.Type {
		case vg.MoveComp:
			fmt.Fprintf(buf, "M%.*g,%.*g", pr, comp.X.Dots(c), pr, comp.Y.Dots(c))
			x = comp.X.Dots(c)
			y = comp.Y.Dots(c)
		case vg.LineComp:
			fmt.Fprintf(buf, "L%.*g,%.*g", pr, comp.X.Dots(c), pr, comp.Y.Dots(c))
			x = comp.X.Dots(c)
			y = comp.Y.Dots(c)
		case vg.ArcComp:
			r := comp.Radius.Dots(c)
			x0 := comp.X.Dots(c) + r*math.Cos(comp.Start)
			y0 := comp.Y.Dots(c) + r*math.Sin(comp.Start)
			if x0 != x || y0 != y {
				fmt.Fprintf(buf, "L%.*g,%.*g", pr, x0, pr, y0)
			}
			if math.Abs(comp.Angle) >= 2*math.Pi {
				x, y = circle(buf, c, &comp)
			} else {
				x, y = arc(buf, c, &comp)
			}
		case vg.CloseComp:
			buf.WriteString("Z")
		default:
			panic(fmt.Sprintf("Unknown path component type: %d\n", comp.Type))
		}
	}
	return buf.String()
}

// circle adds circle path data to the given writer.
// Circles must be drawn using two arcs because
// SVG disallows the start and end point of an arc
// from being at the same location.
func circle(w io.Writer, c *Canvas, comp *vg.PathComp) (x, y float64) {
	angle := 2 * math.Pi
	if comp.Angle < 0 {
		angle = -2 * math.Pi
	}
	angle += remainder(comp.Angle, 2*math.Pi)
	if angle >= 4*math.Pi {
		panic("Impossible angle")
	}

	r := comp.Radius.Dots(c)
	x0 := comp.X.Dots(c) + r*math.Cos(comp.Start+angle/2)
	y0 := comp.Y.Dots(c) + r*math.Sin(comp.Start+angle/2)
	x = comp.X.Dots(c) + r*math.Cos(comp.Start+angle)
	y = comp.Y.Dots(c) + r*math.Sin(comp.Start+angle)

	fmt.Fprintf(w, "A%.*g,%.*g 0 %d %d %.*g,%.*g", pr, r, pr, r,
		large(angle/2), sweep(angle/2), pr, x0, pr, y0) //
	fmt.Fprintf(w, "A%.*g,%.*g 0 %d %d %.*g,%.*g", pr, r, pr, r,
		large(angle/2), sweep(angle/2), pr, x, pr, y)
	return
}

// remainder returns the remainder of x/y.
// We don't use math.Remainder because it
// seems to return incorrect values due to how
// IEEE defines the remainder operation…
func remainder(x, y float64) float64 {
	return (x/y - math.Trunc(x/y)) * y
}

// arc adds arc path data to the given writer.
// Arc can only be used if the arc's angle is
// less than a full circle, if it is greater then
// circle should be used instead.
func arc(w io.Writer, c *Canvas, comp *vg.PathComp) (x, y float64) {
	r := comp.Radius.Dots(c)
	x = comp.X.Dots(c) + r*math.Cos(comp.Start+comp.Angle)
	y = comp.Y.Dots(c) + r*math.Sin(comp.Start+comp.Angle)
	fmt.Fprintf(w, "A%.*g,%.*g 0 %d %d %.*g,%.*g", pr, r, pr, r,
		large(comp.Angle), sweep(comp.Angle), pr, x, pr, y)
	return
}

// sweep returns the arc sweep flag value for
// the given angle.
func sweep(a float64) int {
	if a < 0 {
		return 0
	}
	return 1
}

// large returns the arc's large flag value for
// the given angle.
func large(a float64) int {
	if math.Abs(a) >= math.Pi {
		return 1
	}
	return 0
}

func (c *Canvas) FillString(font vg.Font, x, y vg.Length, str string) {
	fontStr, ok := fontMap[font.Name()]
	if !ok {
		panic(fmt.Sprintf("Unknown font: %s", font.Name()))
	}
	sty := style(fontStr,
		elm("font-size", "medium", "%.*gpt", pr, font.Size.Points()),
		elm("fill", "#000000", colorString(c.cur().color)))
	if sty != "" {
		sty = "\n\t" + sty
	}
	fmt.Fprintf(c.buf, `<text x="%.*g" y="%.*g" transform="scale(1, -1)"%s>%s</text>`+"\n",
		pr, x.Dots(c), pr, -y.Dots(c), sty, str)
}

var (
	// fontMap maps Postscript-style font names to their
	// corresponding SVG style string.
	fontMap = map[string]string{
		"Courier":               "font-family:Courier;font-weight:normal;font-style:normal",
		"Courier-Bold":          "font-family:Courier;font-weight:bold;font-style:normal",
		"Courier-Oblique":       "font-family:Courier;font-weight:normal;font-style:oblique",
		"Courier-BoldOblique":   "font-family:Courier;font-weight:bold;font-style:oblique",
		"Helvetica":             "font-family:Helvetica;font-weight:normal;font-style:normal",
		"Helvetica-Bold":        "font-family:Helvetica;font-weight:bold;font-style:normal",
		"Helvetica-Oblique":     "font-family:Helvetica;font-weight:normal;font-style:oblique",
		"Helvetica-BoldOblique": "font-family:Helvetica;font-weight:bold;font-style:oblique",
		"Times-Roman":           "font-family:Times;font-weight:normal;font-style:normal",
		"Times-Bold":            "font-family:Times;font-weight:bold;font-style:normal",
		"Times-Italic":          "font-family:Times;font-weight:normal;font-style:italic",
		"Times-BoldItalic":      "font-family:Times;font-weight:bold;font-style:italic",
	}
)

func (c *Canvas) DPI() float64 {
	return dpi
}

// Save saves the canvas to a file.
func (c *Canvas) Save(path string) error {
	f, err := os.Create(path)
	if err != nil {
		return err
	}
	defer f.Close()

	b := bufio.NewWriter(f)
	_, err = c.buf.WriteTo(b)
	if err != nil {
		return err
	}

	// Cose the groups and svg in the output buffer
	// so that the Canvas is not closed and can be
	// used again if needed.
	for i := 0; i < c.nEnds(); i++ {
		_, err = fmt.Fprintln(b, "</g>")
		if err != nil {
			return err
		}
	}

	fmt.Fprintln(b, "</svg>\n")

	return b.Flush()
}

// nEnds returns the number of group ends
// needed before the SVG is saved.
func (c *Canvas) nEnds() int {
	n := 1 // close the transform that moves the origin
	for _, ctx := range c.stk {
		n += ctx.gEnds
	}
	return n
}

// style returns a style string composed of
// all of the given elements.  If the elements
// are all empty then the empty string is
// returned.
func style(elms ...string) string {
	str := ""
	for _, e := range elms {
		if e == "" {
			continue
		}
		if str != "" {
			str += ";"
		}
		str += e
	}
	if str == "" {
		return ""
	}
	return "style=\"" + str + "\""
}

// elm returns a style element string with the
// given key and value.  If the value matches
// default then the empty string is returned.
func elm(key, def, f string, vls ...interface{}) string {
	value := fmt.Sprintf(f, vls...)
	if value == def {
		return ""
	}
	return key + ":" + value
}

// dashArrayString returns a string representing the
// dash array specification.
func dashArrayString(c *Canvas) string {
	str := ""
	for i, d := range c.cur().dashArray {
		str += fmt.Sprintf("%.*g", pr, d.Dots(c))
		if i < len(c.cur().dashArray)-1 {
			str += ","
		}
	}
	if str == "" {
		str = "none"
	}
	return str
}

// colorString returns the hexadecimal string
// representation of the coloro.
func colorString(clr color.Color) string {
	if clr == nil {
		clr = color.Black
	}
	r, g, b, _ := clr.RGBA()
	return fmt.Sprintf("#%02X%02X%02X",
		int(float64(r)/math.MaxUint16)*255,
		int(float64(g)/math.MaxUint16)*255,
		int(float64(b)/math.MaxUint16)*255)
}

// opacityString returns the opacity value of
// the given color.
func opacityString(clr color.Color) string {
	_, _, _, a := clr.RGBA()
	return fmt.Sprintf("%.*g", pr, float64(a)/math.MaxUint16)
}
<commit_msg>Properly draw non-255 colors in vecsvg. Fixes issue 49.<commit_after>// Copyright 2012 The Plotinum Authors. All rights reserved.
// Use of this source code is governed by an MIT-style license
// that can be found in the LICENSE file.

// The vecsvg package uses svgo as a backend for vg.
package vecsvg

import (
	"bufio"
	"bytes"
	"code.google.com/p/plotinum/vg"
	"fmt"
	svgo "github.com/ajstarks/svgo"
	"image/color"
	"io"
	"math"
	"os"
)

const (
	// inkscape, Chrome, FireFox, and gpicview all seem
	// to use 90 dots-per-inch.  I can't find anywhere that
	// this is actually specified, however.
	dpi = 90

	// pr is the amount of precision to use when outputting float64s.
	pr = 5
)

type Canvas struct {
	svg *svgo.SVG
	buf *bytes.Buffer
	ht  float64
	stk []context
}

type context struct {
	color      color.Color
	dashArray  []vg.Length
	dashOffset vg.Length
	lineWidth  vg.Length
	gEnds      int
}

func New(w, h vg.Length) *Canvas {
	buf := new(bytes.Buffer)
	c := &Canvas{
		svg: svgo.New(buf),
		buf: buf,
		ht:  w.Points(),
		stk: []context{context{}},
	}

	// This is like svg.Start, except it uses floats
	// and specifies the units.
	fmt.Fprintf(buf, `<?xml version="1.0"?>
<!-- Generated by SVGo and Plotinum VG -->
<svg width="%.*gin" height="%.*gin"
	xmlns="http://www.w3.org/2000/svg"
	xmlns:xlink="http://www.w3.org/1999/xlink">`+"\n",
		pr, w.Inches(), pr, h.Inches())

	// Swap the origin to the bottom left.
	// This must be matched with a </g> when saving,
	// before the closing </svg>.
	c.svg.Gtransform(fmt.Sprintf("scale(1, -1) translate(0, -%.*g)", pr, h.Dots(c)))

	vg.Initialize(c)
	return c
}

func (c *Canvas) cur() *context {
	return &c.stk[len(c.stk)-1]
}

func (c *Canvas) SetLineWidth(w vg.Length) {
	c.cur().lineWidth = w
}

func (c *Canvas) SetLineDash(dashes []vg.Length, offs vg.Length) {
	c.cur().dashArray = dashes
	c.cur().dashOffset = offs
}

func (c *Canvas) SetColor(clr color.Color) {
	c.cur().color = clr
}

func (c *Canvas) Rotate(rot float64) {
	rot = rot * 180 / math.Pi
	c.svg.Rotate(rot)
	c.cur().gEnds++
}

func (c *Canvas) Translate(x, y vg.Length) {
	c.svg.Gtransform(fmt.Sprintf("translate(%.*g, %.*g)", pr, x.Points(), pr, y.Points()))
	c.cur().gEnds++
}

func (c *Canvas) Scale(x, y float64) {
	c.svg.ScaleXY(x, y)
	c.cur().gEnds++
}

func (c *Canvas) Push() {
	top := *c.cur()
	top.gEnds = 0
	c.stk = append(c.stk, top)
}

func (c *Canvas) Pop() {
	for i := 0; i < c.cur().gEnds; i++ {
		c.svg.Gend()
	}
	c.stk = c.stk[:len(c.stk)-1]
}

func (c *Canvas) Stroke(path vg.Path) {
	c.svg.Path(c.pathData(path),
		style(elm("fill", "#000000", "none"),
			elm("stroke", "none", colorString(c.cur().color)),
			elm("stroke-opacity", "1", opacityString(c.cur().color)),
			elm("stroke-width", "1", "%.*g", pr, c.cur().lineWidth.Dots(c)),
			elm("stroke-dasharray", "none", dashArrayString(c)),
			elm("stroke-dashoffset", "0", "%.*g", pr, c.cur().dashOffset.Dots(c))))
}

func (c *Canvas) Fill(path vg.Path) {
	c.svg.Path(c.pathData(path),
		style(elm("fill", "#000000", colorString(c.cur().color))))
}

func (c *Canvas) pathData(path vg.Path) string {
	buf := new(bytes.Buffer)
	var x, y float64
	for _, comp := range path {
		switch comp.Type {
		case vg.MoveComp:
			fmt.Fprintf(buf, "M%.*g,%.*g", pr, comp.X.Dots(c), pr, comp.Y.Dots(c))
			x = comp.X.Dots(c)
			y = comp.Y.Dots(c)
		case vg.LineComp:
			fmt.Fprintf(buf, "L%.*g,%.*g", pr, comp.X.Dots(c), pr, comp.Y.Dots(c))
			x = comp.X.Dots(c)
			y = comp.Y.Dots(c)
		case vg.ArcComp:
			r := comp.Radius.Dots(c)
			x0 := comp.X.Dots(c) + r*math.Cos(comp.Start)
			y0 := comp.Y.Dots(c) + r*math.Sin(comp.Start)
			if x0 != x || y0 != y {
				fmt.Fprintf(buf, "L%.*g,%.*g", pr, x0, pr, y0)
			}
			if math.Abs(comp.Angle) >= 2*math.Pi {
				x, y = circle(buf, c, &comp)
			} else {
				x, y = arc(buf, c, &comp)
			}
		case vg.CloseComp:
			buf.WriteString("Z")
		default:
			panic(fmt.Sprintf("Unknown path component type: %d\n", comp.Type))
		}
	}
	return buf.String()
}

// circle adds circle path data to the given writer.
// Circles must be drawn using two arcs because
// SVG disallows the start and end point of an arc
// from being at the same location.
func circle(w io.Writer, c *Canvas, comp *vg.PathComp) (x, y float64) {
	angle := 2 * math.Pi
	if comp.Angle < 0 {
		angle = -2 * math.Pi
	}
	angle += remainder(comp.Angle, 2*math.Pi)
	if angle >= 4*math.Pi {
		panic("Impossible angle")
	}

	r := comp.Radius.Dots(c)
	x0 := comp.X.Dots(c) + r*math.Cos(comp.Start+angle/2)
	y0 := comp.Y.Dots(c) + r*math.Sin(comp.Start+angle/2)
	x = comp.X.Dots(c) + r*math.Cos(comp.Start+angle)
	y = comp.Y.Dots(c) + r*math.Sin(comp.Start+angle)

	fmt.Fprintf(w, "A%.*g,%.*g 0 %d %d %.*g,%.*g", pr, r, pr, r,
		large(angle/2), sweep(angle/2), pr, x0, pr, y0) //
	fmt.Fprintf(w, "A%.*g,%.*g 0 %d %d %.*g,%.*g", pr, r, pr, r,
		large(angle/2), sweep(angle/2), pr, x, pr, y)
	return
}

// remainder returns the remainder of x/y.
// We don't use math.Remainder because it
// seems to return incorrect values due to how
// IEEE defines the remainder operation…
func remainder(x, y float64) float64 {
	return (x/y - math.Trunc(x/y)) * y
}

// arc adds arc path data to the given writer.
// Arc can only be used if the arc's angle is
// less than a full circle, if it is greater then
// circle should be used instead.
func arc(w io.Writer, c *Canvas, comp *vg.PathComp) (x, y float64) {
	r := comp.Radius.Dots(c)
	x = comp.X.Dots(c) + r*math.Cos(comp.Start+comp.Angle)
	y = comp.Y.Dots(c) + r*math.Sin(comp.Start+comp.Angle)
	fmt.Fprintf(w, "A%.*g,%.*g 0 %d %d %.*g,%.*g", pr, r, pr, r,
		large(comp.Angle), sweep(comp.Angle), pr, x, pr, y)
	return
}

// sweep returns the arc sweep flag value for
// the given angle.
func sweep(a float64) int {
	if a < 0 {
		return 0
	}
	return 1
}

// large returns the arc's large flag value for
// the given angle.
func large(a float64) int {
	if math.Abs(a) >= math.Pi {
		return 1
	}
	return 0
}

func (c *Canvas) FillString(font vg.Font, x, y vg.Length, str string) {
	fontStr, ok := fontMap[font.Name()]
	if !ok {
		panic(fmt.Sprintf("Unknown font: %s", font.Name()))
	}
	sty := style(fontStr,
		elm("font-size", "medium", "%.*gpt", pr, font.Size.Points()),
		elm("fill", "#000000", colorString(c.cur().color)))
	if sty != "" {
		sty = "\n\t" + sty
	}
	fmt.Fprintf(c.buf, `<text x="%.*g" y="%.*g" transform="scale(1, -1)"%s>%s</text>`+"\n",
		pr, x.Dots(c), pr, -y.Dots(c), sty, str)
}

var (
	// fontMap maps Postscript-style font names to their
	// corresponding SVG style string.
	fontMap = map[string]string{
		"Courier":               "font-family:Courier;font-weight:normal;font-style:normal",
		"Courier-Bold":          "font-family:Courier;font-weight:bold;font-style:normal",
		"Courier-Oblique":       "font-family:Courier;font-weight:normal;font-style:oblique",
		"Courier-BoldOblique":   "font-family:Courier;font-weight:bold;font-style:oblique",
		"Helvetica":             "font-family:Helvetica;font-weight:normal;font-style:normal",
		"Helvetica-Bold":        "font-family:Helvetica;font-weight:bold;font-style:normal",
		"Helvetica-Oblique":     "font-family:Helvetica;font-weight:normal;font-style:oblique",
		"Helvetica-BoldOblique": "font-family:Helvetica;font-weight:bold;font-style:oblique",
		"Times-Roman":           "font-family:Times;font-weight:normal;font-style:normal",
		"Times-Bold":            "font-family:Times;font-weight:bold;font-style:normal",
		"Times-Italic":          "font-family:Times;font-weight:normal;font-style:italic",
		"Times-BoldItalic":      "font-family:Times;font-weight:bold;font-style:italic",
	}
)

func (c *Canvas) DPI() float64 {
	return dpi
}

// Save saves the canvas to a file.
func (c *Canvas) Save(path string) error {
	f, err := os.Create(path)
	if err != nil {
		return err
	}
	defer f.Close()

	b := bufio.NewWriter(f)
	_, err = c.buf.WriteTo(b)
	if err != nil {
		return err
	}

	// Cose the groups and svg in the output buffer
	// so that the Canvas is not closed and can be
	// used again if needed.
	for i := 0; i < c.nEnds(); i++ {
		_, err = fmt.Fprintln(b, "</g>")
		if err != nil {
			return err
		}
	}

	fmt.Fprintln(b, "</svg>\n")

	return b.Flush()
}

// nEnds returns the number of group ends
// needed before the SVG is saved.
func (c *Canvas) nEnds() int {
	n := 1 // close the transform that moves the origin
	for _, ctx := range c.stk {
		n += ctx.gEnds
	}
	return n
}

// style returns a style string composed of
// all of the given elements.  If the elements
// are all empty then the empty string is
// returned.
func style(elms ...string) string {
	str := ""
	for _, e := range elms {
		if e == "" {
			continue
		}
		if str != "" {
			str += ";"
		}
		str += e
	}
	if str == "" {
		return ""
	}
	return "style=\"" + str + "\""
}

// elm returns a style element string with the
// given key and value.  If the value matches
// default then the empty string is returned.
func elm(key, def, f string, vls ...interface{}) string {
	value := fmt.Sprintf(f, vls...)
	if value == def {
		return ""
	}
	return key + ":" + value
}

// dashArrayString returns a string representing the
// dash array specification.
func dashArrayString(c *Canvas) string {
	str := ""
	for i, d := range c.cur().dashArray {
		str += fmt.Sprintf("%.*g", pr, d.Dots(c))
		if i < len(c.cur().dashArray)-1 {
			str += ","
		}
	}
	if str == "" {
		str = "none"
	}
	return str
}

// colorString returns the hexadecimal string
// representation of the coloro.
func colorString(clr color.Color) string {
	if clr == nil {
		clr = color.Black
	}
	r, g, b, _ := clr.RGBA()
	return fmt.Sprintf("#%02X%02X%02X",
		int(float64(r)/math.MaxUint16 * 255),
		int(float64(g)/math.MaxUint16 * 255),
		int(float64(b)/math.MaxUint16 * 255))
}

// opacityString returns the opacity value of
// the given color.
func opacityString(clr color.Color) string {
	_, _, _, a := clr.RGBA()
	return fmt.Sprintf("%.*g", pr, float64(a)/math.MaxUint16)
}
<|endoftext|>