<commit_before>package endpoint

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"sync"
	"time"
)

const (
	httpExpiresAfter       = time.Second * 30
	httpRequestTimeout     = time.Second * 5
	httpMaxIdleConnections = 20
)

type HTTPEndpointConn struct {
	mu     sync.Mutex
	ep     Endpoint
	client *http.Client
}

func newHTTPEndpointConn(ep Endpoint) *HTTPEndpointConn {
	return &HTTPEndpointConn{
		ep: ep,
	}
}

func (conn *HTTPEndpointConn) Expired() bool {
	return false
}

func (conn *HTTPEndpointConn) Send(msg string) error {
	conn.mu.Lock()
	defer conn.mu.Unlock()

	if conn.client == nil {
		conn.client = &http.Client{
			Transport: &http.Transport{
				MaxIdleConnsPerHost: httpMaxIdleConnections,
				IdleConnTimeout:     httpExpiresAfter,
			},
			Timeout: httpRequestTimeout,
		}
	}
	req, err := http.NewRequest("POST", conn.ep.Original, bytes.NewBufferString(msg))
	if err != nil {
		return err
	}

	req.Header.Set("Content-Type", "application/json")
	resp, err := conn.client.Do(req)
	if err != nil {
		return err
	}
	// close the connection to reuse it
	defer resp.Body.Close()
	// discard response
	if _, err := io.Copy(ioutil.Discard, resp.Body); err != nil {
		return err
	}
	// we only care about the 200 response
	if resp.StatusCode != 200 {
		return fmt.Errorf("invalid status: %s", resp.Status)
	}
	return nil
}
<commit_msg>remove mutex from HTTP endpoint (#148)<commit_after>package endpoint

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"time"
)

const (
	httpExpiresAfter       = time.Second * 30
	httpRequestTimeout     = time.Second * 5
	httpMaxIdleConnections = 20
)

type HTTPEndpointConn struct {
	ep     Endpoint
	client *http.Client
}

func newHTTPEndpointConn(ep Endpoint) *HTTPEndpointConn {
	return &HTTPEndpointConn{
		ep: ep,
		client: &http.Client{
			Transport: &http.Transport{
				MaxIdleConnsPerHost: httpMaxIdleConnections,
				IdleConnTimeout:     httpExpiresAfter,
			},
			Timeout: httpRequestTimeout,
		},
	}
}

func (conn *HTTPEndpointConn) Expired() bool {
	return false
}

func (conn *HTTPEndpointConn) Send(msg string) error {
	req, err := http.NewRequest("POST", conn.ep.Original, bytes.NewBufferString(msg))
	if err != nil {
		return err
	}

	req.Header.Set("Content-Type", "application/json")
	resp, err := conn.client.Do(req)
	if err != nil {
		return err
	}
	// close the connection to reuse it
	defer resp.Body.Close()
	// discard response
	if _, err := io.Copy(ioutil.Discard, resp.Body); err != nil {
		return err
	}
	// we only care about the 200 response
	if resp.StatusCode != 200 {
		return fmt.Errorf("invalid status: %s", resp.Status)
	}
	return nil
}
<|endoftext|>


<commit_before>package projects

import (
	"encoding/json"
	"net/http"
	"strings"

	h "github.com/ernestio/api-gateway/helpers"
	"github.com/ernestio/api-gateway/models"
)

// Get : responds to GET /projects/:id:/ with the specified
// project details
func Get(au models.User, project string) (int, []byte) {
	var envs []models.Env
	var r models.Role
	var roles []models.Role
	var d models.Project
	var body []byte
	var err error

	if st, res := h.IsAuthorizedToResource(&au, h.GetProject, d.GetType(), project); st != 200 {
		return st, res
	}

	appended := make(map[string]string)

	if err := d.FindByName(project); err != nil {
		return 404, models.NewJSONError("Project not found")
	}

	query := make(map[string]interface{}, 0)
	query["datacenter_id"] = d.ID
	envs, err = au.EnvsBy(query)
	if err == nil {
		for _, v := range envs {
			nameParts := strings.Split(v.Name, models.EnvNameSeparator)
			if _, ok := appended[nameParts[1]]; !ok {
				d.Environments = append(d.Environments, nameParts[1])
				appended[nameParts[1]] = "x"
			}
		}
	}

	if err := r.FindAllByResource(d.GetID(), d.GetType(), &roles); err == nil {
		for _, v := range roles {
			d.Roles = append(d.Roles, v.UserID+" ("+v.Role+")")
		}
	}

	if body, err = json.Marshal(d); err != nil {
		return 500, models.NewJSONError("Internal server error")
	}

	return http.StatusOK, body
}
<commit_msg>Fix environment selection<commit_after>package projects

import (
	"encoding/json"
	"net/http"
	"strings"

	h "github.com/ernestio/api-gateway/helpers"
	"github.com/ernestio/api-gateway/models"
)

// Get : responds to GET /projects/:id:/ with the specified
// project details
func Get(au models.User, project string) (int, []byte) {
	var envs []models.Env
	var r models.Role
	var roles []models.Role
	var d models.Project
	var body []byte
	var err error

	if st, res := h.IsAuthorizedToResource(&au, h.GetProject, d.GetType(), project); st != 200 {
		return st, res
	}

	if err := d.FindByName(project); err != nil {
		return 404, models.NewJSONError("Project not found")
	}

	query := make(map[string]interface{}, 0)
	query["datacenter_id"] = d.ID
	envs, err = au.EnvsBy(query)
	if err == nil {
		for _, v := range envs {
			nameParts := strings.Split(v.Name, models.EnvNameSeparator)
			if nameParts[0] == project {
				d.Environments = append(d.Environments, nameParts[1])
			}
		}
	}

	if err := r.FindAllByResource(d.GetID(), d.GetType(), &roles); err == nil {
		for _, v := range roles {
			d.Roles = append(d.Roles, v.UserID+" ("+v.Role+")")
		}
	}

	if body, err = json.Marshal(d); err != nil {
		return 500, models.NewJSONError("Internal server error")
	}

	return http.StatusOK, body
}
<|endoftext|>


<commit_before>package reviewdog

import (
	"context"
	"net/http"
	"net/http/httptest"
	"testing"

	gitlab "github.com/xanzy/go-gitlab"
)

func TestGitLabMergeRequestDiff_Diff(t *testing.T) {
	getMRAPICall := 0
	getBranchAPICall := 0
	mux := http.NewServeMux()
	mux.HandleFunc("/api/v4/projects/o/r/merge_requests/14", func(w http.ResponseWriter, r *http.Request) {
		getMRAPICall++
		if r.Method != "GET" {
			t.Errorf("unexpected access: %v %v", r.Method, r.URL)
		}
		w.Write([]byte(`{"target_project_id": 14, "target_branch": "test-branch"}`))
	})
	mux.HandleFunc("/api/v4/projects/14/repository/branches/test-branch", func(w http.ResponseWriter, r *http.Request) {
		getBranchAPICall++
		if r.Method != "GET" {
			t.Errorf("unexpected access: %v %v", r.Method, r.URL)
		}
		w.Write([]byte(`{"commit": {"id": "master"}}`))
	})

	ts := httptest.NewServer(mux)
	defer ts.Close()

	cli := gitlab.NewClient(nil, "")
	cli.SetBaseURL(ts.URL + "/api/v4")

	g, err := NewGitLabMergeRequestDiff(cli, "o", "r", 14, "HEAD")
	if err != nil {
		t.Fatal(err)
	}
	if _, err := g.Diff(context.Background()); err != nil {
		t.Fatal(err)
	}
	if getMRAPICall != 1 {
		t.Errorf("Get GitLab MergeRequest API called %v times, want once", getMRAPICall)
	}
	if getBranchAPICall != 1 {
		t.Errorf("Get GitLab Branch API called %v times, want once", getBranchAPICall)
	}
}
<commit_msg>gitlab: use HEAD~ for target branch sha<commit_after>package reviewdog

import (
	"context"
	"net/http"
	"net/http/httptest"
	"testing"

	gitlab "github.com/xanzy/go-gitlab"
)

func TestGitLabMergeRequestDiff_Diff(t *testing.T) {
	getMRAPICall := 0
	getBranchAPICall := 0
	mux := http.NewServeMux()
	mux.HandleFunc("/api/v4/projects/o/r/merge_requests/14", func(w http.ResponseWriter, r *http.Request) {
		getMRAPICall++
		if r.Method != "GET" {
			t.Errorf("unexpected access: %v %v", r.Method, r.URL)
		}
		w.Write([]byte(`{"target_project_id": 14, "target_branch": "test-branch"}`))
	})
	mux.HandleFunc("/api/v4/projects/14/repository/branches/test-branch", func(w http.ResponseWriter, r *http.Request) {
		getBranchAPICall++
		if r.Method != "GET" {
			t.Errorf("unexpected access: %v %v", r.Method, r.URL)
		}
		w.Write([]byte(`{"commit": {"id": "HEAD~"}}`))
	})

	ts := httptest.NewServer(mux)
	defer ts.Close()

	cli := gitlab.NewClient(nil, "")
	cli.SetBaseURL(ts.URL + "/api/v4")

	g, err := NewGitLabMergeRequestDiff(cli, "o", "r", 14, "HEAD")
	if err != nil {
		t.Fatal(err)
	}
	if _, err := g.Diff(context.Background()); err != nil {
		t.Fatal(err)
	}
	if getMRAPICall != 1 {
		t.Errorf("Get GitLab MergeRequest API called %v times, want once", getMRAPICall)
	}
	if getBranchAPICall != 1 {
		t.Errorf("Get GitLab Branch API called %v times, want once", getBranchAPICall)
	}
}
<|endoftext|>


<commit_before>// Copyright 2016 The Cockroach 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.
//
// Author: Tamir Duberstein (tamird@gmail.com)

// This file includes test-only helper methods added to types in
// package storage. These methods are only linked in to tests in this
// directory (but may be used from tests in both package storage and
// package storage_test).

package storage

import (
	"fmt"
	"time"

	"github.com/cockroachdb/cockroach/config"
	"github.com/cockroachdb/cockroach/internal/client"
	"github.com/cockroachdb/cockroach/roachpb"
	"github.com/cockroachdb/cockroach/storage/engine/enginepb"
	"github.com/cockroachdb/cockroach/util/hlc"
)

// ComputeMVCCStats immediately computes correct total MVCC usage statistics
// for the store, returning the computed values (but without modifying the
// store).
func (s *Store) ComputeMVCCStats() (enginepb.MVCCStats, error) {
	var totalStats enginepb.MVCCStats
	var err error

	visitor := newStoreRangeSet(s)
	now := s.Clock().PhysicalNow()
	visitor.Visit(func(r *Replica) bool {
		var stats enginepb.MVCCStats
		stats, err = ComputeStatsForRange(r.Desc(), s.Engine(), now)
		if err != nil {
			return false
		}
		totalStats.Add(stats)
		return true
	})
	return totalStats, err
}

// ForceReplicationScanAndProcess iterates over all ranges and
// enqueues any that need to be replicated.
func (s *Store) ForceReplicationScanAndProcess() {
	s.mu.Lock()
	for _, r := range s.mu.replicas {
		s.replicateQueue.MaybeAdd(r, s.ctx.Clock.Now())
	}
	s.mu.Unlock()

	s.replicateQueue.DrainQueue(s.ctx.Clock)
}

// ForceReplicaGCScanAndProcess iterates over all ranges and enqueues any that
// may need to be GC'd.
func (s *Store) ForceReplicaGCScanAndProcess() {
	s.mu.Lock()
	for _, r := range s.mu.replicas {
		s.replicaGCQueue.MaybeAdd(r, s.ctx.Clock.Now())
	}
	s.mu.Unlock()

	s.replicaGCQueue.DrainQueue(s.ctx.Clock)
}

// ForceRaftLogScanAndProcess iterates over all ranges and enqueues any that
// need their raft logs truncated and then process each of them.
func (s *Store) ForceRaftLogScanAndProcess() {
	// Gather the list of replicas to call MaybeAdd on to avoid locking the
	// Mutex twice.
	s.mu.Lock()
	replicas := make([]*Replica, 0, len(s.mu.replicas))
	for _, r := range s.mu.replicas {
		replicas = append(replicas, r)
	}
	s.mu.Unlock()

	// Add each replica to the queue.
	for _, r := range replicas {
		s.raftLogQueue.MaybeAdd(r, s.ctx.Clock.Now())
	}

	s.raftLogQueue.DrainQueue(s.ctx.Clock)
}

// GetDeadReplicas exports s.deadReplicas for tests.
func (s *Store) GetDeadReplicas() roachpb.StoreDeadReplicas {
	return s.deadReplicas()
}

// LeaseExpiration returns an int64 to increment a manual clock with to
// make sure that all active range leases expire.
func (s *Store) LeaseExpiration(clock *hlc.Clock) int64 {
	// Due to lease extensions, the remaining interval can be longer than just
	// the sum of the offset (=length of stasis period) and the active
	// duration, but definitely not by 2x.
	return 2 * int64(s.ctx.rangeLeaseActiveDuration+clock.MaxOffset())
}

