<commit_before>// client
package main

// TODO: for encryption, make sure use Message Authentication Code
// Alternatively, you can apply your own message authentication, as
// follows. First, encrypt the message using an appropriate
// symmetric-key encryption scheme (e.g., AES-CBC). Then, take the
// entire ciphertext (including any IVs, nonces, or other values
// needed for decryption), apply a message authentication code (e.g.,
// AES-CMAC, SHA1-HMAC, SHA256-HMAC), and append the resulting MAC
// digest to the ciphertext before transmission. On the receiving
// side, check that the MAC digest is valid before decrypting. This is
// known as the encrypt-then-authenticate construction. (See also: 1,
// 2.) This also works fine, but requires a little more care from you.

import (
	"bytes"
	"crypto/rc4"
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"os"
	"strings"
	"time"
)

const (
	RC4_TRASH_BYTES = 256
)

////////////////////////////////////////
// types
////////////////////////////////////////

// client download
//
// NOTE: seems like N2R is wasted effort when most resources not
// served by favorite remove
//
// send N2R to favorite remove to resolve
// if code == 200
//   done
// if code == 404
//   find_and_download(urn)

// func find_and_download(urn)
//   send N2Ls to favorite remove to resolve
//   if code == 404
//     resource does not exist
//   if code == 303
//     for _,url := range urls {
//       send GET to url
//       if err == nil {
//         if cHash fails validation {
//           optionally send bad cHash message to server
//           this message signed by client and sig ver by server to prevent DOS
//           SERVER may in remove resource if hash invalid,
//           which must be protected against DOS attack
//         }
//         break
//       }
//     }
//     if content == nill {
//       still could not find it
//     }

////////////////////////////////////////
// encryption / decryption
////////////////////////////////////////

func selectIV(algorithm, hName string, blob []byte) (iv []byte, err error) {
	var ivSize int
	var hash string

	switch {
	case strings.HasSuffix(algorithm, "128"):
		ivSize = 16
	case strings.HasSuffix(algorithm, "192"):
		ivSize = 24
	case strings.HasSuffix(algorithm, "256"):
		ivSize = 32
	case algorithm == "rc4":
		return
	default:
		return nil, fmt.Errorf("unknown encryption algorithm: " + algorithm)
	}

	size := fmt.Sprintf("%d", len(blob))
	hash, err = computeHash(hName, []byte(size))
	if err != nil {
		return
	}
	iv = make([]byte, ivSize)
	copy(iv, []byte(hash))
	return
}

func encrypt(blob []byte, algorithm, key string, iv []byte) ([]byte, error) {
	switch {
	case algorithm == "-":
		return blob, nil
		// case strings.HasPrefix(algorithm, "aes"):
		// 	// FIXME: key must be 16, 24, or 32 bytes for AES-128, -192, or -256
		// 	c,err = aes.NewCipher([]byte(key))
		// 	if err != nil {
		// 		return nil, err
		// 	}
		// 	eblob = make([]byte, len(blob)) // padding?
		// 	// TODO
		// 	return eblob, nil
	case strings.HasPrefix(algorithm, "rc4"):
		return encryptRC4(blob, key)
	}
	return nil, fmt.Errorf("unknown encryption algorithm: %s", algorithm)
}

func encryptRC4(blob []byte, key string) ([]byte, error) {
	// key must be between 1 and 256 bytes
	rc4Key := make([]byte, 256) // ???
	copy(rc4Key, []byte(key))   // ???
	c, err := rc4.NewCipher(rc4Key)
	if err != nil {
		return nil, err
	}
	// throw away at least 256 bytes of data
	junk := make([]byte, RC4_TRASH_BYTES)
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	for i := 0; i < len(junk); i++ {
		junk[i] = byte(r.Intn(256))
	}
	c.XORKeyStream(junk, junk)
	// actual encryption
	c.XORKeyStream(blob, blob)
	c.Reset()
	return blob, nil
}

func decrypt(blob []byte, algorithm, key string, iv []byte) ([]byte, error) {
	switch {
	case algorithm == "-":
		return blob, nil
	case strings.HasPrefix(algorithm, "rc4"):
		return encryptRC4(blob, key)
	}
	return nil, fmt.Errorf("unknown encryption algorithm: " + algorithm)
}

////////////////////////////////////////
// upload / download
////////////////////////////////////////

func doUpload(pathname string, meta *metadata, client *http.Client, rem *remote) {
	err := upload(pathname, meta, client, rem)
	if err != nil {
		log.Fatal(err)
	}
}

func upload(pathname string, meta *metadata, client *http.Client, rem *remote) (err error) {
	fi, err := os.Stat(pathname)
	if err != nil {
		return
	}
	plainBytes, err := ioutil.ReadFile(pathname)
	if err != nil {
		return
	}
	meta.pHash, err = computeHash(meta.hName, plainBytes)
	if err != nil {
		return
	}
	iv, err := selectIV(meta.eName, meta.hName, plainBytes)
	if err != nil {
		return
	}
	cipherBytes, err := encrypt(plainBytes, meta.eName, meta.pHash, iv)
	if err != nil {
		return
	}
	meta.cHash, err = computeHash(meta.hName, cipherBytes)
	if err != nil {
		return
	}
	url := urlFromRemoteAndResource(rem, meta.cHash)
	log.Print("PUT: " + url)
	reader := bytes.NewReader(cipherBytes)
	req, err := http.NewRequest("PUT", url, reader)
	if err != nil {
		return
	}
	req.Header = http.Header{
		"X-Amber-Hash":       {meta.hName},
		"X-Amber-Encryption": {meta.eName},
	}
	req.ContentLength = fi.Size()
	// PUT
	resp, err := client.Do(req)
	if err != nil {
		return
	}
	out, err := ioutil.ReadAll(resp.Body)
	defer resp.Body.Close()
	if err != nil {
		return
	}
	log.Printf("pHash: %s\n", meta.pHash)
	log.Printf("upload response:\n%v", string(out))
	return
}

func resourceFromUrl(url string) (cHash string) {
	i := strings.LastIndex(url, "/")
	if i == -1 {
		return ""
	}
	return url[i+1:]
}

func doDownload(rem remote, urn, pathname, pHash string) {
	var meta metadata
	var err error
	var resource string

	i := strings.LastIndex(urn, ":")
	switch {
	case i == -1:
		err = fmt.Errorf("cannot find colon: %v", urn)
		return
	default:
		resource = urn[i+1:]
	}

	urls, err := resolveUrls(urn, rem)
	if err != nil {
		log.Fatal(err)
	}

	meta, cipherBytes, err := downloadResourceFromUrls(urls)
	if err != nil {
		log.Fatal(err)
	}
	if _, err := checkHash(meta.hName, cipherBytes, resource); err != nil {
		log.Fatal(err)
	}

	iv, err := selectIV(meta.eName, meta.hName, cipherBytes)
	if err != nil {
		return
	}
	plainBytes, err := decrypt(cipherBytes, meta.eName, pHash, iv)
	if err != nil {
		log.Fatal(err)
	}
	if _, err := checkHash(meta.hName, plainBytes, pHash); err != nil {
		log.Fatal(err)
	}

	if err = writeFile(pathname, plainBytes); err != nil {
		log.Fatal(err)
	}
}

func downloadResourceFromUrls(urls []string) (meta metadata, blob []byte, err error) {
	var last_err error
	for _, url := range urls {
		meta, blob, err = downloadResource(url)
		if err == nil {
			return
		}
		log.Println(err)
		last_err = err
	}
	err = last_err
	return
}

func downloadResource(url string) (meta metadata, blob []byte, err error) {
	log.Printf("downloadResource: %s", url)
	resp, err := http.Get(url)
	if err != nil {
		return
	}
	defer resp.Body.Close()
	meta.eName, err = mustLookupHeader(resp.Header, "X-Amber-Encryption")
	if err != nil {
		meta.eName = "-"
		err = nil
	}
	meta.hName, err = mustLookupHeader(resp.Header, "X-Amber-Hash")
	if err != nil {
		return
	}
	blob, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}
	return
}

func resolveUrls(urn string, rem remote) (urls []string, err error) {
	query := fmt.Sprintf("http://%s:%d/N2Ls?%s", rem.hostname, rem.port, urn)
	log.Printf("resolveUrls: %s", query)
	resp, err := http.Get(query)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	// check for 303 or other status code?

	bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}
	return parseUriList(string(bytes)), nil
}

func parseUriList(blob string) (urls []string) {
	lines := strings.Split(blob, crlf)
	for _, line := range lines {
		if len(line) > 0 {
			i := strings.IndexRune(line, '#')
			switch {
			case i == 0: // comment
				continue
			case i == -1: // not found; append entire line
				urls = append(urls, line)
			default: // skip comment portion of line
				urls = append(urls, line[:i])
			}
		}
	}
	return
}

func resolveMeta(urn string, rem remote) (meta metadata, err error) {
	query := fmt.Sprintf("http://%s:%d/N2C?%s", rem.hostname, rem.port, urn)
	log.Printf("resolveMeta: %s", query)
	resp, err := http.Get(query)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	// check for 303 or other status code?

	bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}
	meta, err = parseUrc(bytes)
	if err != nil {
		return
	}
	i := strings.LastIndex(urn, ":")
	if i == -1 {
		err = fmt.Errorf("cannot find colon: %s", urn)
		return
	}
	meta.cHash = urn[i+1:]
	return
}

func parseUrc(blob []byte) (meta metadata, err error) {
	lines := strings.Split(string(blob), crlf)
	for _, line := range lines {
		if line == "" {
			break
		}
		fields := strings.Fields(line)
		if len(fields) != 2 {
			err = fmt.Errorf("invalid line format: %s", line)
			return
		}
		// trim colon from right side of key string
		key := fields[0][:len(fields[0])-1]
		switch {
		case key == "X-Amber-Hash":
			meta.hName = fields[1]
		case key == "X-Amber-Encryption":
			meta.eName = fields[1]
		case key == "Content-Length":
			meta.size = fields[1]
		}
	}
	return
}
<commit_msg>moved cHash check to download loop, so a checkHash failure on a downloaded blob will cause the downloader to attempt to download from the next uri<commit_after>// client
package main

// TODO: for encryption, make sure use Message Authentication Code
// Alternatively, you can apply your own message authentication, as
// follows. First, encrypt the message using an appropriate
// symmetric-key encryption scheme (e.g., AES-CBC). Then, take the
// entire ciphertext (including any IVs, nonces, or other values
// needed for decryption), apply a message authentication code (e.g.,
// AES-CMAC, SHA1-HMAC, SHA256-HMAC), and append the resulting MAC
// digest to the ciphertext before transmission. On the receiving
// side, check that the MAC digest is valid before decrypting. This is
// known as the encrypt-then-authenticate construction. (See also: 1,
// 2.) This also works fine, but requires a little more care from you.

import (
	"bytes"
	"crypto/rc4"
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"os"
	"strings"
	"time"
)

const (
	RC4_TRASH_BYTES = 256
)

////////////////////////////////////////
// types
////////////////////////////////////////

// client download
//
// NOTE: seems like N2R is wasted effort when most resources not
// served by favorite remove
//
// send N2R to favorite remove to resolve
// if code == 200
//   done
// if code == 404
//   find_and_download(urn)

// func find_and_download(urn)
//   send N2Ls to favorite remove to resolve
//   if code == 404
//     resource does not exist
//   if code == 303
//     for _,url := range urls {
//       send GET to url
//       if err == nil {
//         if cHash fails validation {
//           optionally send bad cHash message to server
//           this message signed by client and sig ver by server to prevent DOS
//           SERVER may in remove resource if hash invalid,
//           which must be protected against DOS attack
//         }
//         break
//       }
//     }
//     if content == nill {
//       still could not find it
//     }

////////////////////////////////////////
// encryption / decryption
////////////////////////////////////////

