<commit_before>// The MIT License (MIT)
//
// Copyright (c) 2016 aerth
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

// package seconf allows your software to store non-plaintext configuration files.
// check out the example application in _examples/hello for a working example.
// fmt.Println("Welcome to " + sn + ", " + configarray[0])
// fmt.Printf("Your %s is %s \n", os.Args[3], configarray[0])
// fmt.Printf("Your %s is %s \n", os.Args[4], configarray[1])
// fmt.Printf("Your %s is %s \n", os.Args[5], configarray[2])
package seconf

import (
	"bufio"
	"crypto/rand"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"strings"

	"github.com/bgentry/speakeasy"
	"golang.org/x/crypto/nacl/secretbox"
)

const keySize = 32
const nonceSize = 24

// secustom is the filename that gets stored. for example, if secustom is "test", the configuration file will be saved as $HOME/.test
var secustom string
var username string
var password string
var hashbar = strings.Repeat("#", 80)

var configuser = ""
var configpass = ""

var configlock = ""

// Seconf is the struct for the seconf pathname and fields.
type Seconf struct {
	Id   int64
	Path string
	Args []string
}

/*
type Fielder struct {
	Id       int64
	Name     string
	Password bool
}
*/

// constainsString returns true if a slice contains a string.
func containsString(slice []string, element string) bool {
	return !(posString(slice, element) == -1)
}

// askForConfirmation returns true if the user types one of the "okayResponses"
// https://gist.github.com/albrow/5882501
func askForConfirmation() bool {
	var response string
	_, err := fmt.Scanln(&response)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	okayResponses := []string{"y", "Y", "yes", "Yes", "YES"}
	nokayResponses := []string{"n", "N", "no", "No", "NO"}
	quitResponses := []string{"q", "Q", "exit", "quit"}
	if containsString(okayResponses, response) {
		return true
	} else if containsString(nokayResponses, response) {
		return false
	} else if containsString(quitResponses, response) {
		return false
	} else {
		fmt.Println("\nNot valid answer, try again. [y/n] [yes/no]")
		return askForConfirmation()
	}
}

// posString returns the first index of element in slice.
// If slice does not contain element, returns -1.
func posString(slice []string, element string) int {
	for index, elem := range slice {
		if elem == element {
			return index
		}
	}
	return -1
}

// Prompt the user for the particular field.
func Prompt(header string) string {
	fmt.Printf("\n### " + header + " ###\n")
	fmt.Printf("\nPress ENTER when you are finished typing.\n\n")
	scanner := bufio.NewScanner(os.Stdin)
	if scanner.Scan() {
		line := scanner.Text()
		return line
	}
	if err := scanner.Err(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	return ""
}

// Create initializes a new configuration file, at $HOME/secustom with the title servicename and as many fields as needed. Any field starting with "pass" will be assumed a password and input will not be echoed.
func Create(secustom string, servicename string, arg ...string) {
	bar(servicename)
	configfields := &Seconf{
		Path: secustom,
		Args: arg,
	}

	var m1 map[int]string = map[int]string{}
	var newsplice []string
	for i := range configfields.Args {
		bar(servicename)
		if len(configfields.Args[i]) > 4 {
			if configfields.Args[i][0:4] == "pass" || configfields.Args[i][0:4] == "Pass" {
				//		fmt.Printf("\n### " + servicename + " ###\n")
				m1[i], _ = speakeasy.Ask(servicename + " " + configfields.Args[i] + ":")
				if m1[i] == "" {
					bar(secustom)
					m1[i], _ = speakeasy.Ask(servicename + " " + configfields.Args[i] + ":")
				}
				if m1[i] == "" {
					bar(secustom)
					m1[i], _ = speakeasy.Ask(servicename + " " + configfields.Args[i] + ":")
				}
				if m1[i] == "" {
					bar(secustom)
					fmt.Println(configfields.Args[i] + " cannot be blank.")
					os.Exit(1)
				}

			} else {
				m1[i] = Prompt(configfields.Args[i])
				if m1[i] == "" {
					bar(servicename)
					fmt.Println("Can not be blank.")
					m1[i] = Prompt(configfields.Args[i])
				}
				if m1[i] == "" {
					bar(servicename)
					fmt.Println("Can not be blank.")
					m1[i] = Prompt(configfields.Args[i])
				}
				if m1[i] == "" {
					bar(servicename)
					fmt.Println(configfields.Args[i] + " cannot be blank.")
					os.Exit(1)
				}
			}
		} else {
			m1[i] = Prompt(configfields.Args[i])
		}
		newsplice = append(newsplice, m1[i]+"::::")
	}

	bar(servicename)
	configlock, _ := speakeasy.Ask("Create a password to encrypt config file:\nPress ENTER for no password.")
	var userKey = configlock
	var pad = []byte("«super jumpy fox jumps all over»")

	var messagebox = strings.Join(newsplice, "")
	messagebox = strings.TrimSuffix(messagebox, "::::")
	var message = []byte(messagebox)
	key := []byte(userKey)
	key = append(key, pad...)
	naclKey := new([keySize]byte)
	copy(naclKey[:], key[:keySize])
	nonce := new([nonceSize]byte)
	// Read bytes from random and put them in nonce until it is full.
	_, err := io.ReadFull(rand.Reader, nonce[:])
	if err != nil {
		fmt.Println("Could not read from random:", err)
		os.Exit(1)
	}
	out := make([]byte, nonceSize)
	copy(out, nonce[:])
	out = secretbox.Seal(out, message, nonce, naclKey)
	err = ioutil.WriteFile(ReturnHome()+"/."+secustom, out, 0600)
	if err != nil {
		fmt.Println("Error while writing config file: ", err)
		os.Exit(1)
	}
	fmt.Printf("Config file saved at "+ReturnHome()+"/."+secustom+" \nTotal size is %d bytes.\n",
		len(out))
	os.Exit(0)
}

// Detect returns TRUE if a seconf file exists.
func Detect(secustom string) bool {
	_, err := ioutil.ReadFile(ReturnHome() + "/." + secustom)
	if err != nil {
		return false
	}
	return true
}

// Read returns the decoded configuration file, or an error. Fields are separated by 4 colons. ("::::")
func Read(secustom string) (config string, err error) {
	bar(secustom)
	fmt.Println("Unlocking config file")
	configlock, err = speakeasy.Ask("Password: ")
	bar(secustom)
	var userKey = configlock
	var pad = []byte("«super jumpy fox jumps all over»")
	key := []byte(userKey)
	key = append(key, pad...)
	naclKey := new([keySize]byte)
	copy(naclKey[:], key[:keySize])
	nonce := new([nonceSize]byte)
	in, err := ioutil.ReadFile(ReturnHome() + "/." + secustom)
	if err != nil {
		fmt.Println(err)

	}
	copy(nonce[:], in[:nonceSize])
	configbytes, ok := secretbox.Open(nil, in[nonceSize:], nonce, naclKey)
	if !ok {
		fmt.Println("Could not decrypt the config file. Wrong password?")
		os.Exit(1)
	}
	return string(configbytes), nil

}

// Cheap and effective way of clearing screen on unix. Ugly on windows.
func bar(secustom string) {
	versionbar := strings.Repeat("#", 10) + "\t" + secustom + "\t" + strings.Repeat("#", 30)
	print("\033[H\033[2J")
	fmt.Println(versionbar)
}

// ReturnHome is a cross-OS way of getting a HOMEDIR.
func ReturnHome() (homedir string) {
	homedir = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
	if homedir == "" {
		homedir = os.Getenv("USERPROFILE")
	}
	if homedir == "" {
		homedir = os.Getenv("HOME")
	}
	return
}
<commit_msg>i dont know how godoc works with markdown<commit_after>// The MIT License (MIT)
//
// Copyright (c) 2016 aerth
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

// package seconf allows your software to store non-plaintext configuration files.
//
// check out the example application in _examples/hello for a working example.
//
// ```fmt.Println("Welcome to " + sn + ", " + configarray[0])
// fmt.Printf("Your %s is %s \n", os.Args[3], configarray[0])
// fmt.Printf("Your %s is %s \n", os.Args[4], configarray[1])
// fmt.Printf("Your %s is %s \n", os.Args[5], configarray[2])```
package seconf

import (
	"bufio"
	"crypto/rand"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"strings"

	"github.com/bgentry/speakeasy"
	"golang.org/x/crypto/nacl/secretbox"
)

const keySize = 32
const nonceSize = 24

// secustom is the filename that gets stored. for example, if secustom is "test", the configuration file will be saved as $HOME/.test
var secustom string
var username string
var password string
var hashbar = strings.Repeat("#", 80)

var configuser = ""
var configpass = ""

var configlock = ""

// Seconf is the struct for the seconf pathname and fields.
type Seconf struct {
	Id   int64
	Path string
	Args []string
}

/*
type Fielder struct {
	Id       int64
	Name     string
	Password bool
}
*/

// constainsString returns true if a slice contains a string.
func containsString(slice []string, element string) bool {
	return !(posString(slice, element) == -1)
}

// askForConfirmation returns true if the user types one of the "okayResponses"
// https://gist.github.com/albrow/5882501
func askForConfirmation() bool {
	var response string
	_, err := fmt.Scanln(&response)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	okayResponses := []string{"y", "Y", "yes", "Yes", "YES"}
	nokayResponses := []string{"n", "N", "no", "No", "NO"}
	quitResponses := []string{"q", "Q", "exit", "quit"}
	if containsString(okayResponses, response) {
		return true
	} else if containsString(nokayResponses, response) {
		return false
	} else if containsString(quitResponses, response) {
		return false
	} else {
		fmt.Println("\nNot valid answer, try again. [y/n] [yes/no]")
		return askForConfirmation()
	}
}

// posString returns the first index of element in slice.
// If slice does not contain element, returns -1.
func posString(slice []string, element string) int {
	for index, elem := range slice {
		if elem == element {
			return index
		}
	}
	return -1
}

// Prompt the user for the particular field.
func Prompt(header string) string {
	fmt.Printf("\n### " + header + " ###\n")
	fmt.Printf("\nPress ENTER when you are finished typing.\n\n")
	scanner := bufio.NewScanner(os.Stdin)
	if scanner.Scan() {
		line := scanner.Text()
		return line
	}
	if err := scanner.Err(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	return ""
}

// Create initializes a new configuration file, at $HOME/secustom with the title servicename and as many fields as needed. Any field starting with "pass" will be assumed a password and input will not be echoed.
func Create(secustom string, servicename string, arg ...string) {
	bar(servicename)
	configfields := &Seconf{
		Path: secustom,
		Args: arg,
	}

	var m1 map[int]string = map[int]string{}
	var newsplice []string
	for i := range configfields.Args {
		bar(servicename)
		if len(configfields.Args[i]) > 4 {
			if configfields.Args[i][0:4] == "pass" || configfields.Args[i][0:4] == "Pass" {
				//		fmt.Printf("\n### " + servicename + " ###\n")
				m1[i], _ = speakeasy.Ask(servicename + " " + configfields.Args[i] + ":")
				if m1[i] == "" {
					bar(secustom)
					m1[i], _ = speakeasy.Ask(servicename + " " + configfields.Args[i] + ":")
				}
				if m1[i] == "" {
					bar(secustom)
					m1[i], _ = speakeasy.Ask(servicename + " " + configfields.Args[i] + ":")
				}
				if m1[i] == "" {
					bar(secustom)
					fmt.Println(configfields.Args[i] + " cannot be blank.")
					os.Exit(1)
				}

			} else {
				m1[i] = Prompt(configfields.Args[i])
				if m1[i] == "" {
					bar(servicename)
					fmt.Println("Can not be blank.")
					m1[i] = Prompt(configfields.Args[i])
				}
				if m1[i] == "" {
					bar(servicename)
					fmt.Println("Can not be blank.")
					m1[i] = Prompt(configfields.Args[i])
				}
				if m1[i] == "" {
					bar(servicename)
					fmt.Println(configfields.Args[i] + " cannot be blank.")
					os.Exit(1)
				}
			}
		} else {
			m1[i] = Prompt(configfields.Args[i])
		}
		newsplice = append(newsplice, m1[i]+"::::")
	}

	bar(servicename)
	configlock, _ := speakeasy.Ask("Create a password to encrypt config file:\nPress ENTER for no password.")
	var userKey = configlock
	var pad = []byte("«super jumpy fox jumps all over»")

	var messagebox = strings.Join(newsplice, "")
	messagebox = strings.TrimSuffix(messagebox, "::::")
	var message = []byte(messagebox)
	key := []byte(userKey)
	key = append(key, pad...)
	naclKey := new([keySize]byte)
	copy(naclKey[:], key[:keySize])
	nonce := new([nonceSize]byte)
	// Read bytes from random and put them in nonce until it is full.
	_, err := io.ReadFull(rand.Reader, nonce[:])
	if err != nil {
		fmt.Println("Could not read from random:", err)
		os.Exit(1)
	}
	out := make([]byte, nonceSize)
	copy(out, nonce[:])
	out = secretbox.Seal(out, message, nonce, naclKey)
	err = ioutil.WriteFile(ReturnHome()+"/."+secustom, out, 0600)
	if err != nil {
		fmt.Println("Error while writing config file: ", err)
		os.Exit(1)
	}
	fmt.Printf("Config file saved at "+ReturnHome()+"/."+secustom+" \nTotal size is %d bytes.\n",
		len(out))
	os.Exit(0)
}

// Detect returns TRUE if a seconf file exists.
func Detect(secustom string) bool {
	_, err := ioutil.ReadFile(ReturnHome() + "/." + secustom)
	if err != nil {
		return false
	}
	return true
}

// Read returns the decoded configuration file, or an error. Fields are separated by 4 colons. ("::::")
func Read(secustom string) (config string, err error) {
	bar(secustom)
	fmt.Println("Unlocking config file")
	configlock, err = speakeasy.Ask("Password: ")
	bar(secustom)
	var userKey = configlock
	var pad = []byte("«super jumpy fox jumps all over»")
	key := []byte(userKey)
	key = append(key, pad...)
	naclKey := new([keySize]byte)
	copy(naclKey[:], key[:keySize])
	nonce := new([nonceSize]byte)
	in, err := ioutil.ReadFile(ReturnHome() + "/." + secustom)
	if err != nil {
		fmt.Println(err)

	}
	copy(nonce[:], in[:nonceSize])
	configbytes, ok := secretbox.Open(nil, in[nonceSize:], nonce, naclKey)
	if !ok {
		fmt.Println("Could not decrypt the config file. Wrong password?")
		os.Exit(1)
	}
	return string(configbytes), nil

}

// Cheap and effective way of clearing screen on unix. Ugly on windows.
func bar(secustom string) {
	versionbar := strings.Repeat("#", 10) + "\t" + secustom + "\t" + strings.Repeat("#", 30)
	print("\033[H\033[2J")
	fmt.Println(versionbar)
}

// ReturnHome is a cross-OS way of getting a HOMEDIR.
func ReturnHome() (homedir string) {
	homedir = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
	if homedir == "" {
		homedir = os.Getenv("USERPROFILE")
	}
	if homedir == "" {
		homedir = os.Getenv("HOME")
	}
	return
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"

	"github.com/go-martini/martini"
	"github.com/justone/go-minibus"
	"github.com/martini-contrib/cors"
	"github.com/martini-contrib/encoder"
	// "github.com/martini-contrib/staticbin"
)

type Payment struct {
	Location string  `json:"location"`
	Amount   float32 `json:"amount"`
}

type Data struct {
	StartAmount float32   `json:"start-amount"`
	Payments    []Payment `json:"previous-payments"`
}

func main() {

	m := martini.New()
	route := martini.NewRouter()

	m.Use(cors.Allow(&cors.Options{
		AllowOrigins:     []string{"http://localhost:3449"},
		AllowMethods:     []string{"GET", "PATCH"},
		AllowHeaders:     []string{"Origin"},
		ExposeHeaders:    []string{"Content-Length"},
		AllowCredentials: true,
	}))

	// generated with "go-bindata -o ../../static.go public/..."
	// m.Use(staticbin.Static("public", Asset))

	m.Use(func(c martini.Context, w http.ResponseWriter) {
		c.MapTo(encoder.JsonEncoder{PrettyPrint: true}, (*encoder.Encoder)(nil))
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
	})

	route.Get("/test", func(enc encoder.Encoder) (int, []byte) {
		result := &Data{
			513.22,
			[]Payment{
				{"Rite-aid", 5.28},
				{"Trader Joe's", 104.22},
			},
		}

		return http.StatusOK, encoder.Must(enc.Encode(result))
	})

	mb := minibus.Init()

	route.Get("/conn/:cust/:conn", func(res http.ResponseWriter, params martini.Params) {
		cust := params["cust"]
		conn := params["conn"]

		message, err := mb.Receive(cust, conn)
		if err != nil {
			http.Error(res, "Timeout", http.StatusRequestTimeout)
		} else {
			fmt.Fprintln(res, message.Contents)
		}
	})
	route.Post("/conn/:cust", func(res http.ResponseWriter, req *http.Request, params martini.Params) {
		cust := params["cust"]

		body, err := ioutil.ReadAll(req.Body)
		if err != nil {
			http.Error(res, "Unable to read request body: "+err.Error(), http.StatusInternalServerError)
			return
		}

		mb.Send(cust, string(body))
	})

	m.Action(route.Handle)

	log.Println("Waiting for connections...")

	m.Run()
}
<commit_msg>get martini encoder working again<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strconv"

	"github.com/go-martini/martini"
	"github.com/justone/go-minibus"
	"github.com/martini-contrib/cors"
	"github.com/martini-contrib/encoder"
	// "github.com/martini-contrib/staticbin"
)

type Payment struct {
	Location string  `json:"location"`
	Amount   float32 `json:"amount"`
}

func (this Payment) Filter() interface{} {
	return this
}

type Data struct {
	StartAmount float32   `json:"start-amount"`
	Payments    []Payment `json:"previous-payments"`
}

func (this Data) Filter() interface{} {
	return this
}

func main() {

	m := martini.New()
	route := martini.NewRouter()

	m.Use(cors.Allow(&cors.Options{
		AllowOrigins:     []string{"http://localhost:3449"},
		AllowMethods:     []string{"GET", "PATCH"},
		AllowHeaders:     []string{"Origin"},
		ExposeHeaders:    []string{"Content-Length"},
		AllowCredentials: true,
	}))

	// generated with "go-bindata -o ../../static.go public/..."
	// m.Use(staticbin.Static("public", Asset))

	m.Use(func(c martini.Context, w http.ResponseWriter, r *http.Request) {
		// Use indentations. &pretty=1
		pretty, _ := strconv.ParseBool(r.FormValue("pretty"))

		c.MapTo(encoder.JsonEncoder{PrettyPrint: pretty}, (*encoder.Encoder)(nil))
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
	})

	route.Get("/test", func(enc encoder.Encoder) (int, []byte) {
		result := &Data{
			513.22,
			[]Payment{
				{"Rite-aid", 5.28},
				{"Trader Joe's", 104.22},
			},
		}

		return http.StatusOK, encoder.Must(enc.Encode(result))
	})

	mb := minibus.Init()

	route.Get("/conn/:cust/:conn", func(res http.ResponseWriter, params martini.Params) {
		cust := params["cust"]
		conn := params["conn"]

		message, err := mb.Receive(cust, conn)
		if err != nil {
			http.Error(res, "Timeout", http.StatusRequestTimeout)
		} else {
			fmt.Fprintln(res, message.Contents)
		}
	})
	route.Post("/conn/:cust", func(res http.ResponseWriter, req *http.Request, params martini.Params) {
		cust := params["cust"]

		body, err := ioutil.ReadAll(req.Body)
		if err != nil {
			http.Error(res, "Unable to read request body: "+err.Error(), http.StatusInternalServerError)
			return
		}

		mb.Send(cust, string(body))
	})

	m.Action(route.Handle)

	log.Println("Waiting for connections...")

	m.Run()
}
<|endoftext|>


<commit_before>package imageproxy

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
)