// LogReplicaChangeTest adds a fake replica change event to the log for the
// range which contains the given key.
func (s *Store) LogReplicaChangeTest(txn *client.Txn, changeType roachpb.ReplicaChangeType, replica roachpb.ReplicaDescriptor, desc roachpb.RangeDescriptor) error {
	return s.logChange(txn, changeType, replica, desc)
}

// ReplicateQueuePurgatoryLength returns the number of replicas in replicate
// queue purgatory.
func (s *Store) ReplicateQueuePurgatoryLength() int {
	return s.replicateQueue.PurgatoryLength()
}

// SetRaftLogQueueActive enables or disables the raft log queue.
func (s *Store) SetRaftLogQueueActive(active bool) {
	s.setRaftLogQueueActive(active)
}

// SetReplicaGCQueueActive enables or disables the replica GC queue.
func (s *Store) SetReplicaGCQueueActive(active bool) {
	s.setReplicaGCQueueActive(active)
}

// SetSplitQueueActive enables or disables the split queue.
func (s *Store) SetSplitQueueActive(active bool) {
	s.setSplitQueueActive(active)
}

// SetReplicaScannerActive enables or disables the scanner. Note that while
// inactive, removals are still processed.
func (s *Store) SetReplicaScannerActive(active bool) {
	s.setScannerActive(active)
}

// EnqueueRaftUpdateCheck enqueues the replica for a Raft update check, forcing
// the replica's Raft group into existence.
func (s *Store) EnqueueRaftUpdateCheck(rangeID roachpb.RangeID) {
	s.enqueueRaftUpdateCheck(rangeID)
}

// ManualReplicaGC processes the specified replica using the store's GC queue.
func (s *Store) ManualReplicaGC(repl *Replica) error {
	cfg, ok := s.Gossip().GetSystemConfig()
	if !ok {
		return fmt.Errorf("%s: system config not yet available", s)
	}
	return s.gcQueue.process(s.Ctx(), s.Clock().Now(), repl, cfg)
}

func (r *Replica) RaftLock() {
	r.raftMu.Lock()
}

func (r *Replica) RaftUnlock() {
	r.raftMu.Unlock()
}

// GetLastIndex is the same function as LastIndex but it does not require
// that the replica lock is held.
func (r *Replica) GetLastIndex() (uint64, error) {
	r.mu.Lock()
	defer r.mu.Unlock()
	return r.LastIndex()
}

// GetLease exposes replica.getLease for tests.
// If you just need information about the lease holder, consider issuing a
// LeaseInfoRequest instead of using this internal method.
func (r *Replica) GetLease() (*roachpb.Lease, *roachpb.Lease) {
	return r.getLease()
}

// GetTimestampCacheLowWater returns the timestamp cache low water mark.
func (r *Replica) GetTimestampCacheLowWater() hlc.Timestamp {
	r.mu.Lock()
	defer r.mu.Unlock()
	return r.mu.tsCache.lowWater
}

// GetStoreList is the same function as GetStoreList exposed for tests only.
func (sp *StorePool) GetStoreList(constraints config.Constraints, deterministic bool) (StoreList, int, int) {
	return sp.getStoreList(constraints, deterministic)
}

// IsQuiescent returns whether the replica is quiescent or not.
func (r *Replica) IsQuiescent() bool {
	r.mu.Lock()
	defer r.mu.Unlock()
	return r.mu.quiescent
}

func GetGCQueueTxnCleanupThreshold() time.Duration {
	return txnCleanupThreshold
}
<commit_msg>storage: reuse code in test helpers<commit_after>// Copyright 2016 The Cockroach 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.
//
// Author: Tamir Duberstein (tamird@gmail.com)

// This file includes test-only helper methods added to types in
// package storage. These methods are only linked in to tests in this
// directory (but may be used from tests in both package storage and
// package storage_test).

package storage

import (
	"fmt"
	"time"

	"github.com/cockroachdb/cockroach/config"
	"github.com/cockroachdb/cockroach/internal/client"
	"github.com/cockroachdb/cockroach/roachpb"
	"github.com/cockroachdb/cockroach/storage/engine/enginepb"
	"github.com/cockroachdb/cockroach/util/hlc"
)

// ComputeMVCCStats immediately computes correct total MVCC usage statistics
// for the store, returning the computed values (but without modifying the
// store).
func (s *Store) ComputeMVCCStats() (enginepb.MVCCStats, error) {
	var totalStats enginepb.MVCCStats
	var err error

	visitor := newStoreRangeSet(s)
	now := s.Clock().PhysicalNow()
	visitor.Visit(func(r *Replica) bool {
		var stats enginepb.MVCCStats
		stats, err = ComputeStatsForRange(r.Desc(), s.Engine(), now)
		if err != nil {
			return false
		}
		totalStats.Add(stats)
		return true
	})
	return totalStats, err
}

func forceScanAndProcess(s *Store, q baseQueue) {
	newStoreRangeSet(s).Visit(func(repl *Replica) bool {
		q.MaybeAdd(repl, s.ctx.Clock.Now())
		return true
	})

	q.DrainQueue(s.ctx.Clock)
}

// ForceReplicationScanAndProcess iterates over all ranges and
// enqueues any that need to be replicated.
func (s *Store) ForceReplicationScanAndProcess() {
	forceScanAndProcess(s, s.replicateQueue.baseQueue)
}

// ForceReplicaGCScanAndProcess iterates over all ranges and enqueues any that
// may need to be GC'd.
func (s *Store) ForceReplicaGCScanAndProcess() {
	forceScanAndProcess(s, s.replicaGCQueue.baseQueue)
}

// ForceRaftLogScanAndProcess iterates over all ranges and enqueues any that
// need their raft logs truncated and then process each of them.
func (s *Store) ForceRaftLogScanAndProcess() {
	forceScanAndProcess(s, s.raftLogQueue.baseQueue)
}

// GetDeadReplicas exports s.deadReplicas for tests.
func (s *Store) GetDeadReplicas() roachpb.StoreDeadReplicas {
	return s.deadReplicas()
}

// LeaseExpiration returns an int64 to increment a manual clock with to
// make sure that all active range leases expire.
func (s *Store) LeaseExpiration(clock *hlc.Clock) int64 {
	// Due to lease extensions, the remaining interval can be longer than just
	// the sum of the offset (=length of stasis period) and the active
	// duration, but definitely not by 2x.
	return 2 * int64(s.ctx.rangeLeaseActiveDuration+clock.MaxOffset())
}

// LogReplicaChangeTest adds a fake replica change event to the log for the
// range which contains the given key.
func (s *Store) LogReplicaChangeTest(txn *client.Txn, changeType roachpb.ReplicaChangeType, replica roachpb.ReplicaDescriptor, desc roachpb.RangeDescriptor) error {
	return s.logChange(txn, changeType, replica, desc)
}

// ReplicateQueuePurgatoryLength returns the number of replicas in replicate
// queue purgatory.
func (s *Store) ReplicateQueuePurgatoryLength() int {
	return s.replicateQueue.PurgatoryLength()
}

// SetRaftLogQueueActive enables or disables the raft log queue.
func (s *Store) SetRaftLogQueueActive(active bool) {
	s.setRaftLogQueueActive(active)
}

// SetReplicaGCQueueActive enables or disables the replica GC queue.
func (s *Store) SetReplicaGCQueueActive(active bool) {
	s.setReplicaGCQueueActive(active)
}

// SetSplitQueueActive enables or disables the split queue.
func (s *Store) SetSplitQueueActive(active bool) {
	s.setSplitQueueActive(active)
}

// SetReplicaScannerActive enables or disables the scanner. Note that while
// inactive, removals are still processed.
func (s *Store) SetReplicaScannerActive(active bool) {
	s.setScannerActive(active)
}

// EnqueueRaftUpdateCheck enqueues the replica for a Raft update check, forcing
// the replica's Raft group into existence.
func (s *Store) EnqueueRaftUpdateCheck(rangeID roachpb.RangeID) {
	s.enqueueRaftUpdateCheck(rangeID)
}

// ManualReplicaGC processes the specified replica using the store's GC queue.
func (s *Store) ManualReplicaGC(repl *Replica) error {
	cfg, ok := s.Gossip().GetSystemConfig()
	if !ok {
		return fmt.Errorf("%s: system config not yet available", s)
	}
	return s.gcQueue.process(s.Ctx(), s.Clock().Now(), repl, cfg)
}

func (r *Replica) RaftLock() {
	r.raftMu.Lock()
}

func (r *Replica) RaftUnlock() {
	r.raftMu.Unlock()
}

// GetLastIndex is the same function as LastIndex but it does not require
// that the replica lock is held.
func (r *Replica) GetLastIndex() (uint64, error) {
	r.mu.Lock()
	defer r.mu.Unlock()
	return r.LastIndex()
}

// GetLease exposes replica.getLease for tests.
// If you just need information about the lease holder, consider issuing a
// LeaseInfoRequest instead of using this internal method.
func (r *Replica) GetLease() (*roachpb.Lease, *roachpb.Lease) {
	return r.getLease()
}

// GetTimestampCacheLowWater returns the timestamp cache low water mark.
func (r *Replica) GetTimestampCacheLowWater() hlc.Timestamp {
	r.mu.Lock()
	defer r.mu.Unlock()
	return r.mu.tsCache.lowWater
}

// GetStoreList is the same function as GetStoreList exposed for tests only.
func (sp *StorePool) GetStoreList(constraints config.Constraints, deterministic bool) (StoreList, int, int) {
	return sp.getStoreList(constraints, deterministic)
}

// IsQuiescent returns whether the replica is quiescent or not.
func (r *Replica) IsQuiescent() bool {
	r.mu.Lock()
	defer r.mu.Unlock()
	return r.mu.quiescent
}

func GetGCQueueTxnCleanupThreshold() time.Duration {
	return txnCleanupThreshold
}
<|endoftext|>


<commit_before>package keyring

/*
#cgo pkg-config: gnome-keyring-1 glib-2.0
#include <stdio.h>
#include <stdlib.h>
#include "gnome-keyring.h"

GnomeKeyringPasswordSchema keyring_schema =
  { GNOME_KEYRING_ITEM_GENERIC_SECRET,
    { { "username", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
      { "service",  GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
      { NULL,      0 } } };

GnomeKeyringResult gkr_set_password(gchar *description, gchar *service, gchar *username, gchar *password) {
	return gnome_keyring_store_password_sync(
		&keyring_schema,
		NULL,
		description,
		password,
		"service", service,
		"username", username,
		NULL);
}

GnomeKeyringResult gkr_get_password(gchar *service, gchar *username, gchar **password) {
	return gnome_keyring_find_password_sync(
		&keyring_schema,
		password,
		"service", service,
		"username", username,
		NULL);
}

*/
import "C"

import "unsafe"
import "fmt"

var (
	ErrDaemonCommunicationError = fmt.Errorf("Error communicating with the gnome-keyring daemon")
)

var gk_errors = map[int]error{
	6: ErrDaemonCommunicationError,
}

type gnomeKeyring struct{}

func (p gnomeKeyring) Set(Service, Username, Password string) error {
	desc := (*C.gchar)(C.CString("Username and password for " + Service))
	username := (*C.gchar)(C.CString(Username))
	service := (*C.gchar)(C.CString(Service))
	password := (*C.gchar)(C.CString(Password))
	defer C.free(unsafe.Pointer(desc))
	defer C.free(unsafe.Pointer(username))
	defer C.free(unsafe.Pointer(service))
	defer C.free(unsafe.Pointer(password))

	result := C.gkr_set_password(desc,
		service,
		username,
		password)
	if result != C.GNOME_KEYRING_RESULT_OK {
		if knownErr, ok := gk_errors[int(result)]; ok {
			return knownErr
		}
		return fmt.Errorf("Unknown gnome-keyring error: %d", int(result))
	}
	return nil
}

func (p gnomeKeyring) Get(Service string, Username string) (string, error) {

	username := (*C.gchar)(C.CString(Username))
	service := (*C.gchar)(C.CString(Service))
	defer C.free(unsafe.Pointer(username))
	defer C.free(unsafe.Pointer(service))

	var pw *C.char
	pw = (*C.char)(C.malloc(C.size_t(300) * C.size_t(unsafe.Sizeof(pw))))
	defer C.free(unsafe.Pointer(pw))

	pwg := (*C.gchar)(pw)

	result := C.gkr_get_password(service,
		username,
		&pwg)
	if result != C.GNOME_KEYRING_RESULT_OK {
		if knownErr, ok := gk_errors[int(result)]; ok {
			return "", knownErr
		}
		return "", fmt.Errorf("Unknown gnome-keyring error: %d", int(result))
	}
	fmt.Println("result:", result, pw, C.GNOME_KEYRING_RESULT_OK)
	return C.GoString(pw), nil
}

func init() {
	registerProvider("gnome-keyring", gnomeKeyring{}, true)
}
<commit_msg>Small fixups<commit_after>package keyring