func selectIV(algorithm, hName string, blob []byte) (iv []byte, err error) {
	var ivSize int
	var hash string

	switch {
	case strings.HasSuffix(algorithm, "128"):
		ivSize = 16
	case strings.HasSuffix(algorithm, "192"):
		ivSize = 24
	case strings.HasSuffix(algorithm, "256"):
		ivSize = 32
	case algorithm == "rc4":
		return
	default:
		return nil, fmt.Errorf("unknown encryption algorithm: " + algorithm)
	}

	size := fmt.Sprintf("%d", len(blob))
	hash, err = computeHash(hName, []byte(size))
	if err != nil {
		return
	}
	iv = make([]byte, ivSize)
	copy(iv, []byte(hash))
	return
}

func encrypt(blob []byte, algorithm, key string, iv []byte) ([]byte, error) {
	switch {
	case algorithm == "-":
		return blob, nil
		// case strings.HasPrefix(algorithm, "aes"):
		// 	// FIXME: key must be 16, 24, or 32 bytes for AES-128, -192, or -256
		// 	c,err = aes.NewCipher([]byte(key))
		// 	if err != nil {
		// 		return nil, err
		// 	}
		// 	eblob = make([]byte, len(blob)) // padding?
		// 	// TODO
		// 	return eblob, nil
	case strings.HasPrefix(algorithm, "rc4"):
		return encryptRC4(blob, key)
	}
	return nil, fmt.Errorf("unknown encryption algorithm: %s", algorithm)
}

func encryptRC4(blob []byte, key string) ([]byte, error) {
	// key must be between 1 and 256 bytes
	rc4Key := make([]byte, 256) // ???
	copy(rc4Key, []byte(key))   // ???
	c, err := rc4.NewCipher(rc4Key)
	if err != nil {
		return nil, err
	}
	// throw away at least 256 bytes of data
	junk := make([]byte, RC4_TRASH_BYTES)
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	for i := 0; i < len(junk); i++ {
		junk[i] = byte(r.Intn(256))
	}
	c.XORKeyStream(junk, junk)
	// actual encryption
	c.XORKeyStream(blob, blob)
	c.Reset()
	return blob, nil
}

func decrypt(blob []byte, algorithm, key string, iv []byte) ([]byte, error) {
	switch {
	case algorithm == "-":
		return blob, nil
	case strings.HasPrefix(algorithm, "rc4"):
		return encryptRC4(blob, key)
	}
	return nil, fmt.Errorf("unknown encryption algorithm: " + algorithm)
}

////////////////////////////////////////
// upload / download
////////////////////////////////////////

func doUpload(pathname string, meta *metadata, client *http.Client, rem *remote) {
	err := upload(pathname, meta, client, rem)
	if err != nil {
		log.Fatal(err)
	}
}

func upload(pathname string, meta *metadata, client *http.Client, rem *remote) (err error) {
	fi, err := os.Stat(pathname)
	if err != nil {
		return
	}
	plainBytes, err := ioutil.ReadFile(pathname)
	if err != nil {
		return
	}
	meta.pHash, err = computeHash(meta.hName, plainBytes)
	if err != nil {
		return
	}
	iv, err := selectIV(meta.eName, meta.hName, plainBytes)
	if err != nil {
		return
	}
	cipherBytes, err := encrypt(plainBytes, meta.eName, meta.pHash, iv)
	if err != nil {
		return
	}
	meta.cHash, err = computeHash(meta.hName, cipherBytes)
	if err != nil {
		return
	}
	url := urlFromRemoteAndResource(rem, meta.cHash)
	log.Print("PUT: " + url)
	reader := bytes.NewReader(cipherBytes)
	req, err := http.NewRequest("PUT", url, reader)
	if err != nil {
		return
	}
	req.Header = http.Header{
		"X-Amber-Hash":       {meta.hName},
		"X-Amber-Encryption": {meta.eName},
	}
	req.ContentLength = fi.Size()
	// PUT
	resp, err := client.Do(req)
	if err != nil {
		return
	}
	out, err := ioutil.ReadAll(resp.Body)
	defer resp.Body.Close()
	if err != nil {
		return
	}
	log.Printf("pHash: %s\n", meta.pHash)
	log.Printf("upload response:\n%v", string(out))
	return
}

func resourceFromUrl(url string) (cHash string) {
	i := strings.LastIndex(url, "/")
	if i == -1 {
		return ""
	}
	return url[i+1:]
}

func doDownload(rem remote, urn, pathname, pHash string) {
	var meta metadata
	var err error

	i := strings.LastIndex(urn, ":")
	if i == -1 {
		err = fmt.Errorf("cannot find colon: %v", urn)
		return
	}
	resource := urn[i+1:]

	urls, err := resolveUrls(urn, rem)
	if err != nil {
		log.Fatal(err)
	}

	meta, cipherBytes, err := downloadResourceFromUrls(urls, resource)
	if err != nil {
		log.Fatal(err)
	}

	iv, err := selectIV(meta.eName, meta.hName, cipherBytes)
	if err != nil {
		return
	}
	plainBytes, err := decrypt(cipherBytes, meta.eName, pHash, iv)
	if err != nil {
		log.Fatal(err)
	}
	if _, err := checkHash(meta.hName, plainBytes, pHash); err != nil {
		log.Fatal(err)
	}

	if err = writeFile(pathname, plainBytes); err != nil {
		log.Fatal(err)
	}
}

func downloadResourceFromUrls(urls []string, cHash string) (meta metadata, blob []byte, err error) {
	var last_err error
	for _, url := range urls {
		meta, blob, err = downloadResource(url, cHash)
		if err == nil {
			return
		}
		log.Println(err)
		last_err = err
	}
	err = last_err
	return
}

func downloadResource(url, cHash string) (meta metadata, blob []byte, err error) {
	log.Printf("downloadResource: %s", url)
	resp, err := http.Get(url)
	if err != nil {
		return
	}
	defer resp.Body.Close()
	meta.cHash = cHash
	meta.hName, err = mustLookupHeader(resp.Header, "X-Amber-Hash")
	if err != nil {
		return
	}
	meta.eName, err = mustLookupHeader(resp.Header, "X-Amber-Encryption")
	if err != nil {
		meta.eName = "-"
		err = nil
	}
	blob, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}
	if _, err = checkHash(meta.hName, blob, meta.cHash); err != nil {
		return
	}
	return
}

func resolveUrls(urn string, rem remote) (urls []string, err error) {
	query := fmt.Sprintf("http://%s:%d/N2Ls?%s", rem.hostname, rem.port, urn)
	log.Printf("resolveUrls: %s", query)
	resp, err := http.Get(query)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	// check for 303 or other status code?

	bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}
	return parseUriList(string(bytes)), nil
}

func parseUriList(blob string) (urls []string) {
	lines := strings.Split(blob, crlf)
	for _, line := range lines {
		if len(line) > 0 {
			i := strings.IndexRune(line, '#')
			switch {
			case i == 0: // comment
				continue
			case i == -1: // not found; append entire line
				urls = append(urls, line)
			default: // skip comment portion of line
				urls = append(urls, line[:i])
			}
		}
	}
	return
}

func resolveMeta(urn string, rem remote) (meta metadata, err error) {
	query := fmt.Sprintf("http://%s:%d/N2C?%s", rem.hostname, rem.port, urn)
	log.Printf("resolveMeta: %s", query)
	resp, err := http.Get(query)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	// check for 303 or other status code?

	bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}
	meta, err = parseUrc(bytes)
	if err != nil {
		return
	}
	i := strings.LastIndex(urn, ":")
	if i == -1 {
		err = fmt.Errorf("cannot find colon: %s", urn)
		return
	}
	meta.cHash = urn[i+1:]
	return
}

func parseUrc(blob []byte) (meta metadata, err error) {
	lines := strings.Split(string(blob), crlf)
	for _, line := range lines {
		if line == "" {
			break
		}
		fields := strings.Fields(line)
		if len(fields) != 2 {
			err = fmt.Errorf("invalid line format: %s", line)
			return
		}
		// trim colon from right side of key string
		key := fields[0][:len(fields[0])-1]
		switch {
		case key == "X-Amber-Hash":
			meta.hName = fields[1]
		case key == "X-Amber-Encryption":
			meta.eName = fields[1]
		case key == "Content-Length":
			meta.size = fields[1]
		}
	}
	return
}
<|endoftext|>


<commit_before>package handler

import (
	"github.com/charles-l/gitamite"
	"github.com/charles-l/gitamite/server/context"
	"github.com/libgit2/git2go"

	"github.com/labstack/echo"

	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
)

// TODO: Move to library
func readAuthJSONRequest(c echo.Context) (*gitamite.AuthRequest, error) {
	blob, err := ioutil.ReadAll(c.Request().Body)
	if err != nil {
		return nil, err
	}

	var a gitamite.AuthRequest
	err = json.Unmarshal(blob, &a)
	if err != nil {
		return nil, err
	}

	if len(a.Signature) == 0 || a.Data == nil {
		return nil, fmt.Errorf("Need data and signature")
	}

	if err := a.VerifyRequest(); err != nil {
		return nil, fmt.Errorf("Invalid signature")
	}
	return &a, nil
}

func exists(filepath string) bool {
	if stat, err := os.Stat(filepath); err == nil && stat.IsDir() {
		return true
	}
	return false
}

func DeleteRepo(c echo.Context) error {
	a, err := readAuthJSONRequest(c)
	if err != nil {
		return err
	}

	name, ok := a.Data.(map[string]interface{})["Name"].(string)
	name = path.Clean(name) // sanatize

	if !ok {
		return fmt.Errorf("need a valid repo name")
	}

	repoPath := path.Join(gitamite.GlobalConfig.RepoDir, name)
	if path.Dir(repoPath) == "/" || repoPath == "/" {
		log.Fatal("cowardly bailing out 'cause I don't want to accidentally delete something important: " + repoPath)
	}
	if !exists(repoPath) {
		return fmt.Errorf("repo doesn't exist")
	}
	log.Printf("deleting repo %s", repoPath)
	os.RemoveAll(repoPath)
	delete(c.(*server.Context).Repos, name)
	return nil
}

func CreateRepo(c echo.Context) error {
	a, err := readAuthJSONRequest(c)
	if err != nil {
		return err
	}

	name, ok := a.Data.(map[string]interface{})["Name"].(string)
	name = path.Clean(name) // sanatize

	if !ok {
		return fmt.Errorf("need a valid repo name")
	}

	newRepoPath := path.Join(gitamite.GlobalConfig.RepoDir, name)
	log.Printf("creating new repo: %s", newRepoPath)

	repo, err := git.InitRepository(newRepoPath, true)
	if err != nil {
		return err
	}
	c.(*server.Context).Repos[name] = &gitamite.Repo{name, newRepoPath, "", repo}
	return nil
}
<commit_msg>more error handling<commit_after>package handler

import (
	"github.com/charles-l/gitamite"
	"github.com/charles-l/gitamite/server/context"
	"github.com/libgit2/git2go"

	"github.com/labstack/echo"

	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
)

// TODO: Move to library
func readAuthJSONRequest(c echo.Context) (*gitamite.AuthRequest, error) {
	blob, err := ioutil.ReadAll(c.Request().Body)
	if err != nil {
		return nil, err
	}

	var a gitamite.AuthRequest
	err = json.Unmarshal(blob, &a)
	if err != nil {
		return nil, err
	}

	if len(a.Signature) == 0 || a.Data == nil {
		return nil, fmt.Errorf("Need data and signature")
	}

	if err := a.VerifyRequest(); err != nil {
		return nil, fmt.Errorf("Invalid signature")
	}
	return &a, nil
}

func exists(filepath string) bool {
	if stat, err := os.Stat(filepath); err == nil && stat.IsDir() {
		return true
	}
	return false
}