type Server struct {
	httpServer *http.Server
	cache      Cache
}

func NewServer(httpServer *http.Server, cache Cache) *Server {
	return &Server{
		httpServer: httpServer,
		cache:      cache,
	}
}

func (server *Server) Run() {
	serveMux := http.NewServeMux()
	serveMux.HandleFunc("/", server.handleHttpRequest)
	server.httpServer.Handler = serveMux
	server.httpServer.ListenAndServe()
}

func (server *Server) handleHttpRequest(writer http.ResponseWriter, request *http.Request) {
	source, parameters, err := server.parseRequest(request)
	if err != nil {
		http.Error(writer, err.Error(), http.StatusBadRequest)
		return
	}

	fmt.Println(source, parameters, err)

	sourceImage, err := server.getSourceImage(source)
	fmt.Println(sourceImage, err)
}

func (server *Server) parseRequest(request *http.Request) (source *url.URL, parameters *Parameters, err error) {
	if request.Method != "GET" {
		err = errors.New("Invalid request method")
		return
	}

	query := request.URL.Query()

	if len(query["source"]) == 0 {
		err = errors.New("Missing source parameter")
		return
	}
	source, err = url.ParseRequestURI(query["source"][0])
	if err != nil {
		err = fmt.Errorf("Invalid source parameter (%s)", err)
		return
	}

	parameters = &Parameters{}

	if len(query["width"]) > 0 {
		parameters.Width, err = strconv.Atoi(query["width"][0])
		if err != nil {
			return
		}
		if parameters.Width < 0 {
			err = errors.New("Invalid width parameter")
			return
		}
	}

	if len(query["height"]) > 0 {
		parameters.Height, err = strconv.Atoi(query["height"][0])
		if err != nil {
			return
		}
		if parameters.Height < 0 {
			err = errors.New("Invalid height parameter")
			return
		}
	}

	return
}

func (server *Server) getSourceImage(source *url.URL) (image *Image, err error) {
	response, err := http.Get(source.String())
	if err != nil {
		return
	}

	if response.StatusCode != 200 {
		err = errors.New("Error while downloading source")
		return
	}

	fmt.Println(response)

	defer response.Body.Close()
	data, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return
	}
	image = &Image{
		Data: data,
	}

	return
}
<commit_msg>get type from source image<commit_after>package imageproxy

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
)

type Server struct {
	httpServer *http.Server
	cache      Cache
}

func NewServer(httpServer *http.Server, cache Cache) *Server {
	return &Server{
		httpServer: httpServer,
		cache:      cache,
	}
}

func (server *Server) Run() {
	serveMux := http.NewServeMux()
	serveMux.HandleFunc("/", server.handleHttpRequest)
	server.httpServer.Handler = serveMux
	server.httpServer.ListenAndServe()
}

func (server *Server) handleHttpRequest(writer http.ResponseWriter, request *http.Request) {
	source, parameters, err := server.parseRequest(request)
	if err != nil {
		http.Error(writer, err.Error(), http.StatusBadRequest)
		return
	}

	fmt.Println(source, parameters, err)

	sourceImage, err := server.getSourceImage(source)
	fmt.Println(sourceImage, err)
}

func (server *Server) parseRequest(request *http.Request) (source *url.URL, parameters *Parameters, err error) {
	if request.Method != "GET" {
		err = errors.New("Invalid request method")
		return
	}

	query := request.URL.Query()

	sourceString := query.Get("source")
	if len(sourceString) == 0 {
		err = errors.New("Missing source parameter")
		return
	}
	source, err = url.ParseRequestURI(sourceString)
	if err != nil {
		err = fmt.Errorf("Invalid source parameter (%s)", err)
		return
	}

	parameters = &Parameters{}

	widthString := query.Get("width")
	if len(widthString) > 0 {
		parameters.Width, err = strconv.Atoi(widthString)
		if err != nil {
			return
		}
		if parameters.Width < 0 {
			err = errors.New("Invalid width parameter")
			return
		}
	}

	heightString := query.Get("height")
	if len(heightString) > 0 {
		parameters.Height, err = strconv.Atoi(heightString)
		if err != nil {
			return
		}
		if parameters.Height < 0 {
			err = errors.New("Invalid height parameter")
			return
		}
	}

	return
}

func (server *Server) getSourceImage(source *url.URL) (image *Image, err error) {
	response, err := http.Get(source.String())
	if err != nil {
		return
	}

	if response.StatusCode != 200 {
		err = errors.New("Error while downloading source")
		return
	}

	image = &Image{}

	contentType := response.Header.Get("Content-Type")
	if len(contentType) > 0 {
		r, _ := regexp.Compile("image/(.+)")
		matches := r.FindStringSubmatch(contentType)
		if matches != nil && len(matches) == 2 {
			image.Type = matches[1]
		}
	}

	defer response.Body.Close()
	image.Data, err = ioutil.ReadAll(response.Body)
	if err != nil {
		return
	}

	return
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"path/filepath"
	"strings"
	"time"

	"github.com/coreos/go-etcd/etcd"
	"github.com/miekg/dns"
	"github.com/spf13/cobra"
)