/*
#cgo pkg-config: gnome-keyring-1 glib-2.0
#include <stdio.h>
#include <stdlib.h>
#include "gnome-keyring.h"

GnomeKeyringPasswordSchema keyring_schema =
  { GNOME_KEYRING_ITEM_GENERIC_SECRET,
    { { "username", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
      { "service",  GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
      { NULL,      0 } } };

GnomeKeyringResult gkr_set_password(gchar *description, gchar *service, gchar *username, gchar *password) {
	return gnome_keyring_store_password_sync(
		&keyring_schema,
		NULL,
		description,
		password,
		"service", service,
		"username", username,
		NULL);
}

GnomeKeyringResult gkr_get_password(gchar *service, gchar *username, gchar **password) {
	return gnome_keyring_find_password_sync(
		&keyring_schema,
		password,
		"service", service,
		"username", username,
		NULL);
}

*/
import "C"

import "unsafe"
import "fmt"

var (
	// Problem talking to the gnome-keychain daemon
	ErrDaemonCommunicationError = fmt.Errorf("Error communicating with the gnome-keyring daemon")
)

var gkErrors = map[int]error{
	6: ErrDaemonCommunicationError,
}

type gnomeKeyring struct{}

func (p gnomeKeyring) Set(Service, Username, Password string) error {
	desc := (*C.gchar)(C.CString("Username and password for " + Service))
	username := (*C.gchar)(C.CString(Username))
	service := (*C.gchar)(C.CString(Service))
	password := (*C.gchar)(C.CString(Password))
	defer C.free(unsafe.Pointer(desc))
	defer C.free(unsafe.Pointer(username))
	defer C.free(unsafe.Pointer(service))
	defer C.free(unsafe.Pointer(password))

	result := C.gkr_set_password(desc,
		service,
		username,
		password)
	if result != C.GNOME_KEYRING_RESULT_OK {
		if knownErr, ok := gkErrors[int(result)]; ok {
			return knownErr
		}
		return fmt.Errorf("Unknown gnome-keyring error: %d", int(result))
	}
	return nil
}

func (p gnomeKeyring) Get(Service string, Username string) (string, error) {

	username := (*C.gchar)(C.CString(Username))
	service := (*C.gchar)(C.CString(Service))
	defer C.free(unsafe.Pointer(username))
	defer C.free(unsafe.Pointer(service))

	var pw *C.char
	pw = (*C.char)(C.malloc(C.size_t(300) * C.size_t(unsafe.Sizeof(pw))))
	defer C.free(unsafe.Pointer(pw))

	pwg := (*C.gchar)(pw)

	result := C.gkr_get_password(service,
		username,
		&pwg)
	if result != C.GNOME_KEYRING_RESULT_OK {
		if knownErr, ok := gkErrors[int(result)]; ok {
			return "", knownErr
		}
		return "", fmt.Errorf("Unknown gnome-keyring error: %d", int(result))
	}
	fmt.Println("result:", result, pw, C.GNOME_KEYRING_RESULT_OK)
	return C.GoString(pw), nil
}

func init() {
	registerProvider("gnome-keyring", gnomeKeyring{}, true)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"github.com/BurntSushi/toml"
	"github.com/dimfeld/glog"
	"github.com/dimfeld/goconfig"
	"os"
	"os/signal"
	"path/filepath"
	"text/template"
)

type Hook struct {
	// URL at which this hook should be available.
	Url string
	// Dir is the working directory from which the command should be run.
	// If blank, the current working directory is used.
	Dir string
	// Env is a list of environment variables to set. If empty, the current
	// environment is used. Each item takes the form "key=value"
	Env []string

	// If PerCommit is true, call the hook once for each commit in the message.
	// Otherwise it is just called once per message.
	PerCommit bool

	// If empty, all events are accepted.
	AllowEvent []string

	// Commands to run.
	Commands [][]string

	// Override the default timeout.
	Timeout int

	// Secret required in the request. Requests that don't have a matching
	// Secret will be ignored. Note that Gitlab does not support this feature.
	// If specified, this overrides any server-wide secret.
	// If a secret is present in the server-wide configuration, it can be disabled for
	// this hook by setting the hook's secret to "none".
	Secret string

	cmdTemplate [][]*template.Template
	envTemplate []*template.Template
	dirTemplate *template.Template
}

type Hooks struct {
	Hook []*Hook
}

type Config struct {
	ListenAddress string

	LogDir string

	// The maximum amount of time to wait for a command to finish.
	// Default is 5 seconds.
	CommandTimeout int

	// Accept connections from only the given IP addresses.
	AcceptIps []string

	// Default secret required in requests. See the Hook struct for more description.
	Secret string

	// Paths to search for hook files
	HookPaths []string

	Hook []*Hook
}

func (c *Config) MergeHooks(other *Hooks) {
	c.Hook = append(c.Hook, other.Hook...)
}

func (c *Config) AddHookFile(file string) {
	var err error
	h := &Hooks{}

	f := os.Stdin

	if file == "-" {
		// Change file here so that any error messages will look better.
		file = "stdin"
	} else {
		f, err = os.Open(file)
		if err != nil {
			glog.Fatalf("Error loading %s: %s", file, err)
			return
		}
		defer f.Close()
	}

	glog.Infoln("Reading hooks from", file)

	_, err = toml.DecodeReader(f, h)
	if err != nil {
		glog.Fatalf("Error loading %s: %s", file, err)
		return
	}

	c.MergeHooks(h)
}

func (c *Config) AddHookPath(p string) {
	info, err := os.Stat(p)
	if err != nil {
		glog.Fatalf("Error loading %s: %s", p, err)
		return
	}

	if info.IsDir() {
		filepath.Walk(p,
			func(path string, info os.FileInfo, err error) error {
				if err != nil {
					glog.Fatalf("Error loading %s, %s", path, err)
					return err
				}
				if info.IsDir() {
					return nil
				}

				c.AddHookFile(path)
				return nil
			})
	} else {
		c.AddHookFile(p)
	}
}

func catchSIGINT(f func(), quit bool) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		for _ = range c {
			glog.Info("SIGINT received...")
			f()
			if quit {
				os.Exit(1)
			}
		}
	}()
}

func main() {
	flag.Parse()

	config := &Config{
		ListenAddress:  ":80",
		CommandTimeout: 5,
	}

	mainConfigPath := os.Getenv("UNWEBHOOK_CONFFILE")
	hooksStartIndex := 0
	if mainConfigPath == "" {
		if flag.NArg() != 0 {
			mainConfigPath = flag.Arg(0)
			hooksStartIndex = 1
		} else {
			mainConfigPath = os.Args[0] + ".conf"
		}
	}

	if mainConfigPath == "-" {
		fmt.Fprintf(os.Stderr, "Loading main config from stdin")
		goconfig.Load(config, os.Stdin, "UNWEBHOOK")
	} else {
		f, err := os.Open(mainConfigPath)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Failed to open config file %s: %s\n",
				mainConfigPath, err)
			os.Exit(1)
		}
		fmt.Fprintf(os.Stderr, "Loading main config from %s", mainConfigPath)
		err = goconfig.Load(config, f, "UNWEBHOOK")
		f.Close()
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error reading config file %s: %s\n",
				mainConfigPath, err)
			os.Exit(1)
		}
	}

	// Use config.LogDir if not given on the command line.
	dir := flag.CommandLine.Lookup("log_dir")
	if dir != nil && dir.Value.String() == "" {
		if config.LogDir == "" {
			config.LogDir = "."
		}
		flag.Set("log_dir", config.LogDir)
	}

	for _, h := range config.HookPaths {
		config.AddHookPath(h)
	}

	if flag.NArg() > hooksStartIndex {
		for _, arg := range flag.Args()[hooksStartIndex:] {
			config.AddHookPath(arg)
		}
	}

	closer := func() {
		glog.Flush()
	}
	catchSIGINT(closer, true)
	defer closer()

	failed := false
	for _, h := range config.Hook {
		glog.Infoln("Loading hook", h.Url)

		if h.Timeout == 0 {
			h.Timeout = config.CommandTimeout
		}

		if h.Secret == "none" {
			h.Secret = ""
		} else if h.Secret == "" {
			h.Secret = config.Secret
		}

		err := h.CreateTemplates()
		if err != nil {
			glog.Errorf("Failed parsing template %s: %s", h.Url, err)
			failed = true
		}
	}

	if failed {
		os.Exit(1)
	}

	RunServer(config)
}
<commit_msg>Create log directory, if needed<commit_after>package main

import (
	"flag"
	"fmt"
	"github.com/BurntSushi/toml"
	"github.com/dimfeld/glog"
	"github.com/dimfeld/goconfig"
	"os"
	"os/signal"
	"path/filepath"
	"text/template"
)

type Hook struct {
	// URL at which this hook should be available.
	Url string
	// Dir is the working directory from which the command should be run.
	// If blank, the current working directory is used.
	Dir string
	// Env is a list of environment variables to set. If empty, the current
	// environment is used. Each item takes the form "key=value"
	Env []string

	// If PerCommit is true, call the hook once for each commit in the message.
	// Otherwise it is just called once per message.
	PerCommit bool

	// If empty, all events are accepted.
	AllowEvent []string

	// Commands to run.
	Commands [][]string

	// Override the default timeout.
	Timeout int

	// Secret required in the request. Requests that don't have a matching
	// Secret will be ignored. Note that Gitlab does not support this feature.
	// If specified, this overrides any server-wide secret.
	// If a secret is present in the server-wide configuration, it can be disabled for
	// this hook by setting the hook's secret to "none".
	Secret string

	cmdTemplate [][]*template.Template
	envTemplate []*template.Template
	dirTemplate *template.Template
}

type Hooks struct {
	Hook []*Hook
}

type Config struct {
	ListenAddress string

	LogDir string

	// The maximum amount of time to wait for a command to finish.
	// Default is 5 seconds.
	CommandTimeout int

	// Accept connections from only the given IP addresses.
	AcceptIps []string

	// Default secret required in requests. See the Hook struct for more description.
	Secret string

	// Paths to search for hook files
	HookPaths []string

	Hook []*Hook
}

func (c *Config) MergeHooks(other *Hooks) {
	c.Hook = append(c.Hook, other.Hook...)
}

func (c *Config) AddHookFile(file string) {
	var err error
	h := &Hooks{}

	f := os.Stdin

	if file == "-" {
		// Change file here so that any error messages will look better.
		file = "stdin"
	} else {
		f, err = os.Open(file)
		if err != nil {
			glog.Fatalf("Error loading %s: %s", file, err)
			return
		}
		defer f.Close()
	}

	glog.Infoln("Reading hooks from", file)

	_, err = toml.DecodeReader(f, h)
	if err != nil {
		glog.Fatalf("Error loading %s: %s", file, err)
		return
	}

	c.MergeHooks(h)
}

func (c *Config) AddHookPath(p string) {
	info, err := os.Stat(p)
	if err != nil {
		glog.Fatalf("Error loading %s: %s", p, err)
		return
	}

	if info.IsDir() {
		filepath.Walk(p,
			func(path string, info os.FileInfo, err error) error {
				if err != nil {
					glog.Fatalf("Error loading %s, %s", path, err)
					return err
				}
				if info.IsDir() {
					return nil
				}

				c.AddHookFile(path)
				return nil
			})
	} else {
		c.AddHookFile(p)
	}
}

func catchSIGINT(f func(), quit bool) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		for _ = range c {
			glog.Info("SIGINT received...")
			f()
			if quit {
				os.Exit(1)
			}
		}
	}()
}

func isDirectory(dirPath string) bool {
	stat, err := os.Stat(dirPath)
	if err != nil || !stat.IsDir() {
		return false
	}
	return true
}

func main() {
	flag.Parse()

	config := &Config{
		ListenAddress:  ":80",
		CommandTimeout: 5,
	}

	mainConfigPath := os.Getenv("UNWEBHOOK_CONFFILE")
	hooksStartIndex := 0
	if mainConfigPath == "" {
		if flag.NArg() != 0 {
			mainConfigPath = flag.Arg(0)
			hooksStartIndex = 1
		} else {
			mainConfigPath = os.Args[0] + ".conf"
		}
	}

	if mainConfigPath == "-" {
		fmt.Fprintf(os.Stderr, "Loading main config from stdin")
		goconfig.Load(config, os.Stdin, "UNWEBHOOK")
	} else {
		f, err := os.Open(mainConfigPath)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Failed to open config file %s: %s\n",
				mainConfigPath, err)
			os.Exit(1)
		}
		fmt.Fprintf(os.Stderr, "Loading main config from %s", mainConfigPath)
		err = goconfig.Load(config, f, "UNWEBHOOK")
		f.Close()
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error reading config file %s: %s\n",
				mainConfigPath, err)
			os.Exit(1)
		}
	}

	// Use config.LogDir if not given on the command line.
	dir := flag.CommandLine.Lookup("log_dir")
	if dir != nil && dir.Value.String() == "" {
		if config.LogDir == "" {
			config.LogDir = "."
		}
		flag.Set("log_dir", config.LogDir)

		if !isDirectory(config.LogDir) {
			err := os.MkdirAll(config.LogDir, 0755)
			if err != nil {
				fmt.Fprintf(os.Stderr, "Failed to create log directory: %s\n", err)
				fmt.Fprintf(os.Stderr, "Logs will go to $TMPDIR\n", err)
			}
		}
	}

	for _, h := range config.HookPaths {
		config.AddHookPath(h)
	}

	if flag.NArg() > hooksStartIndex {
		for _, arg := range flag.Args()[hooksStartIndex:] {
			config.AddHookPath(arg)
		}
	}

	closer := func() {
		glog.Flush()
	}
	catchSIGINT(closer, true)
	defer closer()

	failed := false
	for _, h := range config.Hook {
		glog.Infoln("Loading hook", h.Url)

		if h.Timeout == 0 {
			h.Timeout = config.CommandTimeout
		}

		if h.Secret == "none" {
			h.Secret = ""
		} else if h.Secret == "" {
			h.Secret = config.Secret
		}

		err := h.CreateTemplates()
		if err != nil {
			glog.Errorf("Failed parsing template %s: %s", h.Url, err)
			failed = true
		}
	}

	if failed {
		os.Exit(1)
	}

	RunServer(config)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"strings"

	"github.com/adnanh/webhook/hook"

	"github.com/codegangsta/negroni"
	"github.com/gorilla/mux"

	fsnotify "gopkg.in/fsnotify.v1"
)