func DeleteRepo(c echo.Context) error {
	a, err := readAuthJSONRequest(c)
	if err != nil {
		return err
	}

	name, ok := a.Data.(map[string]interface{})["Name"].(string)
	name = path.Clean(name) // sanatize

	if !ok {
		return fmt.Errorf("need a valid repo name")
	}

	repoPath := path.Join(gitamite.GlobalConfig.RepoDir, name)
	if path.Dir(repoPath) == "/" || repoPath == "/" {
		log.Fatal("cowardly bailing out 'cause I don't want to accidentally delete something important: " + repoPath)
	}
	if !exists(repoPath) {
		return fmt.Errorf("repo doesn't exist")
	}
	log.Printf("deleting repo %s", repoPath)
	os.RemoveAll(repoPath)
	delete(c.(*server.Context).Repos, name)
	return nil
}

func CreateRepo(c echo.Context) error {
	a, err := readAuthJSONRequest(c)
	if err != nil {
		return err
	}

	name, ok := a.Data.(map[string]interface{})["Name"].(string)
	name = path.Clean(name) // sanatize

	if !ok {
		return fmt.Errorf("need a valid repo name")
	}

	newRepoPath := path.Join(gitamite.GlobalConfig.RepoDir, name)
	if exists(newRepoPath) {
		return fmt.Errorf("repo already exists")
	}

	log.Printf("creating new repo: %s", newRepoPath)

	repo, err := git.InitRepository(newRepoPath, true)
	if err != nil {
		return err
	}
	c.(*server.Context).Repos[name] = &gitamite.Repo{name, newRepoPath, "", repo}
	return nil
}
<|endoftext|>


<commit_before>package twitch2go

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"

	"golang.org/x/net/context/ctxhttp"

	cleanhttp "github.com/hashicorp/go-cleanhttp"
	goon "github.com/shurcooL/go-goon"
)

type Client struct {
	ClientID   string
	HTTPClient *http.Client
	apiURL     *url.URL
}

type doOptions struct {
	data      interface{}
	forceJSON bool
	headers   map[string]string
	context   context.Context
}

var (
	apiURL = "https://api.twitch.tv/kraken"
)

// Error represents failures in the API. It represents a failure from the API.
type Error struct {
	Status  int
	Message string
}

func newError(resp *http.Response) *Error {
	defer resp.Body.Close()
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return &Error{Status: resp.StatusCode, Message: fmt.Sprintf("cannot read body, err: %v", err)}
	}
	return &Error{Status: resp.StatusCode, Message: string(data)}
}

func (e *Error) Error() string {
	return fmt.Sprintf("API error (%d): %s", e.Status, e.Message)
}

func NewClient(ClientID string) *Client {
	url, err := url.Parse(apiURL)
	if err != nil {
		log.Fatal(err)
	}
	return &Client{
		ClientID:   ClientID,
		apiURL:     url,
		HTTPClient: cleanhttp.DefaultClient(),
	}
}

// if error in context, return that instead of generic http error
func chooseError(ctx context.Context, err error) error {
	select {
	case <-ctx.Done():
		return ctx.Err()
	default:
		return err
	}
}

func (c *Client) do(method, path string, doOptions doOptions) (*http.Response, error) {
	var params io.Reader
	if doOptions.data != nil || doOptions.forceJSON {
		buf, err := json.Marshal(doOptions.data)
		if err != nil {
			return nil, err
		}
		params = bytes.NewBuffer(buf)
	}
	httpClient := c.HTTPClient
	url, err := c.apiURL.Parse(path)
	if err != nil {
		return nil, err
	}
	u := url.String()
	req, err := http.NewRequest(method, u, params)
	if err != nil {
		return nil, err
	}
	req.Header.Set("accept", "application/vnd.twitchtv.v3+json")
	req.Header.Set("client-id", c.ClientID)
	for k, v := range doOptions.headers {
		req.Header.Set(k, v)
	}
	ctx := doOptions.context
	if ctx == nil {
		ctx = context.Background()
	}
	goon.Dump(req)
	resp, err := ctxhttp.Do(ctx, httpClient, req)
	if err != nil {
		return nil, chooseError(ctx, err)
	}
	if resp.StatusCode < 200 || resp.StatusCode >= 400 {
		return nil, newError(resp)
	}
	return resp, nil
}
<commit_msg>Fix API Path<commit_after>package twitch2go

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"path"

	"golang.org/x/net/context/ctxhttp"

	cleanhttp "github.com/hashicorp/go-cleanhttp"
)

type Client struct {
	ClientID   string
	HTTPClient *http.Client
	apiURL     *url.URL
}

type doOptions struct {
	data      interface{}
	forceJSON bool
	headers   map[string]string
	context   context.Context
}

var (
	apiURL  = "https://api.twitch.tv"
	apiPath = "kraken"
)

// Error represents failures in the API. It represents a failure from the API.
type Error struct {
	Status  int
	Message string
}

func newError(resp *http.Response) *Error {
	defer resp.Body.Close()
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return &Error{Status: resp.StatusCode, Message: fmt.Sprintf("cannot read body, err: %v", err)}
	}
	return &Error{Status: resp.StatusCode, Message: string(data)}
}

func (e *Error) Error() string {
	return fmt.Sprintf("API error (%d): %s", e.Status, e.Message)
}

func NewClient(ClientID string) *Client {
	url, err := url.Parse(apiURL)
	if err != nil {
		log.Fatal(err)
	}
	return &Client{
		ClientID:   ClientID,
		apiURL:     url,
		HTTPClient: cleanhttp.DefaultClient(),
	}
}

// if error in context, return that instead of generic http error
func chooseError(ctx context.Context, err error) error {
	select {
	case <-ctx.Done():
		return ctx.Err()
	default:
		return err
	}
}

func (c *Client) do(method, urlPath string, doOptions doOptions) (*http.Response, error) {
	var params io.Reader
	if doOptions.data != nil || doOptions.forceJSON {
		buf, err := json.Marshal(doOptions.data)
		if err != nil {
			return nil, err
		}
		params = bytes.NewBuffer(buf)
	}
	httpClient := c.HTTPClient
	p := path.Join(apiPath, urlPath)
	url, err := c.apiURL.Parse(p)
	u := url.String()
	req, err := http.NewRequest(method, u, params)
	if err != nil {
		return nil, err
	}
	req.Header.Set("accept", "application/vnd.twitchtv.v3+json")
	req.Header.Set("client-id", c.ClientID)
	for k, v := range doOptions.headers {
		req.Header.Set(k, v)
	}
	ctx := doOptions.context
	if ctx == nil {
		ctx = context.Background()
	}
	resp, err := ctxhttp.Do(ctx, httpClient, req)
	if err != nil {
		return nil, chooseError(ctx, err)
	}
	if resp.StatusCode < 200 || resp.StatusCode >= 400 {
		return nil, newError(resp)
	}
	return resp, nil
}
<|endoftext|>


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

package restkit

import (
	"bytes"
	"encoding/json"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	syspath "path"
)

type RestClient struct {
	baseURL *url.URL

	RequestBuilder func(method, path string, query url.Values, reqBody interface{}) (*http.Request, error)
	ResultParser   func(resp *http.Response, body []byte, result interface{}) error
	ErrorParser    func(resp *http.Response, body []byte) error
	ResponseParser func(resp *http.Response, result interface{}) error
}

func NewRestClient(baseURL *url.URL) *RestClient {
	c := &RestClient{
		baseURL: baseURL,
	}
	c.RequestBuilder = c.DefaultRequestBuilder
	c.ResultParser = c.DefaultResultParser
	c.ErrorParser = c.DefaultErrorParser
	c.ResponseParser = c.DefaultResponseParser
	return c
}

// Request executes a client request.
// method: GET|POST|PUT|DELETE|HEAD
// path: Path relative to the path of the baseURL
// query: Query string (can be nil)
// reqBody: Object to marshal into the request body
// result: Reference to object to unmarshal the response into
func (c *RestClient) Request(method, path string, query url.Values, reqBody interface{}, result interface{}) error {
	req, err := c.RequestBuilder(method, path, query, reqBody)
	if err != nil {
		return maskAny(err)
	}
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return maskAny(err)
	}

	if err := c.ResponseParser(resp, result); err != nil {
		return maskAny(err)
	}
	return nil
}

// DefaultRequestBuilder implements the default RequestBuilder behavior.
func (c *RestClient) DefaultRequestBuilder(method, path string, query url.Values, reqBody interface{}) (*http.Request, error) {
	url := *c.baseURL
	url.Path = syspath.Join(url.Path, path)
	if query != nil {
		url.RawQuery = query.Encode()
	}

	var reqReader io.Reader
	var contentType string
	if reqBody != nil {
		content, err := json.Marshal(reqBody)
		if err != nil {
			return nil, maskAny(err)
		}
		reqReader = bytes.NewBuffer(content)
		contentType = "application/json"
	}

	req, err := http.NewRequest(method, url.String(), reqReader)
	if err != nil {
		return nil, maskAny(err)
	}
	if contentType != "" {
		req.Header.Set("Content-Type", contentType)
	}
	return req, nil
}

// DefaultResponseParser implements the default ResponseParser behavior.
// It reads the response body.
// Then if the status == OK, it tries to parse it into the result.
// Otherwise if the body is not empty, it tries to parse it into an ErrorResponse.
func (c *RestClient) DefaultResponseParser(resp *http.Response, result interface{}) error {
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return maskAny(err)
	}

	if resp.StatusCode == http.StatusOK {
		if err := c.ResultParser(resp, body, result); err != nil {
			return maskAny(err)
		}
		return nil
	}

	if err := c.ErrorParser(resp, body); err != nil {
		return maskAny(err)
	}
	return nil
}

// DefaultErrorParser tries to parse the given response body into a the given result object.
func (c *RestClient) DefaultResultParser(resp *http.Response, body []byte, result interface{}) error {
	if result != nil {
		if err := json.Unmarshal(body, result); err != nil {
			return maskAny(err)
		}
	}
	return nil
}

// DefaultErrorParser tries to parse the given response body into an ErrorResponse.
func (c *RestClient) DefaultErrorParser(resp *http.Response, body []byte) error {
	var er ErrorResponse
	if len(body) > 0 {
		if err := json.Unmarshal(body, &er); err != nil {
			return maskAny(err)
		}
	} else {
		er.TheError.Message = resp.Status
	}
	er.statusCode = resp.StatusCode

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

package restkit

import (
	"bytes"
	"encoding/json"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	syspath "path"
	"strconv"
)

type RestClient struct {
	baseURL *url.URL

	RequestBuilder func(method, path string, query url.Values, reqBody interface{}) (*http.Request, error)
	ResultParser   func(resp *http.Response, body []byte, result interface{}) error
	ErrorParser    func(resp *http.Response, body []byte) error
	ResponseParser func(resp *http.Response, result interface{}) error
}

func NewRestClient(baseURL *url.URL) *RestClient {
	c := &RestClient{
		baseURL: baseURL,
	}
	c.RequestBuilder = c.DefaultRequestBuilder
	c.ResultParser = c.DefaultResultParser
	c.ErrorParser = c.DefaultErrorParser
	c.ResponseParser = c.DefaultResponseParser
	return c
}

// Request executes a client request.
// method: GET|POST|PUT|DELETE|HEAD
// path: Path relative to the path of the baseURL
// query: Query string (can be nil)
// reqBody: Object to marshal into the request body
// result: Reference to object to unmarshal the response into
func (c *RestClient) Request(method, path string, query url.Values, reqBody interface{}, result interface{}) error {
	req, err := c.RequestBuilder(method, path, query, reqBody)
	if err != nil {
		return maskAny(err)
	}
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return maskAny(err)
	}

	if err := c.ResponseParser(resp, result); err != nil {
		return maskAny(err)
	}
	return nil
}

// DefaultRequestBuilder implements the default RequestBuilder behavior.
func (c *RestClient) DefaultRequestBuilder(method, path string, query url.Values, reqBody interface{}) (*http.Request, error) {
	url := *c.baseURL
	url.Path = syspath.Join(url.Path, path)
	if query != nil {
		url.RawQuery = query.Encode()
	}

	var reqReader io.Reader
	var contentType string
	var contentLength string
	if reqBody != nil {
		content, err := json.Marshal(reqBody)
		if err != nil {
			return nil, maskAny(err)
		}
		reqReader = bytes.NewBuffer(content)
		contentType = "application/json"
		contentLength = strconv.Itoa(len(content))
	}

	req, err := http.NewRequest(method, url.String(), reqReader)
	if err != nil {
		return nil, maskAny(err)
	}
	if contentType != "" {
		req.Header.Set("Content-Type", contentType)
	}
	if contentLength != "" {
		req.Header.Set("Content-Length", contentLength)
	}
	return req, nil
}