type (
	server struct {
		etcd   *etcd.Client
		domain string
		prefix string
		ttl    uint32
	}
)

func nameError(w dns.ResponseWriter, req *dns.Msg) {
	m := &dns.Msg{}
	m.SetReply(req)
	m.SetRcode(req, dns.RcodeNameError)
	_ = w.WriteMsg(m)
}

func (s *server) GetNode(name string) (*node, error) {
	name = strings.ToLower(name)
	name = strings.TrimSuffix(name, ".nodes.")
	key := filepath.Join("/", s.prefix, "nodes", name)

	resp, err := s.etcd.Get(key, false, false)

	if err != nil {
		return nil, err
	}

	var n node
	err = json.Unmarshal([]byte(resp.Node.Value), &n)
	if err != nil {
		return nil, err
	}

	if n.IP == nil {
		return nil, nil
	}
	return &n, nil
}

func (s *server) GetService(name string) ([]*record, error) {
	name = strings.TrimSuffix(name, ".services.")

	key := filepath.Join("/", s.prefix, "services", name)

	resp, err := s.etcd.Get(key, false, true)

	if err != nil {
		return nil, err
	}

	records := make([]*record, 0, len(resp.Node.Nodes))

	for _, n := range resp.Node.Nodes {
		var rec record
		err := json.Unmarshal([]byte(n.Value), &rec)
		if err != nil {
			log.Printf("json.Unmarshal failed for %s: %s", n.Key, err)
			continue
		}

		// should match against a regex?
		if rec.Target == "" {
			continue
		}

		records = append(records, &rec)
	}

	return records, nil
}

func (s *server) ServicesA(w dns.ResponseWriter, r *dns.Msg, name string) (*dns.Msg, error) {
	records, err := s.GetService(name)

	if err != nil {
		return nil, err
	}

	if len(records) == 0 {
		return nil, nil
	}
	m := &dns.Msg{}
	m.SetReply(r)

	header := dns.RR_Header{
		Name:   r.Question[0].Name,
		Rrtype: r.Question[0].Qtype,
		Class:  r.Question[0].Qclass,
		Ttl:    s.ttl,
	}

	m.Answer = make([]dns.RR, 0, len(records))
	for _, record := range records {
		// any target that contains a "." is assumed to not be a node
		if strings.Contains(record.Target, ".") {
			continue
		}

		node, err := s.GetNode(record.Target)
		if err != nil {
			continue
		}

		answer := &dns.A{
			Hdr: header,
			A:   node.IP,
		}

		m.Answer = append(m.Answer, answer)
	}

	return m, nil

}

func (s *server) ServicesSRV(w dns.ResponseWriter, r *dns.Msg, name string) (*dns.Msg, error) {
	records, err := s.GetService(name)
	if err != nil {
		return nil, err
	}

	if len(records) == 0 {
		return nil, nil
	}

	m := &dns.Msg{}
	m.SetReply(r)

	header := dns.RR_Header{
		Name:   r.Question[0].Name,
		Rrtype: r.Question[0].Qtype,
		Class:  r.Question[0].Qclass,
		Ttl:    s.ttl,
	}

	m.Answer = make([]dns.RR, 0, len(records))
	m.Extra = make([]dns.RR, 0, len(records))
	for _, record := range records {

		isNode := false

		target := strings.ToLower(record.Target)
		if !strings.Contains(target, ".") {
			target = strings.Join([]string{target, s.domain}, ".")
			isNode = true
			// should we make sure it exists before we add it?
		}

		answer := &dns.SRV{
			Hdr:      header,
			Priority: record.Priority,
			Weight:   record.Weight,
			Port:     record.Port,
			Target:   target,
		}

		m.Answer = append(m.Answer, answer)

		if !isNode {
			continue
		}

		node, err := s.GetNode(record.Target)
		if err != nil {
			// just skip it
			continue
		}

		extra := &dns.A{
			Hdr: dns.RR_Header{
				Name:   target,
				Rrtype: dns.TypeA,
				Class:  r.Question[0].Qclass,
				Ttl:    s.ttl,
			},
			A: node.IP,
		}

		m.Extra = append(m.Extra, extra)
	}

	return m, nil

}

func (s *server) NodesA(w dns.ResponseWriter, r *dns.Msg, name string) (*dns.Msg, error) {
	node, err := s.GetNode(name)

	if err != nil {
		return nil, err
	}

	m := &dns.Msg{}
	m.SetReply(r)

	header := dns.RR_Header{
		Name:   r.Question[0].Name,
		Rrtype: r.Question[0].Qtype,
		Class:  r.Question[0].Qclass,
		Ttl:    s.ttl,
	}

	m.Answer = []dns.RR{
		&dns.A{
			Hdr: header,
			A:   node.IP,
		},
	}

	return m, nil

}
func (s *server) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {

	question := strings.ToLower(r.Question[0].Name)
	name := strings.TrimSuffix(question, s.domain)
	parts := strings.Split(name, ".")
	parts = parts[:len(parts)-1]

	if len(parts) != 2 {
		log.Printf("invalid query: %s", question)
		nameError(w, r)
		return
	}

	rType := parts[1]
	qType := r.Question[0].Qtype

	var m *dns.Msg
	var err error
	switch rType {
	case "services":
		switch qType {
		case dns.TypeA:
			m, err = s.ServicesA(w, r, name)
		case dns.TypeSRV:
			m, err = s.ServicesSRV(w, r, name)
		}
	case "nodes":
		switch qType {
		case dns.TypeA:
			m, err = s.NodesA(w, r, name)
		}
	}

	if err != nil {
		log.Println(err)
		if isKeyNotFound(err) {
			nameError(w, r)
		} else {
			dns.HandleFailed(w, r)
		}
		return
	}

	if m == nil {
		log.Printf("%s: not found", question)
		nameError(w, r)
		return
	}

	_ = w.WriteMsg(m)
}

// TODO: start a simple http status interface.  should be a different service?

func runServer(cmd *cobra.Command, args []string) {
	e := etcd.NewClient(([]string{etcdAddress}))

	parts := strings.Split(dnsDomain, ".")
	dom := make([]string, 0, len(parts))
	for _, p := range parts {
		if len(p) > 0 {
			dom = append(dom, p)
		}
	}

	s := &server{
		etcd:   e,
		domain: strings.ToLower(strings.Join(dom, ".") + "."),
		prefix: etcdPrefix,
		ttl:    uint32(dnsTTL),
	}

	dns.Handle(s.domain, s)

	server := &dns.Server{
		Addr:         fmt.Sprintf(":%d", dnsPort),
		Net:          "udp",
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 10 * time.Second,
	}

	log.Fatal(server.ListenAndServe())
}
<commit_msg>log an error when node has no address<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"path/filepath"
	"strings"
	"time"

	"github.com/coreos/go-etcd/etcd"
	"github.com/miekg/dns"
	"github.com/spf13/cobra"
)

type (
	server struct {
		etcd   *etcd.Client
		domain string
		prefix string
		ttl    uint32
	}
)

func nameError(w dns.ResponseWriter, req *dns.Msg) {
	m := &dns.Msg{}
	m.SetReply(req)
	m.SetRcode(req, dns.RcodeNameError)
	_ = w.WriteMsg(m)
}

func (s *server) GetNode(name string) (*node, error) {
	name = strings.ToLower(name)
	name = strings.TrimSuffix(name, ".nodes.")
	key := filepath.Join("/", s.prefix, "nodes", name)

	resp, err := s.etcd.Get(key, false, false)

	if err != nil {
		return nil, err
	}

	var n node
	err = json.Unmarshal([]byte(resp.Node.Value), &n)
	if err != nil {
		return nil, err
	}

	if n.IP == nil {
		log.Printf("node %s has no address", name)
		return nil, nil
	}
	return &n, nil
}

func (s *server) GetService(name string) ([]*record, error) {
	name = strings.TrimSuffix(name, ".services.")

	key := filepath.Join("/", s.prefix, "services", name)

	resp, err := s.etcd.Get(key, false, true)

	if err != nil {
		return nil, err
	}

	records := make([]*record, 0, len(resp.Node.Nodes))

	for _, n := range resp.Node.Nodes {
		var rec record
		err := json.Unmarshal([]byte(n.Value), &rec)
		if err != nil {
			log.Printf("json.Unmarshal failed for %s: %s", n.Key, err)
			continue
		}

		// should match against a regex?
		if rec.Target == "" {
			continue
		}

		records = append(records, &rec)
	}

	return records, nil
}

func (s *server) ServicesA(w dns.ResponseWriter, r *dns.Msg, name string) (*dns.Msg, error) {
	records, err := s.GetService(name)

	if err != nil {
		return nil, err
	}

	if len(records) == 0 {
		return nil, nil
	}
	m := &dns.Msg{}
	m.SetReply(r)

	header := dns.RR_Header{
		Name:   r.Question[0].Name,
		Rrtype: r.Question[0].Qtype,
		Class:  r.Question[0].Qclass,
		Ttl:    s.ttl,
	}

	m.Answer = make([]dns.RR, 0, len(records))
	for _, record := range records {
		// any target that contains a "." is assumed to not be a node
		if strings.Contains(record.Target, ".") {
			continue
		}

		node, err := s.GetNode(record.Target)
		if err != nil {
			continue
		}

		answer := &dns.A{
			Hdr: header,
			A:   node.IP,
		}

		m.Answer = append(m.Answer, answer)
	}

	return m, nil

}

func (s *server) ServicesSRV(w dns.ResponseWriter, r *dns.Msg, name string) (*dns.Msg, error) {
	records, err := s.GetService(name)
	if err != nil {
		return nil, err
	}

	if len(records) == 0 {
		return nil, nil
	}

	m := &dns.Msg{}
	m.SetReply(r)

	header := dns.RR_Header{
		Name:   r.Question[0].Name,
		Rrtype: r.Question[0].Qtype,
		Class:  r.Question[0].Qclass,
		Ttl:    s.ttl,
	}

	m.Answer = make([]dns.RR, 0, len(records))
	m.Extra = make([]dns.RR, 0, len(records))
	for _, record := range records {

		isNode := false

		target := strings.ToLower(record.Target)
		if !strings.Contains(target, ".") {
			target = strings.Join([]string{target, s.domain}, ".")
			isNode = true
			// should we make sure it exists before we add it?
		}

		answer := &dns.SRV{
			Hdr:      header,
			Priority: record.Priority,
			Weight:   record.Weight,
			Port:     record.Port,
			Target:   target,
		}

		m.Answer = append(m.Answer, answer)

		if !isNode {
			continue
		}

		node, err := s.GetNode(record.Target)
		if err != nil {
			// just skip it
			continue
		}

		extra := &dns.A{
			Hdr: dns.RR_Header{
				Name:   target,
				Rrtype: dns.TypeA,
				Class:  r.Question[0].Qclass,
				Ttl:    s.ttl,
			},
			A: node.IP,
		}

		m.Extra = append(m.Extra, extra)
	}

	return m, nil

}

func (s *server) NodesA(w dns.ResponseWriter, r *dns.Msg, name string) (*dns.Msg, error) {
	node, err := s.GetNode(name)

	if err != nil {
		return nil, err
	}

	m := &dns.Msg{}
	m.SetReply(r)

	header := dns.RR_Header{
		Name:   r.Question[0].Name,
		Rrtype: r.Question[0].Qtype,
		Class:  r.Question[0].Qclass,
		Ttl:    s.ttl,
	}

	m.Answer = []dns.RR{
		&dns.A{
			Hdr: header,
			A:   node.IP,
		},
	}

	return m, nil

}
func (s *server) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {

	question := strings.ToLower(r.Question[0].Name)
	name := strings.TrimSuffix(question, s.domain)
	parts := strings.Split(name, ".")
	parts = parts[:len(parts)-1]

	if len(parts) != 2 {
		log.Printf("invalid query: %s", question)
		nameError(w, r)
		return
	}

	rType := parts[1]
	qType := r.Question[0].Qtype

	var m *dns.Msg
	var err error
	switch rType {
	case "services":
		switch qType {
		case dns.TypeA:
			m, err = s.ServicesA(w, r, name)
		case dns.TypeSRV:
			m, err = s.ServicesSRV(w, r, name)
		}
	case "nodes":
		switch qType {
		case dns.TypeA:
			m, err = s.NodesA(w, r, name)
		}
	}

	if err != nil {
		log.Println(err)
		if isKeyNotFound(err) {
			nameError(w, r)
		} else {
			dns.HandleFailed(w, r)
		}
		return
	}

	if m == nil {
		log.Printf("%s: not found", question)
		nameError(w, r)
		return
	}

	_ = w.WriteMsg(m)
}