const (
	version = "2.3.0"
)

var (
	ip             = flag.String("ip", "", "ip the webhook should serve hooks on")
	port           = flag.Int("port", 9000, "port the webhook should serve hooks on")
	verbose        = flag.Bool("verbose", false, "show verbose output")
	hotReload      = flag.Bool("hotreload", false, "watch hooks file for changes and reload them automatically")
	hooksFilePath  = flag.String("hooks", "hooks.json", "path to the json file containing defined hooks the webhook should serve")
	hooksURLPrefix = flag.String("urlprefix", "hooks", "url prefix to use for served hooks (protocol://yourserver:port/PREFIX/:hook-id)")
	secure         = flag.Bool("secure", false, "use HTTPS instead of HTTP")
	cert           = flag.String("cert", "cert.pem", "path to the HTTPS certificate pem file")
	key            = flag.String("key", "key.pem", "path to the HTTPS certificate private key pem file")

	watcher *fsnotify.Watcher

	hooks hook.Hooks
)

func init() {
	hooks = hook.Hooks{}

	flag.Parse()

	log.SetPrefix("[webhook] ")
	log.SetFlags(log.Ldate | log.Ltime)

	if !*verbose {
		log.SetOutput(ioutil.Discard)
	}

	log.Println("version " + version + " starting")

	// load and parse hooks
	log.Printf("attempting to load hooks from %s\n", *hooksFilePath)

	err := hooks.LoadFromFile(*hooksFilePath)

	if err != nil {
		log.Printf("couldn't load hooks from file! %+v\n", err)
	} else {
		log.Printf("loaded %d hook(s) from file\n", len(hooks))

		for _, hook := range hooks {
			log.Printf("\t> %s\n", hook.ID)
		}
	}
}

func main() {
	if *hotReload {
		// set up file watcher
		log.Printf("setting up file watcher for %s\n", *hooksFilePath)

		var err error

		watcher, err = fsnotify.NewWatcher()
		if err != nil {
			log.Fatal("error creating file watcher instance", err)
		}

		defer watcher.Close()

		go watchForFileChange()

		err = watcher.Add(*hooksFilePath)
		if err != nil {
			log.Fatal("error adding hooks file to the watcher", err)
		}
	}

	l := negroni.NewLogger()
	l.Logger = log.New(os.Stdout, "[webhook] ", log.Ldate|log.Ltime)

	negroniRecovery := &negroni.Recovery{
		Logger:     l.Logger,
		PrintStack: true,
		StackAll:   false,
		StackSize:  1024 * 8,
	}

	n := negroni.New(negroniRecovery, l)

	router := mux.NewRouter()

	var hooksURL string

	if *hooksURLPrefix == "" {
		hooksURL = "/{id}"
	} else {
		hooksURL = "/" + *hooksURLPrefix + "/{id}"
	}

	router.HandleFunc(hooksURL, hookHandler)

	n.UseHandler(router)

	if *secure {
		log.Printf("starting secure (https) webhook on %s:%d", *ip, *port)
		log.Fatal(http.ListenAndServeTLS(fmt.Sprintf("%s:%d", *ip, *port), *cert, *key, n))
	} else {
		log.Printf("starting insecure (http) webhook on %s:%d", *ip, *port)
		log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", *ip, *port), n))
	}

}

func hookHandler(w http.ResponseWriter, r *http.Request) {
	id := mux.Vars(r)["id"]

	hook := hooks.Match(id)

	if hook != nil {
		log.Printf("%s got matched\n", id)

		body, err := ioutil.ReadAll(r.Body)
		if err != nil {
			log.Printf("error reading the request body. %+v\n", err)
		}

		// parse headers
		headers := valuesToMap(r.Header)

		// parse query variables
		query := valuesToMap(r.URL.Query())

		// parse body
		var payload map[string]interface{}

		contentType := r.Header.Get("Content-Type")

		if strings.HasPrefix(contentType, "application/json") {
			decoder := json.NewDecoder(strings.NewReader(string(body)))
			decoder.UseNumber()

			err := decoder.Decode(&payload)

			if err != nil {
				log.Printf("error parsing JSON payload %+v\n", err)
			}
		} else if strings.HasPrefix(contentType, "application/x-www-form-urlencoded") {
			fd, err := url.ParseQuery(string(body))
			if err != nil {
				log.Printf("error parsing form payload %+v\n", err)
			} else {
				payload = valuesToMap(fd)
			}
		}

		hook.ParseJSONParameters(&headers, &query, &payload)

		// handle hook
		go handleHook(hook, &headers, &query, &payload, &body)

		// send the hook defined response message
		fmt.Fprintf(w, hook.ResponseMessage)
	} else {
		w.WriteHeader(http.StatusNotFound)
		fmt.Fprintf(w, "Hook not found.")
	}
}

func handleHook(hook *hook.Hook, headers, query, payload *map[string]interface{}, body *[]byte) {
	if hook.TriggerRule == nil || hook.TriggerRule != nil && hook.TriggerRule.Evaluate(headers, query, payload, body) {
		log.Printf("%s hook triggered successfully\n", hook.ID)

		cmd := exec.Command(hook.ExecuteCommand)
		cmd.Args = hook.ExtractCommandArguments(headers, query, payload)
		cmd.Dir = hook.CommandWorkingDirectory

		log.Printf("executing %s (%s) with arguments %s using %s as cwd\n", hook.ExecuteCommand, cmd.Path, cmd.Args, cmd.Dir)

		out, err := cmd.Output()

		log.Printf("stdout: %s\n", out)

		if err != nil {
			log.Printf("stderr: %+v\n", err)
		}
		log.Printf("finished handling %s\n", hook.ID)
	} else {
		log.Printf("%s hook did not get triggered\n", hook.ID)
	}
}

func watchForFileChange() {
	for {
		select {
		case event := <-(*watcher).Events:
			if event.Op&fsnotify.Write == fsnotify.Write {
				log.Println("hooks file modified")

				newHooks := hook.Hooks{}

				// parse and swap
				log.Printf("attempting to reload hooks from %s\n", *hooksFilePath)

				err := newHooks.LoadFromFile(*hooksFilePath)

				if err != nil {
					log.Printf("couldn't load hooks from file! %+v\n", err)
				} else {
					log.Printf("loaded %d hook(s) from file\n", len(hooks))

					for _, hook := range hooks {
						log.Printf("\t> %s\n", hook.ID)
					}

					hooks = newHooks
				}
			}
		case err := <-(*watcher).Errors:
			log.Println("watcher error:", err)
		}
	}
}

// valuesToMap converts map[string][]string to a map[string]string object
func valuesToMap(values map[string][]string) map[string]interface{} {
	ret := make(map[string]interface{})

	for key, value := range values {
		if len(value) > 0 {
			ret[key] = value[0]
		}
	}

	return ret
}
<commit_msg>bumping up the minor version<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"strings"

	"github.com/adnanh/webhook/hook"

	"github.com/codegangsta/negroni"
	"github.com/gorilla/mux"

	fsnotify "gopkg.in/fsnotify.v1"
)

const (
	version = "2.3.1"
)

var (
	ip             = flag.String("ip", "", "ip the webhook should serve hooks on")
	port           = flag.Int("port", 9000, "port the webhook should serve hooks on")
	verbose        = flag.Bool("verbose", false, "show verbose output")
	hotReload      = flag.Bool("hotreload", false, "watch hooks file for changes and reload them automatically")
	hooksFilePath  = flag.String("hooks", "hooks.json", "path to the json file containing defined hooks the webhook should serve")
	hooksURLPrefix = flag.String("urlprefix", "hooks", "url prefix to use for served hooks (protocol://yourserver:port/PREFIX/:hook-id)")
	secure         = flag.Bool("secure", false, "use HTTPS instead of HTTP")
	cert           = flag.String("cert", "cert.pem", "path to the HTTPS certificate pem file")
	key            = flag.String("key", "key.pem", "path to the HTTPS certificate private key pem file")

	watcher *fsnotify.Watcher

	hooks hook.Hooks
)

func init() {
	hooks = hook.Hooks{}

	flag.Parse()

	log.SetPrefix("[webhook] ")
	log.SetFlags(log.Ldate | log.Ltime)

	if !*verbose {
		log.SetOutput(ioutil.Discard)
	}

	log.Println("version " + version + " starting")

	// load and parse hooks
	log.Printf("attempting to load hooks from %s\n", *hooksFilePath)

	err := hooks.LoadFromFile(*hooksFilePath)

	if err != nil {
		log.Printf("couldn't load hooks from file! %+v\n", err)
	} else {
		log.Printf("loaded %d hook(s) from file\n", len(hooks))

		for _, hook := range hooks {
			log.Printf("\t> %s\n", hook.ID)
		}
	}
}

func main() {
	if *hotReload {
		// set up file watcher
		log.Printf("setting up file watcher for %s\n", *hooksFilePath)

		var err error

		watcher, err = fsnotify.NewWatcher()
		if err != nil {
			log.Fatal("error creating file watcher instance", err)
		}

		defer watcher.Close()

		go watchForFileChange()

		err = watcher.Add(*hooksFilePath)
		if err != nil {
			log.Fatal("error adding hooks file to the watcher", err)
		}
	}

	l := negroni.NewLogger()
	l.Logger = log.New(os.Stdout, "[webhook] ", log.Ldate|log.Ltime)

	negroniRecovery := &negroni.Recovery{
		Logger:     l.Logger,
		PrintStack: true,
		StackAll:   false,
		StackSize:  1024 * 8,
	}

	n := negroni.New(negroniRecovery, l)

	router := mux.NewRouter()

	var hooksURL string

	if *hooksURLPrefix == "" {
		hooksURL = "/{id}"
	} else {
		hooksURL = "/" + *hooksURLPrefix + "/{id}"
	}

	router.HandleFunc(hooksURL, hookHandler)

	n.UseHandler(router)

	if *secure {
		log.Printf("starting secure (https) webhook on %s:%d", *ip, *port)
		log.Fatal(http.ListenAndServeTLS(fmt.Sprintf("%s:%d", *ip, *port), *cert, *key, n))
	} else {
		log.Printf("starting insecure (http) webhook on %s:%d", *ip, *port)
		log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", *ip, *port), n))
	}

}

func hookHandler(w http.ResponseWriter, r *http.Request) {
	id := mux.Vars(r)["id"]

	hook := hooks.Match(id)

	if hook != nil {
		log.Printf("%s got matched\n", id)

		body, err := ioutil.ReadAll(r.Body)
		if err != nil {
			log.Printf("error reading the request body. %+v\n", err)
		}

		// parse headers
		headers := valuesToMap(r.Header)

		// parse query variables
		query := valuesToMap(r.URL.Query())

		// parse body
		var payload map[string]interface{}

		contentType := r.Header.Get("Content-Type")

		if strings.HasPrefix(contentType, "application/json") {
			decoder := json.NewDecoder(strings.NewReader(string(body)))
			decoder.UseNumber()

			err := decoder.Decode(&payload)

			if err != nil {
				log.Printf("error parsing JSON payload %+v\n", err)
			}
		} else if strings.HasPrefix(contentType, "application/x-www-form-urlencoded") {
			fd, err := url.ParseQuery(string(body))
			if err != nil {
				log.Printf("error parsing form payload %+v\n", err)
			} else {
				payload = valuesToMap(fd)
			}
		}

		hook.ParseJSONParameters(&headers, &query, &payload)

		// handle hook
		go handleHook(hook, &headers, &query, &payload, &body)

		// send the hook defined response message
		fmt.Fprintf(w, hook.ResponseMessage)
	} else {
		w.WriteHeader(http.StatusNotFound)
		fmt.Fprintf(w, "Hook not found.")
	}
}