// DefaultResponseParser implements the default ResponseParser behavior.
// It reads the response body.
// Then if the status == OK, it tries to parse it into the result.
// Otherwise if the body is not empty, it tries to parse it into an ErrorResponse.
func (c *RestClient) DefaultResponseParser(resp *http.Response, result interface{}) error {
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return maskAny(err)
	}

	if resp.StatusCode == http.StatusOK {
		if err := c.ResultParser(resp, body, result); err != nil {
			return maskAny(err)
		}
		return nil
	}

	if err := c.ErrorParser(resp, body); err != nil {
		return maskAny(err)
	}
	return nil
}

// DefaultErrorParser tries to parse the given response body into a the given result object.
func (c *RestClient) DefaultResultParser(resp *http.Response, body []byte, result interface{}) error {
	if result != nil {
		if err := json.Unmarshal(body, result); err != nil {
			return maskAny(err)
		}
	}
	return nil
}

// DefaultErrorParser tries to parse the given response body into an ErrorResponse.
func (c *RestClient) DefaultErrorParser(resp *http.Response, body []byte) error {
	var er ErrorResponse
	if len(body) > 0 {
		if err := json.Unmarshal(body, &er); err != nil {
			return maskAny(err)
		}
	} else {
		er.TheError.Message = resp.Status
	}
	er.statusCode = resp.StatusCode

	return maskAny(&er)
}
<|endoftext|>


<commit_before>package roster

import (
	"time"
	"errors"
	"math/rand"
	"net"
	"os"
	"strings"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/dynamodb"
	"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
)

const (
	HeartbeatInterval = 1000 * time.Millisecond
	ServiceTTL = 5
)

var (
	ErrServiceNotFound = errors.New("roster: No matching service found")
	ErrLocalIpNotFound = errors.New("roster: No non loopback local IP address could be found")
)

type Service struct {
	Name string
	Endpoint string
	Expiry int64
	stopHeartbeat chan bool
}

// Unregister the service
func (s *Service) Unregister() {
	s.stopHeartbeat <- true

	// Pause (block) for connections to drain
	time.Sleep(ServiceTTL * time.Second)
}

type ClientConfig struct {
	RegistryName string
	Region string
	Endpoint string
}

func (cc ClientConfig) GetRegistryName() *string {
	if cc.RegistryName != "" {
		return aws.String(cc.RegistryName)
	} else {
		// Default
		return aws.String("roster")
	}
}

func (cc ClientConfig) GetConfig() *aws.Config {

	//Explicitley set
	region := cc.Region

	// Environment var
	if region == "" {
		region = os.Getenv("AWS_REGION")
	}

	// Default
	if region == "" {
		region = "us-west-2"
	}

	// Create AWS config
	config := aws.NewConfig().WithRegion(region)

	// Check to see if DynamoDB is running locally
	endpoint := cc.Endpoint
	if endpoint == "" {
		// Env variable must be this format for Docker Compose to work, and also must replace tcp with http
		endpoint = os.Getenv("DYNAMODB_PORT")
		endpoint = strings.Replace(endpoint, "tcp", "http", 1)
	}

	if endpoint == "" {
		return config
	} else {
		return config.WithEndpoint(endpoint)
	}

}

type Client struct {
	config ClientConfig
	svc *dynamodb.DynamoDB
	Registry *Registry

}

func NewClient(config ClientConfig) *Client {
	svc := dynamodb.New(session.New(), config.GetConfig())
	Registry := NewRegistry(svc,config.GetRegistryName())

	return &Client{svc: svc, config: config, Registry: Registry}
}

// Register the service in the registry
func (c *Client) Register(name string, endpoint string) (*Service,error) {

	// Check whether the registry has been previously created. If not create before registration.
	if exists,err := c.Registry.Exists(); err != nil {
		return nil,err
	} else if !exists {
		if err := c.Registry.Create(); err != nil {
			return nil,err
		}
	}

	// Create Service
	service := &Service{Name: name, Endpoint: endpoint, stopHeartbeat: make(chan bool)}

	// Heartbeat function - updates expiry
	heartbeat := func() {
		// Update service Expiry based on TTL and current time
		service.Expiry = time.Now().Unix() + ServiceTTL

		// Update service entry in registry
		if av, err := dynamodbattribute.ConvertToMap(*service); err != nil {
			return
		} else {
			_, err := c.svc.PutItem(&dynamodb.PutItemInput{
				Item: av,
				TableName: c.config.GetRegistryName(),
			})

			if err != nil {
				return
			}
		}
	}

	// Ensure call heartbeat at least once
	heartbeat()

	// Start goroutine to send heartbeat
	go func() {
	    for {
			select {
				case <- service.stopHeartbeat:
					return
		        default:
					// Pause for interval
					time.Sleep(HeartbeatInterval)

					// Call heartbeat function
					heartbeat()
		    }
	    }
	}()

	return service,nil
}

// Query the registry for named service
func (c *Client) Discover(name string) (*Service, error) {

	// Make sure registry is active
	if c.Registry.IsActive() {
		expressionAttributeValues := map[string]interface{}{
	    	":NameVal": name,
	    	":ExpiryVal":   time.Now().Unix(),
		}

		ean := map[string]*string{
	    	"#N": aws.String("Name"),
		}

		eav, err := dynamodbattribute.ConvertToMap(expressionAttributeValues)
		if err != nil {
			return nil,err
		}

		resp, err := c.svc.Query(&dynamodb.QueryInput{
			TableName: c.config.GetRegistryName(),
			KeyConditionExpression: aws.String("#N = :NameVal"),
			FilterExpression: aws.String("Expiry > :ExpiryVal"),
			ExpressionAttributeValues: eav,
			ExpressionAttributeNames: ean,
		})

		if err != nil {
			return nil, err
		}

		if len(resp.Items) > 0 {
			// Randomly select one of the available endpoints (in effect load balancing between available endpoints)
			service := Service{}
			err = dynamodbattribute.ConvertFromMap(resp.Items[rand.Intn(len(resp.Items))], &service)
			if err != nil {
				return nil, err
			}
			return &service, nil
		} else {
			// No service found
			return nil,ErrServiceNotFound
		}
	} else {
		return nil,ErrRegistryNotActive
	}
}

// Returns the non loopback local IP of the host the client is running on
func (c *Client) GetLocalIP() (string, error) {
    addrs, err := net.InterfaceAddrs()
    if err != nil {
        return "",err
    }
    for _, address := range addrs {
        // check the address type and if it is not a loopback the display it
        if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
            if ipnet.IP.To4() != nil {
                return ipnet.IP.String(),nil
            }
        }
    }
    return "",ErrLocalIpNotFound
}
<commit_msg>Return active bool<commit_after>package roster

import (
	"time"
	"errors"
	"math/rand"
	"net"
	"os"
	"strings"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/dynamodb"
	"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
)

const (
	HeartbeatInterval = 1000 * time.Millisecond
	ServiceTTL = 5
)

var (
	ErrServiceNotFound = errors.New("roster: No matching service found")
	ErrLocalIpNotFound = errors.New("roster: No non loopback local IP address could be found")
)

type Service struct {
	Name string
	Endpoint string
	Expiry int64
	stopHeartbeat chan bool
}

// Unregister the service
func (s *Service) Unregister() {
	s.stopHeartbeat <- true

	// Pause (block) for connections to drain
	time.Sleep(ServiceTTL * time.Second)
}

type ClientConfig struct {
	RegistryName string
	Region string
	Endpoint string
}

func (cc ClientConfig) GetRegistryName() *string {
	if cc.RegistryName != "" {
		return aws.String(cc.RegistryName)
	} else {
		// Default
		return aws.String("roster")
	}
}

func (cc ClientConfig) GetConfig() *aws.Config {

	//Explicitley set
	region := cc.Region

	// Environment var
	if region == "" {
		region = os.Getenv("AWS_REGION")
	}

	// Default
	if region == "" {
		region = "us-west-2"
	}

	// Create AWS config
	config := aws.NewConfig().WithRegion(region)

	// Check to see if DynamoDB is running locally
	endpoint := cc.Endpoint
	if endpoint == "" {
		// Env variable must be this format for Docker Compose to work, and also must replace tcp with http
		endpoint = os.Getenv("DYNAMODB_PORT")
		endpoint = strings.Replace(endpoint, "tcp", "http", 1)
	}

	if endpoint == "" {
		return config
	} else {
		return config.WithEndpoint(endpoint)
	}

}

type Client struct {
	config ClientConfig
	svc *dynamodb.DynamoDB
	Registry *Registry

}

func NewClient(config ClientConfig) *Client {
	svc := dynamodb.New(session.New(), config.GetConfig())
	Registry := NewRegistry(svc,config.GetRegistryName())

	return &Client{svc: svc, config: config, Registry: Registry}
}

// Register the service in the registry
func (c *Client) Register(name string, endpoint string) (*Service,error) {

	// Check whether the registry has been previously created. If not create before registration.
	if exists,err := c.Registry.Exists(); err != nil {
		return nil,err
	} else if !exists {
		if err := c.Registry.Create(); err != nil {
			return nil,err
		}
	}

	// Create Service
	service := &Service{Name: name, Endpoint: endpoint, stopHeartbeat: make(chan bool)}

	// Heartbeat function - updates expiry
	heartbeat := func() {
		// Update service Expiry based on TTL and current time
		service.Expiry = time.Now().Unix() + ServiceTTL

		// Update service entry in registry
		if av, err := dynamodbattribute.ConvertToMap(*service); err != nil {
			return
		} else {
			_, err := c.svc.PutItem(&dynamodb.PutItemInput{
				Item: av,
				TableName: c.config.GetRegistryName(),
			})

			if err != nil {
				return
			}
		}
	}

	// Ensure call heartbeat at least once
	heartbeat()

	// Start goroutine to send heartbeat
	go func() {
	    for {
			select {
				case <- service.stopHeartbeat:
					return
		        default:
					// Pause for interval
					time.Sleep(HeartbeatInterval)

					// Call heartbeat function
					heartbeat()
		    }
	    }
	}()

	return service,nil
}

// Query the registry for named service
func (c *Client) Discover(name string) (*Service, error) {

	// Make sure registry is active
	if active,err := c.Registry.IsActive(); active == true {
		expressionAttributeValues := map[string]interface{}{
	    	":NameVal": name,
	    	":ExpiryVal":   time.Now().Unix(),
		}

		ean := map[string]*string{
	    	"#N": aws.String("Name"),
		}

		eav, err := dynamodbattribute.ConvertToMap(expressionAttributeValues)
		if err != nil {
			return nil,err
		}

		resp, err := c.svc.Query(&dynamodb.QueryInput{
			TableName: c.config.GetRegistryName(),
			KeyConditionExpression: aws.String("#N = :NameVal"),
			FilterExpression: aws.String("Expiry > :ExpiryVal"),
			ExpressionAttributeValues: eav,
			ExpressionAttributeNames: ean,
		})

		if err != nil {
			return nil, err
		}

		if len(resp.Items) > 0 {
			// Randomly select one of the available endpoints (in effect load balancing between available endpoints)
			service := Service{}
			err = dynamodbattribute.ConvertFromMap(resp.Items[rand.Intn(len(resp.Items))], &service)
			if err != nil {
				return nil, err
			}
			return &service, nil
		} else {
			// No service found
			return nil,ErrServiceNotFound
		}
	} else {
		return nil,ErrRegistryNotActive
	}
}