// TODO: start a simple http status interface.  should be a different service?

func runServer(cmd *cobra.Command, args []string) {
	e := etcd.NewClient(([]string{etcdAddress}))

	parts := strings.Split(dnsDomain, ".")
	dom := make([]string, 0, len(parts))
	for _, p := range parts {
		if len(p) > 0 {
			dom = append(dom, p)
		}
	}

	s := &server{
		etcd:   e,
		domain: strings.ToLower(strings.Join(dom, ".") + "."),
		prefix: etcdPrefix,
		ttl:    uint32(dnsTTL),
	}

	dns.Handle(s.domain, s)

	server := &dns.Server{
		Addr:         fmt.Sprintf(":%d", dnsPort),
		Net:          "udp",
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 10 * time.Second,
	}

	log.Fatal(server.ListenAndServe())
}
<|endoftext|>


<commit_before>package main

import (
	"./uuid"
	"code.google.com/p/go.net/websocket"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
	"strings"
	"text/template"
)

type ServerConfig struct {
	Hostname     string `json:"hostname"`
	Port         string `json:"port"`
	NotifyPrefix string `json:"notifyPrefix"`
}

var gServerConfig ServerConfig

type Client struct {
	Websocket *websocket.Conn `json:"-"`
	UAID      string          `json:"uaid"`
	Ip        string          `json:"ip"`
	Port      float64         `json:"port"`
}

type Channel struct {
	UAID      string `json:"uaid"`
	ChannelID string `json:"channelID"`
	Version   string `json:"version"`
}

type ChannelIDSet map[string]*Channel

type ServerState struct {
	// Mapping from a UAID to the Client object
	ConnectedClients map[string]*Client `json:"connectedClients"`

	// Mapping from a UAID to all channelIDs owned by that UAID
	// where channelIDs are represented as a map-backed set
	UAIDToChannelIDs map[string]ChannelIDSet `json:"uaidToChannels"`

	// Mapping from a ChannelID to the cooresponding Channel
	ChannelIDToChannel ChannelIDSet `json:"channelIDToChannel"`
}

var gServerState ServerState

func readConfig() {

	var data []byte
	var err error

	data, err = ioutil.ReadFile("config.json")
	if err != nil {
		log.Println("Not configured.  Could not find config.json")
		os.Exit(-1)
	}

	err = json.Unmarshal(data, &gServerConfig)
	if err != nil {
		log.Println("Could not unmarshal config.json", err)
		os.Exit(-1)
		return
	}
}

func openState() {
	var data []byte
	var err error

	data, err = ioutil.ReadFile("serverstate.json")
	if err == nil {
		err = json.Unmarshal(data, &gServerState)
		if err == nil {
			return
		}
	}

	log.Println(" -> creating new server state")
	gServerState.UAIDToChannelIDs = make(map[string]ChannelIDSet)
	gServerState.ChannelIDToChannel = make(ChannelIDSet)
	gServerState.ConnectedClients = make(map[string]*Client)
}

func saveState() {
	log.Println(" -> saving state..")

	var data []byte
	var err error

	data, err = json.Marshal(gServerState)
	if err != nil {
		return
	}
	ioutil.WriteFile("serverstate.json", data, 0644)
}

func handleRegister(client *Client, f map[string]interface{}) {
	type RegisterResponse struct {
		Name         string `json:"messageType"`
		Status       int    `json:"status"`
		PushEndpoint string `json:"pushEndpoint"`
		ChannelID    string `json:"channelID"`
	}

	if f["channelID"] == nil {
		log.Println("channelID is missing!")
		return
	}

	var channelID = f["channelID"].(string)

	register := RegisterResponse{"register", 0, "", channelID}

	prevEntry, exists := gServerState.ChannelIDToChannel[channelID]
	if exists && prevEntry.UAID != client.UAID {
		register.Status = 409
	} else {
		// TODO https!
		var pushEndpoint = "http://" + gServerConfig.Hostname + ":" + gServerConfig.Port + gServerConfig.NotifyPrefix + channelID

		channel := &Channel{client.UAID, channelID, ""}

		if gServerState.UAIDToChannelIDs[client.UAID] == nil {
			gServerState.UAIDToChannelIDs[client.UAID] = make(ChannelIDSet)
		}
		gServerState.UAIDToChannelIDs[client.UAID][channelID] = channel
		gServerState.ChannelIDToChannel[channelID] = channel

		register.Status = 200
		register.PushEndpoint = pushEndpoint
	}

	if register.Status == 0 {
		panic("Register(): status field was left unset when replying to client")
	}

	j, err := json.Marshal(register)
	if err != nil {
		log.Println("Could not convert register response to json %s", err)
		return
	}

	if err = websocket.Message.Send(client.Websocket, string(j)); err != nil {
		// we could not send the message to a peer
		log.Println("Could not send message to ", client.Websocket, err.Error())
	}
}

func handleUnregister(client *Client, f map[string]interface{}) {

	if f["channelID"] == nil {
		log.Println("channelID is missing!")
		return
	}

	var channelID = f["channelID"].(string)
	_, ok := gServerState.ChannelIDToChannel[channelID]
	if ok {
		// only delete if UA owns this channel
		_, owns := gServerState.UAIDToChannelIDs[client.UAID][channelID]
		if owns {
			// remove ownership
			delete(gServerState.UAIDToChannelIDs[client.UAID], channelID)
			// delete the channel itself
			delete(gServerState.ChannelIDToChannel, channelID)
		}
	}

	type UnregisterResponse struct {
		Name      string `json:"messageType"`
		Status    int    `json:"status"`
		ChannelID string `json:"channelID"`
	}

	unregister := UnregisterResponse{"unregister", 200, channelID}

	j, err := json.Marshal(unregister)
	if err != nil {
		log.Println("Could not convert unregister response to json %s", err)
		return
	}

	if err = websocket.Message.Send(client.Websocket, string(j)); err != nil {
		// we could not send the message to a peer
		log.Println("Could not send message to ", client.Websocket, err.Error())
	}
}

func handleHello(client *Client, f map[string]interface{}) {

	status := 200

	if f["uaid"] == nil {
		uaid, err := uuid.GenUUID()
		if err != nil {
			status = 400
			log.Println("GenUUID error %s", err)
		}
		client.UAID = uaid
	} else {
		client.UAID = f["uaid"].(string)

		// BUG(nikhilm): Does not deal with sending
		// a new UAID if their is a channel that was sent
		// by the UA which the server does not know about.
		// Which means in the case of this memory only server
		// it should actually always send a new UAID when it was
		// restarted
		if f["channelIDs"] != nil {
			for _, foo := range f["channelIDs"].([]interface{}) {
				channelID := foo.(string)

				c := &Channel{client.UAID, channelID, ""}
				gServerState.UAIDToChannelIDs[client.UAID][channelID] = c
				gServerState.ChannelIDToChannel[channelID] = c
			}
		}
	}

	gServerState.ConnectedClients[client.UAID] = client

	if f["interface"] != nil {
		m := f["interface"].(map[string]interface{})
		client.Ip = m["ip"].(string)
		client.Port = m["port"].(float64)
	}

	type HelloResponse struct {
		Name   string `json:"messageType"`
		Status int    `json:"status"`
		UAID   string `json:"uaid"`
	}

	hello := HelloResponse{"hello", status, client.UAID}

	j, err := json.Marshal(hello)
	if err != nil {
		log.Println("Could not convert hello response to json %s", err)
		return
	}

	if err = websocket.Message.Send(client.Websocket, string(j)); err != nil {
		log.Println("Could not send message to ", client.Websocket, err.Error())
	}
}

func handleAck(client *Client, f map[string]interface{}) {
}

func pushHandler(ws *websocket.Conn) {

	client := &Client{ws, "", "", 0}

	for {
		var f map[string]interface{}

		var err error
		if err = websocket.JSON.Receive(ws, &f); err != nil {
			log.Println("Websocket Disconnected.", err.Error())
			break
		}

		log.Println("pushHandler msg: ", f["messageType"])

		switch f["messageType"] {
		case "hello":
			handleHello(client, f)
			break

		case "register":
			handleRegister(client, f)
			break

		case "unregister":
			handleUnregister(client, f)
			break

		case "ack":
			handleAck(client, f)
			break

		default:
			log.Println(" -> Unknown", f)
			break
		}

		saveState()
	}

	log.Println("Closing Websocket!")
	ws.Close()

	gServerState.ConnectedClients[client.UAID].Websocket = nil
}

func notifyHandler(w http.ResponseWriter, r *http.Request) {
	log.Println("Got notification from app server ", r.URL)

	if r.Method != "PUT" {
		log.Println("NOT A PUT")
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Method must be PUT."))
		return
	}

	channelID := strings.Replace(r.URL.Path, gServerConfig.NotifyPrefix, "", 1)

	if strings.Contains(channelID, "/") {
		log.Println("Could not find a valid channelID")
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Could not find a valid channelID."))
		return
	}

	value := r.FormValue("version")

	if value == "" {
		log.Println("Could not find version")
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Could not find version."))
		return
	}

	channel, found := gServerState.ChannelIDToChannel[channelID]
	if !found {
		log.Println("Could not find channel " + channelID)
		return
	}
	channel.Version = value

	client := gServerState.ConnectedClients[channel.UAID]

	saveState()

	if client == nil {
		log.Println("no known client for the channel.")
	} else if client.Websocket == nil {
		wakeupClient(client)
	} else {
		sendNotificationToClient(client, channel)
	}

	w.WriteHeader(http.StatusOK)
	w.Write([]byte("OK"))
}

func wakeupClient(client *Client) {

	// TODO probably want to do this a few times before
	// giving up.

	log.Println("wakeupClient: ", client)
	service := fmt.Sprintf("%s:%g", client.Ip, client.Port)

	udpAddr, err := net.ResolveUDPAddr("up4", service)
	if err != nil {
		log.Println("ResolveUDPAddr error ", err.Error())
		return
	}

	conn, err := net.DialUDP("udp", nil, udpAddr)
	if err != nil {
		log.Println("DialUDP error ", err.Error())
		return
	}

	_, err = conn.Write([]byte(""))
	if err != nil {
		log.Println("UDP Write error ", err.Error())
		return
	}

}

func sendNotificationToClient(client *Client, channel *Channel) {

	type NotificationResponse struct {
		Name     string    `json:"messageType"`
		Channels []Channel `json:"updates"`
	}

	var channels []Channel
	channels = append(channels, *channel)

	notification := NotificationResponse{"notification", channels}

	j, err := json.Marshal(notification)
	if err != nil {
		log.Println("Could not convert hello response to json %s", err)
		return
	}

	if err = websocket.Message.Send(client.Websocket, string(j)); err != nil {
		log.Println("Could not send message to ", channel, err.Error())
	}
}

func admin(w http.ResponseWriter, r *http.Request) {

	type User struct {
		UAID      string
		Connected bool
		Channels  []*Channel
	}

	type Arguments struct {
		PushEndpointPrefix string
		Users              []User
	}

	// TODO https!
	arguments := Arguments{"http://" + gServerConfig.Hostname + ":" + gServerConfig.Port + gServerConfig.NotifyPrefix, nil}

	for uaid, channelIDSet := range gServerState.UAIDToChannelIDs {
		log.Println("Foo ", uaid)
		connected := gServerState.ConnectedClients[uaid] != nil
		var channels []*Channel
		for _, channel := range channelIDSet {
			channels = append(channels, channel)
		}
		u := User{uaid, connected, channels}
		arguments.Users = append(arguments.Users, u)
	}

	t := template.New("users.template")
	s1, _ := t.ParseFiles("templates/users.template")
	s1.Execute(w, arguments)
}