func handleHook(hook *hook.Hook, headers, query, payload *map[string]interface{}, body *[]byte) {
	if hook.TriggerRule == nil || hook.TriggerRule != nil && hook.TriggerRule.Evaluate(headers, query, payload, body) {
		log.Printf("%s hook triggered successfully\n", hook.ID)

		cmd := exec.Command(hook.ExecuteCommand)
		cmd.Args = hook.ExtractCommandArguments(headers, query, payload)
		cmd.Dir = hook.CommandWorkingDirectory

		log.Printf("executing %s (%s) with arguments %s using %s as cwd\n", hook.ExecuteCommand, cmd.Path, cmd.Args, cmd.Dir)

		out, err := cmd.Output()

		log.Printf("stdout: %s\n", out)

		if err != nil {
			log.Printf("stderr: %+v\n", err)
		}
		log.Printf("finished handling %s\n", hook.ID)
	} else {
		log.Printf("%s hook did not get triggered\n", hook.ID)
	}
}

func watchForFileChange() {
	for {
		select {
		case event := <-(*watcher).Events:
			if event.Op&fsnotify.Write == fsnotify.Write {
				log.Println("hooks file modified")

				newHooks := hook.Hooks{}

				// parse and swap
				log.Printf("attempting to reload hooks from %s\n", *hooksFilePath)

				err := newHooks.LoadFromFile(*hooksFilePath)

				if err != nil {
					log.Printf("couldn't load hooks from file! %+v\n", err)
				} else {
					log.Printf("loaded %d hook(s) from file\n", len(hooks))

					for _, hook := range hooks {
						log.Printf("\t> %s\n", hook.ID)
					}

					hooks = newHooks
				}
			}
		case err := <-(*watcher).Errors:
			log.Println("watcher error:", err)
		}
	}
}

// valuesToMap converts map[string][]string to a map[string]string object
func valuesToMap(values map[string][]string) map[string]interface{} {
	ret := make(map[string]interface{})

	for key, value := range values {
		if len(value) > 0 {
			ret[key] = value[0]
		}
	}

	return ret
}
<|endoftext|>


<commit_before>package wp

import (
	"encoding/json"
	"errors"
	"fmt"
	"html"
	"html/template"
	"io/ioutil"
	"log"
	"strings"

	"github.com/valyala/fasthttp"
)

type wpResponse struct {
	Errors            []string                    `json:"errors"`
	Warning           []string                    `json:"warnings"`
	Version           string                      `json:"version"`
	Hash              string                      `json:"hash"`
	PublicPath        string                      `json:"publicPath"`
	AssetsByChunkName map[string]*json.RawMessage `json:"assetsByChunkName"`
	Assets            []*json.RawMessage          `json:"assets"`
}

const host = "localhost:3808"

var c *fasthttp.HostClient
var dev bool
var assets map[string][]string
var webpackBase string

// filter array of strings by function
func filter(vs []string, f func(string) bool) []string {
	vsf := make([]string, 0)
	for _, v := range vs {
		if f(v) {
			vsf = append(vsf, v)
		}
	}
	return vsf
}

func devManifest() (data []byte) {
	manifestURL := fmt.Sprint("http://", host, "/webpack/manifest.json")
	statusCode, body, err := c.Get(nil, manifestURL)
	if err != nil {
		log.Fatalf("Error when loading manifest from %s: %s", manifestURL, err)
	}
	if statusCode != fasthttp.StatusOK {
		log.Fatalf("Unexpected status code: %d. Expecting %d", statusCode, fasthttp.StatusOK)
	}
	return body
}

func prodManifest() (data []byte) {
	body, err := ioutil.ReadFile("./public/webpack/manifest.json")
	if err != nil {
		log.Fatalf("Error when loading manifest from file: %s", err)
	}
	return body
}

// Manifest Get webpack manifest according to current environment
func Manifest() map[string][]string {
	var data []byte
	if dev {
		data = devManifest()
	} else {
		data = prodManifest()
	}
	resp := wpResponse{}
	json.Unmarshal(data, &resp)
	webpackBase = resp.PublicPath

	ast := make(map[string][]string, len(resp.AssetsByChunkName))
	var err error
	for akey, aval := range resp.AssetsByChunkName {
		var d []string
		err = json.Unmarshal(*aval, &d)
		if err != nil {
			var sd string
			err = json.Unmarshal(*aval, &sd)
			if err != nil {
				log.Fatalf("Error when parsing manifest for %s: %s %s", akey, err, string(aval))
				continue
			}
			d = []string{sd}
		}
		ast[akey] = filter(d, func(v string) bool {
			return !strings.Contains(v, ".map")
		})
	}
	return ast
}

// AssetHelper Template helper for asset
func AssetHelper(key string) (template.HTML, error) {
	var ast map[string][]string
	if dev {
		ast = Manifest()
	} else {
		ast = assets
	}

	dat := strings.Split(key, ".")

	buf := []string{}
	var err error
	v, ok := ast[dat[0]]
	if !ok {
		return "", errors.New(fmt.Sprint("asset file ", dat[0], " not found in manifest"))
	}
	for _, s := range v {
		if dat[1] == "css" {
			if strings.HasSuffix(s, ".css") {
				buf = append(buf, fmt.Sprint("<link type=\"text/css\" rel=\"stylesheet\" href=\"", webpackBase, html.EscapeString(s), "\"></script>"))
			}
		} else if dat[1] == "js" {
			if strings.HasSuffix(s, ".js") {
				buf = append(buf, fmt.Sprint("<script type=\"text/javascript\" src=\"", webpackBase, html.EscapeString(s), "\"></script>"))
			}
		}
	}

	return template.HTML(strings.Join(buf, "\n")), err
}

// Init Set current environment and preload manifest
func Init(isDev bool) {
	if isDev {
		c = &fasthttp.HostClient{
			Addr: host,
		}

		// Try to preload manifest, so we can show an error if webpack-dev-server is not running
		Manifest()
	} else {
		assets = Manifest()
	}
}
<commit_msg>rename package + fix type<commit_after>package webpack

import (
	"encoding/json"
	"errors"
	"fmt"
	"html"
	"html/template"
	"io/ioutil"
	"log"
	"strings"

	"github.com/valyala/fasthttp"
)

type wpResponse struct {
	Errors            []string                    `json:"errors"`
	Warning           []string                    `json:"warnings"`
	Version           string                      `json:"version"`
	Hash              string                      `json:"hash"`
	PublicPath        string                      `json:"publicPath"`
	AssetsByChunkName map[string]*json.RawMessage `json:"assetsByChunkName"`
	Assets            []*json.RawMessage          `json:"assets"`
}

const host = "localhost:3808"

var c *fasthttp.HostClient
var dev bool
var assets map[string][]string
var webpackBase string

// filter array of strings by function
func filter(vs []string, f func(string) bool) []string {
	vsf := make([]string, 0)
	for _, v := range vs {
		if f(v) {
			vsf = append(vsf, v)
		}
	}
	return vsf
}

func devManifest() (data []byte) {
	manifestURL := fmt.Sprint("http://", host, "/webpack/manifest.json")
	statusCode, body, err := c.Get(nil, manifestURL)
	if err != nil {
		log.Fatalf("Error when loading manifest from %s: %s", manifestURL, err)
	}
	if statusCode != fasthttp.StatusOK {
		log.Fatalf("Unexpected status code: %d. Expecting %d", statusCode, fasthttp.StatusOK)
	}
	return body
}

func prodManifest() (data []byte) {
	body, err := ioutil.ReadFile("./public/webpack/manifest.json")
	if err != nil {
		log.Fatalf("Error when loading manifest from file: %s", err)
	}
	return body
}

// Manifest Get webpack manifest according to current environment
func Manifest() map[string][]string {
	var data []byte
	if dev {
		data = devManifest()
	} else {
		data = prodManifest()
	}
	resp := wpResponse{}
	json.Unmarshal(data, &resp)
	webpackBase = resp.PublicPath

	ast := make(map[string][]string, len(resp.AssetsByChunkName))
	var err error
	for akey, aval := range resp.AssetsByChunkName {
		var d []string
		err = json.Unmarshal(*aval, &d)
		if err != nil {
			var sd string
			err = json.Unmarshal(*aval, &sd)
			if err != nil {
				log.Fatalf("Error when parsing manifest for %s: %s %s", akey, err, string(*aval))
				continue
			}
			d = []string{sd}
		}
		ast[akey] = filter(d, func(v string) bool {
			return !strings.Contains(v, ".map")
		})
	}
	return ast
}

// AssetHelper Template helper for asset
func AssetHelper(key string) (template.HTML, error) {
	var ast map[string][]string
	if dev {
		ast = Manifest()
	} else {
		ast = assets
	}

	dat := strings.Split(key, ".")

	buf := []string{}
	var err error
	v, ok := ast[dat[0]]
	if !ok {
		return "", errors.New(fmt.Sprint("asset file ", dat[0], " not found in manifest"))
	}
	for _, s := range v {
		if dat[1] == "css" {
			if strings.HasSuffix(s, ".css") {
				buf = append(buf, fmt.Sprint("<link type=\"text/css\" rel=\"stylesheet\" href=\"", webpackBase, html.EscapeString(s), "\"></script>"))
			}
		} else if dat[1] == "js" {
			if strings.HasSuffix(s, ".js") {
				buf = append(buf, fmt.Sprint("<script type=\"text/javascript\" src=\"", webpackBase, html.EscapeString(s), "\"></script>"))
			}
		}
	}

	return template.HTML(strings.Join(buf, "\n")), err
}

// Init Set current environment and preload manifest
func Init(isDev bool) {
	if isDev {
		c = &fasthttp.HostClient{
			Addr: host,
		}

		// Try to preload manifest, so we can show an error if webpack-dev-server is not running
		Manifest()
	} else {
		assets = Manifest()
	}
}
<|endoftext|>


<commit_before>package strava

import (
	"fmt"
	"strings"

	stravalib "github.com/svdberg/syncmysport-runkeeper/Godeps/_workspace/src/github.com/strava/go.strava"
)

type StravaClientInt interface {
	GetSTVActivitiesSince(timestamp int) ([]*stravalib.ActivitySummary, error)
	GetSTVDetailedActivity(activityId int64) (*stravalib.ActivityDetailed, error)
	GetSTVActivityStream(activityId int64, streamType string) (*stravalib.StreamSet, error)
	ValidateToken(token string) bool
	RefreshToken(refresh_token string) (string, string, error)
	DeAuthorize(token string) error
}

type StravaDetailed stravalib.ActivityDetailed

type StravaClient struct {
	BearerToken string
	Client      *stravalib.Client
}

func CreateStravaClient(token string) StravaClientInt {
	client := stravalib.NewClient(token)
	return &StravaClient{token, client}
}

func CreateStravaClientWithSecretAndId(token string, clientId int, secret string) StravaClientInt {
	stravalib.ClientSecret = secret
	stravalib.ClientId = clientId
	client := stravalib.NewClient(token)
	return &StravaClient{token, client}
}

func (c StravaClient) RefreshToken(refresh_token string) (string, string, error) {
	access_token, refresh_token, err := stravalib.NewOAuthService(c.Client).RefreshToken(refresh_token).Do()
	if err != nil {
		return "", "", err
	}
	return access_token, refresh_token, nil
}

func (c StravaClient) GetSTVActivitiesSince(timestamp int) ([]*stravalib.ActivitySummary, error) {
	service := stravalib.NewCurrentAthleteService(c.Client)
	call := service.ListActivities()
	call.After(timestamp)
	activities, err := call.Do()
	return activities, err
}

func (c StravaClient) GetSTVDetailedActivity(activityId int64) (*stravalib.ActivityDetailed, error) {
	service := stravalib.NewActivitiesService(c.Client)
	call := service.Get(activityId)
	return call.Do()
}

func (c StravaClient) GetSTVActivityStream(activityId int64, streamType string) (*stravalib.StreamSet, error) {
	//improve this so we collect all streams in one call
	service := stravalib.NewActivityStreamsService(c.Client)
	var types = make([]stravalib.StreamType, 1)
	if streamType == "GPS" {
		types = append(types, stravalib.StreamTypes.Location)
	} else if streamType == "Heartrate" {
		types = append(types, stravalib.StreamTypes.HeartRate)
	} else if streamType == "Time" {
		types = append(types, stravalib.StreamTypes.Time)
	} else if streamType == "Altitude" {
		types = append(types, stravalib.StreamTypes.Elevation)
	}
	stream, err := service.Get(activityId, types).Do()
	if err != nil && strings.Contains(err.Error(), "Record Not Found") {
		return nil, nil
	} else if err == nil {
		return stream, nil
	} else {
		return nil, err
	}
}

func (c StravaClient) ValidateToken(token string) bool {
	//create a new client to validate this token
	verifyClient := stravalib.NewClient(token)
	service := stravalib.NewAthletesService(verifyClient)
	_, err := service.Get(1304613).Do() //hey, thats me!
	if err != nil {
		return false
	}
	return true
}

func (c StravaClient) DeAuthorize(token string) error {
	deAuthClient := stravalib.NewClient(token)
	return stravalib.NewOAuthService(deAuthClient).Deauthorize().Do()
}

func (da StravaDetailed) String() string {
	return fmt.Sprintf("{Id: %d, Name: '%s'}", da.Id, da.Name)
}
<commit_msg>Fix the call to verify the access token<commit_after>package strava

import (
	"fmt"
	"strings"

	stravalib "github.com/svdberg/syncmysport-runkeeper/Godeps/_workspace/src/github.com/strava/go.strava"
)