// Returns the non loopback local IP of the host the client is running on
func (c *Client) GetLocalIP() (string, error) {
    addrs, err := net.InterfaceAddrs()
    if err != nil {
        return "",err
    }
    for _, address := range addrs {
        // check the address type and if it is not a loopback the display it
        if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
            if ipnet.IP.To4() != nil {
                return ipnet.IP.String(),nil
            }
        }
    }
    return "",ErrLocalIpNotFound
}
<|endoftext|>


<commit_before>package wsevent

import (
	"bytes"
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"net/http"
	"sync"
	"time"

	ws "github.com/gorilla/websocket"
)

//Client represents a server-side client
type Client struct {
	ID      string        //Session ID
	Request *http.Request //http Request when connection was upgraded

	writeMu *sync.Mutex
	conn    *ws.Conn
	server  *Server
}

type request struct {
	ID   string          `json:"id"`
	Data json.RawMessage `json:"data"`

	next *request
}

type reply struct {
	ID   string      `json:"id"`
	Data interface{} `json:"data"`

	next *reply
}

func genID(r *http.Request) string {
	buff := make([]byte, 5)
	rand.Read(buff)
	b := bytes.NewBuffer(buff)
	b.WriteString(r.RemoteAddr)

	return base64.URLEncoding.EncodeToString(b.Bytes())
}

func (s *Server) NewClientWithID(upgrader ws.Upgrader, w http.ResponseWriter, r *http.Request, id string) (*Client, error) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		return nil, err
	}

	client := &Client{
		ID:      id,
		Request: r,

		writeMu: new(sync.Mutex),
		conn:    conn,
		server:  s,
	}

	go client.listener(s)

	return client, nil
}

func (s *Server) NewClient(upgrader ws.Upgrader, w http.ResponseWriter, r *http.Request) (*Client, error) {
	return s.NewClientWithID(upgrader, w, r, genID(r))
}

//A thread-safe variant of WriteMessage
func (c *Client) Emit(data string) {
	c.writeMu.Lock()
	c.conn.WriteMessage(ws.TextMessage, []byte(data))
	c.writeMu.Unlock()
}

type emitJS struct {
	Id   int         `json:"id"`
	Data interface{} `json:"data"`
}

//A thread-safe variant of EmitJSON
func (c *Client) EmitJSON(v interface{}) error {
	js := emitJS{}
	js.Id = -1
	js.Data = v

	bytes, err := json.Marshal(js)
	if err != nil {
		return err
	}

	c.Emit(string(bytes))
	return nil
}

func (c *Client) Close() {
	c.conn.Close()
}

func (c *Client) cleanup(s *Server) {
	c.conn.Close()

	s.joinedRoomsMu.RLock()
	for _, room := range s.joinedRooms[c.ID] {
		//log.Println(room)
		s.roomsMu.Lock()
		for i, client := range s.rooms[room] {
			if client.ID == c.ID {
				clients := s.rooms[room]
				clients[i] = clients[len(clients)-1]
				clients[len(clients)-1] = nil
				s.rooms[room] = clients[:len(clients)-1]
				if len(s.rooms[room]) == 0 {
					delete(s.rooms, room)
				}
				break
			}
		}

		s.roomsMu.Unlock()
	}
	s.joinedRoomsMu.RUnlock()

	s.joinedRoomsMu.Lock()
	delete(s.joinedRooms, c.ID)
	s.joinedRoomsMu.Unlock()

	if s.OnDisconnect != nil {
		s.OnDisconnect(c.ID)
	}
}

func (c *Client) listener(s *Server) {
	tick := time.NewTicker(time.Millisecond * 10)
	for {
		<-tick.C
		_, data, err := c.conn.ReadMessage()
		if err != nil {
			c.cleanup(s)
			tick.Stop()
			return
		}

		req := s.getRequest()

		if err := json.Unmarshal(data, &req); err != nil {
			continue
		}

		callName := s.codec.ReadName(req.Data)

		s.handlersLock.RLock()
		f, ok := s.handlers[callName]
		s.handlersLock.RUnlock()

		if !ok {
			if s.defaultHandler.IsNil() {
				continue
			}
			f = s.defaultHandler
		}

		s.Requests.Add(1)
		go func() {
			var err error
			var bytes []byte

			reply := s.getReply()
			reply.ID = req.ID
			reply.Data, err = s.call(c, f, req.Data)
			if err != nil {
				reply.Data = s.codec.Error(err)
			}

			bytes, _ = json.Marshal(reply)

			c.Emit(string(bytes))
			s.freeRequest(req)
			s.freeReply(reply)
			s.Requests.Done()
		}()

	}
}
<commit_msg>Call Done() before Emitting message<commit_after>package wsevent

import (
	"bytes"
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"net/http"
	"sync"
	"time"

	ws "github.com/gorilla/websocket"
)

//Client represents a server-side client
type Client struct {
	ID      string        //Session ID
	Request *http.Request //http Request when connection was upgraded

	writeMu *sync.Mutex
	conn    *ws.Conn
	server  *Server
}

type request struct {
	ID   string          `json:"id"`
	Data json.RawMessage `json:"data"`

	next *request
}

type reply struct {
	ID   string      `json:"id"`
	Data interface{} `json:"data"`

	next *reply
}

func genID(r *http.Request) string {
	buff := make([]byte, 5)
	rand.Read(buff)
	b := bytes.NewBuffer(buff)
	b.WriteString(r.RemoteAddr)

	return base64.URLEncoding.EncodeToString(b.Bytes())
}

func (s *Server) NewClientWithID(upgrader ws.Upgrader, w http.ResponseWriter, r *http.Request, id string) (*Client, error) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		return nil, err
	}

	client := &Client{
		ID:      id,
		Request: r,

		writeMu: new(sync.Mutex),
		conn:    conn,
		server:  s,
	}

	go client.listener(s)

	return client, nil
}

func (s *Server) NewClient(upgrader ws.Upgrader, w http.ResponseWriter, r *http.Request) (*Client, error) {
	return s.NewClientWithID(upgrader, w, r, genID(r))
}

//A thread-safe variant of WriteMessage
func (c *Client) Emit(data string) {
	c.writeMu.Lock()
	c.conn.WriteMessage(ws.TextMessage, []byte(data))
	c.writeMu.Unlock()
}

type emitJS struct {
	Id   int         `json:"id"`
	Data interface{} `json:"data"`
}

//A thread-safe variant of EmitJSON
func (c *Client) EmitJSON(v interface{}) error {
	js := emitJS{}
	js.Id = -1
	js.Data = v

	bytes, err := json.Marshal(js)
	if err != nil {
		return err
	}

	c.Emit(string(bytes))
	return nil
}

func (c *Client) Close() {
	c.conn.Close()
}

func (c *Client) cleanup(s *Server) {
	c.conn.Close()

	s.joinedRoomsMu.RLock()
	for _, room := range s.joinedRooms[c.ID] {
		//log.Println(room)
		s.roomsMu.Lock()
		for i, client := range s.rooms[room] {
			if client.ID == c.ID {
				clients := s.rooms[room]
				clients[i] = clients[len(clients)-1]
				clients[len(clients)-1] = nil
				s.rooms[room] = clients[:len(clients)-1]
				if len(s.rooms[room]) == 0 {
					delete(s.rooms, room)
				}
				break
			}
		}

		s.roomsMu.Unlock()
	}
	s.joinedRoomsMu.RUnlock()

	s.joinedRoomsMu.Lock()
	delete(s.joinedRooms, c.ID)
	s.joinedRoomsMu.Unlock()

	if s.OnDisconnect != nil {
		s.OnDisconnect(c.ID)
	}
}

func (c *Client) listener(s *Server) {
	tick := time.NewTicker(time.Millisecond * 10)
	for {
		<-tick.C
		_, data, err := c.conn.ReadMessage()
		if err != nil {
			c.cleanup(s)
			tick.Stop()
			return
		}

		req := s.getRequest()

		if err := json.Unmarshal(data, &req); err != nil {
			continue
		}

		callName := s.codec.ReadName(req.Data)

		s.handlersLock.RLock()
		f, ok := s.handlers[callName]
		s.handlersLock.RUnlock()

		if !ok {
			if s.defaultHandler.IsNil() {
				continue
			}
			f = s.defaultHandler
		}

		s.Requests.Add(1)
		go func() {
			var err error
			var bytes []byte

			reply := s.getReply()
			reply.ID = req.ID
			reply.Data, err = s.call(c, f, req.Data)
			if err != nil {
				reply.Data = s.codec.Error(err)
			}
			s.Requests.Done()

			bytes, _ = json.Marshal(reply)

			c.Emit(string(bytes))
			s.freeRequest(req)
			s.freeReply(reply)
		}()

	}
}
<|endoftext|>


<commit_before>package main

import (
	crand "crypto/rand"
	"crypto/sha256"
	"crypto/tls"
	"encoding/base64"
	"fmt"
	"github.com/antonholmquist/jason"
	"github.com/julienschmidt/httprouter"
	"io/ioutil"
	"math"
	"math/rand"
	"net/http"
	"net/url"
	"strings"
	"time"
)

// Client methods (one per "slave", communicates with the server)

type Client struct {
}

// Start client
func (s *Client) Start() bool {
	log.Println("Starting client")

	// Start webserver
	go func() {
		router := httprouter.New()
		router.GET("/ping", Ping)

		log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", clientPort), router))
	}()

	// Register with server
	go func() {
		go func() {
			s.PingServer()
		}()
		c := time.Tick(time.Duration(CLIENT_PING_INTERVAL) * time.Second)
		for _ = range c {
			s.PingServer()
		}
	}()

	// Long poll commands
	go func() {
		for {
			s.PollCmds()
		}
	}()

	return true
}

// Fetch commands
func (s *Client) PollCmds() {
	bytes, err := s._get(fmt.Sprintf("client/%s/cmds", url.QueryEscape(hostname)))
	if err == nil {
		obj, jerr := jason.NewObjectFromBytes(bytes)
		if jerr == nil {
			cmds, _ := obj.GetObjectArray("cmds")
			for _, cmd := range cmds {
				id, _ := cmd.GetString("Id")
				command, _ := cmd.GetString("Command")
				timeout, _ := cmd.GetInt64("Timeout")
				cmd := newCmd(command, int(timeout))
				cmd.Id = id
				cmd.Execute()
			}
		}
	}
}

// Ping server
func (s *Client) PingServer() {
	s._get(fmt.Sprintf("client/%s/ping", url.QueryEscape(hostname)))
}

// Get
func (s *Client) _get(uri string) ([]byte, error) {
	return s._req("GET", uri, nil)
}

// Generic request method with retry handling
func (s *Client) _req(method string, uri string, data []byte) ([]byte, error) {
	var bytes []byte = nil
	var err error = nil
	for i := 0; i < 10; i++ {
		bytes, err = s._reqUnsafe(method, uri, data)
		if err == nil {
			return bytes, err
		}

		// Sleep a bit before the retry and apply ~25ms jitter
		var sleep float64 = 25 + float64(rand.Intn(50)) + (math.Pow(float64(i), 2) * 10000)
		time.Sleep(time.Duration(sleep) * time.Millisecond)
	}
	return bytes, err
}

// Generic request method
func (s *Client) _reqUnsafe(method string, uri string, data []byte) ([]byte, error) {
	// Transport
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{
			InsecureSkipVerify: true,
		}, // Ignore certificate as this is self generated and invalid
	}

	// Client
	client := &http.Client{
		Transport: tr,
	}

	// Sanitize urls
	uri = fmt.Sprintf("/%s", strings.TrimLeft(uri, "/"))

	// Append random string to uri
	var randStr string
	c := 32
	b := make([]byte, c)
	_, randErr := crand.Read(b)
	if randErr != nil {
		return nil, randErr
	}
	randStr = base64.URLEncoding.EncodeToString(b)
	if !strings.Contains(uri, "?") {
		uri = fmt.Sprintf("%s?_rand=%s", uri, randStr)
	} else {
		uri = fmt.Sprintf("%s&_rand=%s", uri, randStr)
	}
	url := fmt.Sprintf("%s%s", strings.TrimRight(seedUri, "/"), uri)

	// Req
	// @todo support data
	req, reqErr := http.NewRequest(method, url, nil)
	if reqErr != nil {
		return nil, reqErr
	}

	// Signed token
	hasher := sha256.New()
	hasher.Write([]byte(uri))
	signedToken := base64.URLEncoding.EncodeToString(hasher.Sum(nil))

	// Auth token
	req.Header.Add("X-Auth", signedToken)

	// Execute
	resp, respErr := client.Do(req)
	if respErr != nil {
		return nil, respErr
	}

	// Read body
	body, bodyErr := ioutil.ReadAll(resp.Body)
	if bodyErr != nil {
		return nil, bodyErr
	}
	return body, nil
}