func main() {

	readConfig()

	openState()

	http.HandleFunc("/admin", admin)

	http.Handle("/", websocket.Handler(pushHandler))

	http.HandleFunc(gServerConfig.NotifyPrefix, notifyHandler)

	log.Println("Listening on", gServerConfig.Hostname+":"+gServerConfig.Port)
	log.Fatal(http.ListenAndServe(gServerConfig.Hostname+":"+gServerConfig.Port, nil))
}
<commit_msg>Remove log<commit_after>package main

import (
	"./uuid"
	"code.google.com/p/go.net/websocket"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
	"strings"
	"text/template"
)

type ServerConfig struct {
	Hostname     string `json:"hostname"`
	Port         string `json:"port"`
	NotifyPrefix string `json:"notifyPrefix"`
}

var gServerConfig ServerConfig

type Client struct {
	Websocket *websocket.Conn `json:"-"`
	UAID      string          `json:"uaid"`
	Ip        string          `json:"ip"`
	Port      float64         `json:"port"`
}

type Channel struct {
	UAID      string `json:"uaid"`
	ChannelID string `json:"channelID"`
	Version   string `json:"version"`
}

type ChannelIDSet map[string]*Channel

type ServerState struct {
	// Mapping from a UAID to the Client object
	ConnectedClients map[string]*Client `json:"connectedClients"`

	// Mapping from a UAID to all channelIDs owned by that UAID
	// where channelIDs are represented as a map-backed set
	UAIDToChannelIDs map[string]ChannelIDSet `json:"uaidToChannels"`

	// Mapping from a ChannelID to the cooresponding Channel
	ChannelIDToChannel ChannelIDSet `json:"channelIDToChannel"`
}

var gServerState ServerState

func readConfig() {

	var data []byte
	var err error

	data, err = ioutil.ReadFile("config.json")
	if err != nil {
		log.Println("Not configured.  Could not find config.json")
		os.Exit(-1)
	}

	err = json.Unmarshal(data, &gServerConfig)
	if err != nil {
		log.Println("Could not unmarshal config.json", err)
		os.Exit(-1)
		return
	}
}

func openState() {
	var data []byte
	var err error

	data, err = ioutil.ReadFile("serverstate.json")
	if err == nil {
		err = json.Unmarshal(data, &gServerState)
		if err == nil {
			return
		}
	}

	log.Println(" -> creating new server state")
	gServerState.UAIDToChannelIDs = make(map[string]ChannelIDSet)
	gServerState.ChannelIDToChannel = make(ChannelIDSet)
	gServerState.ConnectedClients = make(map[string]*Client)
}

func saveState() {
	log.Println(" -> saving state..")

	var data []byte
	var err error

	data, err = json.Marshal(gServerState)
	if err != nil {
		return
	}
	ioutil.WriteFile("serverstate.json", data, 0644)
}

func handleRegister(client *Client, f map[string]interface{}) {
	type RegisterResponse struct {
		Name         string `json:"messageType"`
		Status       int    `json:"status"`
		PushEndpoint string `json:"pushEndpoint"`
		ChannelID    string `json:"channelID"`
	}

	if f["channelID"] == nil {
		log.Println("channelID is missing!")
		return
	}

	var channelID = f["channelID"].(string)

	register := RegisterResponse{"register", 0, "", channelID}

	prevEntry, exists := gServerState.ChannelIDToChannel[channelID]
	if exists && prevEntry.UAID != client.UAID {
		register.Status = 409
	} else {
		// TODO https!
		var pushEndpoint = "http://" + gServerConfig.Hostname + ":" + gServerConfig.Port + gServerConfig.NotifyPrefix + channelID

		channel := &Channel{client.UAID, channelID, ""}

		if gServerState.UAIDToChannelIDs[client.UAID] == nil {
			gServerState.UAIDToChannelIDs[client.UAID] = make(ChannelIDSet)
		}
		gServerState.UAIDToChannelIDs[client.UAID][channelID] = channel
		gServerState.ChannelIDToChannel[channelID] = channel

		register.Status = 200
		register.PushEndpoint = pushEndpoint
	}

	if register.Status == 0 {
		panic("Register(): status field was left unset when replying to client")
	}

	j, err := json.Marshal(register)
	if err != nil {
		log.Println("Could not convert register response to json %s", err)
		return
	}

	if err = websocket.Message.Send(client.Websocket, string(j)); err != nil {
		// we could not send the message to a peer
		log.Println("Could not send message to ", client.Websocket, err.Error())
	}
}

func handleUnregister(client *Client, f map[string]interface{}) {

	if f["channelID"] == nil {
		log.Println("channelID is missing!")
		return
	}

	var channelID = f["channelID"].(string)
	_, ok := gServerState.ChannelIDToChannel[channelID]
	if ok {
		// only delete if UA owns this channel
		_, owns := gServerState.UAIDToChannelIDs[client.UAID][channelID]
		if owns {
			// remove ownership
			delete(gServerState.UAIDToChannelIDs[client.UAID], channelID)
			// delete the channel itself
			delete(gServerState.ChannelIDToChannel, channelID)
		}
	}

	type UnregisterResponse struct {
		Name      string `json:"messageType"`
		Status    int    `json:"status"`
		ChannelID string `json:"channelID"`
	}

	unregister := UnregisterResponse{"unregister", 200, channelID}

	j, err := json.Marshal(unregister)
	if err != nil {
		log.Println("Could not convert unregister response to json %s", err)
		return
	}

	if err = websocket.Message.Send(client.Websocket, string(j)); err != nil {
		// we could not send the message to a peer
		log.Println("Could not send message to ", client.Websocket, err.Error())
	}
}

func handleHello(client *Client, f map[string]interface{}) {

	status := 200

	if f["uaid"] == nil {
		uaid, err := uuid.GenUUID()
		if err != nil {
			status = 400
			log.Println("GenUUID error %s", err)
		}
		client.UAID = uaid
	} else {
		client.UAID = f["uaid"].(string)

		// BUG(nikhilm): Does not deal with sending
		// a new UAID if their is a channel that was sent
		// by the UA which the server does not know about.
		// Which means in the case of this memory only server
		// it should actually always send a new UAID when it was
		// restarted
		if f["channelIDs"] != nil {
			for _, foo := range f["channelIDs"].([]interface{}) {
				channelID := foo.(string)

				c := &Channel{client.UAID, channelID, ""}
				gServerState.UAIDToChannelIDs[client.UAID][channelID] = c
				gServerState.ChannelIDToChannel[channelID] = c
			}
		}
	}

	gServerState.ConnectedClients[client.UAID] = client

	if f["interface"] != nil {
		m := f["interface"].(map[string]interface{})
		client.Ip = m["ip"].(string)
		client.Port = m["port"].(float64)
	}

	type HelloResponse struct {
		Name   string `json:"messageType"`
		Status int    `json:"status"`
		UAID   string `json:"uaid"`
	}

	hello := HelloResponse{"hello", status, client.UAID}

	j, err := json.Marshal(hello)
	if err != nil {
		log.Println("Could not convert hello response to json %s", err)
		return
	}

	if err = websocket.Message.Send(client.Websocket, string(j)); err != nil {
		log.Println("Could not send message to ", client.Websocket, err.Error())
	}
}

func handleAck(client *Client, f map[string]interface{}) {
}

func pushHandler(ws *websocket.Conn) {

	client := &Client{ws, "", "", 0}

	for {
		var f map[string]interface{}

		var err error
		if err = websocket.JSON.Receive(ws, &f); err != nil {
			log.Println("Websocket Disconnected.", err.Error())
			break
		}

		log.Println("pushHandler msg: ", f["messageType"])

		switch f["messageType"] {
		case "hello":
			handleHello(client, f)
			break

		case "register":
			handleRegister(client, f)
			break

		case "unregister":
			handleUnregister(client, f)
			break

		case "ack":
			handleAck(client, f)
			break

		default:
			log.Println(" -> Unknown", f)
			break
		}

		saveState()
	}

	log.Println("Closing Websocket!")
	ws.Close()

	gServerState.ConnectedClients[client.UAID].Websocket = nil
}

func notifyHandler(w http.ResponseWriter, r *http.Request) {
	log.Println("Got notification from app server ", r.URL)

	if r.Method != "PUT" {
		log.Println("NOT A PUT")
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Method must be PUT."))
		return
	}

	channelID := strings.Replace(r.URL.Path, gServerConfig.NotifyPrefix, "", 1)

	if strings.Contains(channelID, "/") {
		log.Println("Could not find a valid channelID")
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Could not find a valid channelID."))
		return
	}

	value := r.FormValue("version")

	if value == "" {
		log.Println("Could not find version")
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Could not find version."))
		return
	}

	channel, found := gServerState.ChannelIDToChannel[channelID]
	if !found {
		log.Println("Could not find channel " + channelID)
		return
	}
	channel.Version = value

	client := gServerState.ConnectedClients[channel.UAID]

	saveState()

	if client == nil {
		log.Println("no known client for the channel.")
	} else if client.Websocket == nil {
		wakeupClient(client)
	} else {
		sendNotificationToClient(client, channel)
	}

	w.WriteHeader(http.StatusOK)
	w.Write([]byte("OK"))
}

func wakeupClient(client *Client) {

	// TODO probably want to do this a few times before
	// giving up.

	log.Println("wakeupClient: ", client)
	service := fmt.Sprintf("%s:%g", client.Ip, client.Port)

	udpAddr, err := net.ResolveUDPAddr("up4", service)
	if err != nil {
		log.Println("ResolveUDPAddr error ", err.Error())
		return
	}

	conn, err := net.DialUDP("udp", nil, udpAddr)
	if err != nil {
		log.Println("DialUDP error ", err.Error())
		return
	}

	_, err = conn.Write([]byte(""))
	if err != nil {
		log.Println("UDP Write error ", err.Error())
		return
	}

}

func sendNotificationToClient(client *Client, channel *Channel) {

	type NotificationResponse struct {
		Name     string    `json:"messageType"`
		Channels []Channel `json:"updates"`
	}

	var channels []Channel
	channels = append(channels, *channel)

	notification := NotificationResponse{"notification", channels}

	j, err := json.Marshal(notification)
	if err != nil {
		log.Println("Could not convert hello response to json %s", err)
		return
	}

	if err = websocket.Message.Send(client.Websocket, string(j)); err != nil {
		log.Println("Could not send message to ", channel, err.Error())
	}
}

func admin(w http.ResponseWriter, r *http.Request) {

	type User struct {
		UAID      string
		Connected bool
		Channels  []*Channel
	}

	type Arguments struct {
		PushEndpointPrefix string
		Users              []User
	}

	// TODO https!
	arguments := Arguments{"http://" + gServerConfig.Hostname + ":" + gServerConfig.Port + gServerConfig.NotifyPrefix, nil}

	for uaid, channelIDSet := range gServerState.UAIDToChannelIDs {
		connected := gServerState.ConnectedClients[uaid] != nil
		var channels []*Channel
		for _, channel := range channelIDSet {
			channels = append(channels, channel)
		}
		u := User{uaid, connected, channels}
		arguments.Users = append(arguments.Users, u)
	}

	t := template.New("users.template")
	s1, _ := t.ParseFiles("templates/users.template")
	s1.Execute(w, arguments)
}

func main() {

	readConfig()

	openState()

	http.HandleFunc("/admin", admin)

	http.Handle("/", websocket.Handler(pushHandler))

	http.HandleFunc(gServerConfig.NotifyPrefix, notifyHandler)

	log.Println("Listening on", gServerConfig.Hostname+":"+gServerConfig.Port)
	log.Fatal(http.ListenAndServe(gServerConfig.Hostname+":"+gServerConfig.Port, nil))
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"html/template"
	"log"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"

	"bitbucket.org/gosimple/oauth2"
	"github.com/gorilla/sessions"
	"github.com/russross/blackfriday"
	"github.com/titanous/go.xml"
	"github.com/titanous/sparklemotion/appcast"
	"launchpad.net/goamz/aws"
	"launchpad.net/goamz/s3"
)

type indexInfo struct {
	RequireSignature bool
}

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