type StravaClientInt interface {
	GetSTVActivitiesSince(timestamp int) ([]*stravalib.ActivitySummary, error)
	GetSTVDetailedActivity(activityId int64) (*stravalib.ActivityDetailed, error)
	GetSTVActivityStream(activityId int64, streamType string) (*stravalib.StreamSet, error)
	ValidateToken(token string) bool
	RefreshToken(refresh_token string) (string, string, error)
	DeAuthorize(token string) error
}

type StravaDetailed stravalib.ActivityDetailed

type StravaClient struct {
	BearerToken string
	Client      *stravalib.Client
}

func CreateStravaClient(token string) StravaClientInt {
	client := stravalib.NewClient(token)
	return &StravaClient{token, client}
}

func CreateStravaClientWithSecretAndId(token string, clientId int, secret string) StravaClientInt {
	stravalib.ClientSecret = secret
	stravalib.ClientId = clientId
	client := stravalib.NewClient(token)
	return &StravaClient{token, client}
}

func (c StravaClient) RefreshToken(refresh_token string) (string, string, error) {
	access_token, refresh_token, err := stravalib.NewOAuthService(c.Client).RefreshToken(refresh_token).Do()
	if err != nil {
		return "", "", err
	}
	return access_token, refresh_token, nil
}

func (c StravaClient) GetSTVActivitiesSince(timestamp int) ([]*stravalib.ActivitySummary, error) {
	service := stravalib.NewCurrentAthleteService(c.Client)
	call := service.ListActivities()
	call.After(timestamp)
	activities, err := call.Do()
	return activities, err
}

func (c StravaClient) GetSTVDetailedActivity(activityId int64) (*stravalib.ActivityDetailed, error) {
	service := stravalib.NewActivitiesService(c.Client)
	call := service.Get(activityId)
	return call.Do()
}

func (c StravaClient) GetSTVActivityStream(activityId int64, streamType string) (*stravalib.StreamSet, error) {
	//improve this so we collect all streams in one call
	service := stravalib.NewActivityStreamsService(c.Client)
	var types = make([]stravalib.StreamType, 1)
	if streamType == "GPS" {
		types = append(types, stravalib.StreamTypes.Location)
	} else if streamType == "Heartrate" {
		types = append(types, stravalib.StreamTypes.HeartRate)
	} else if streamType == "Time" {
		types = append(types, stravalib.StreamTypes.Time)
	} else if streamType == "Altitude" {
		types = append(types, stravalib.StreamTypes.Elevation)
	}
	stream, err := service.Get(activityId, types).Do()
	if err != nil && strings.Contains(err.Error(), "Record Not Found") {
		return nil, nil
	} else if err == nil {
		return stream, nil
	} else {
		return nil, err
	}
}

func (c StravaClient) ValidateToken(token string) bool {
	//create a new client to validate this token
	verifyClient := stravalib.NewClient(token)
	service := stravalib.NewCurrentAthleteService(verifyClient)
	_, err := service.Get().Do()
	if err != nil {
		return false
	}
	return true
}

func (c StravaClient) DeAuthorize(token string) error {
	deAuthClient := stravalib.NewClient(token)
	return stravalib.NewOAuthService(deAuthClient).Deauthorize().Do()
}

func (da StravaDetailed) String() string {
	return fmt.Sprintf("{Id: %d, Name: '%s'}", da.Id, da.Name)
}
<|endoftext|>


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

package client

import (
	"errors"
	"time"

	"golang.org/x/net/context"

	"github.com/keybase/cli"
	"github.com/keybase/client/go/libcmdline"
	"github.com/keybase/client/go/libkb"
	keybase1 "github.com/keybase/client/go/protocol"
	rpc "github.com/keybase/go-framed-msgpack-rpc"
)

func NewCmdLogin(cl *libcmdline.CommandLine, g *libkb.GlobalContext) cli.Command {
	cmd := cli.Command{
		Name:         "login",
		ArgumentHelp: "[username]",
		Usage:        "Establish a session with the keybase server",
		Action: func(c *cli.Context) {
			cl.ChooseCommand(NewCmdLoginRunner(g), "login", c)
		},
	}
	// Note we'll only be able to set this via mode via Environment variable
	// since it's too early to check command-line setting of it.
	if g.Env.GetRunMode() == libkb.DevelRunMode {
		cmd.Flags = append(cmd.Flags, cli.BoolFlag{
			Name:  "emulate-gui",
			Usage: "emulate GUI signing and fork GPG from the service",
		})
	}
	return cmd
}

type CmdLogin struct {
	libkb.Contextified
	username   string
	clientType keybase1.ClientType
	cancel     func()
	done       chan struct{}
}

func NewCmdLoginRunner(g *libkb.GlobalContext) *CmdLogin {
	return &CmdLogin{
		Contextified: libkb.NewContextified(g),
		clientType:   keybase1.ClientType_CLI,
		done:         make(chan struct{}, 1),
	}
}

func (c *CmdLogin) Run() error {
	protocols := []rpc.Protocol{
		NewProvisionUIProtocol(c.G(), libkb.KexRoleProvisionee),
		NewLoginUIProtocol(c.G()),
		NewSecretUIProtocol(c.G()),
		NewGPGUIProtocol(c.G()),
	}
	if err := RegisterProtocolsWithContext(protocols, c.G()); err != nil {
		return err
	}
	client, err := GetLoginClient(c.G())
	if err != nil {
		return err
	}

	// TODO: it would be nice to move this up a level and have keybase/main.go create
	// a context and pass it to Command.Run(), then it can handle cancel itself
	// instead of using Command.Cancel().
	ctx, cancel := context.WithCancel(context.Background())
	c.cancel = cancel
	defer func() {
		c.cancel()
		c.cancel = nil
	}()

	err = client.Login(ctx,
		keybase1.LoginArg{
			Username:   c.username,
			DeviceType: libkb.DeviceTypeDesktop,
			ClientType: c.clientType,
		})
	c.done <- struct{}{}

	// Provide explicit error messages in these two cases.
	switch err.(type) {
	case libkb.NoSyncedPGPKeyError:
		err = c.errNoSyncedKey()
	case libkb.PassphraseError:
		err = c.errPassphrase()
	}

	return err
}

func (c *CmdLogin) ParseArgv(ctx *cli.Context) error {
	nargs := len(ctx.Args())
	if nargs > 1 {
		return errors.New("Invalid arguments.")
	}

	if nargs == 1 {
		c.username = ctx.Args()[0]
		if libkb.CheckEmail.F(c.username) {
			return errors.New("Please login again via `keybase login [username]`")
		}
		if !libkb.CheckUsername.F(c.username) {
			return errors.New("Invalid username format. Please login again via `keybase login [username]`")
		}
	}
	if ctx.Bool("emulate-gui") {
		c.clientType = keybase1.ClientType_GUI
	}
	return nil
}

func (c *CmdLogin) GetUsage() libkb.Usage {
	return libkb.Usage{
		Config:    true,
		KbKeyring: true,
		API:       true,
	}
}

func (c *CmdLogin) Cancel() error {
	c.G().Log.Debug("received request to cancel running login command")
	if c.cancel != nil {
		c.G().Log.Debug("command cancel function exists, calling it")
		c.cancel()

		// In go-framed-msgpack-rpc, dispatch.handleCall() starts a goroutine to check the context being
		// canceled.
		// So, need to wait here for call to actually finish in order for the cancel message to make it
		// to the daemon.
		select {
		case <-c.done:
			c.G().Log.Debug("command finished, cancel complete")
		case <-time.After(5 * time.Second):
			c.G().Log.Debug("timed out waiting for command to finish")
		}
	}
	return nil
}

func (c *CmdLogin) errNoSyncedKey() error {
	return errors.New(`in Login

Sorry, you have a PGP private key that Keybase doesn't have a copy of.
You need to prove you're you. Any of these will unlock this computer:

   - reset your account and start fresh: https://keybase.io/#account-reset
   - install GPG and put your PGP private key on this machine and try again
   - go back and provision with another device or paper key
`)
}

func (c *CmdLogin) errPassphrase() error {
	return errors.New(`in Login

The server rejected your login attempt.

If you'd like to reset your passphrase, go to https://keybase.io/#password-reset
`)
}
<commit_msg>login: fix scary and wrong text<commit_after>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package client

import (
	"errors"
	"time"

	"golang.org/x/net/context"

	"github.com/keybase/cli"
	"github.com/keybase/client/go/libcmdline"
	"github.com/keybase/client/go/libkb"
	keybase1 "github.com/keybase/client/go/protocol"
	rpc "github.com/keybase/go-framed-msgpack-rpc"
)

func NewCmdLogin(cl *libcmdline.CommandLine, g *libkb.GlobalContext) cli.Command {
	cmd := cli.Command{
		Name:         "login",
		ArgumentHelp: "[username]",
		Usage:        "Establish a session with the keybase server",
		Action: func(c *cli.Context) {
			cl.ChooseCommand(NewCmdLoginRunner(g), "login", c)
		},
	}
	// Note we'll only be able to set this via mode via Environment variable
	// since it's too early to check command-line setting of it.
	if g.Env.GetRunMode() == libkb.DevelRunMode {
		cmd.Flags = append(cmd.Flags, cli.BoolFlag{
			Name:  "emulate-gui",
			Usage: "emulate GUI signing and fork GPG from the service",
		})
	}
	return cmd
}

type CmdLogin struct {
	libkb.Contextified
	username   string
	clientType keybase1.ClientType
	cancel     func()
	done       chan struct{}
}

func NewCmdLoginRunner(g *libkb.GlobalContext) *CmdLogin {
	return &CmdLogin{
		Contextified: libkb.NewContextified(g),
		clientType:   keybase1.ClientType_CLI,
		done:         make(chan struct{}, 1),
	}
}

func (c *CmdLogin) Run() error {
	protocols := []rpc.Protocol{
		NewProvisionUIProtocol(c.G(), libkb.KexRoleProvisionee),
		NewLoginUIProtocol(c.G()),
		NewSecretUIProtocol(c.G()),
		NewGPGUIProtocol(c.G()),
	}
	if err := RegisterProtocolsWithContext(protocols, c.G()); err != nil {
		return err
	}
	client, err := GetLoginClient(c.G())
	if err != nil {
		return err
	}

	// TODO: it would be nice to move this up a level and have keybase/main.go create
	// a context and pass it to Command.Run(), then it can handle cancel itself
	// instead of using Command.Cancel().
	ctx, cancel := context.WithCancel(context.Background())
	c.cancel = cancel
	defer func() {
		c.cancel()
		c.cancel = nil
	}()

	err = client.Login(ctx,
		keybase1.LoginArg{
			Username:   c.username,
			DeviceType: libkb.DeviceTypeDesktop,
			ClientType: c.clientType,
		})
	c.done <- struct{}{}

	// Provide explicit error messages in these two cases.
	switch err.(type) {
	case libkb.NoSyncedPGPKeyError:
		err = c.errNoSyncedKey()
	case libkb.PassphraseError:
		err = c.errPassphrase()
	}

	return err
}

func (c *CmdLogin) ParseArgv(ctx *cli.Context) error {
	nargs := len(ctx.Args())
	if nargs > 1 {
		return errors.New("Invalid arguments.")
	}

	if nargs == 1 {
		c.username = ctx.Args()[0]
		if libkb.CheckEmail.F(c.username) {
			return errors.New("Please login again via `keybase login [username]`")
		}
		if !libkb.CheckUsername.F(c.username) {
			return errors.New("Invalid username format. Please login again via `keybase login [username]`")
		}
	}
	if ctx.Bool("emulate-gui") {
		c.clientType = keybase1.ClientType_GUI
	}
	return nil
}

func (c *CmdLogin) GetUsage() libkb.Usage {
	return libkb.Usage{
		Config:    true,
		KbKeyring: true,
		API:       true,
	}
}

func (c *CmdLogin) Cancel() error {
	c.G().Log.Debug("received request to cancel running login command")
	if c.cancel != nil {
		c.G().Log.Debug("command cancel function exists, calling it")
		c.cancel()

		// In go-framed-msgpack-rpc, dispatch.handleCall() starts a goroutine to check the context being
		// canceled.
		// So, need to wait here for call to actually finish in order for the cancel message to make it
		// to the daemon.
		select {
		case <-c.done:
			c.G().Log.Debug("command finished, cancel complete")
		case <-time.After(5 * time.Second):
			c.G().Log.Debug("timed out waiting for command to finish")
		}
	}
	return nil
}

func (c *CmdLogin) errNoSyncedKey() error {
	return errors.New(`in Login

Sorry, your account is already established with a PGP public key, but this
utility cannot access the corresponding private key. You need to prove
you're you. We suggest one of the following:

   - install GPG and put your PGP private key on this machine and try again
   - reset your account and start fresh: https://keybase.io/#account-reset
   - go back and provision with another device or paper key
`)
}

func (c *CmdLogin) errPassphrase() error {
	return errors.New(`in Login

The server rejected your login attempt.

If you'd like to reset your passphrase, go to https://keybase.io/#password-reset
`)
}
<|endoftext|>