// Create new client
func newClient() *Client {
	return &Client{}
}
<commit_msg>Client webserver fail is not fatal, it is just stats<commit_after>package main

import (
	crand "crypto/rand"
	"crypto/sha256"
	"crypto/tls"
	"encoding/base64"
	"fmt"
	"github.com/antonholmquist/jason"
	"github.com/julienschmidt/httprouter"
	"io/ioutil"
	"math"
	"math/rand"
	"net/http"
	"net/url"
	"strings"
	"time"
)

// Client methods (one per "slave", communicates with the server)

type Client struct {
}

// Start client
func (s *Client) Start() bool {
	log.Println("Starting client")

	// Start webserver
	go func() {
		router := httprouter.New()
		router.GET("/ping", Ping)

		log.Printf("%v", http.ListenAndServe(fmt.Sprintf(":%d", clientPort), router))
	}()

	// Register with server
	go func() {
		go func() {
			s.PingServer()
		}()
		c := time.Tick(time.Duration(CLIENT_PING_INTERVAL) * time.Second)
		for _ = range c {
			s.PingServer()
		}
	}()

	// Long poll commands
	go func() {
		for {
			s.PollCmds()
		}
	}()

	return true
}

// Fetch commands
func (s *Client) PollCmds() {
	bytes, err := s._get(fmt.Sprintf("client/%s/cmds", url.QueryEscape(hostname)))
	if err == nil {
		obj, jerr := jason.NewObjectFromBytes(bytes)
		if jerr == nil {
			cmds, _ := obj.GetObjectArray("cmds")
			for _, cmd := range cmds {
				id, _ := cmd.GetString("Id")
				command, _ := cmd.GetString("Command")
				timeout, _ := cmd.GetInt64("Timeout")
				cmd := newCmd(command, int(timeout))
				cmd.Id = id
				cmd.Execute()
			}
		}
	}
}

// Ping server
func (s *Client) PingServer() {
	s._get(fmt.Sprintf("client/%s/ping", url.QueryEscape(hostname)))
}

// Get
func (s *Client) _get(uri string) ([]byte, error) {
	return s._req("GET", uri, nil)
}

// Generic request method with retry handling
func (s *Client) _req(method string, uri string, data []byte) ([]byte, error) {
	var bytes []byte = nil
	var err error = nil
	for i := 0; i < 10; i++ {
		bytes, err = s._reqUnsafe(method, uri, data)
		if err == nil {
			return bytes, err
		}

		// Sleep a bit before the retry and apply ~25ms jitter
		var sleep float64 = 25 + float64(rand.Intn(50)) + (math.Pow(float64(i), 2) * 10000)
		time.Sleep(time.Duration(sleep) * time.Millisecond)
	}
	return bytes, err
}

// Generic request method
func (s *Client) _reqUnsafe(method string, uri string, data []byte) ([]byte, error) {
	// Transport
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{
			InsecureSkipVerify: true,
		}, // Ignore certificate as this is self generated and invalid
	}

	// Client
	client := &http.Client{
		Transport: tr,
	}

	// Sanitize urls
	uri = fmt.Sprintf("/%s", strings.TrimLeft(uri, "/"))

	// Append random string to uri
	var randStr string
	c := 32
	b := make([]byte, c)
	_, randErr := crand.Read(b)
	if randErr != nil {
		return nil, randErr
	}
	randStr = base64.URLEncoding.EncodeToString(b)
	if !strings.Contains(uri, "?") {
		uri = fmt.Sprintf("%s?_rand=%s", uri, randStr)
	} else {
		uri = fmt.Sprintf("%s&_rand=%s", uri, randStr)
	}
	url := fmt.Sprintf("%s%s", strings.TrimRight(seedUri, "/"), uri)

	// Req
	// @todo support data
	req, reqErr := http.NewRequest(method, url, nil)
	if reqErr != nil {
		return nil, reqErr
	}

	// Signed token
	hasher := sha256.New()
	hasher.Write([]byte(uri))
	signedToken := base64.URLEncoding.EncodeToString(hasher.Sum(nil))

	// Auth token
	req.Header.Add("X-Auth", signedToken)

	// Execute
	resp, respErr := client.Do(req)
	if respErr != nil {
		return nil, respErr
	}

	// Read body
	body, bodyErr := ioutil.ReadAll(resp.Body)
	if bodyErr != nil {
		return nil, bodyErr
	}
	return body, nil
}

// Create new client
func newClient() *Client {
	return &Client{}
}
<|endoftext|>


<commit_before>package client

import (
	"fmt"
	"sync"
	"sync/atomic"

	"github.com/kadirahq/kadiyadb-transport"
)

// message types
const (
	MsgTypeTrack = iota + 1
	MsgTypeFetch
)

// Client is a kadiyadb Client
type Client struct {
	conn     *transport.Conn
	tran     *transport.Transport
	inflight map[uint64]chan [][]byte
	mtx      *sync.RWMutex
	nextID   uint64
}

// New creates a new kadiyadb Client
func New(addr string) (c *Client, err error) {
	conn, err := transport.Dial(addr)
	if err != nil {
		return nil, err
	}

	c = &Client{
		conn:     conn,
		tran:     transport.New(conn),
		inflight: make(map[uint64]chan [][]byte, 1),
		mtx:      &sync.RWMutex{},
	}

	go c.read()

	return c, nil
}

func (c *Client) read() {
	for {
		// `msgType` is dropped. Its not important for the client
		data, id, _, err := c.tran.ReceiveBatch()
		if err != nil {
			fmt.Println(err)
			break
		}

		c.mtx.RLock()
		ch, ok := c.inflight[id]
		c.mtx.RUnlock()

		if !ok {
			fmt.Println("Unknown response id")
			continue
		}

		ch <- data
		c.mtx.Lock()
		delete(c.inflight, id)
		c.mtx.Unlock()
	}
}

func (c *Client) call(b [][]byte, msgType uint8) ([][]byte, error) {
	ch := make(chan [][]byte, 1)
	id := atomic.AddUint64(&c.nextID, 1)

	c.mtx.Lock()
	c.inflight[id] = ch
	c.mtx.Unlock()

	if err := c.tran.SendBatch(b, id, msgType); err != nil {
		// Error during a `SendBatch` call makes the connection unusable
		// Data sent following such an error may not be parsable
		c.conn.Close()
		return nil, err
	}

	return <-ch, nil
}

// Track tracks kadiyadb points
func (c *Client) Track(reqs []*ReqTrack) (ress []*ResTrack, err error) {
	reqData := make([][]byte, len(reqs))
	for i, req := range reqs {
		if reqData[i], err = req.Marshal(); err != nil {
			return nil, err
		}
	}

	resData, err := c.call(reqData, MsgTypeTrack)
	if err != nil {
		return nil, err
	}

	ress = make([]*ResTrack, len(reqs))
	for i := range ress {
		res := &ResTrack{}
		ress[i] = res

		if err := res.Unmarshal(resData[i]); err != nil {
			return nil, err
		}
	}

	return ress, nil
}

// Fetch fetches kadiyadb point data
func (c *Client) Fetch(reqs []*ReqFetch) (ress []*ResFetch, err error) {
	reqData := make([][]byte, len(reqs))
	for i, req := range reqs {
		if reqData[i], err = req.Marshal(); err != nil {
			return nil, err
		}
	}

	resData, err := c.call(reqData, MsgTypeTrack)
	if err != nil {
		return nil, err
	}

	ress = make([]*ResFetch, len(reqs))
	for i := range ress {
		res := &ResFetch{}
		ress[i] = res

		if err := res.Unmarshal(resData[i]); err != nil {
			return nil, err
		}
	}

	return ress, nil
}
<commit_msg>Return errors<commit_after>package client

import (
	"errors"
	"sync"
	"sync/atomic"

	"github.com/kadirahq/kadiyadb-transport"
)

// message types
const (
	MsgTypeTrack = iota + 1
	MsgTypeFetch
)

// Client is a kadiyadb Client
type Client struct {
	conn     *transport.Conn
	tran     *transport.Transport
	inflight map[uint64]chan [][]byte
	mtx      *sync.RWMutex
	nextID   uint64
}

// New creates a new kadiyadb Client
func New(addr string) (c *Client, err error) {
	conn, err := transport.Dial(addr)
	if err != nil {
		return nil, err
	}

	c = &Client{
		conn:     conn,
		tran:     transport.New(conn),
		inflight: make(map[uint64]chan [][]byte, 1),
		mtx:      &sync.RWMutex{},
	}

	go c.read()

	return c, nil
}

func (c *Client) read() {
	for {
		// `msgType` is dropped. Its not important for the client
		data, id, _, err := c.tran.ReceiveBatch()
		if err != nil {
			break
		}

		c.mtx.Lock()
		ch, ok := c.inflight[id]
		if !ok {
			c.mtx.Unlock()
			continue
		} else {
			delete(c.inflight, id)
			c.mtx.Unlock()
		}

		ch <- data
	}
}

func (c *Client) call(b [][]byte, msgType uint8) ([][]byte, error) {
	ch := make(chan [][]byte, 1)
	id := atomic.AddUint64(&c.nextID, 1)

	c.mtx.Lock()
	c.inflight[id] = ch
	c.mtx.Unlock()

	if err := c.tran.SendBatch(b, id, msgType); err != nil {
		// Error during a `SendBatch` call makes the connection unusable
		// Data sent following such an error may not be parsable
		c.conn.Close()
		return nil, err
	}

	return <-ch, nil
}

// Track tracks kadiyadb points
func (c *Client) Track(reqs []*ReqTrack) (ress []*ResTrack, err error) {
	reqData := make([][]byte, len(reqs))
	for i, req := range reqs {
		if reqData[i], err = req.Marshal(); err != nil {
			return nil, err
		}
	}

	resData, err := c.call(reqData, MsgTypeTrack)
	if err != nil {
		return nil, err
	}

	ress = make([]*ResTrack, len(reqs))
	for i := range ress {
		res := &ResTrack{}
		ress[i] = res

		if err := res.Unmarshal(resData[i]); err != nil {
			res.Error = err.Error()
			continue
		}

		if res.Error != "" {
			// using return value (err)
			// final error will return
			err = errors.New(res.Error)
		}
	}

	return ress, err
}

// Fetch fetches kadiyadb point data
func (c *Client) Fetch(reqs []*ReqFetch) (ress []*ResFetch, err error) {
	reqData := make([][]byte, len(reqs))
	for i, req := range reqs {
		var err error
		if reqData[i], err = req.Marshal(); err != nil {
			return nil, err
		}
	}

	resData, err := c.call(reqData, MsgTypeFetch)
	if err != nil {
		return nil, err
	}

	ress = make([]*ResFetch, len(reqs))
	for i := range ress {
		res := &ResFetch{}
		ress[i] = res

		if err := res.Unmarshal(resData[i]); err != nil {
			res.Error = err.Error()
			continue
		}

		if res.Error != "" {
			// using return value (err)
			// final error will return
			err = errors.New(res.Error)
		}
	}

	return ress, err
}
<|endoftext|>


<commit_before>package retrogit

import (
	"bytes"
	"fmt"
	"sort"
	"strings"
	"time"

	"appengine"

	"github.com/google/go-github/github"
)