var indexHandler = func(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/html")
	err := indexTemplate.Execute(w, &indexInfo{requireSig})
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

var (
	versionPattern   = regexp.MustCompile(`[0-9]\.[0-9]\w*`)
	signaturePattern = regexp.MustCompile(`[A-Za-z0-9/+]+=*`)
)

var pushHandler = func(w http.ResponseWriter, r *http.Request) {
	// Force all file parts to be written to disk so that we can get the size from the filesystem
	err := r.ParseMultipartForm(0)
	if err != nil {
		internalError(w, err)
	}

	channel := r.FormValue("channel")
	signature := r.FormValue("signature")
	version := r.FormValue("version")
	title := r.FormValue("title")
	notes := r.FormValue("notes")
	if channel != "alpha" && channel != "beta" && channel != "stable" {
		http.Error(w, "Invalid release channel", http.StatusBadRequest)
		return
	}
	if !versionPattern.MatchString(version) {
		http.Error(w, "Invalid version number", http.StatusBadRequest)
		return
	}
	if title == "" {
		http.Error(w, "Missing title", http.StatusBadRequest)
		return
	}
	if requireSig && signature == "" {
		http.Error(w, "Missing signature", http.StatusBadRequest)
		return
	}
	if signature != "" && !signaturePattern.MatchString(signature) {
		http.Error(w, "Invalid DSA signature", http.StatusBadRequest)
	}
	f, fh, err := r.FormFile("file")
	if err == http.ErrMissingFile {
		http.Error(w, "Missing file", http.StatusBadRequest)
		return
	}
	if err != nil {
		internalError(w, err)
		return
	}

	fstat, err := f.(*os.File).Stat()
	if err != nil {
		internalError(w, err)
	}
	length := fstat.Size()
	fileURL, err := uploadFile(f, fh, version, length)
	f.Close()
	if err != nil {
		internalError(w, err)
		return
	}

	var notesURL string
	if notes != "" {
		notesURL, err = uploadReleaseNotes(notes, version)
		if err != nil {
			internalError(w, err)
			return
			// TODO: cleanup uploaded file
		}
	}

	item := &appcast.Item{
		PubDate: time.Now().Format(time.RFC3339),
		Title:   title,
		Enclosure: &appcast.Enclosure{
			URL:       fileURL,
			Length:    strconv.FormatInt(length, 10),
			Type:      "application/octet-stream",
			Version:   version,
			Signature: signature,
		},
	}

	if notesURL != "" {
		item.ReleaseNotesLink = notesURL
	}
	if signature != "" {
		item.Enclosure.Signature = signature
	}

	switch channel {
	case "stable":
		err = updateAppcast(item, "stable")
		if err != nil {
			internalError(w, err)
			return
			// TODO: cleanup uploaded file
		}
		fallthrough
	case "beta":
		err = updateAppcast(item, "beta")
		if err != nil {
			internalError(w, err)
			return
			// TODO: cleanup uploaded file
		}
		fallthrough
	case "alpha":
		err = updateAppcast(item, "alpha")
		if err != nil {
			internalError(w, err)
			return
			// TODO: cleanup uploaded file
		}
	}

	fmt.Fprintf(w, "BOOM!")
}

func uploadFile(f multipart.File, fh *multipart.FileHeader, version string, length int64) (url string, err error) {
	ext := filepath.Ext(fh.Filename)
	filename := appName + "-" + version + ext

	// Check if the file already exists
	// TODO: this should be a HEAD request
	rc, err := s3Bucket.GetReader(filename)
	if rc != nil {
		rc.Close()
	}
	if err != nil {
		if s3err, ok := err.(*s3.Error); ok {
			if s3err.StatusCode != 404 {
				return
			}
		} else {
			return
		}
	} else {
		return "", errors.New("App version already exists")
	}

	err = s3Bucket.PutReader(filename, f, length, "application/octet-stream", s3.PublicRead)
	return s3Bucket.URL(filename), err
}

func uploadReleaseNotes(markdown, version string) (url string, err error) {
	filename := appName + "-" + version + ".html"
	html := blackfriday.MarkdownCommon([]byte(markdown))
	err = s3Bucket.Put(filename, html, "text/html; charset=utf-8", s3.PublicRead)
	return s3Bucket.URL(filename), err
}

func updateAppcast(item *appcast.Item, channel string) error {
	filename := appName + "-" + channel + ".xml"
	current, err := s3Bucket.GetReader(filename)
	if err != nil {
		if s3err, ok := err.(*s3.Error); ok {
			if s3err.StatusCode != 404 {
				return err
			}
		} else {
			return err
		}
	}

	feed := &appcast.RSS{
		Version: "2.0",
		Channel: &appcast.Channel{
			Title:       appName,
			Description: fmt.Sprintf("Updates for %s (%s)", appName, channel),
			Link:        s3Bucket.URL(filename),
		},
	}
	if current != nil {
		defer current.Close()
		err = xml.NewDecoder(current).Decode(feed)
		if err != nil {
			return err
		}
	}

	feed.Channel.Items = append([]*appcast.Item{item}, feed.Channel.Items...)
	if len(feed.Channel.Items) > 10 {
		feed.Channel.Items = feed.Channel.Items[:10]
	}

	newFeed, err := xml.Marshal(feed)
	if err != nil {
		return err
	}

	return s3Bucket.Put(filename, newFeed, "text/xml; charset=utf-8", s3.PublicRead)
}

func internalError(w http.ResponseWriter, err error) {
	log.Printf("%T: %s", err, err)
	http.Error(w, err.Error(), http.StatusInternalServerError)
}

var cookieStore = sessions.NewCookieStore([]byte(os.Getenv("COOKIE_SECRET")))

func protect(action http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if baseURL.Scheme == "https" {
			w.Header().Set("Strict-Transport-Security", "max-age=31536000")
			if r.Header.Get("X-Forwarded-Proto") != "https" {
				http.Redirect(w, r, baseURL.ResolveReference(r.URL).String(), http.StatusMovedPermanently)
				return
			}
		}

		sess := getSession(r)
		var authorized bool
		if sess.Values["user"] != nil {
			for _, u := range authUsers {
				if sess.Values["user"].(string) == u {
					authorized = true
					break
				}
			}
		} else {
			http.Redirect(w, r, "/auth", http.StatusFound)
			return
		}

		if authorized {
			action(w, r)
		} else {
			http.Error(w, "You're not allowed here!", http.StatusForbidden)
		}
	}
}

func randString(bytes int) string {
	b := make([]byte, bytes)
	rand.Read(b)
	return hex.EncodeToString(b)
}

func getSession(r *http.Request) *sessions.Session {
	s, _ := cookieStore.Get(r, "sparklemotion")
	return s
}

var authSetupHandler = func(w http.ResponseWriter, r *http.Request) {
	state := randString(8)
	sess := getSession(r)
	sess.Values["state"] = state
	sess.Save(r, w)
	http.Redirect(w, r, githubAuth.GetAuthorizeURL(state), http.StatusFound)
}

type githubUser struct {
	Login string `json:"login"`
}

var authFinalizeHandler = func(w http.ResponseWriter, r *http.Request) {
	sess := getSession(r)
	if sess.Values["state"] == nil || r.FormValue("state") != sess.Values["state"].(string) {
		http.Error(w, "Invalid session state", http.StatusBadRequest)
	}
	delete(sess.Values, "state")

	token, err := githubAuth.GetAccessToken(r.FormValue("code"))
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	github := oauth2.Request("https://api.github.com/", token.AccessToken)
	github.AccessTokenInHeader = true
	github.AccessTokenInHeaderScheme = "token"
	userInfo, err := github.Get("user")
	if err != nil {
		internalError(w, err)
		return
	}
	defer userInfo.Body.Close()
	user := &githubUser{}
	err = json.NewDecoder(userInfo.Body).Decode(user)
	if err != nil {
		internalError(w, err)
		return
	}

	sess.Values["user"] = user.Login
	sess.Save(r, w)
	http.Redirect(w, r, "/", http.StatusFound)
}

func main() {
	http.HandleFunc("/", protect(indexHandler))
	http.HandleFunc("/push", protect(pushHandler))
	http.HandleFunc("/auth", authSetupHandler)
	http.HandleFunc("/auth/return", authFinalizeHandler)
	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
	log.Fatal(http.ListenAndServe(":"+port, nil))
}

var (
	appName    = os.Getenv("APP_NAME")
	s3Bucket   = s3.New(aws.Auth{os.Getenv("AWS_ACCESS_KEY_ID"), os.Getenv("AWS_SECRET_ACCESS_KEY")}, aws.USEast).Bucket(os.Getenv("S3_BUCKET"))
	authUsers  = strings.Split(os.Getenv("AUTHORIZED_USERS"), ",")
	requireSig = os.Getenv("REQUIRE_SIGNATURE") != ""
	githubAuth *oauth2.OAuth2Service
	baseURL    *url.URL
)