<commit_before>// Package client is a CT log client implementation and contains types and code
// for interacting with RFC6962-compliant CT Log instances.
// See http://tools.ietf.org/html/rfc6962 for details
package client

import (
	"bytes"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strconv"
	"time"

	"github.com/google/certificate-transparency/go"
	"github.com/mreiferson/go-httpclient"
	"golang.org/x/net/context"
)

// URI paths for CT Log endpoints
const (
	AddChainPath    = "/ct/v1/add-chain"
	AddPreChainPath = "/ct/v1/add-pre-chain"
	AddJSONPath     = "/ct/v1/add-json"
	GetSTHPath      = "/ct/v1/get-sth"
	GetEntriesPath  = "/ct/v1/get-entries"
)

// LogClient represents a client for a given CT Log instance
type LogClient struct {
	uri        string       // the base URI of the log. e.g. http://ct.googleapis/pilot
	httpClient *http.Client // used to interact with the log via HTTP
}

//////////////////////////////////////////////////////////////////////////////////
// JSON structures follow.
// These represent the structures returned by the CT Log server.
//////////////////////////////////////////////////////////////////////////////////

// addChainRequest represents the JSON request body sent to the add-chain CT
// method.
type addChainRequest struct {
	Chain []string `json:"chain"`
}

// addChainResponse represents the JSON response to the add-chain CT method.
// An SCT represents a Log's promise to integrate a [pre-]certificate into the
// log within a defined period of time.
type addChainResponse struct {
	SCTVersion ct.Version `json:"sct_version"` // SCT structure version
	ID         string     `json:"id"`          // Log ID
	Timestamp  uint64     `json:"timestamp"`   // Timestamp of issuance
	Extensions string     `json:"extensions"`  // Holder for any CT extensions
	Signature  string     `json:"signature"`   // Log signature for this SCT
}

// addJSONRequest represents the JSON request body sent ot the add-json CT
// method.
type addJSONRequest struct {
	Data interface{} `json:"data"`
}

// getSTHResponse respresents the JSON response to the get-sth CT method
type getSTHResponse struct {
	TreeSize          uint64 `json:"tree_size"`           // Number of certs in the current tree
	Timestamp         uint64 `json:"timestamp"`           // Time that the tree was created
	SHA256RootHash    string `json:"sha256_root_hash"`    // Root hash of the tree
	TreeHeadSignature string `json:"tree_head_signature"` // Log signature for this STH
}

// base64LeafEntry respresents a Base64 encoded leaf entry
type base64LeafEntry struct {
	LeafInput string `json:"leaf_input"`
	ExtraData string `json:"extra_data"`
}

// getEntriesReponse respresents the JSON response to the CT get-entries method
type getEntriesResponse struct {
	Entries []base64LeafEntry `json:"entries"` // the list of returned entries
}

// getConsistencyProofResponse represents the JSON response to the CT get-consistency-proof method
type getConsistencyProofResponse struct {
	Consistency []string `json:"consistency"`
}

// getAuditProofResponse represents the JSON response to the CT get-audit-proof method
type getAuditProofResponse struct {
	Hash     []string `json:"hash"`      // the hashes which make up the proof
	TreeSize uint64   `json:"tree_size"` // the tree size against which this proof is constructed
}

// getAcceptedRootsResponse represents the JSON response to the CT get-roots method.
type getAcceptedRootsResponse struct {
	Certificates []string `json:"certificates"`
}

// getEntryAndProodReponse represents the JSON response to the CT get-entry-and-proof method
type getEntryAndProofResponse struct {
	LeafInput string   `json:"leaf_input"` // the entry itself
	ExtraData string   `json:"extra_data"` // any chain provided when the entry was added to the log
	AuditPath []string `json:"audit_path"` // the corresponding proof
}

// New constructs a new LogClient instance.
// |uri| is the base URI of the CT log instance to interact with, e.g.
// http://ct.googleapis.com/pilot
func New(uri string) *LogClient {
	var c LogClient
	c.uri = uri
	transport := &httpclient.Transport{
		ConnectTimeout:        10 * time.Second,
		RequestTimeout:        30 * time.Second,
		ResponseHeaderTimeout: 30 * time.Second,
		MaxIdleConnsPerHost:   10,
		DisableKeepAlives:     false,
	}
	c.httpClient = &http.Client{Transport: transport}
	return &c
}

// Makes a HTTP call to |uri|, and attempts to parse the response as a JSON
// representation of the structure in |res|.
// Returns a non-nil |error| if there was a problem.
func (c *LogClient) fetchAndParse(uri string, res interface{}) error {
	req, err := http.NewRequest("GET", uri, nil)
	if err != nil {
		return err
	}
	resp, err := c.httpClient.Do(req)
	var body []byte
	if resp != nil {
		body, err = ioutil.ReadAll(resp.Body)
		resp.Body.Close()
		if err != nil {
			return err
		}
	}
	if err != nil {
		return err
	}
	if err = json.Unmarshal(body, &res); err != nil {
		return err
	}
	return nil
}

// Makes a HTTP POST call to |uri|, and attempts to parse the response as a JSON
// representation of the structure in |res|.
// Returns a non-nil |error| if there was a problem.
func (c *LogClient) postAndParse(uri string, req interface{}, res interface{}) (*http.Response, string, error) {
	postBody, err := json.Marshal(req)
	if err != nil {
		return nil, "", err
	}
	httpReq, err := http.NewRequest("POST", uri, bytes.NewReader(postBody))
	if err != nil {
		return nil, "", err
	}
	httpReq.Header.Set("Keep-Alive", "timeout=15, max=100")
	httpReq.Header.Set("Content-Type", "application/json")
	resp, err := c.httpClient.Do(httpReq)
	// Read all of the body, if there is one, so that the http.Client can do
	// Keep-Alive:
	var body []byte
	if resp != nil {
		body, err = ioutil.ReadAll(resp.Body)
		resp.Body.Close()
	}
	if err != nil {
		return resp, string(body), err
	}
	if resp.StatusCode == 200 {
		if err != nil {
			return resp, string(body), err
		}
		if err = json.Unmarshal(body, &res); err != nil {
			return resp, string(body), err
		}
	}
	return resp, string(body), nil
}

func backoffForRetry(ctx context.Context, d time.Duration) error {
	backoffTimer := time.NewTimer(d)
	if ctx != nil {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-backoffTimer.C:
		}
	} else {
		<-backoffTimer.C
	}
	return nil
}

// Attempts to add |chain| to the log, using the api end-point specified by
// |path|. If provided context expires before submission is complete an
// error will be returned.
func (c *LogClient) addChainWithRetry(ctx context.Context, path string, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	var resp addChainResponse
	var req addChainRequest
	for _, link := range chain {
		req.Chain = append(req.Chain, base64.StdEncoding.EncodeToString(link))
	}
	httpStatus := "Unknown"
	backoffSeconds := 0
	done := false
	for !done {
		if backoffSeconds > 0 {
			log.Printf("Got %s, backing-off %d seconds", httpStatus, backoffSeconds)
		}
		err := backoffForRetry(ctx, time.Second*time.Duration(backoffSeconds))
		if err != nil {
			return nil, err
		}
		if backoffSeconds > 0 {
			backoffSeconds = 0
		}
		httpResp, errorBody, err := c.postAndParse(c.uri+path, &req, &resp)
		if err != nil {
			backoffSeconds = 10
			continue
		}
		switch {
		case httpResp.StatusCode == 200:
			done = true
		case httpResp.StatusCode == 408:
			// request timeout, retry immediately
		case httpResp.StatusCode == 503:
			// Retry
			backoffSeconds = 10
			if retryAfter := httpResp.Header.Get("Retry-After"); retryAfter != "" {
				if seconds, err := strconv.Atoi(retryAfter); err == nil {
					backoffSeconds = seconds
				}
			}
		default:
			return nil, fmt.Errorf("got HTTP Status %s: %s", httpResp.Status, errorBody)
		}
		httpStatus = httpResp.Status
	}

	rawLogID, err := base64.StdEncoding.DecodeString(resp.ID)
	if err != nil {
		return nil, err
	}
	rawSignature, err := base64.StdEncoding.DecodeString(resp.Signature)
	if err != nil {
		return nil, err
	}
	ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(rawSignature))
	if err != nil {
		return nil, err
	}
	var logID ct.SHA256Hash
	copy(logID[:], rawLogID)
	return &ct.SignedCertificateTimestamp{
		SCTVersion: resp.SCTVersion,
		LogID:      logID,
		Timestamp:  resp.Timestamp,
		Extensions: ct.CTExtensions(resp.Extensions),
		Signature:  *ds}, nil
}

// AddChain adds the (DER represented) X509 |chain| to the log.
func (c *LogClient) AddChain(chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	return c.addChainWithRetry(nil, AddChainPath, chain)
}

// AddPreChain adds the (DER represented) Precertificate |chain| to the log.
func (c *LogClient) AddPreChain(chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	return c.addChainWithRetry(nil, AddPreChainPath, chain)
}

// AddChainWithContext adds the (DER represented) X509 |chain| to the log and
// fails if the provided context expires before the chain is submitted.
func (c *LogClient) AddChainWithContext(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	return c.addChainWithRetry(ctx, AddChainPath, chain)
}

func (c *LogClient) AddJSON(data interface{}) (*ct.SignedCertificateTimestamp, error) {
	req := addJSONRequest{
		Data: data,
	}
	var resp addChainResponse
	_, _, err := c.postAndParse(c.uri+AddJSONPath, &req, &resp)
	if err != nil {
		return nil, err
	}
	rawLogID, err := base64.StdEncoding.DecodeString(resp.ID)
	if err != nil {
		return nil, err
	}
	rawSignature, err := base64.StdEncoding.DecodeString(resp.Signature)
	if err != nil {
		return nil, err
	}
	ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(rawSignature))
	if err != nil {
		return nil, err
	}
	var logID ct.SHA256Hash
	copy(logID[:], rawLogID)
	return &ct.SignedCertificateTimestamp{
		SCTVersion: resp.SCTVersion,
		LogID:      logID,
		Timestamp:  resp.Timestamp,
		Extensions: ct.CTExtensions(resp.Extensions),
		Signature:  *ds}, nil
}

// GetSTH retrieves the current STH from the log.
// Returns a populated SignedTreeHead, or a non-nil error.
func (c *LogClient) GetSTH() (sth *ct.SignedTreeHead, err error) {
	var resp getSTHResponse
	if err = c.fetchAndParse(c.uri+GetSTHPath, &resp); err != nil {
		return
	}
	sth = &ct.SignedTreeHead{
		TreeSize:  resp.TreeSize,
		Timestamp: resp.Timestamp,
	}

	rawRootHash, err := base64.StdEncoding.DecodeString(resp.SHA256RootHash)
	if err != nil {
		return nil, fmt.Errorf("invalid base64 encoding in sha256_root_hash: %v", err)
	}
	if len(rawRootHash) != sha256.Size {
		return nil, fmt.Errorf("sha256_root_hash is invalid length, expected %d got %d", sha256.Size, len(rawRootHash))
	}
	copy(sth.SHA256RootHash[:], rawRootHash)

	rawSignature, err := base64.StdEncoding.DecodeString(resp.TreeHeadSignature)
	if err != nil {
		return nil, errors.New("invalid base64 encoding in tree_head_signature")
	}
	ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(rawSignature))
	if err != nil {
		return nil, err
	}
	// TODO(alcutter): Verify signature
	sth.TreeHeadSignature = *ds
	return
}

// GetEntries attempts to retrieve the entries in the sequence [|start|, |end|] from the CT
// log server. (see section 4.6.)
// Returns a slice of LeafInputs or a non-nil error.
func (c *LogClient) GetEntries(start, end int64) ([]ct.LogEntry, error) {
	if end < 0 {
		return nil, errors.New("end should be >= 0")
	}
	if end < start {
		return nil, errors.New("start should be <= end")
	}
	var resp getEntriesResponse
	err := c.fetchAndParse(fmt.Sprintf("%s%s?start=%d&end=%d", c.uri, GetEntriesPath, start, end), &resp)
	if err != nil {
		return nil, err
	}
	entries := make([]ct.LogEntry, len(resp.Entries))
	for index, entry := range resp.Entries {
		leafBytes, err := base64.StdEncoding.DecodeString(entry.LeafInput)
		leaf, err := ct.ReadMerkleTreeLeaf(bytes.NewBuffer(leafBytes))
		if err != nil {
			return nil, err
		}
		entries[index].Leaf = *leaf
		chainBytes, err := base64.StdEncoding.DecodeString(entry.ExtraData)

		var chain []ct.ASN1Cert
		switch leaf.TimestampedEntry.EntryType {
		case ct.X509LogEntryType:
			chain, err = ct.UnmarshalX509ChainArray(chainBytes)

		case ct.PrecertLogEntryType:
			chain, err = ct.UnmarshalPrecertChainArray(chainBytes)

		default:
			return nil, fmt.Errorf("saw unknown entry type: %v", leaf.TimestampedEntry.EntryType)
		}
		if err != nil {
			return nil, err
		}
		entries[index].Chain = chain
		entries[index].Index = start + int64(index)
	}
	return entries, nil
}
<commit_msg>Disable Keep-Alive header from postAndParse<commit_after>// Package client is a CT log client implementation and contains types and code
// for interacting with RFC6962-compliant CT Log instances.
// See http://tools.ietf.org/html/rfc6962 for details
package client