const (
	CommitDisplayDateFormat      = "3:04pm"
	CommitDisplayDateFullFormat  = "Monday January 2 3:04pm"
	DigestDisplayDateFormat      = "January 2, 2006"
	DigestDisplayShortDateFormat = "January 2"
	DigestDisplayDayOfWeekFormat = "Monday"
)

type DigestCommit struct {
	DisplaySHA       string
	URL              string
	Title            string
	Message          string
	PushDate         time.Time
	CommitDate       time.Time
	RepositoryCommit *github.RepositoryCommit
}

func safeFormattedDate(date string) string {
	// Insert zero-width spaces every few characters so that Apple Data
	// Detectors and Gmail's calendar event dection don't pick up on these
	// dates.
	var buffer bytes.Buffer
	dateLength := len(date)
	for i := 0; i < dateLength; i += 2 {
		if i == dateLength-1 {
			buffer.WriteString(date[i : i+1])
		} else {
			buffer.WriteString(date[i : i+2])
		}
		buffer.WriteString("\u200b")
	}
	return buffer.String()
}

func newDigestCommit(commit *github.RepositoryCommit, repo *Repo, location *time.Location) DigestCommit {
	messagePieces := strings.SplitN(*commit.Commit.Message, "\n", 2)
	title := messagePieces[0]
	message := ""
	if len(messagePieces) == 2 {
		message = messagePieces[1]
	}
	return DigestCommit{
		DisplaySHA:       (*commit.SHA)[:7],
		URL:              fmt.Sprintf("https://github.com/%s/commit/%s", *repo.FullName, *commit.SHA),
		Title:            title,
		Message:          message,
		PushDate:         commit.Commit.Committer.Date.In(location),
		CommitDate:       commit.Commit.Author.Date.In(location),
		RepositoryCommit: commit,
	}
}

func (commit DigestCommit) DisplayDate() string {
	// Prefer the date the commit was pushed, since that's what GitHub filters
	// and sorts by.
	return safeFormattedDate(commit.PushDate.Format(CommitDisplayDateFormat))
}

func (commit DigestCommit) WeeklyDisplayDate() string {
	return safeFormattedDate(commit.PushDate.Format(CommitDisplayDateFullFormat))
}

func (commit DigestCommit) DisplayDateTooltip() string {
	// But show the full details in a tooltip
	return fmt.Sprintf(
		"Pushed at %s\nCommited at %s",
		commit.PushDate.Format(CommitDisplayDateFullFormat),
		commit.CommitDate.Format(CommitDisplayDateFullFormat))
}

type RepoDigest struct {
	Repo    *Repo
	Commits []DigestCommit
}

// sort.Interface implementation for sorting RepoDigests.
type ByRepoFullName []*RepoDigest

func (a ByRepoFullName) Len() int           { return len(a) }
func (a ByRepoFullName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByRepoFullName) Less(i, j int) bool { return *a[i].Repo.FullName < *a[j].Repo.FullName }

type IntervalDigest struct {
	yearDelta   int
	StartTime   time.Time
	EndTime     time.Time
	Weekly      bool
	RepoDigests []*RepoDigest
	repos       []*Repo
}

func (digest *IntervalDigest) Empty() bool {
	for i := range digest.RepoDigests {
		if len(digest.RepoDigests[i].Commits) > 0 {
			return false
		}
	}
	return true
}

func (digest *IntervalDigest) Header() string {
	if digest.yearDelta == -1 {
		return "1 Year Ago"
	}
	return fmt.Sprintf("%d Years Ago", -digest.yearDelta)
}

func (digest *IntervalDigest) Description() string {
	commitCount := 0
	for i := range digest.RepoDigests {
		commitCount += len(digest.RepoDigests[i].Commits)
	}
	var formattedCommitCount string
	if commitCount == 0 {
		formattedCommitCount = "no commits"
	} else if commitCount == 1 {
		formattedCommitCount = "1 commit"
	} else {
		formattedCommitCount = fmt.Sprintf("%d commits", commitCount)
	}
	repoCount := len(digest.RepoDigests)
	var formattedRepoCount string
	if repoCount == 1 {
		formattedRepoCount = "1 repository"
	} else {
		formattedRepoCount = fmt.Sprintf("%d repositories", repoCount)
	}

	if !digest.Weekly {
		return fmt.Sprintf("%s was a %s. You had %s in %s that day.",
			safeFormattedDate(digest.StartTime.Format(DigestDisplayDateFormat)),
			safeFormattedDate(digest.StartTime.Format(DigestDisplayDayOfWeekFormat)),
			formattedCommitCount,
			formattedRepoCount)
	}

	formattedEndTime := digest.EndTime.Format(DigestDisplayDateFormat)
	var formattedStartTime string
	if digest.StartTime.Year() == digest.EndTime.Year() {
		formattedStartTime = digest.StartTime.Format(DigestDisplayShortDateFormat)
	} else {
		formattedStartTime = digest.StartTime.Format(DigestDisplayDateFormat)
	}
	return fmt.Sprintf("You had %s in %s the week of %s to %s.",
		formattedCommitCount,
		formattedRepoCount,
		safeFormattedDate(formattedStartTime),
		safeFormattedDate(formattedEndTime))
}

type Digest struct {
	User             *github.User
	TimezoneLocation *time.Location
	IntervalDigests  []*IntervalDigest
}