func init() {
	e := []string{"AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "APP_NAME", "S3_BUCKET", "COOKIE_SECRET", "AUTHORIZED_USERS", "GITHUB_CLIENT_ID", "GITHUB_CLIENT_SECRET", "BASE_URL"}
	for _, env := range e {
		if os.Getenv(env) == "" {
			log.Fatalf("Missing %s environment variable", env)
		}
	}

	var err error
	baseURL, err = url.Parse(os.Getenv("BASE_URL"))
	if err != nil {
		log.Fatal("Invalid BASE_URL:", err)
	}

	githubAuth = oauth2.Service(os.Getenv("GITHUB_CLIENT_ID"), os.Getenv("GITHUB_CLIENT_SECRET"), "https://github.com/login/oauth/authorize", "https://github.com/login/oauth/access_token")
	ret, _ := url.Parse("/auth/return")
	githubAuth.RedirectURL = baseURL.ResolveReference(ret).String()
}
<commit_msg>Add a streaming response with status messages<commit_after>package main

import (
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"html/template"
	"log"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"

	"bitbucket.org/gosimple/oauth2"
	"github.com/gorilla/sessions"
	"github.com/russross/blackfriday"
	"github.com/titanous/go.xml"
	"github.com/titanous/sparklemotion/appcast"
	"launchpad.net/goamz/aws"
	"launchpad.net/goamz/s3"
)

type indexInfo struct {
	RequireSignature bool
}

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

var indexHandler = func(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/html")
	err := indexTemplate.Execute(w, &indexInfo{requireSig})
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

var (
	versionPattern   = regexp.MustCompile(`[0-9]\.[0-9]\w*`)
	signaturePattern = regexp.MustCompile(`[A-Za-z0-9/+]+=*`)
)

var pushHandler = func(w http.ResponseWriter, r *http.Request) {
	// Force all file parts to be written to disk so that we can get the size from the filesystem
	err := r.ParseMultipartForm(0)
	if err != nil {
		internalError(w, err)
	}

	channel := r.FormValue("channel")
	signature := r.FormValue("signature")
	version := r.FormValue("version")
	title := r.FormValue("title")
	notes := r.FormValue("notes")
	if channel != "alpha" && channel != "beta" && channel != "stable" {
		http.Error(w, "Invalid release channel", http.StatusBadRequest)
		return
	}
	if !versionPattern.MatchString(version) {
		http.Error(w, "Invalid version number", http.StatusBadRequest)
		return
	}
	if title == "" {
		http.Error(w, "Missing title", http.StatusBadRequest)
		return
	}
	if requireSig && signature == "" {
		http.Error(w, "Missing signature", http.StatusBadRequest)
		return
	}
	if signature != "" && !signaturePattern.MatchString(signature) {
		http.Error(w, "Invalid DSA signature", http.StatusBadRequest)
	}
	f, fh, err := r.FormFile("file")
	if err == http.ErrMissingFile {
		http.Error(w, "Missing file", http.StatusBadRequest)
		return
	}
	if err != nil {
		internalError(w, err)
		return
	}

	w.Header().Set("Content-Type", "text/html")
	// Push 2KB down the wire so that Chrome starts partial rendering of our streaming response
	fmt.Fprint(w, strings.Repeat(" ", 2048))
	writeStatus(w, "Uploading binary to S3...")

	fstat, err := f.(*os.File).Stat()
	if err != nil {
		internalError(w, err)
	}
	length := fstat.Size()
	fileURL, err := uploadFile(f, fh, version, length)
	f.Close()
	if err != nil {
		internalError(w, err)
		return
	}

	var notesURL string
	if notes != "" {
		writeStatus(w, "Uploading release notes...")
		notesURL, err = uploadReleaseNotes(notes, version)
		if err != nil {
			internalError(w, err)
			return
			// TODO: cleanup uploaded file
		}
	}

	item := &appcast.Item{
		PubDate: time.Now().Format(time.RFC3339),
		Title:   title,
		Enclosure: &appcast.Enclosure{
			URL:       fileURL,
			Length:    strconv.FormatInt(length, 10),
			Type:      "application/octet-stream",
			Version:   version,
			Signature: signature,
		},
	}

	if notesURL != "" {
		item.ReleaseNotesLink = notesURL
	}
	if signature != "" {
		item.Enclosure.Signature = signature
	}

	switch channel {
	case "stable":
		writeStatus(w, "Updating stable appcast...")
		err = updateAppcast(item, "stable")
		if err != nil {
			internalError(w, err)
			return
			// TODO: cleanup uploaded file
		}
		fallthrough
	case "beta":
		writeStatus(w, "Updating beta appcast...")
		err = updateAppcast(item, "beta")
		if err != nil {
			internalError(w, err)
			return
			// TODO: cleanup uploaded file
		}
		fallthrough
	case "alpha":
		writeStatus(w, "Updating alpha appcast...")
		err = updateAppcast(item, "alpha")
		if err != nil {
			internalError(w, err)
			return
			// TODO: cleanup uploaded file
		}
	}

	writeStatus(w, "BOOM!")
}

func uploadFile(f multipart.File, fh *multipart.FileHeader, version string, length int64) (url string, err error) {
	ext := filepath.Ext(fh.Filename)
	filename := appName + "-" + version + ext

	// Check if the file already exists
	// TODO: this should be a HEAD request
	rc, err := s3Bucket.GetReader(filename)
	if rc != nil {
		rc.Close()
	}
	if err != nil {
		if s3err, ok := err.(*s3.Error); ok {
			if s3err.StatusCode != 404 {
				return
			}
		} else {
			return
		}
	} else {
		return "", errors.New("App version already exists")
	}

	err = s3Bucket.PutReader(filename, f, length, "application/octet-stream", s3.PublicRead)
	return s3Bucket.URL(filename), err
}

func uploadReleaseNotes(markdown, version string) (url string, err error) {
	filename := appName + "-" + version + ".html"
	html := blackfriday.MarkdownCommon([]byte(markdown))
	err = s3Bucket.Put(filename, html, "text/html; charset=utf-8", s3.PublicRead)
	return s3Bucket.URL(filename), err
}

func updateAppcast(item *appcast.Item, channel string) error {
	filename := appName + "-" + channel + ".xml"
	current, err := s3Bucket.GetReader(filename)
	if err != nil {
		if s3err, ok := err.(*s3.Error); ok {
			if s3err.StatusCode != 404 {
				return err
			}
		} else {
			return err
		}
	}

	feed := &appcast.RSS{
		Version: "2.0",
		Channel: &appcast.Channel{
			Title:       appName,
			Description: fmt.Sprintf("Updates for %s (%s)", appName, channel),
			Link:        s3Bucket.URL(filename),
		},
	}
	if current != nil {
		defer current.Close()
		err = xml.NewDecoder(current).Decode(feed)
		if err != nil {
			return err
		}
	}

	feed.Channel.Items = append([]*appcast.Item{item}, feed.Channel.Items...)
	if len(feed.Channel.Items) > 10 {
		feed.Channel.Items = feed.Channel.Items[:10]
	}

	newFeed, err := xml.Marshal(feed)
	if err != nil {
		return err
	}

	return s3Bucket.Put(filename, newFeed, "text/xml; charset=utf-8", s3.PublicRead)
}

func writeStatus(w http.ResponseWriter, msg string) {
	fmt.Fprintf(w, "<p>%s</p>", msg)
	if f, ok := w.(http.Flusher); ok {
		f.Flush()
	}
}

func internalError(w http.ResponseWriter, err error) {
	log.Printf("%T: %s", err, err)
	http.Error(w, err.Error(), http.StatusInternalServerError)
}

var cookieStore = sessions.NewCookieStore([]byte(os.Getenv("COOKIE_SECRET")))

func protect(action http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if baseURL.Scheme == "https" {
			w.Header().Set("Strict-Transport-Security", "max-age=31536000")
			if r.Header.Get("X-Forwarded-Proto") != "https" {
				http.Redirect(w, r, baseURL.ResolveReference(r.URL).String(), http.StatusMovedPermanently)
				return
			}
		}

		sess := getSession(r)
		var authorized bool
		if sess.Values["user"] != nil {
			for _, u := range authUsers {
				if sess.Values["user"].(string) == u {
					authorized = true
					break
				}
			}
		} else {
			http.Redirect(w, r, "/auth", http.StatusFound)
			return
		}

		if authorized {
			action(w, r)
		} else {
			http.Error(w, "You're not allowed here!", http.StatusForbidden)
		}
	}
}

func randString(bytes int) string {
	b := make([]byte, bytes)
	rand.Read(b)
	return hex.EncodeToString(b)
}

func getSession(r *http.Request) *sessions.Session {
	s, _ := cookieStore.Get(r, "sparklemotion")
	return s
}

var authSetupHandler = func(w http.ResponseWriter, r *http.Request) {
	state := randString(8)
	sess := getSession(r)
	sess.Values["state"] = state
	sess.Save(r, w)
	http.Redirect(w, r, githubAuth.GetAuthorizeURL(state), http.StatusFound)
}

type githubUser struct {
	Login string `json:"login"`
}

var authFinalizeHandler = func(w http.ResponseWriter, r *http.Request) {
	sess := getSession(r)
	if sess.Values["state"] == nil || r.FormValue("state") != sess.Values["state"].(string) {
		http.Error(w, "Invalid session state", http.StatusBadRequest)
	}
	delete(sess.Values, "state")

	token, err := githubAuth.GetAccessToken(r.FormValue("code"))
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	github := oauth2.Request("https://api.github.com/", token.AccessToken)
	github.AccessTokenInHeader = true
	github.AccessTokenInHeaderScheme = "token"
	userInfo, err := github.Get("user")
	if err != nil {
		internalError(w, err)
		return
	}
	defer userInfo.Body.Close()
	user := &githubUser{}
	err = json.NewDecoder(userInfo.Body).Decode(user)
	if err != nil {
		internalError(w, err)
		return
	}

	sess.Values["user"] = user.Login
	sess.Save(r, w)
	http.Redirect(w, r, "/", http.StatusFound)
}

func main() {
	http.HandleFunc("/", protect(indexHandler))
	http.HandleFunc("/push", protect(pushHandler))
	http.HandleFunc("/auth", authSetupHandler)
	http.HandleFunc("/auth/return", authFinalizeHandler)
	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
	log.Fatal(http.ListenAndServe(":"+port, nil))
}

var (
	appName    = os.Getenv("APP_NAME")
	s3Bucket   = s3.New(aws.Auth{os.Getenv("AWS_ACCESS_KEY_ID"), os.Getenv("AWS_SECRET_ACCESS_KEY")}, aws.USEast).Bucket(os.Getenv("S3_BUCKET"))
	authUsers  = strings.Split(os.Getenv("AUTHORIZED_USERS"), ",")
	requireSig = os.Getenv("REQUIRE_SIGNATURE") != ""
	githubAuth *oauth2.OAuth2Service
	baseURL    *url.URL
)

func init() {
	e := []string{"AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "APP_NAME", "S3_BUCKET", "COOKIE_SECRET", "AUTHORIZED_USERS", "GITHUB_CLIENT_ID", "GITHUB_CLIENT_SECRET", "BASE_URL"}
	for _, env := range e {
		if os.Getenv(env) == "" {
			log.Fatalf("Missing %s environment variable", env)
		}
	}

	var err error
	baseURL, err = url.Parse(os.Getenv("BASE_URL"))
	if err != nil {
		log.Fatal("Invalid BASE_URL:", err)
	}

	githubAuth = oauth2.Service(os.Getenv("GITHUB_CLIENT_ID"), os.Getenv("GITHUB_CLIENT_SECRET"), "https://github.com/login/oauth/authorize", "https://github.com/login/oauth/access_token")
	ret, _ := url.Parse("/auth/return")
	githubAuth.RedirectURL = baseURL.ResolveReference(ret).String()
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"image"
	"io"
	"io/ioutil"
	"log"
	"mime/multipart"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/PuerkitoBio/throttled"
	"github.com/PuerkitoBio/throttled/store"
	"github.com/codegangsta/cli"
	"github.com/go-martini/martini"
	"github.com/martini-contrib/binding"
)

// UploadForm is a form structure to use when an image is POSTed to the server
type UploadForm struct {
	PhotoUpload *multipart.FileHeader `form:"photo"`
}

func main() {
	// Set up logging for CLI
	log.SetPrefix("")
	log.SetFlags(0) // Remove the timestamp

	// Connect to redis
	err := redisInit()
	if err != nil {
		log.Println("Connecting to redis failed", err)
	}

	app := cli.NewApp()
	app.Name = "pixlserv"
	app.Usage = "transform and serve images"
	app.Version = "1.0"
	app.Commands = []cli.Command{
		{
			Name:  "run",
			Usage: "Runs the server (run [config-file])",
			Action: func(c *cli.Context) {
				// Set up logging for server
				log.SetPrefix("[pixlserv] ")

				if len(c.Args()) < 1 {
					log.Println("You need to provide a path to a config file")
					return
				}
				configFilePath := c.Args().First()

				// Initialise configuration
				err := configInit(configFilePath)
				if err != nil {
					log.Println("Configuration reading failed:", err)
					return
				}
				log.Printf("Running with config: %+v", Config)

				// Initialise authentication
				err = authInit()
				if err != nil {
					log.Println("Authentication initialisation failed:", err)
					return
				}

				// Initialise storage
				err = storageInit()
				if err != nil {
					log.Println("Storage initialisation failed:", err)
					return
				}

				// Run the server
				m := martini.Classic()
				if Config.throttlingRate > 0 {
					m.Use(throttler(Config.throttlingRate))
				}
				m.Get("/((?P<apikey>[A-Z0-9]+)/)?image/:parameters/**", transformationHandler)
				m.Post("/((?P<apikey>[A-Z0-9]+)/)?upload", binding.MultipartForm(UploadForm{}), uploadHandler)
				go m.Run()

				// Wait for when the program is terminated
				ch := make(chan os.Signal)
				signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
				<-ch

				// Clean up
				redisCleanUp()
				storageCleanUp()
			},
		},
		{
			Name:  "api-key",
			Usage: "Manages API keys",
			Subcommands: []cli.Command{
				{
					Name:  "add",
					Usage: "Adds a new one",
					Action: func(c *cli.Context) {
						key, err := generateKey()
						if err != nil {
							log.Println("Adding a new API key failed, please try again")
							return
						}

						log.Println("Key added:", key)
					},
				},
				{
					Name:  "info",
					Usage: "Shows information about a key (info [key])",
					Action: func(c *cli.Context) {
						if len(c.Args()) < 1 {
							log.Println("You need to provide an existing key")
							return
						}
						key := c.Args().First()
						permissions, err := infoAboutKey(key)
						if err != nil {
							log.Println(err.Error())
							return
						}
						log.Println("Key:", key)
						log.Println("Permissions:", permissions)
					},
				},
				{
					Name:  "list",
					Usage: "Shows all keys",
					Action: func(c *cli.Context) {
						keys, err := listKeys()
						if err != nil {
							log.Println("Retrieving the list of all keys failed")
							return
						}

						log.Println("Keys:", keys)
					},
				},
				{
					Name:  "modify",
					Usage: "Modifies permissions for a key (modify [key] [add/remove] [" + authPermissionsOptions() + "])",
					Action: func(c *cli.Context) {
						if len(c.Args()) < 3 {
							log.Println("You need to provide an existing key, operation and a permission")
							return
						}
						key := c.Args().First()
						err := modifyKey(key, c.Args()[1], c.Args()[2])
						if err != nil {
							log.Println(err.Error())
							return
						}
						log.Println("The key has been updated")
					},
				},
				{
					Name:  "remove",
					Usage: "Removes an existing key (remove [key])",
					Action: func(c *cli.Context) {
						if len(c.Args()) < 1 {
							log.Println("You need to provide an existing key")
							return
						}
						err := removeKey(c.Args().First())
						if err != nil {
							log.Println(err.Error())
							return
						}
						log.Println("The key was successfully removed")
					},
				},
			},
		},
	}

	app.Run(os.Args)
}

func transformationHandler(params martini.Params) (int, string) {
	if !hasPermission(params["apikey"], GetPermission) {
		return http.StatusUnauthorized, ""
	}

	var transformation Transformation
	transformationName := parseTransformationName(params["parameters"])
	if transformationName != "" {
		var ok bool
		transformation, ok = Config.transformations[transformationName]
		if !ok {
			return http.StatusBadRequest, "Unknown transformation: " + transformationName
		}
	} else if Config.allowCustomTransformations {
		parameters, err := parseParameters(params["parameters"])
		if err != nil {
			return http.StatusBadRequest, err.Error()
		}
		transformation = Transformation{&parameters, nil}
	} else {
		return http.StatusBadRequest, "Custom transformations not allowed"
	}
	baseImagePath, scale := parseBasePathAndScale(params["_1"])
	if Config.allowCustomScale {
		parameters := transformation.params.WithScale(scale)
		transformation.params = &parameters
	}

	// Check if the image with the given parameters already exists
	// and return it
	fullImagePath, _ := createFilePath(baseImagePath, transformation.params)
	img, format, err := loadFromCache(fullImagePath)
	if err == nil {
		var buffer bytes.Buffer
		writeImage(img, format, &buffer)

		return http.StatusOK, buffer.String()
	}

	// Load the original image and process it
	if !imageExists(baseImagePath) {
		return http.StatusNotFound, "Image not found: " + baseImagePath
	}

	img, format, err = loadImage(baseImagePath)
	if err != nil {
		return http.StatusInternalServerError, err.Error()
	}

	imgNew := transformCropAndResize(img, &transformation)

	var buffer bytes.Buffer
	err = writeImage(imgNew, format, &buffer)
	if err != nil {
		log.Println("Writing an image to the response failed:", err)
	}

	// Cache the image asynchronously to speed up the response
	go func() {
		err = addToCache(fullImagePath, imgNew, format)
		if err != nil {
			log.Println("Saving an image to cache failed:", err)
		}
	}()

	return http.StatusOK, buffer.String()
}

func uploadHandler(params martini.Params, uf UploadForm) (int, string) {
	if !hasPermission(params["apikey"], UploadPermission) {
		return http.StatusUnauthorized, ""
	}

	file, err := uf.PhotoUpload.Open()
	if err != nil {
		return http.StatusBadRequest, err.Error()
	}

	limit := io.LimitReader(file, int64(Config.uploadMaxFileSize+1))
	data, err := ioutil.ReadAll(limit)
	if err != nil {
		return http.StatusBadRequest, err.Error()
	}
	if len(data) > Config.uploadMaxFileSize {
		return http.StatusBadRequest, "max file size exceeded"
	}

	img, format, err := image.Decode(bytes.NewReader(data))
	if err != nil {
		return http.StatusBadRequest, err.Error()
	}

	defer file.Close()

	// Not a big fan of .jpeg file extensions
	baseImagePath := fmt.Sprintf("%d.%s", time.Now().Unix(), strings.Replace(format, "jpeg", "jpg", 1))
	log.Printf("Uploading %s", baseImagePath)

	// Eager transformations
	eagerlyTransform := func() {
		if len(Config.eagerTransformations) > 0 {
			for _, transformation := range Config.eagerTransformations {
				imgNew := transformCropAndResize(img, &transformation)
				fullImagePath, _ := createFilePath(baseImagePath, transformation.params)
				addToCache(fullImagePath, imgNew, format)
			}
		}
	}

	if Config.asyncUploads {
		go func() {
			_, err := saveImage(img, format, baseImagePath)
			if err != nil {
				log.Println("Error saving image:", err)
				return
			}
			go eagerlyTransform()
		}()
	} else {
		_, err := saveImage(img, format, baseImagePath)
		if err != nil {
			return http.StatusInternalServerError, "Error saving image: " + err.Error()
		}
		go eagerlyTransform()
	}

	return http.StatusOK, ""
}

func throttler(perMinRate int) http.Handler {
	t := throttled.RateLimit(throttled.PerMin(perMinRate), &throttled.VaryBy{RemoteAddr: true}, store.NewMemStore(1000))
	return t.Throttle(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Nothing needed here
	}))
}
<commit_msg>Added an index page<commit_after>package main