import (
	"bytes"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strconv"
	"time"

	"github.com/google/certificate-transparency/go"
	"github.com/mreiferson/go-httpclient"
	"golang.org/x/net/context"
)

// URI paths for CT Log endpoints
const (
	AddChainPath    = "/ct/v1/add-chain"
	AddPreChainPath = "/ct/v1/add-pre-chain"
	AddJSONPath     = "/ct/v1/add-json"
	GetSTHPath      = "/ct/v1/get-sth"
	GetEntriesPath  = "/ct/v1/get-entries"
)

// LogClient represents a client for a given CT Log instance
type LogClient struct {
	uri        string       // the base URI of the log. e.g. http://ct.googleapis/pilot
	httpClient *http.Client // used to interact with the log via HTTP
}

//////////////////////////////////////////////////////////////////////////////////
// JSON structures follow.
// These represent the structures returned by the CT Log server.
//////////////////////////////////////////////////////////////////////////////////

// addChainRequest represents the JSON request body sent to the add-chain CT
// method.
type addChainRequest struct {
	Chain []string `json:"chain"`
}

// addChainResponse represents the JSON response to the add-chain CT method.
// An SCT represents a Log's promise to integrate a [pre-]certificate into the
// log within a defined period of time.
type addChainResponse struct {
	SCTVersion ct.Version `json:"sct_version"` // SCT structure version
	ID         string     `json:"id"`          // Log ID
	Timestamp  uint64     `json:"timestamp"`   // Timestamp of issuance
	Extensions string     `json:"extensions"`  // Holder for any CT extensions
	Signature  string     `json:"signature"`   // Log signature for this SCT
}

// addJSONRequest represents the JSON request body sent ot the add-json CT
// method.
type addJSONRequest struct {
	Data interface{} `json:"data"`
}

// getSTHResponse respresents the JSON response to the get-sth CT method
type getSTHResponse struct {
	TreeSize          uint64 `json:"tree_size"`           // Number of certs in the current tree
	Timestamp         uint64 `json:"timestamp"`           // Time that the tree was created
	SHA256RootHash    string `json:"sha256_root_hash"`    // Root hash of the tree
	TreeHeadSignature string `json:"tree_head_signature"` // Log signature for this STH
}

// base64LeafEntry respresents a Base64 encoded leaf entry
type base64LeafEntry struct {
	LeafInput string `json:"leaf_input"`
	ExtraData string `json:"extra_data"`
}

// getEntriesReponse respresents the JSON response to the CT get-entries method
type getEntriesResponse struct {
	Entries []base64LeafEntry `json:"entries"` // the list of returned entries
}

// getConsistencyProofResponse represents the JSON response to the CT get-consistency-proof method
type getConsistencyProofResponse struct {
	Consistency []string `json:"consistency"`
}

// getAuditProofResponse represents the JSON response to the CT get-audit-proof method
type getAuditProofResponse struct {
	Hash     []string `json:"hash"`      // the hashes which make up the proof
	TreeSize uint64   `json:"tree_size"` // the tree size against which this proof is constructed
}

// getAcceptedRootsResponse represents the JSON response to the CT get-roots method.
type getAcceptedRootsResponse struct {
	Certificates []string `json:"certificates"`
}

// getEntryAndProodReponse represents the JSON response to the CT get-entry-and-proof method
type getEntryAndProofResponse struct {
	LeafInput string   `json:"leaf_input"` // the entry itself
	ExtraData string   `json:"extra_data"` // any chain provided when the entry was added to the log
	AuditPath []string `json:"audit_path"` // the corresponding proof
}

// New constructs a new LogClient instance.
// |uri| is the base URI of the CT log instance to interact with, e.g.
// http://ct.googleapis.com/pilot
func New(uri string) *LogClient {
	var c LogClient
	c.uri = uri
	transport := &httpclient.Transport{
		ConnectTimeout:        10 * time.Second,
		RequestTimeout:        30 * time.Second,
		ResponseHeaderTimeout: 30 * time.Second,
		MaxIdleConnsPerHost:   10,
		DisableKeepAlives:     false,
	}
	c.httpClient = &http.Client{Transport: transport}
	return &c
}

// Makes a HTTP call to |uri|, and attempts to parse the response as a JSON
// representation of the structure in |res|.
// Returns a non-nil |error| if there was a problem.
func (c *LogClient) fetchAndParse(uri string, res interface{}) error {
	req, err := http.NewRequest("GET", uri, nil)
	if err != nil {
		return err
	}
	resp, err := c.httpClient.Do(req)
	var body []byte
	if resp != nil {
		body, err = ioutil.ReadAll(resp.Body)
		resp.Body.Close()
		if err != nil {
			return err
		}
	}
	if err != nil {
		return err
	}
	if err = json.Unmarshal(body, &res); err != nil {
		return err
	}
	return nil
}

// Makes a HTTP POST call to |uri|, and attempts to parse the response as a JSON
// representation of the structure in |res|.
// Returns a non-nil |error| if there was a problem.
func (c *LogClient) postAndParse(uri string, req interface{}, res interface{}) (*http.Response, string, error) {
	postBody, err := json.Marshal(req)
	if err != nil {
		return nil, "", err
	}
	httpReq, err := http.NewRequest("POST", uri, bytes.NewReader(postBody))
	if err != nil {
		return nil, "", err
	}
	httpReq.Header.Set("Content-Type", "application/json")
	resp, err := c.httpClient.Do(httpReq)
	// Read all of the body, if there is one, so that the http.Client can do
	// Keep-Alive:
	var body []byte
	if resp != nil {
		body, err = ioutil.ReadAll(resp.Body)
		resp.Body.Close()
	}
	if err != nil {
		return resp, string(body), err
	}
	if resp.StatusCode == 200 {
		if err != nil {
			return resp, string(body), err
		}
		if err = json.Unmarshal(body, &res); err != nil {
			return resp, string(body), err
		}
	}
	return resp, string(body), nil
}

func backoffForRetry(ctx context.Context, d time.Duration) error {
	backoffTimer := time.NewTimer(d)
	if ctx != nil {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-backoffTimer.C:
		}
	} else {
		<-backoffTimer.C
	}
	return nil
}

// Attempts to add |chain| to the log, using the api end-point specified by
// |path|. If provided context expires before submission is complete an
// error will be returned.
func (c *LogClient) addChainWithRetry(ctx context.Context, path string, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	var resp addChainResponse
	var req addChainRequest
	for _, link := range chain {
		req.Chain = append(req.Chain, base64.StdEncoding.EncodeToString(link))
	}
	httpStatus := "Unknown"
	backoffSeconds := 0
	done := false
	for !done {
		if backoffSeconds > 0 {
			log.Printf("Got %s, backing-off %d seconds", httpStatus, backoffSeconds)
		}
		err := backoffForRetry(ctx, time.Second*time.Duration(backoffSeconds))
		if err != nil {
			return nil, err
		}
		if backoffSeconds > 0 {
			backoffSeconds = 0
		}
		httpResp, errorBody, err := c.postAndParse(c.uri+path, &req, &resp)
		if err != nil {
			backoffSeconds = 10
			continue
		}
		switch {
		case httpResp.StatusCode == 200:
			done = true
		case httpResp.StatusCode == 408:
			// request timeout, retry immediately
		case httpResp.StatusCode == 503:
			// Retry
			backoffSeconds = 10
			if retryAfter := httpResp.Header.Get("Retry-After"); retryAfter != "" {
				if seconds, err := strconv.Atoi(retryAfter); err == nil {
					backoffSeconds = seconds
				}
			}
		default:
			return nil, fmt.Errorf("got HTTP Status %s: %s", httpResp.Status, errorBody)
		}
		httpStatus = httpResp.Status
	}

	rawLogID, err := base64.StdEncoding.DecodeString(resp.ID)
	if err != nil {
		return nil, err
	}
	rawSignature, err := base64.StdEncoding.DecodeString(resp.Signature)
	if err != nil {
		return nil, err
	}
	ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(rawSignature))
	if err != nil {
		return nil, err
	}
	var logID ct.SHA256Hash
	copy(logID[:], rawLogID)
	return &ct.SignedCertificateTimestamp{
		SCTVersion: resp.SCTVersion,
		LogID:      logID,
		Timestamp:  resp.Timestamp,
		Extensions: ct.CTExtensions(resp.Extensions),
		Signature:  *ds}, nil
}

// AddChain adds the (DER represented) X509 |chain| to the log.
func (c *LogClient) AddChain(chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	return c.addChainWithRetry(nil, AddChainPath, chain)
}

// AddPreChain adds the (DER represented) Precertificate |chain| to the log.
func (c *LogClient) AddPreChain(chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	return c.addChainWithRetry(nil, AddPreChainPath, chain)
}

// AddChainWithContext adds the (DER represented) X509 |chain| to the log and
// fails if the provided context expires before the chain is submitted.
func (c *LogClient) AddChainWithContext(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	return c.addChainWithRetry(ctx, AddChainPath, chain)
}

func (c *LogClient) AddJSON(data interface{}) (*ct.SignedCertificateTimestamp, error) {
	req := addJSONRequest{
		Data: data,
	}
	var resp addChainResponse
	_, _, err := c.postAndParse(c.uri+AddJSONPath, &req, &resp)
	if err != nil {
		return nil, err
	}
	rawLogID, err := base64.StdEncoding.DecodeString(resp.ID)
	if err != nil {
		return nil, err
	}
	rawSignature, err := base64.StdEncoding.DecodeString(resp.Signature)
	if err != nil {
		return nil, err
	}
	ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(rawSignature))
	if err != nil {
		return nil, err
	}
	var logID ct.SHA256Hash
	copy(logID[:], rawLogID)
	return &ct.SignedCertificateTimestamp{
		SCTVersion: resp.SCTVersion,
		LogID:      logID,
		Timestamp:  resp.Timestamp,
		Extensions: ct.CTExtensions(resp.Extensions),
		Signature:  *ds}, nil
}

// GetSTH retrieves the current STH from the log.
// Returns a populated SignedTreeHead, or a non-nil error.
func (c *LogClient) GetSTH() (sth *ct.SignedTreeHead, err error) {
	var resp getSTHResponse
	if err = c.fetchAndParse(c.uri+GetSTHPath, &resp); err != nil {
		return
	}
	sth = &ct.SignedTreeHead{
		TreeSize:  resp.TreeSize,
		Timestamp: resp.Timestamp,
	}

	rawRootHash, err := base64.StdEncoding.DecodeString(resp.SHA256RootHash)
	if err != nil {
		return nil, fmt.Errorf("invalid base64 encoding in sha256_root_hash: %v", err)
	}
	if len(rawRootHash) != sha256.Size {
		return nil, fmt.Errorf("sha256_root_hash is invalid length, expected %d got %d", sha256.Size, len(rawRootHash))
	}
	copy(sth.SHA256RootHash[:], rawRootHash)

	rawSignature, err := base64.StdEncoding.DecodeString(resp.TreeHeadSignature)
	if err != nil {
		return nil, errors.New("invalid base64 encoding in tree_head_signature")
	}
	ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(rawSignature))
	if err != nil {
		return nil, err
	}
	// TODO(alcutter): Verify signature
	sth.TreeHeadSignature = *ds
	return
}

// GetEntries attempts to retrieve the entries in the sequence [|start|, |end|] from the CT
// log server. (see section 4.6.)
// Returns a slice of LeafInputs or a non-nil error.
func (c *LogClient) GetEntries(start, end int64) ([]ct.LogEntry, error) {
	if end < 0 {
		return nil, errors.New("end should be >= 0")
	}
	if end < start {
		return nil, errors.New("start should be <= end")
	}
	var resp getEntriesResponse
	err := c.fetchAndParse(fmt.Sprintf("%s%s?start=%d&end=%d", c.uri, GetEntriesPath, start, end), &resp)
	if err != nil {
		return nil, err
	}
	entries := make([]ct.LogEntry, len(resp.Entries))
	for index, entry := range resp.Entries {
		leafBytes, err := base64.StdEncoding.DecodeString(entry.LeafInput)
		leaf, err := ct.ReadMerkleTreeLeaf(bytes.NewBuffer(leafBytes))
		if err != nil {
			return nil, err
		}
		entries[index].Leaf = *leaf
		chainBytes, err := base64.StdEncoding.DecodeString(entry.ExtraData)

		var chain []ct.ASN1Cert
		switch leaf.TimestampedEntry.EntryType {
		case ct.X509LogEntryType:
			chain, err = ct.UnmarshalX509ChainArray(chainBytes)

		case ct.PrecertLogEntryType:
			chain, err = ct.UnmarshalPrecertChainArray(chainBytes)

		default:
			return nil, fmt.Errorf("saw unknown entry type: %v", leaf.TimestampedEntry.EntryType)
		}
		if err != nil {
			return nil, err
		}
		entries[index].Chain = chain
		entries[index].Index = start + int64(index)
	}
	return entries, nil
}
<|endoftext|>