func newDigest(c appengine.Context, githubClient *github.Client, account *Account) (*Digest, error) {
	user, _, err := githubClient.Users.Get("")
	if err != nil {
		return nil, err
	}

	repos, err := getRepos(c, githubClient, account, user)
	if err != nil {
		return nil, err
	}

	oldestDigestTime := repos.OldestVintage.In(account.TimezoneLocation)
	intervalDigests := make([]*IntervalDigest, 0)
	now := time.Now().In(account.TimezoneLocation)
	for yearDelta := -1; ; yearDelta-- {
		digestStartTime := time.Date(now.Year()+yearDelta, now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
		if digestStartTime.Before(oldestDigestTime) {
			break
		}
		daysInDigest := 1
		if account.Frequency == "weekly" {
			daysInDigest = 7
		}
		digestEndTime := digestStartTime.AddDate(0, 0, daysInDigest)

		// Only look at repos that may have activity in the digest interval.
		var intervalRepos []*Repo
		for _, repo := range repos.AllRepos {
			if repo.IncludeInDigest && repo.Vintage.Before(digestEndTime) && repo.PushedAt != nil &&
				repo.PushedAt.After(digestStartTime) {
				intervalRepos = append(intervalRepos, repo)
			}
		}

		intervalDigests = append(intervalDigests, &IntervalDigest{
			yearDelta:   yearDelta,
			repos:       intervalRepos,
			RepoDigests: make([]*RepoDigest, 0, len(intervalRepos)),
			StartTime:   digestStartTime,
			EndTime:     digestEndTime,
			Weekly:      account.Frequency == "weekly",
		})
	}

	digest := &Digest{
		User:             user,
		TimezoneLocation: account.TimezoneLocation,
		IntervalDigests:  intervalDigests,
	}

	err = digest.fetch(githubClient)
	return digest, err
}

func (digest *Digest) fetch(githubClient *github.Client) error {
	type RepoDigestResponse struct {
		intervalDigest *IntervalDigest
		repoDigest     *RepoDigest
		err            error
	}
	fetchCount := 0
	ch := make(chan *RepoDigestResponse)
	for _, intervalDigest := range digest.IntervalDigests {
		for _, repo := range intervalDigest.repos {
			go func(intervalDigest *IntervalDigest, repo *Repo) {
				commits := make([]github.RepositoryCommit, 0)
				page := 1
				for {
					pageCommits, response, err := githubClient.Repositories.ListCommits(
						*repo.Owner.Login,
						*repo.Name,
						&github.CommitsListOptions{
							ListOptions: github.ListOptions{
								Page:    page,
								PerPage: 100,
							},
							Author: *digest.User.Login,
							Since:  intervalDigest.StartTime.UTC(),
							Until:  intervalDigest.EndTime.UTC(),
						})
					if err != nil {
						ch <- &RepoDigestResponse{nil, nil, err}
						return
					}
					commits = append(commits, pageCommits...)
					if response.NextPage == 0 {
						break
					}
					page = response.NextPage
				}
				digestCommits := make([]DigestCommit, len(commits))
				for i := range commits {
					digestCommits[len(commits)-i-1] = newDigestCommit(&commits[i], repo, digest.TimezoneLocation)
				}
				ch <- &RepoDigestResponse{intervalDigest, &RepoDigest{repo, digestCommits}, nil}
			}(intervalDigest, repo)
			fetchCount++
		}
	}
	for i := 0; i < fetchCount; i++ {
		select {
		case r := <-ch:
			if r.err != nil {
				return r.err
			}
			if len(r.repoDigest.Commits) > 0 {
				r.intervalDigest.RepoDigests = append(r.intervalDigest.RepoDigests, r.repoDigest)
			}
		}
	}
	nonEmptyIntervalDigests := make([]*IntervalDigest, 0, len(digest.IntervalDigests))
	for _, intervalDigest := range digest.IntervalDigests {
		if !intervalDigest.Empty() {
			nonEmptyIntervalDigests = append(nonEmptyIntervalDigests, intervalDigest)
			sort.Sort(ByRepoFullName(intervalDigest.RepoDigests))
		}
	}
	digest.IntervalDigests = nonEmptyIntervalDigests
	return nil
}

func (digest *Digest) Empty() bool {
	return len(digest.IntervalDigests) == 0
}
<commit_msg>Less aggressive whitespace insertion.<commit_after>package retrogit

import (
	"bytes"
	"fmt"
	"sort"
	"strings"
	"time"

	"appengine"

	"github.com/google/go-github/github"
)

const (
	CommitDisplayDateFormat      = "3:04pm"
	CommitDisplayDateFullFormat  = "Monday January 2 3:04pm"
	DigestDisplayDateFormat      = "January 2, 2006"
	DigestDisplayShortDateFormat = "January 2"
	DigestDisplayDayOfWeekFormat = "Monday"
)

type DigestCommit struct {
	DisplaySHA       string
	URL              string
	Title            string
	Message          string
	PushDate         time.Time
	CommitDate       time.Time
	RepositoryCommit *github.RepositoryCommit
}

func safeFormattedDate(date string) string {
	// Insert zero-width spaces every few characters so that Apple Data
	// Detectors and Gmail's calendar event dection don't pick up on these
	// dates.
	var buffer bytes.Buffer
	dateLength := len(date)
	for i := 0; i < dateLength; i += 2 {
		if i == dateLength-1 {
			buffer.WriteString(date[i : i+1])
		} else {
			buffer.WriteString(date[i : i+2])
			if date[i] != ' ' && date[i+1] != ' ' && i < dateLength-2 {
				buffer.WriteString("\u200b")
			}
		}
	}
	return buffer.String()
}

func newDigestCommit(commit *github.RepositoryCommit, repo *Repo, location *time.Location) DigestCommit {
	messagePieces := strings.SplitN(*commit.Commit.Message, "\n", 2)
	title := messagePieces[0]
	message := ""
	if len(messagePieces) == 2 {
		message = messagePieces[1]
	}
	return DigestCommit{
		DisplaySHA:       (*commit.SHA)[:7],
		URL:              fmt.Sprintf("https://github.com/%s/commit/%s", *repo.FullName, *commit.SHA),
		Title:            title,
		Message:          message,
		PushDate:         commit.Commit.Committer.Date.In(location),
		CommitDate:       commit.Commit.Author.Date.In(location),
		RepositoryCommit: commit,
	}
}

func (commit DigestCommit) DisplayDate() string {
	// Prefer the date the commit was pushed, since that's what GitHub filters
	// and sorts by.
	return safeFormattedDate(commit.PushDate.Format(CommitDisplayDateFormat))
}

func (commit DigestCommit) WeeklyDisplayDate() string {
	return safeFormattedDate(commit.PushDate.Format(CommitDisplayDateFullFormat))
}

func (commit DigestCommit) DisplayDateTooltip() string {
	// But show the full details in a tooltip
	return fmt.Sprintf(
		"Pushed at %s\nCommited at %s",
		commit.PushDate.Format(CommitDisplayDateFullFormat),
		commit.CommitDate.Format(CommitDisplayDateFullFormat))
}

type RepoDigest struct {
	Repo    *Repo
	Commits []DigestCommit
}

// sort.Interface implementation for sorting RepoDigests.
type ByRepoFullName []*RepoDigest

func (a ByRepoFullName) Len() int           { return len(a) }
func (a ByRepoFullName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByRepoFullName) Less(i, j int) bool { return *a[i].Repo.FullName < *a[j].Repo.FullName }

type IntervalDigest struct {
	yearDelta   int
	StartTime   time.Time
	EndTime     time.Time
	Weekly      bool
	RepoDigests []*RepoDigest
	repos       []*Repo
}

func (digest *IntervalDigest) Empty() bool {
	for i := range digest.RepoDigests {
		if len(digest.RepoDigests[i].Commits) > 0 {
			return false
		}
	}
	return true
}

func (digest *IntervalDigest) Header() string {
	if digest.yearDelta == -1 {
		return "1 Year Ago"
	}
	return fmt.Sprintf("%d Years Ago", -digest.yearDelta)
}

func (digest *IntervalDigest) Description() string {
	commitCount := 0
	for i := range digest.RepoDigests {
		commitCount += len(digest.RepoDigests[i].Commits)
	}
	var formattedCommitCount string
	if commitCount == 0 {
		formattedCommitCount = "no commits"
	} else if commitCount == 1 {
		formattedCommitCount = "1 commit"
	} else {
		formattedCommitCount = fmt.Sprintf("%d commits", commitCount)
	}
	repoCount := len(digest.RepoDigests)
	var formattedRepoCount string
	if repoCount == 1 {
		formattedRepoCount = "1 repository"
	} else {
		formattedRepoCount = fmt.Sprintf("%d repositories", repoCount)
	}

	if !digest.Weekly {
		return fmt.Sprintf("%s was a %s. You had %s in %s that day.",
			safeFormattedDate(digest.StartTime.Format(DigestDisplayDateFormat)),
			safeFormattedDate(digest.StartTime.Format(DigestDisplayDayOfWeekFormat)),
			formattedCommitCount,
			formattedRepoCount)
	}

	formattedEndTime := digest.EndTime.Format(DigestDisplayDateFormat)
	var formattedStartTime string
	if digest.StartTime.Year() == digest.EndTime.Year() {
		formattedStartTime = digest.StartTime.Format(DigestDisplayShortDateFormat)
	} else {
		formattedStartTime = digest.StartTime.Format(DigestDisplayDateFormat)
	}
	return fmt.Sprintf("You had %s in %s the week of %s to %s.",
		formattedCommitCount,
		formattedRepoCount,
		safeFormattedDate(formattedStartTime),
		safeFormattedDate(formattedEndTime))
}

type Digest struct {
	User             *github.User
	TimezoneLocation *time.Location
	IntervalDigests  []*IntervalDigest
}

func newDigest(c appengine.Context, githubClient *github.Client, account *Account) (*Digest, error) {
	user, _, err := githubClient.Users.Get("")
	if err != nil {
		return nil, err
	}

	repos, err := getRepos(c, githubClient, account, user)
	if err != nil {
		return nil, err
	}

	oldestDigestTime := repos.OldestVintage.In(account.TimezoneLocation)
	intervalDigests := make([]*IntervalDigest, 0)
	now := time.Now().In(account.TimezoneLocation)
	for yearDelta := -1; ; yearDelta-- {
		digestStartTime := time.Date(now.Year()+yearDelta, now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
		if digestStartTime.Before(oldestDigestTime) {
			break
		}
		daysInDigest := 1
		if account.Frequency == "weekly" {
			daysInDigest = 7
		}
		digestEndTime := digestStartTime.AddDate(0, 0, daysInDigest)

		// Only look at repos that may have activity in the digest interval.
		var intervalRepos []*Repo
		for _, repo := range repos.AllRepos {
			if repo.IncludeInDigest && repo.Vintage.Before(digestEndTime) && repo.PushedAt != nil &&
				repo.PushedAt.After(digestStartTime) {
				intervalRepos = append(intervalRepos, repo)
			}
		}

		intervalDigests = append(intervalDigests, &IntervalDigest{
			yearDelta:   yearDelta,
			repos:       intervalRepos,
			RepoDigests: make([]*RepoDigest, 0, len(intervalRepos)),
			StartTime:   digestStartTime,
			EndTime:     digestEndTime,
			Weekly:      account.Frequency == "weekly",
		})
	}

	digest := &Digest{
		User:             user,
		TimezoneLocation: account.TimezoneLocation,
		IntervalDigests:  intervalDigests,
	}

	err = digest.fetch(githubClient)
	return digest, err
}

func (digest *Digest) fetch(githubClient *github.Client) error {
	type RepoDigestResponse struct {
		intervalDigest *IntervalDigest
		repoDigest     *RepoDigest
		err            error
	}
	fetchCount := 0
	ch := make(chan *RepoDigestResponse)
	for _, intervalDigest := range digest.IntervalDigests {
		for _, repo := range intervalDigest.repos {
			go func(intervalDigest *IntervalDigest, repo *Repo) {
				commits := make([]github.RepositoryCommit, 0)
				page := 1
				for {
					pageCommits, response, err := githubClient.Repositories.ListCommits(
						*repo.Owner.Login,
						*repo.Name,
						&github.CommitsListOptions{
							ListOptions: github.ListOptions{
								Page:    page,
								PerPage: 100,
							},
							Author: *digest.User.Login,
							Since:  intervalDigest.StartTime.UTC(),
							Until:  intervalDigest.EndTime.UTC(),
						})
					if err != nil {
						ch <- &RepoDigestResponse{nil, nil, err}
						return
					}
					commits = append(commits, pageCommits...)
					if response.NextPage == 0 {
						break
					}
					page = response.NextPage
				}
				digestCommits := make([]DigestCommit, len(commits))
				for i := range commits {
					digestCommits[len(commits)-i-1] = newDigestCommit(&commits[i], repo, digest.TimezoneLocation)
				}
				ch <- &RepoDigestResponse{intervalDigest, &RepoDigest{repo, digestCommits}, nil}
			}(intervalDigest, repo)
			fetchCount++
		}
	}
	for i := 0; i < fetchCount; i++ {
		select {
		case r := <-ch:
			if r.err != nil {
				return r.err
			}
			if len(r.repoDigest.Commits) > 0 {
				r.intervalDigest.RepoDigests = append(r.intervalDigest.RepoDigests, r.repoDigest)
			}
		}
	}
	nonEmptyIntervalDigests := make([]*IntervalDigest, 0, len(digest.IntervalDigests))
	for _, intervalDigest := range digest.IntervalDigests {
		if !intervalDigest.Empty() {
			nonEmptyIntervalDigests = append(nonEmptyIntervalDigests, intervalDigest)
			sort.Sort(ByRepoFullName(intervalDigest.RepoDigests))
		}
	}
	digest.IntervalDigests = nonEmptyIntervalDigests
	return nil
}

func (digest *Digest) Empty() bool {
	return len(digest.IntervalDigests) == 0
}
<|endoftext|>


<commit_before>package githop

import (
	"encoding/json"
	"html/template"
	"io/ioutil"
	"net/http"

	"appengine"
	"appengine/urlfetch"

	"code.google.com/p/goauth2/oauth"
	"github.com/google/go-github/github"
)

func init() {
	http.HandleFunc("/", index)
}

var indexTemplate = template.Must(template.ParseFiles("templates/index.html"))

func index(w http.ResponseWriter, r *http.Request) {
	// TODO: Don't do this every request
	github_oauth_config_path := "config/github-oauth"
	if appengine.IsDevAppServer() {
		github_oauth_config_path += "-dev"
	}
	github_oauth_config_path += ".json"
	github_oauth_config_bytes, err := ioutil.ReadFile(github_oauth_config_path)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	var github_oauth_config oauth.Config
	err = json.Unmarshal(github_oauth_config_bytes, &github_oauth_config)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	github_oauth_config.Scope = "repo"
	github_oauth_config.AuthURL = "https://github.com/login/oauth/authorize"
	github_oauth_config.TokenURL = "https://github.com/login/oauth/access_token"

	code := r.FormValue("code")
	if code == "" {
		http.Redirect(w, r, github_oauth_config.AuthCodeURL(""), http.StatusFound)
		return
	}
	appengine_context := appengine.NewContext(r)
	oauth_transport := &oauth.Transport{
		Config:    &github_oauth_config,
		Transport: &urlfetch.Transport{Context: appengine_context},
	}
	token, _ := oauth_transport.Exchange(code)
	oauth_transport.Token = token

	github_client := github.NewClient(oauth_transport.Client())
	events, _, err := github_client.Activity.ListEventsPerformedByUser("mihaip", false, nil)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if err := indexTemplate.Execute(w, events); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}
<commit_msg>Separate index and GitHub OAuth callback handlers.<commit_after>package githop

import (
	"encoding/base64"
	"encoding/json"
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"

	"appengine"
	"appengine/urlfetch"

	"code.google.com/p/goauth2/oauth"
	"github.com/google/go-github/github"
	"github.com/gorilla/mux"
)

var router *mux.Router
var githubOauthConfig oauth.Config

func initGithubOAuthConfig() {
	path := "config/github-oauth"
	if appengine.IsDevAppServer() {
		path += "-dev"
	}
	path += ".json"
	configBytes, err := ioutil.ReadFile(path)
	if err != nil {
		log.Panicf("Could not read GitHut OAuth config from %s: %s", path, err.Error())
	}
	err = json.Unmarshal(configBytes, &githubOauthConfig)
	if err != nil {
		log.Panicf("Could not parse GitHut OAuth %s", err.Error())
	}
	githubOauthConfig.Scope = "repo"
	githubOauthConfig.AuthURL = "https://github.com/login/oauth/authorize"
	githubOauthConfig.TokenURL = "https://github.com/login/oauth/access_token"
}

func init() {
	initGithubOAuthConfig()

	router = mux.NewRouter()
	router.HandleFunc("/", indexHandler).Name("index")
	router.HandleFunc("/github/callback", githubOAuthCallbackHandler)
	http.Handle("/", router)
}

var indexTemplate = template.Must(template.ParseFiles("templates/index.html"))

func indexHandler(w http.ResponseWriter, r *http.Request) {
	tokenEncoded := r.FormValue("token")
	if tokenEncoded == "" {
		http.Redirect(w, r, githubOauthConfig.AuthCodeURL(""), http.StatusFound)
		return
	}
	tokenBytes, err := base64.URLEncoding.DecodeString(tokenEncoded)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	var token oauth.Token
	err = json.Unmarshal(tokenBytes, &token)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	appengineContext := appengine.NewContext(r)
	oauthTransport := &oauth.Transport{
		Config:    &githubOauthConfig,
		Transport: &urlfetch.Transport{Context: appengineContext},
		Token:     &token,
	}
	githubClient := github.NewClient(oauthTransport.Client())
	events, _, err := githubClient.Activity.ListEventsPerformedByUser("mihaip", false, nil)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if err := indexTemplate.Execute(w, events); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func githubOAuthCallbackHandler(w http.ResponseWriter, r *http.Request) {
	code := r.FormValue("code")
	appengineContext := appengine.NewContext(r)
	oauthTransport := &oauth.Transport{
		Config:    &githubOauthConfig,
		Transport: &urlfetch.Transport{Context: appengineContext},
	}
	token, err := oauthTransport.Exchange(code)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	tokenBytes, err := json.Marshal(token)
	tokenEncoded := base64.StdEncoding.EncodeToString(tokenBytes)
	redirectUrl, err := router.GetRoute("index").URL()
	redirectParams := url.Values{}
	redirectParams.Add("token", tokenEncoded)
	redirectUrl.RawQuery = redirectParams.Encode()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	http.Redirect(w, r, redirectUrl.String(), http.StatusFound)
}
<|endoftext|>