import (
	"bytes"
	"fmt"
	"image"
	"io"
	"io/ioutil"
	"log"
	"mime/multipart"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/PuerkitoBio/throttled"
	"github.com/PuerkitoBio/throttled/store"
	"github.com/codegangsta/cli"
	"github.com/go-martini/martini"
	"github.com/martini-contrib/binding"
)

// UploadForm is a form structure to use when an image is POSTed to the server
type UploadForm struct {
	PhotoUpload *multipart.FileHeader `form:"photo"`
}

func main() {
	// Set up logging for CLI
	log.SetPrefix("")
	log.SetFlags(0) // Remove the timestamp

	// Connect to redis
	err := redisInit()
	if err != nil {
		log.Println("Connecting to redis failed", err)
	}

	app := cli.NewApp()
	app.Name = "pixlserv"
	app.Usage = "transform and serve images"
	app.Version = "1.0"
	app.Commands = []cli.Command{
		{
			Name:  "run",
			Usage: "Runs the server (run [config-file])",
			Action: func(c *cli.Context) {
				// Set up logging for server
				log.SetPrefix("[pixlserv] ")

				if len(c.Args()) < 1 {
					log.Println("You need to provide a path to a config file")
					return
				}
				configFilePath := c.Args().First()

				// Initialise configuration
				err := configInit(configFilePath)
				if err != nil {
					log.Println("Configuration reading failed:", err)
					return
				}
				log.Printf("Running with config: %+v", Config)

				// Initialise authentication
				err = authInit()
				if err != nil {
					log.Println("Authentication initialisation failed:", err)
					return
				}

				// Initialise storage
				err = storageInit()
				if err != nil {
					log.Println("Storage initialisation failed:", err)
					return
				}

				// Run the server
				m := martini.Classic()
				if Config.throttlingRate > 0 {
					m.Use(throttler(Config.throttlingRate))
				}
				m.Get("/", func() string {
					return "It works!"
				})
				m.Get("/((?P<apikey>[A-Z0-9]+)/)?image/:parameters/**", transformationHandler)
				m.Post("/((?P<apikey>[A-Z0-9]+)/)?upload", binding.MultipartForm(UploadForm{}), uploadHandler)
				go m.Run()

				// Wait for when the program is terminated
				ch := make(chan os.Signal)
				signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
				<-ch

				// Clean up
				redisCleanUp()
				storageCleanUp()
			},
		},
		{
			Name:  "api-key",
			Usage: "Manages API keys",
			Subcommands: []cli.Command{
				{
					Name:  "add",
					Usage: "Adds a new one",
					Action: func(c *cli.Context) {
						key, err := generateKey()
						if err != nil {
							log.Println("Adding a new API key failed, please try again")
							return
						}

						log.Println("Key added:", key)
					},
				},
				{
					Name:  "info",
					Usage: "Shows information about a key (info [key])",
					Action: func(c *cli.Context) {
						if len(c.Args()) < 1 {
							log.Println("You need to provide an existing key")
							return
						}
						key := c.Args().First()
						permissions, err := infoAboutKey(key)
						if err != nil {
							log.Println(err.Error())
							return
						}
						log.Println("Key:", key)
						log.Println("Permissions:", permissions)
					},
				},
				{
					Name:  "list",
					Usage: "Shows all keys",
					Action: func(c *cli.Context) {
						keys, err := listKeys()
						if err != nil {
							log.Println("Retrieving the list of all keys failed")
							return
						}

						log.Println("Keys:", keys)
					},
				},
				{
					Name:  "modify",
					Usage: "Modifies permissions for a key (modify [key] [add/remove] [" + authPermissionsOptions() + "])",
					Action: func(c *cli.Context) {
						if len(c.Args()) < 3 {
							log.Println("You need to provide an existing key, operation and a permission")
							return
						}
						key := c.Args().First()
						err := modifyKey(key, c.Args()[1], c.Args()[2])
						if err != nil {
							log.Println(err.Error())
							return
						}
						log.Println("The key has been updated")
					},
				},
				{
					Name:  "remove",
					Usage: "Removes an existing key (remove [key])",
					Action: func(c *cli.Context) {
						if len(c.Args()) < 1 {
							log.Println("You need to provide an existing key")
							return
						}
						err := removeKey(c.Args().First())
						if err != nil {
							log.Println(err.Error())
							return
						}
						log.Println("The key was successfully removed")
					},
				},
			},
		},
	}

	app.Run(os.Args)
}

func transformationHandler(params martini.Params) (int, string) {
	if !hasPermission(params["apikey"], GetPermission) {
		return http.StatusUnauthorized, ""
	}

	var transformation Transformation
	transformationName := parseTransformationName(params["parameters"])
	if transformationName != "" {
		var ok bool
		transformation, ok = Config.transformations[transformationName]
		if !ok {
			return http.StatusBadRequest, "Unknown transformation: " + transformationName
		}
	} else if Config.allowCustomTransformations {
		parameters, err := parseParameters(params["parameters"])
		if err != nil {
			return http.StatusBadRequest, err.Error()
		}
		transformation = Transformation{&parameters, nil}
	} else {
		return http.StatusBadRequest, "Custom transformations not allowed"
	}
	baseImagePath, scale := parseBasePathAndScale(params["_1"])
	if Config.allowCustomScale {
		parameters := transformation.params.WithScale(scale)
		transformation.params = &parameters
	}

	// Check if the image with the given parameters already exists
	// and return it
	fullImagePath, _ := createFilePath(baseImagePath, transformation.params)
	img, format, err := loadFromCache(fullImagePath)
	if err == nil {
		var buffer bytes.Buffer
		writeImage(img, format, &buffer)

		return http.StatusOK, buffer.String()
	}

	// Load the original image and process it
	if !imageExists(baseImagePath) {
		return http.StatusNotFound, "Image not found: " + baseImagePath
	}

	img, format, err = loadImage(baseImagePath)
	if err != nil {
		return http.StatusInternalServerError, err.Error()
	}

	imgNew := transformCropAndResize(img, &transformation)

	var buffer bytes.Buffer
	err = writeImage(imgNew, format, &buffer)
	if err != nil {
		log.Println("Writing an image to the response failed:", err)
	}

	// Cache the image asynchronously to speed up the response
	go func() {
		err = addToCache(fullImagePath, imgNew, format)
		if err != nil {
			log.Println("Saving an image to cache failed:", err)
		}
	}()

	return http.StatusOK, buffer.String()
}

func uploadHandler(params martini.Params, uf UploadForm) (int, string) {
	if !hasPermission(params["apikey"], UploadPermission) {
		return http.StatusUnauthorized, ""
	}

	file, err := uf.PhotoUpload.Open()
	if err != nil {
		return http.StatusBadRequest, err.Error()
	}

	limit := io.LimitReader(file, int64(Config.uploadMaxFileSize+1))
	data, err := ioutil.ReadAll(limit)
	if err != nil {
		return http.StatusBadRequest, err.Error()
	}
	if len(data) > Config.uploadMaxFileSize {
		return http.StatusBadRequest, "max file size exceeded"
	}

	img, format, err := image.Decode(bytes.NewReader(data))
	if err != nil {
		return http.StatusBadRequest, err.Error()
	}

	defer file.Close()

	// Not a big fan of .jpeg file extensions
	baseImagePath := fmt.Sprintf("%d.%s", time.Now().Unix(), strings.Replace(format, "jpeg", "jpg", 1))
	log.Printf("Uploading %s", baseImagePath)

	// Eager transformations
	eagerlyTransform := func() {
		if len(Config.eagerTransformations) > 0 {
			for _, transformation := range Config.eagerTransformations {
				imgNew := transformCropAndResize(img, &transformation)
				fullImagePath, _ := createFilePath(baseImagePath, transformation.params)
				addToCache(fullImagePath, imgNew, format)
			}
		}
	}

	if Config.asyncUploads {
		go func() {
			_, err := saveImage(img, format, baseImagePath)
			if err != nil {
				log.Println("Error saving image:", err)
				return
			}
			go eagerlyTransform()
		}()
	} else {
		_, err := saveImage(img, format, baseImagePath)
		if err != nil {
			return http.StatusInternalServerError, "Error saving image: " + err.Error()
		}
		go eagerlyTransform()
	}

	return http.StatusOK, ""
}

func throttler(perMinRate int) http.Handler {
	t := throttled.RateLimit(throttled.PerMin(perMinRate), &throttled.VaryBy{RemoteAddr: true}, store.NewMemStore(1000))
	return t.Throttle(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Nothing needed here
	}))
}
<|endoftext|>