<commit_before>package main

import (
	"fmt"

	"github.com/byuoitav/hateoas"
	"github.com/byuoitav/sony-control-microservice/controllers"
	"github.com/byuoitav/wso2jwt"
	"github.com/jessemillar/health"
	"github.com/labstack/echo"
	"github.com/labstack/echo/engine/standard"
	"github.com/labstack/echo/middleware"
)

func main() {
	err := hateoas.Load("https://raw.githubusercontent.com/byuoitav/sony-control-microservice/master/swagger.yml")
	if err != nil {
		fmt.Println("Could not load swagger.yaml file. Error: " + err.Error())
		panic(err)
	}

	port := ":8007"
	router := echo.New()
	router.Pre(middleware.RemoveTrailingSlash())

	router.Group("/command", wso2jwt.ValidateJWT())

	// GET requests
	router.Get("/health", health.Check)

	router.Get("/", hateoas.RootResponse)
	router.Get("/command/:address", controllers.GetCommands)

	router.Post("/command", controllers.SendCommand)

	fmt.Printf("Sony Control microservice is listening on %s\n", port)
	router.Run(standard.New(port))
}
<commit_msg>Moving back to fasthttp after fixing the buffer issue<commit_after>package main

import (
	"fmt"

	"github.com/byuoitav/hateoas"
	"github.com/byuoitav/sony-control-microservice/controllers"
	"github.com/byuoitav/wso2jwt"
	"github.com/jessemillar/health"
	"github.com/labstack/echo"
	"github.com/labstack/echo/engine/fasthttp"
	"github.com/labstack/echo/middleware"
)

func main() {
	err := hateoas.Load("https://raw.githubusercontent.com/byuoitav/sony-control-microservice/master/swagger.yml")
	if err != nil {
		fmt.Println("Could not load swagger.yaml file. Error: " + err.Error())
		panic(err)
	}

	port := ":8007"
	router := echo.New()
	router.Pre(middleware.RemoveTrailingSlash())

	router.Group("/command", wso2jwt.ValidateJWT())

	// GET requests
	router.Get("/health", health.Check)

	router.Get("/", hateoas.RootResponse)
	router.Get("/command/:address", controllers.GetCommands)

	router.Post("/command", controllers.SendCommand)

	fmt.Printf("Sony Control microservice is listening on %s\n", port)
	server := fasthttp.New(port)
	server.ReadBufferSize = 1024 * 10 // Needed to interface properly with WSO2
	router.Run(server)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"log"
	"net/http"
	"os"
	"sync"

	"github.com/google/go-github/github"
	mgo "gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

var dbSession *mgo.Session
var users *mgo.Collection
var repos *mgo.Collection

var specialBranch string
var specialRef string

var handlers map[string]*comm
var handlersMtx *sync.RWMutex

// Repo is a github repo
type Repo struct {
	ID              string `bson:"_id,omitempty"`
	Username        string
	AccessToken     string
	LastBuildOutput string
	LastBuildStatus string
}

// Comm is used to communicate to a worker goroutine
// stop is sent 'true' to ask the goroutine to stop
// stopped is sent 'true' when it stops
type comm struct {
	Stop    chan bool
	Stopped chan bool
}

func main() {
	config := loadConfig()
	config.print()

	specialBranch = config.SpecialBranch
	specialRef = "refs/heads/" + config.SpecialBranch

	dbSession, err := mgo.Dial(config.MongoURL)
	if err != nil {
		panic(err)
	}
	defer dbSession.Close()

	users = dbSession.DB("hugo-pages").C("users")
	repos = dbSession.DB("hugo-pages").C("repos")

	handlers = make(map[string]*comm)
	handlersMtx = &sync.RWMutex{}

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		payload := validate(r)
		if payload == nil {
			return
		}

		fullname := *payload.Repo.FullName
		url := *payload.Repo.URL

		go func() {
			log.Printf("Got: %s", fullname)
			workerComm := synchronize(fullname)
			w := newWorker(fullname, url, workerComm)
			w.work()
			change := bson.M{
				"$set": bson.M{
					"lastbuildoutput": w.log,
					"lastbuildstatus": w.status,
				},
			}
			repos.UpdateId(fullname, change)
		}()
	})

	log.Println("Listening")
	log.Fatal(http.ListenAndServe(config.Address, nil))
}

// synchronize is a step to see if another goroutine is handling a request
// from the same repo
func synchronize(fullname string) *comm {
	// TODO: Make sure that the goroutine that overtakes is actually a new one
	handlersMtx.RLock()
	currentComm := handlers[fullname]
	handlersMtx.RUnlock()

	if currentComm == nil {
		// There is no goroutine handling a request for this repo
		handlersMtx.Lock()
		currentComm = handlers[fullname]
		if currentComm == nil {
			// Still there is no goroutine handling a request for this repo
			currentComm = &comm{
				Stop:    make(chan bool, 3), // Can buffer stop signals upto 3
				Stopped: make(chan bool, 1),
			}
			handlers[fullname] = currentComm
			handlersMtx.Unlock()
		} else {
			// Some goroutine has entered while we were creating a new channel
			handlersMtx.Unlock()
			_waitAndStart(currentComm, fullname)
		}
	} else {
		_waitAndStart(currentComm, fullname)
	}
	return currentComm
}

func _waitAndStart(currentComm *comm, fullname string) {
	currentComm.Stop <- true
	val := <-currentComm.Stopped
	if val == true {
		// This means previous goroutine does not know it was interrupted
		// So clear the signal it was sent
		<-currentComm.Stop
		currentComm.Stopped <- true
		//and resynchronize
		synchronize(fullname)
	}
}

type worker struct {
	stop     chan bool
	stopped  chan bool
	fullname string
	url      string
	log      string
	status   string
}

func newWorker(fullname, url string, workerComm *comm) *worker {
	return &worker{
		stop:     workerComm.Stop,
		stopped:  workerComm.Stopped,
		fullname: fullname,
		url:      url,
		log:      "",
		status:   "incomplete",
	}
}

func (w *worker) work() {
	w.checkAndContinue("starting processing")

	repo := getRepo(w.fullname)
	if repo == nil {
		w.checkAndStop("repo not known")
		return
	}

	path := "/tmp/" + w.fullname
	defer cleanUp(path)

	_, err := Clone(w.url, path, specialBranch)
	if err != nil {
		w.checkAndStop("could not clone")
		return
	}

	w.checkAndContinue("building")
	out, err := HugoBuild(path)
	if err != nil {
		cleanUp(path)
		w.checkAndStop("could not build")
		return
	}
	log.Printf(out)
	w.checkAndContinue(out)

	subrepo, err := Checkout(path + "/public/") // trailing / important
	if err != nil {
		cleanUp(path)
		w.checkAndStop("Could not checkout: " + err.Error())
		return
	}

	w.checkAndContinue("pushing")

	err = Push(formatPushURL(repo.AccessToken, repo.Username, w.fullname), subrepo)
	if err != nil {
		cleanUp(path)
		log.Printf("Could not push: %s", err)
		return
	}
	cleanUp(path)
	w.checkAndStop("finishing up")
	w.status = "complete"
}

func (w *worker) checkAndContinue(msg string) {
	select {
	case <-w.stop:
		log.Printf("stopping: %s %s", msg, w.fullname)
		w.log = w.log + "stopping: " + msg + "\n"
		w.stopped <- false
	default:
		log.Printf("continuing %s %s", msg, w.fullname)
		w.log = w.log + "continuing: " + msg + "\n"
	}
}

func (w *worker) checkAndStop(msg string) {
	select {
	case <-w.stop:
		log.Printf("stopping: %s %s", msg, w.fullname)
		w.log = w.log + "stopping: " + msg + "\n"
		w.stopped <- false
	default:
		handlersMtx.Lock()
		delete(handlers, w.fullname)
		handlersMtx.Unlock()
		log.Printf("stopping (completed): %s %s", msg, w.fullname)
		w.log = w.log + "stopping (completed): " + msg + "\n"
		w.stopped <- true
	}
}

func validate(r *http.Request) *github.WebHookPayload {
	if r.Method != "POST" {
		log.Println("Not POST")
		return nil
	}

	decoder := json.NewDecoder(r.Body)
	var t github.WebHookPayload
	err := decoder.Decode(&t)
	if err != nil {
		return nil
	}

	if *t.Ref != specialRef {
		log.Println("Not hugo pages:", *t.Ref)
		return nil
	}

	return &t
}

func formatPushURL(accessToken, username, fullname string) string {
	// TODO: optimize string concat
	return "https://" + username + ":" + accessToken + "@github.com/" + fullname
}

func getRepo(fullname string) *Repo {
	result := &Repo{}
	err := repos.Find(bson.M{"_id": fullname}).One(result)
	if err != nil {
		log.Println(err)
		return nil
	}

	return result
}

func cleanUp(path string) {
	err := os.RemoveAll(path)
	if err != nil {
		log.Println(err)
	}
}
<commit_msg>Check github signature<commit_after>package main

import (
	"crypto/hmac"
	"crypto/sha1"
	"encoding/hex"
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"sync"

	"github.com/google/go-github/github"
	mgo "gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

var dbSession *mgo.Session
var users *mgo.Collection
var repos *mgo.Collection

var specialBranch string
var specialRef string

var handlers map[string]*comm
var handlersMtx *sync.RWMutex

// Repo is a github repo
type Repo struct {
	ID              string `bson:"_id,omitempty"`
	Username        string
	AccessToken     string
	LastBuildOutput string
	LastBuildStatus string
}

// Comm is used to communicate to a worker goroutine
// stop is sent 'true' to ask the goroutine to stop
// stopped is sent 'true' when it stops
type comm struct {
	Stop    chan bool
	Stopped chan bool
}

func main() {
	config := loadConfig()
	config.print()

	specialBranch = config.SpecialBranch
	specialRef = "refs/heads/" + config.SpecialBranch

	dbSession, err := mgo.Dial(config.MongoURL)
	if err != nil {
		panic(err)
	}
	defer dbSession.Close()

	users = dbSession.DB("hugo-pages").C("users")
	repos = dbSession.DB("hugo-pages").C("repos")

	handlers = make(map[string]*comm)
	handlersMtx = &sync.RWMutex{}

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		payload := validate(r)
		if payload == nil {
			return
		}

		fullname := *payload.Repo.FullName
		url := *payload.Repo.URL

		go func() {
			log.Printf("Got: %s", fullname)
			workerComm := synchronize(fullname)
			w := newWorker(fullname, url, workerComm)
			w.work()
			change := bson.M{
				"$set": bson.M{
					"lastbuildoutput": w.log,
					"lastbuildstatus": w.status,
				},
			}
			repos.UpdateId(fullname, change)
		}()
	})

	log.Println("Listening")
	log.Fatal(http.ListenAndServe(config.Address, nil))
}

// synchronize is a step to see if another goroutine is handling a request
// from the same repo
func synchronize(fullname string) *comm {
	// TODO: Make sure that the goroutine that overtakes is actually a new one
	handlersMtx.RLock()
	currentComm := handlers[fullname]
	handlersMtx.RUnlock()

	if currentComm == nil {
		// There is no goroutine handling a request for this repo
		handlersMtx.Lock()
		currentComm = handlers[fullname]
		if currentComm == nil {
			// Still there is no goroutine handling a request for this repo
			currentComm = &comm{
				Stop:    make(chan bool, 3), // Can buffer stop signals upto 3
				Stopped: make(chan bool, 1),
			}
			handlers[fullname] = currentComm
			handlersMtx.Unlock()
		} else {
			// Some goroutine has entered while we were creating a new channel
			handlersMtx.Unlock()
			_waitAndStart(currentComm, fullname)
		}
	} else {
		_waitAndStart(currentComm, fullname)
	}
	return currentComm
}

func _waitAndStart(currentComm *comm, fullname string) {
	currentComm.Stop <- true
	val := <-currentComm.Stopped
	if val == true {
		// This means previous goroutine does not know it was interrupted
		// So clear the signal it was sent
		<-currentComm.Stop
		currentComm.Stopped <- true
		//and resynchronize
		synchronize(fullname)
	}
}

type worker struct {
	stop     chan bool
	stopped  chan bool
	fullname string
	url      string
	log      string
	status   string
}

func newWorker(fullname, url string, workerComm *comm) *worker {
	return &worker{
		stop:     workerComm.Stop,
		stopped:  workerComm.Stopped,
		fullname: fullname,
		url:      url,
		log:      "",
		status:   "incomplete",
	}
}

func (w *worker) work() {
	w.checkAndContinue("starting processing")

	repo := getRepo(w.fullname)
	if repo == nil {
		w.checkAndStop("repo not known")
		return
	}

	path := "/tmp/" + w.fullname
	defer cleanUp(path)

	_, err := Clone(w.url, path, specialBranch)
	if err != nil {
		w.checkAndStop("could not clone")
		return
	}

	w.checkAndContinue("building")
	out, err := HugoBuild(path)
	if err != nil {
		cleanUp(path)
		w.checkAndStop("could not build")
		return
	}
	log.Printf(out)
	w.checkAndContinue(out)

	subrepo, err := Checkout(path + "/public/") // trailing / important
	if err != nil {
		cleanUp(path)
		w.checkAndStop("Could not checkout: " + err.Error())
		return
	}

	w.checkAndContinue("pushing")

	err = Push(formatPushURL(repo.AccessToken, repo.Username, w.fullname), subrepo)
	if err != nil {
		cleanUp(path)
		log.Printf("Could not push: %s", err)
		return
	}
	cleanUp(path)
	w.checkAndStop("finishing up")
	w.status = "complete"
}

func (w *worker) checkAndContinue(msg string) {
	select {
	case <-w.stop:
		log.Printf("stopping: %s %s", msg, w.fullname)
		w.log = w.log + "stopping: " + msg + "\n"
		w.stopped <- false
	default:
		log.Printf("continuing %s %s", msg, w.fullname)
		w.log = w.log + "continuing: " + msg + "\n"
	}
}

func (w *worker) checkAndStop(msg string) {
	select {
	case <-w.stop:
		log.Printf("stopping: %s %s", msg, w.fullname)
		w.log = w.log + "stopping: " + msg + "\n"
		w.stopped <- false
	default:
		handlersMtx.Lock()
		delete(handlers, w.fullname)
		handlersMtx.Unlock()
		log.Printf("stopping (completed): %s %s", msg, w.fullname)
		w.log = w.log + "stopping (completed): " + msg + "\n"
		w.stopped <- true
	}
}

func validate(r *http.Request) *github.WebHookPayload {
	if r.Method != "POST" {
		log.Println("Not POST")
		return nil
	}

	b, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return nil
	}

	var t github.WebHookPayload
	err = json.Unmarshal(b, &t)
	if err != nil {
		return nil
	}

	if *t.Ref != specialRef {
		log.Println("Not hugo pages:", *t.Ref)
		return nil
	}

	mac := hmac.New(sha1.New, []byte("hugopagessecret"))
	mac.Write(b)
	if err != nil {
		log.Println(err)
		return nil
	}

	expectedMAC := mac.Sum(nil)
	log.Println(r.Header)
	signature := r.Header["X-Hub-Signature"][0]
	log.Println(signature)
	expected := "sha1=" + hex.EncodeToString(expectedMAC)
	log.Println(expected)

	if !hmac.Equal([]byte(signature), []byte(expected)) {
		return nil
	}

	return &t
}

func formatPushURL(accessToken, username, fullname string) string {
	// TODO: optimize string concat
	return "https://" + username + ":" + accessToken + "@github.com/" + fullname
}

func getRepo(fullname string) *Repo {
	result := &Repo{}
	err := repos.Find(bson.M{"_id": fullname}).One(result)
	if err != nil {
		log.Println(err)
		return nil
	}

	return result
}

func cleanUp(path string) {
	err := os.RemoveAll(path)
	if err != nil {
		log.Println(err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"github.com/codegangsta/martini"
	"github.com/codegangsta/martini-contrib/render"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
)

const version = "0.1.2"

func printDocument(res render.Render, req *http.Request, w http.ResponseWriter) {
	fetchDocument := func(url string) *os.File {
		os.Mkdir("./tmp", 0700)

		out, _ := ioutil.TempFile("tmp", "document")
		defer out.Close()

		resp, _ := http.Get(url)
		defer resp.Body.Close()

		io.Copy(out, resp.Body)

		return out
	}

	removeDocument := func(name string) error {
		return os.Remove(name)
	}

	sendToPrinter := func(url string, printer string, orientation string, media string) ([]string, string) {
		document := fetchDocument(url)
		defer removeDocument(document.Name())

		var printOptions []string

		if printer != "" {
			printOptions = append(printOptions, "-P", printer)
		}
		if orientation != "" {
			printOptions = append(printOptions, "-o", orientation)
		}
		if media != "" {
			printOptions = append(printOptions, "-o", fmt.Sprintf("media=%s", media))
		}
		printOptions = append(printOptions, document.Name())

		cmd := exec.Command("lpr", printOptions...)
		var out bytes.Buffer
		cmd.Stdout = &out
		cmd.Run()
		return printOptions, out.String()
	}

	qs := req.URL.Query()
	printer, orientation, media, url := qs.Get("printer"), qs.Get("orientation"), qs.Get("media"), qs.Get("path")

	go sendToPrinter(url, printer, orientation, media)

	w.Header().Set("Access-Control-Allow-Origin", "*")

	res.JSON(200, map[string]interface{}{
		"status": "scheduled",
	})
}

func cancelAll(res render.Render, req *http.Request, w http.ResponseWriter) {
	qs := req.URL.Query()
	printer := qs.Get("printer")

	cmd := exec.Command("cancel", "-a", "-x", printer)
	var out bytes.Buffer
	cmd.Stdout = &out
	cmd.Run()

	w.Header().Set("Access-Control-Allow-Origin", "*")

	res.JSON(200, map[string]interface{}{
		"success": true,
		"printer": printer,
	})
}

func currentUser() (s string) {
	u, _ := user.Current()
	return u.Username
}

func main() {
	m := martini.Classic()
	m.Use(render.Renderer())
	m.Use(martini.Logger())

	m.Get("/", func() string {
		return "
Receipt Printer
"
	})

	m.Get("/cache/clear", func(res render.Render, w http.ResponseWriter) {
		os.RemoveAll(filepath.Join("./tmp/"))
		w.Header().Set("Access-Control-Allow-Origin", "*")
		res.JSON(200, map[string]interface{}{"success": true})
	})

	m.Get("/print", printDocument)

	m.Get("/cancel/all", cancelAll)

	m.Get("/self_update", func(res render.Render, w http.ResponseWriter) {
		cmd := exec.Command("./update")
		cmd.Run()
		w.Header().Set("Access-Control-Allow-Origin", "*")
		res.JSON(200, map[string]interface{}{"success": true, "version": version})
	})

	m.Get("/status", func(res render.Render, w http.ResponseWriter) {
		w.Header().Set("Access-Control-Allow-Origin", "*")
		res.JSON(200, map[string]interface{}{"success": true, "version": version})
	})

	go func() {
		if err := http.ListenAndServe(":5899", m); err != nil {
			log.Fatal(err)
		}
	}()

	if err := http.ListenAndServeTLS(":9631", "cert.pem", "key.pem", m); err != nil {
		log.Fatal(err)
	}

	m.Run()
}
<commit_msg>Updates default http port to 9632<commit_after>package main

import (
	"bytes"
	"fmt"
	"github.com/codegangsta/martini"
	"github.com/codegangsta/martini-contrib/render"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
)

const version = "0.1.2"

func printDocument(res render.Render, req *http.Request, w http.ResponseWriter) {
	fetchDocument := func(url string) *os.File {
		os.Mkdir("./tmp", 0700)

		out, _ := ioutil.TempFile("tmp", "document")
		defer out.Close()

		resp, _ := http.Get(url)
		defer resp.Body.Close()

		io.Copy(out, resp.Body)

		return out
	}

	removeDocument := func(name string) error {
		return os.Remove(name)
	}

	sendToPrinter := func(url string, printer string, orientation string, media string) ([]string, string) {
		document := fetchDocument(url)
		defer removeDocument(document.Name())

		var printOptions []string

		if printer != "" {
			printOptions = append(printOptions, "-P", printer)
		}
		if orientation != "" {
			printOptions = append(printOptions, "-o", orientation)
		}
		if media != "" {
			printOptions = append(printOptions, "-o", fmt.Sprintf("media=%s", media))
		}
		printOptions = append(printOptions, document.Name())

		cmd := exec.Command("lpr", printOptions...)
		var out bytes.Buffer
		cmd.Stdout = &out
		cmd.Run()
		return printOptions, out.String()
	}

	qs := req.URL.Query()
	printer, orientation, media, url := qs.Get("printer"), qs.Get("orientation"), qs.Get("media"), qs.Get("path")

	go sendToPrinter(url, printer, orientation, media)

	w.Header().Set("Access-Control-Allow-Origin", "*")

	res.JSON(200, map[string]interface{}{
		"status": "scheduled",
	})
}

func cancelAll(res render.Render, req *http.Request, w http.ResponseWriter) {
	qs := req.URL.Query()
	printer := qs.Get("printer")

	cmd := exec.Command("cancel", "-a", "-x", printer)
	var out bytes.Buffer
	cmd.Stdout = &out
	cmd.Run()

	w.Header().Set("Access-Control-Allow-Origin", "*")

	res.JSON(200, map[string]interface{}{
		"success": true,
		"printer": printer,
	})
}

func currentUser() (s string) {
	u, _ := user.Current()
	return u.Username
}

func main() {
	m := martini.Classic()
	m.Use(render.Renderer())
	m.Use(martini.Logger())

	m.Get("/", func() string {
		return "
Receipt Printer
"
	})

	m.Get("/cache/clear", func(res render.Render, w http.ResponseWriter) {
		os.RemoveAll(filepath.Join("./tmp/"))
		w.Header().Set("Access-Control-Allow-Origin", "*")
		res.JSON(200, map[string]interface{}{"success": true})
	})

	m.Get("/print", printDocument)

	m.Get("/cancel/all", cancelAll)

	m.Get("/self_update", func(res render.Render, w http.ResponseWriter) {
		cmd := exec.Command("./update")
		cmd.Run()
		w.Header().Set("Access-Control-Allow-Origin", "*")
		res.JSON(200, map[string]interface{}{"success": true, "version": version})
	})

	m.Get("/status", func(res render.Render, w http.ResponseWriter) {
		w.Header().Set("Access-Control-Allow-Origin", "*")
		res.JSON(200, map[string]interface{}{"success": true, "version": version})
	})

	go func() {
		if err := http.ListenAndServe(":9632", m); err != nil {
			log.Fatal(err)
		}
	}()

	if err := http.ListenAndServeTLS(":9631", "cert.pem", "key.pem", m); err != nil {
		log.Fatal(err)
	}

	m.Run()
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"compress/gzip"
	"crypto/subtle"
	"encoding/base64"
	"errors"
	"fmt"
	"image"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

type httpHandler struct{}

func parsePath(r *http.Request) (string, processingOptions, error) {
	var po processingOptions
	var err error

	path := r.URL.Path
	parts := strings.Split(strings.TrimPrefix(path, "/"), "/")

	if len(parts) < 7 {
		return "", po, errors.New("Invalid path")
	}

	token := parts[0]

	if err = validatePath(token, strings.TrimPrefix(path, fmt.Sprintf("/%s", token))); err != nil {
		return "", po, err
	}

	po.resize = parts[1]

	if po.width, err = strconv.Atoi(parts[2]); err != nil {
		return "", po, fmt.Errorf("Invalid width: %s", parts[2])
	}

	if po.height, err = strconv.Atoi(parts[3]); err != nil {
		return "", po, fmt.Errorf("Invalid height: %s", parts[3])
	}

	if g, ok := gravityTypes[parts[4]]; ok {
		po.gravity = g
	} else {
		return "", po, fmt.Errorf("Invalid gravity: %s", parts[4])
	}

	po.enlarge = parts[5] != "0"

	filenameParts := strings.Split(strings.Join(parts[6:], ""), ".")

	if len(filenameParts) < 2 {
		po.format = imageTypes["jpg"]
	} else if f, ok := imageTypes[filenameParts[1]]; ok {
		po.format = f
	} else {
		return "", po, fmt.Errorf("Invalid image format: %s", filenameParts[1])
	}

	filename, err := base64.RawURLEncoding.DecodeString(filenameParts[0])
	if err != nil {
		return "", po, errors.New("Invalid filename encoding")
	}

	return string(filename), po, nil
}

func imageContentType(b []byte) string {
	_, imgtype, _ := image.DecodeConfig(bytes.NewReader(b))
	return fmt.Sprintf("image/%s", imgtype)
}

func logResponse(status int, msg string) {
	var color int

	if status > 500 {
		color = 31
	} else if status > 400 {
		color = 33
	} else {
		color = 32
	}

	log.Printf("|\033[7;%dm %d \033[0m| %s\n", color, status, msg)
}

func respondWithImage(r *http.Request, rw http.ResponseWriter, data []byte, imgURL string, po processingOptions, startTime time.Time) {
	gzipped := strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") && conf.GZipCompression > 0

	rw.Header().Set("Content-Type", imageContentType(data))
	if gzipped {
		rw.Header().Set("Content-Encoding", "gzip")
	}

	rw.WriteHeader(200)

	if gzipped {
		gz, _ := gzip.NewWriterLevel(rw, conf.GZipCompression)
		gz.Write(data)
		gz.Close()
	} else {
		rw.Write(data)
	}

	logResponse(200, fmt.Sprintf("Processed in %s: %s; %+v", time.Since(startTime), imgURL, po))
}

func respondWithError(rw http.ResponseWriter, status int, err error, msg string) {
	logResponse(status, err.Error())

	rw.WriteHeader(status)
	rw.Write([]byte(msg))
}

func repondWithForbidden(rw http.ResponseWriter) {
	logResponse(403, "Invalid secret")

	rw.WriteHeader(403)
	rw.Write([]byte("Forbidden"))
}

func checkSecret(s string) bool {
	return len(conf.Secret) == 0 || subtle.ConstantTimeCompare([]byte(s), []byte(conf.Secret)) == 1
}

func (h httpHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
	log.Printf("GET: %s\n", r.URL.RequestURI())

	t := time.Now()

	if !checkSecret(r.Header.Get("X-Imgproxy-Secret")) {
		repondWithForbidden(rw)
		return
	}

	imgURL, procOpt, err := parsePath(r)
	if err != nil {
		respondWithError(rw, 404, err, "Invalid image url")
		return
	}

	if _, err = url.ParseRequestURI(imgURL); err != nil {
		respondWithError(rw, 404, err, "Invalid image url")
		return
	}

	b, err := downloadImage(imgURL)
	if err != nil {
		respondWithError(rw, 404, err, "Image is unreacable")
		return
	}

	b, err = processImage(b, procOpt)
	if err != nil {
		respondWithError(rw, 500, err, "Error occured while processing image")
		return
	}

	respondWithImage(r, rw, b, imgURL, procOpt, t)
}
<commit_msg>Use Authorization header for secret<commit_after>package main

import (
	"bytes"
	"compress/gzip"
	"crypto/subtle"
	"encoding/base64"
	"errors"
	"fmt"
	"image"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

type httpHandler struct{}

func parsePath(r *http.Request) (string, processingOptions, error) {
	var po processingOptions
	var err error

	path := r.URL.Path
	parts := strings.Split(strings.TrimPrefix(path, "/"), "/")

	if len(parts) < 7 {
		return "", po, errors.New("Invalid path")
	}

	token := parts[0]

	if err = validatePath(token, strings.TrimPrefix(path, fmt.Sprintf("/%s", token))); err != nil {
		return "", po, err
	}

	po.resize = parts[1]

	if po.width, err = strconv.Atoi(parts[2]); err != nil {
		return "", po, fmt.Errorf("Invalid width: %s", parts[2])
	}

	if po.height, err = strconv.Atoi(parts[3]); err != nil {
		return "", po, fmt.Errorf("Invalid height: %s", parts[3])
	}

	if g, ok := gravityTypes[parts[4]]; ok {
		po.gravity = g
	} else {
		return "", po, fmt.Errorf("Invalid gravity: %s", parts[4])
	}

	po.enlarge = parts[5] != "0"

	filenameParts := strings.Split(strings.Join(parts[6:], ""), ".")

	if len(filenameParts) < 2 {
		po.format = imageTypes["jpg"]
	} else if f, ok := imageTypes[filenameParts[1]]; ok {
		po.format = f
	} else {
		return "", po, fmt.Errorf("Invalid image format: %s", filenameParts[1])
	}

	filename, err := base64.RawURLEncoding.DecodeString(filenameParts[0])
	if err != nil {
		return "", po, errors.New("Invalid filename encoding")
	}

	return string(filename), po, nil
}

func imageContentType(b []byte) string {
	_, imgtype, _ := image.DecodeConfig(bytes.NewReader(b))
	return fmt.Sprintf("image/%s", imgtype)
}

func logResponse(status int, msg string) {
	var color int

	if status > 500 {
		color = 31
	} else if status > 400 {
		color = 33
	} else {
		color = 32
	}

	log.Printf("|\033[7;%dm %d \033[0m| %s\n", color, status, msg)
}

func respondWithImage(r *http.Request, rw http.ResponseWriter, data []byte, imgURL string, po processingOptions, startTime time.Time) {
	gzipped := strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") && conf.GZipCompression > 0

	rw.Header().Set("Content-Type", imageContentType(data))
	if gzipped {
		rw.Header().Set("Content-Encoding", "gzip")
	}

	rw.WriteHeader(200)

	if gzipped {
		gz, _ := gzip.NewWriterLevel(rw, conf.GZipCompression)
		gz.Write(data)
		gz.Close()
	} else {
		rw.Write(data)
	}

	logResponse(200, fmt.Sprintf("Processed in %s: %s; %+v", time.Since(startTime), imgURL, po))
}

func respondWithError(rw http.ResponseWriter, status int, err error, msg string) {
	logResponse(status, err.Error())

	rw.WriteHeader(status)
	rw.Write([]byte(msg))
}

func repondWithForbidden(rw http.ResponseWriter) {
	logResponse(403, "Invalid secret")

	rw.WriteHeader(403)
	rw.Write([]byte("Forbidden"))
}

func checkSecret(s string) bool {
	if len(conf.Secret) == 0 {
		return true
	}
	return strings.HasPrefix(s, "Bearer ") && subtle.ConstantTimeCompare([]byte(strings.TrimPrefix(s, "Bearer ")), []byte(conf.Secret)) == 1
}

func (h httpHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
	log.Printf("GET: %s\n", r.URL.RequestURI())

	t := time.Now()

	if !checkSecret(r.Header.Get("Authorization")) {
		repondWithForbidden(rw)
		return
	}

	imgURL, procOpt, err := parsePath(r)
	if err != nil {
		respondWithError(rw, 404, err, "Invalid image url")
		return
	}

	if _, err = url.ParseRequestURI(imgURL); err != nil {
		respondWithError(rw, 404, err, "Invalid image url")
		return
	}

	b, err := downloadImage(imgURL)
	if err != nil {
		respondWithError(rw, 404, err, "Image is unreacable")
		return
	}

	b, err = processImage(b, procOpt)
	if err != nil {
		respondWithError(rw, 500, err, "Error occured while processing image")
		return
	}

	respondWithImage(r, rw, b, imgURL, procOpt, t)
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"regexp"
	"time"

	"github.com/garyburd/redigo/redis"
)

var (
	DefaultIdleTimeout = 5 * time.Minute
	DefaultMaxIdle     = 3

	ErrNoDef      = errors.New("no def")
	ErrDefExists  = errors.New("def exists")
	ErrBadDefName = errors.New("name may only contain [A-Za-z0-9-_.] chars")

	MaxAttempts           = 100
	ErrMaxAttemptsReached = errors.New("max attempts reached")

	nameRegex  *regexp.Regexp
	splitRegex *regexp.Regexp

	// delimiter for return values
	delimiter = " "

	// Prefix for internal use.
	internalPrefix = "_:%s"

	// Prefix for index definitions.
	defPrefix   = "d:%s"
	valuePrefix = "v:%d"
	seqPrefix   = "s:%d"

	// Prefix for keys, aliases, and sequences.
	// These are scoped by the definition id.
	keyPrefix   = "k:%d:%s"
	aliasPrefix = "a:%d:%s"
)

func mk(f string, v ...interface{}) string {
	return fmt.Sprintf(f, v...)
}

func init() {
	nameRegex = regexp.MustCompile(`^[A-Za-z0-9-_\.]+$`)
	splitRegex = regexp.MustCompile(`[\s,\t]+`)
}

type Server struct {
	RedisAddr string
	RedisDB   int
	RedisPass string

	Log  *log.Logger
	Pool *redis.Pool
}

func (s *Server) Close() {
	if s.Pool != nil {
		s.Pool.Close()
	}
}

func (s *Server) Init() error {
	s.Log = log.New(os.Stderr, "aliases: ", 0)

	// Create a pool of Redis connections.
	s.Pool = &redis.Pool{
		Dial: func() (redis.Conn, error) {
			conn, err := redis.Dial("tcp", s.RedisAddr)
			if err != nil {
				return nil, err
			}

			// Password to authenticate.
			if s.RedisPass != "" {
				if _, err := conn.Do("AUTH", s.RedisPass); err != nil {
					return nil, err
				}
			}

			if _, err := conn.Do("SELECT", s.RedisDB); err != nil {
				return nil, err
			}

			return conn, nil
		},
		IdleTimeout: DefaultIdleTimeout,
		MaxIdle:     DefaultMaxIdle,
	}

	return nil
}

func (s *Server) GetDefs() ([]json.RawMessage, error) {
	conn := s.Pool.Get()
	defer conn.Close()

	// Keys of the definitions.
	keys, err := redis.Strings(conn.Do("KEYS", "v:*"))
	if err != nil {
		return nil, err
	}

	if len(keys) == 0 {
		return []json.RawMessage{}, nil
	}

	args := make([]interface{}, len(keys))
	for i, k := range keys {
		args[i] = k
	}

	vals, err := redis.Strings(conn.Do("MGET", args...))
	if err != nil {
		return nil, err
	}

	defs := make([]json.RawMessage, len(vals))

	for i, val := range vals {
		defs[i] = json.RawMessage(val)
	}

	return defs, nil
}

// DelDef marks a index for deletion.
func (s *Server) DelDef(name string) error {
	def, err := s.GetDef(name)
	if err != nil {
		return err
	}

	// Internally mark as deleted to be cleaned up.
	def.Deleted = true
	b, err := json.Marshal(def)
	if err != nil {
		return err
	}

	conn := s.Pool.Get()
	defer conn.Close()

	// Delete name entry to make inaccessable and update definition.
	conn.Send("MULTI")
	conn.Send("DEL", mk(defPrefix, name))
	conn.Send("SET", mk(valuePrefix, def.ID), string(b))
	if _, err := conn.Do("EXEC"); err != nil {
		return err
	}

	s.Log.Printf("deleted '%s'", def.Name)

	return nil
}

func (s *Server) GetDef(name string) (*Def, error) {
	conn := s.Pool.Get()
	defer conn.Close()

	id, err := redis.Int64(conn.Do("GET", mk(defPrefix, name)))
	if err == redis.ErrNil {
		return nil, ErrNoDef
	} else if err != nil {
		return nil, err
	}

	blob, err := redis.Bytes(conn.Do("GET", mk(valuePrefix, id)))
	if err != nil {
		return nil, err
	}

	if blob == nil {
		panic(fmt.Sprintf("missing def value for %s", name))
	}

	var g Def
	if err := json.Unmarshal(blob, &g); err != nil {
		return nil, err
	}

	return &g, nil
}

func (s *Server) validateDef(def *Def) error {
	if def.Name == "" {
		return errors.New("name required")
	}

	if !nameRegex.MatchString(def.Name) {
		return ErrBadDefName
	}

	if def.Type == "" {
		return errors.New("type required")
	}

	switch def.Type {
	case "seq":
	case "rand":
		if def.Minlen < MinRandMinlen {
			return errors.New("rand min length too small")
		}

		if len(def.Chars) < MinRandChars {
			return errors.New("too few chars for rand")
		}
	case "uuid":
	default:
		return errors.New("unknown type")
	}

	return nil
}

// CreateDef creates a new index for generating aliases.
// d:foo -> 0
// v:0 -> { ... }
func (s *Server) CreateDef(def *Def) error {
	if err := s.validateDef(def); err != nil {
		return err
	}

	// Check if there is an existing definition.
	conn := s.Pool.Get()
	defer conn.Close()

	// Lookup up def by name.
	defKey := mk(defPrefix, def.Name)

	exists, err := redis.Bool(conn.Do("EXISTS", defKey))
	if err != nil {
		return err
	}

	// Cannot create a def by the same name.
	if exists {
		return ErrDefExists
	}

	// Get a new key.
	defIdKey := mk(internalPrefix, "def:id")
	id, err := redis.Int64(conn.Do("INCR", defIdKey))
	if err != nil {
		return err
	}

	def.ID = int(id)

	b, err := json.Marshal(def)
	if err != nil {
		return err
	}

	valueKey := mk(valuePrefix, def.ID)

	args := []interface{}{
		defKey, def.ID,
		valueKey, string(b),
	}

	// Initialize the sequence.
	if def.Type == "seq" {
		seqKey := mk(seqPrefix, def.ID)
		args = append(args, seqKey, def.Offset)
	}

	_, err = conn.Do("MSET", args...)
	if err != nil {
		return err
	}

	s.Log.Printf("created def '%s' (id=%d)", def.Name, def.ID)

	return nil
}

func (s *Server) UpdateDef(name string, def *Def) error {
	if err := s.validateDef(def); err != nil {
		return err
	}

	// Check if there is an existing definition.
	conn := s.Pool.Get()
	defer conn.Close()

	b, err := json.Marshal(def)
	if err != nil {
		return err
	}

	// Delete previous definition.
	if name != def.Name {
		_, err = conn.Do("DEL", mk(defPrefix, name))
		if err != nil {
			return err
		}
	}

	// Set name and value key.
	defKey := mk(defPrefix, def.Name)
	valueKey := mk(valuePrefix, def.ID)
	_, err = conn.Do("MSET", defKey, def.ID, valueKey, string(b))
	if err != nil {
		return err
	}

	s.Log.Printf("updated def '%s'", def.Name)

	return nil
}

// Put explicitly sets a set of IDs with an alias.
func (s *Server) Put(def *Def, r io.Reader) error {
	conn := s.Pool.Get()
	defer conn.Close()

	var totalCount int

	sr := bufio.NewScanner(r)

	for sr.Scan() {
		totalCount++

		line := sr.Text()
		toks := splitRegex.Split(line, 2)

		if len(toks) != 2 {
			return errors.New("delimiter should match [\\s\\t,]+")
		}

		key := string(toks[0])
		alias := string(toks[1])

		// key to alias
		lookupKey := mk(keyPrefix, def.ID, key)
		// alias entry for existence check.
		checkKey := mk(aliasPrefix, def.ID, alias)

		_, err := conn.Do("MSET", lookupKey, alias, checkKey, true)
		if err != nil {
			return err
		}
	}

	s.Log.Printf("put %d keys", totalCount)

	return sr.Err()
}

func (s *Server) Del(def *Def, r io.Reader) error {
	conn := s.Pool.Get()
	defer conn.Close()

	sr := bufio.NewScanner(r)

	var (
		removedCount  int
		skippedCount  int
		conflictCount int
		internalCount int
	)

	for sr.Scan() {
		key := sr.Text()

		lookupKey := mk(keyPrefix, def.ID, key)

		// Get the corresponding alias.
		alias, err := redis.String(conn.Do("GET", lookupKey))
		if err == redis.ErrNil {
			skippedCount++
			continue
		}

		if err != nil {
			return err
		}

		removedCount++

		checkKey := mk(aliasPrefix, def.ID, alias)

		n, err := redis.Int64(conn.Do("DEL", lookupKey, checkKey))
		if err != nil {
			return err
		}

		internalCount += int(n)
	}

	s.Log.Printf("%d removed", removedCount)
	s.Log.Printf("%d skipped", skippedCount)
	s.Log.Printf("%d conflicts", conflictCount)
	s.Log.Printf("%d internal", internalCount)

	return sr.Err()
}

func (s *Server) Gen(def *Def, r io.Reader, w io.Writer) error {
	conn := s.Pool.Get()
	defer conn.Close()

	// Generator for this line.
	gen := MakeGen(conn, def)

	sr := bufio.NewScanner(r)

	for sr.Scan() {
		key := sr.Text()

		lookupKey := mk(keyPrefix, def.ID, key)

		// Check if the key already exists. If so, just return it.
		alias, err := redis.String(conn.Do("GET", lookupKey))

		// Exists. Write it out and go to the next one.
		if err == nil {
			// Flag to indicate that this alias was already set
			flag := "0"
			fmt.Fprintln(w, (flag + delimiter + alias))
			continue
		}

		if err != nil && err != redis.ErrNil {
			return err
		}

		var attempt int

		for {
			if attempt == MaxAttempts {
				s.Log.Printf("max attempts reached for '%s' in '%s'", lookupKey, def.Name)
				// TODO: auto-increase minlenth if this occurs.
				return ErrMaxAttemptsReached
			}

			attempt++

			// Generate new key.
			alias, err = gen.New()
			if err != nil {
				return err
			}

			// Check if it exists, otherwise set it.
			checkKey := mk(aliasPrefix, def.ID, alias)

			ok, err := redis.Bool(conn.Do("EXISTS", checkKey))
			if err != nil {
				return err
			}

			// Does not exist, set it.
			if !ok {
				flag := "1"
				_, err := conn.Do("MSET", lookupKey, alias, checkKey, true)
				if err != nil {
					return err
				}

				fmt.Fprintln(w, (flag + delimiter + alias))

				// TODO: add metric for number of attempts. this is an indicator
				// to whether the min length should be increased.
				break
			}
		}
	}

	return sr.Err()
}

func (s *Server) Get(def *Def, r io.Reader, w io.Writer) error {
	conn := s.Pool.Get()
	defer conn.Close()

	flag := "0"
	sr := bufio.NewScanner(r)

	for sr.Scan() {
		key := sr.Text()

		lookupKey := mk(keyPrefix, def.ID, key)

		// Check if the key already exists. If so, just return it.
		alias, err := redis.String(conn.Do("GET", lookupKey))

		// Exists. Write it out and go to the next one.
		if err == nil {
			fmt.Fprintln(w, (flag + delimiter + alias))
			continue
		}

		if err != nil && err != redis.ErrNil {
			return err
		}

		// Write an empty string.
		fmt.Fprintln(w, "")
	}

	return sr.Err()
}
<commit_msg>Address review comments<commit_after>package main

import (
	"bufio"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"regexp"
	"time"

	"github.com/garyburd/redigo/redis"
)

var (
	DefaultIdleTimeout = 5 * time.Minute
	DefaultMaxIdle     = 3

	ErrNoDef      = errors.New("no def")
	ErrDefExists  = errors.New("def exists")
	ErrBadDefName = errors.New("name may only contain [A-Za-z0-9-_.] chars")

	MaxAttempts           = 100
	ErrMaxAttemptsReached = errors.New("max attempts reached")

	nameRegex  *regexp.Regexp
	splitRegex *regexp.Regexp

	// delimiter for return values
	delimiter = " "

	// Prefix for internal use.
	internalPrefix = "_:%s"

	// Prefix for index definitions.
	defPrefix   = "d:%s"
	valuePrefix = "v:%d"
	seqPrefix   = "s:%d"

	// Prefix for keys, aliases, and sequences.
	// These are scoped by the definition id.
	keyPrefix   = "k:%d:%s"
	aliasPrefix = "a:%d:%s"
)

func mk(f string, v ...interface{}) string {
	return fmt.Sprintf(f, v...)
}

func init() {
	nameRegex = regexp.MustCompile(`^[A-Za-z0-9-_\.]+$`)
	splitRegex = regexp.MustCompile(`[\s,\t]+`)
}

type Server struct {
	RedisAddr string
	RedisDB   int
	RedisPass string

	Log  *log.Logger
	Pool *redis.Pool
}

func (s *Server) Close() {
	if s.Pool != nil {
		s.Pool.Close()
	}
}

func (s *Server) Init() error {
	s.Log = log.New(os.Stderr, "aliases: ", 0)

	// Create a pool of Redis connections.
	s.Pool = &redis.Pool{
		Dial: func() (redis.Conn, error) {
			conn, err := redis.Dial("tcp", s.RedisAddr)
			if err != nil {
				return nil, err
			}

			// Password to authenticate.
			if s.RedisPass != "" {
				if _, err := conn.Do("AUTH", s.RedisPass); err != nil {
					return nil, err
				}
			}

			if _, err := conn.Do("SELECT", s.RedisDB); err != nil {
				return nil, err
			}

			return conn, nil
		},
		IdleTimeout: DefaultIdleTimeout,
		MaxIdle:     DefaultMaxIdle,
	}

	return nil
}

func (s *Server) GetDefs() ([]json.RawMessage, error) {
	conn := s.Pool.Get()
	defer conn.Close()

	// Keys of the definitions.
	keys, err := redis.Strings(conn.Do("KEYS", "v:*"))
	if err != nil {
		return nil, err
	}

	if len(keys) == 0 {
		return []json.RawMessage{}, nil
	}

	args := make([]interface{}, len(keys))
	for i, k := range keys {
		args[i] = k
	}

	vals, err := redis.Strings(conn.Do("MGET", args...))
	if err != nil {
		return nil, err
	}

	defs := make([]json.RawMessage, len(vals))

	for i, val := range vals {
		defs[i] = json.RawMessage(val)
	}

	return defs, nil
}

// DelDef marks a index for deletion.
func (s *Server) DelDef(name string) error {
	def, err := s.GetDef(name)
	if err != nil {
		return err
	}

	// Internally mark as deleted to be cleaned up.
	def.Deleted = true
	b, err := json.Marshal(def)
	if err != nil {
		return err
	}

	conn := s.Pool.Get()
	defer conn.Close()

	// Delete name entry to make inaccessable and update definition.
	conn.Send("MULTI")
	conn.Send("DEL", mk(defPrefix, name))
	conn.Send("SET", mk(valuePrefix, def.ID), string(b))
	if _, err := conn.Do("EXEC"); err != nil {
		return err
	}

	s.Log.Printf("deleted '%s'", def.Name)

	return nil
}

func (s *Server) GetDef(name string) (*Def, error) {
	conn := s.Pool.Get()
	defer conn.Close()

	id, err := redis.Int64(conn.Do("GET", mk(defPrefix, name)))
	if err == redis.ErrNil {
		return nil, ErrNoDef
	} else if err != nil {
		return nil, err
	}

	blob, err := redis.Bytes(conn.Do("GET", mk(valuePrefix, id)))
	if err != nil {
		return nil, err
	}

	if blob == nil {
		panic(fmt.Sprintf("missing def value for %s", name))
	}

	var g Def
	if err := json.Unmarshal(blob, &g); err != nil {
		return nil, err
	}

	return &g, nil
}

func (s *Server) validateDef(def *Def) error {
	if def.Name == "" {
		return errors.New("name required")
	}

	if !nameRegex.MatchString(def.Name) {
		return ErrBadDefName
	}

	if def.Type == "" {
		return errors.New("type required")
	}

	switch def.Type {
	case "seq":
	case "rand":
		if def.Minlen < MinRandMinlen {
			return errors.New("rand min length too small")
		}

		if len(def.Chars) < MinRandChars {
			return errors.New("too few chars for rand")
		}
	case "uuid":
	default:
		return errors.New("unknown type")
	}

	return nil
}

// CreateDef creates a new index for generating aliases.
// d:foo -> 0
// v:0 -> { ... }
func (s *Server) CreateDef(def *Def) error {
	if err := s.validateDef(def); err != nil {
		return err
	}

	// Check if there is an existing definition.
	conn := s.Pool.Get()
	defer conn.Close()

	// Lookup up def by name.
	defKey := mk(defPrefix, def.Name)

	exists, err := redis.Bool(conn.Do("EXISTS", defKey))
	if err != nil {
		return err
	}

	// Cannot create a def by the same name.
	if exists {
		return ErrDefExists
	}

	// Get a new key.
	defIdKey := mk(internalPrefix, "def:id")
	id, err := redis.Int64(conn.Do("INCR", defIdKey))
	if err != nil {
		return err
	}

	def.ID = int(id)

	b, err := json.Marshal(def)
	if err != nil {
		return err
	}

	valueKey := mk(valuePrefix, def.ID)

	args := []interface{}{
		defKey, def.ID,
		valueKey, string(b),
	}

	// Initialize the sequence.
	if def.Type == "seq" {
		seqKey := mk(seqPrefix, def.ID)
		args = append(args, seqKey, def.Offset)
	}

	_, err = conn.Do("MSET", args...)
	if err != nil {
		return err
	}

	s.Log.Printf("created def '%s' (id=%d)", def.Name, def.ID)

	return nil
}

func (s *Server) UpdateDef(name string, def *Def) error {
	if err := s.validateDef(def); err != nil {
		return err
	}

	// Check if there is an existing definition.
	conn := s.Pool.Get()
	defer conn.Close()

	b, err := json.Marshal(def)
	if err != nil {
		return err
	}

	// Delete previous definition.
	if name != def.Name {
		_, err = conn.Do("DEL", mk(defPrefix, name))
		if err != nil {
			return err
		}
	}

	// Set name and value key.
	defKey := mk(defPrefix, def.Name)
	valueKey := mk(valuePrefix, def.ID)
	_, err = conn.Do("MSET", defKey, def.ID, valueKey, string(b))
	if err != nil {
		return err
	}

	s.Log.Printf("updated def '%s'", def.Name)

	return nil
}

// Put explicitly sets a set of IDs with an alias.
func (s *Server) Put(def *Def, r io.Reader) error {
	conn := s.Pool.Get()
	defer conn.Close()

	var totalCount int

	sr := bufio.NewScanner(r)

	for sr.Scan() {
		totalCount++

		line := sr.Text()
		toks := splitRegex.Split(line, 2)

		if len(toks) != 2 {
			return errors.New("delimiter should match [\\s\\t,]+")
		}

		key := string(toks[0])
		alias := string(toks[1])

		// key to alias
		lookupKey := mk(keyPrefix, def.ID, key)
		// alias entry for existence check.
		checkKey := mk(aliasPrefix, def.ID, alias)

		_, err := conn.Do("MSET", lookupKey, alias, checkKey, true)
		if err != nil {
			return err
		}
	}

	s.Log.Printf("put %d keys", totalCount)

	return sr.Err()
}

func (s *Server) Del(def *Def, r io.Reader) error {
	conn := s.Pool.Get()
	defer conn.Close()

	sr := bufio.NewScanner(r)

	var (
		removedCount  int
		skippedCount  int
		conflictCount int
		internalCount int
	)

	for sr.Scan() {
		key := sr.Text()

		lookupKey := mk(keyPrefix, def.ID, key)

		// Get the corresponding alias.
		alias, err := redis.String(conn.Do("GET", lookupKey))
		if err == redis.ErrNil {
			skippedCount++
			continue
		}

		if err != nil {
			return err
		}

		removedCount++

		checkKey := mk(aliasPrefix, def.ID, alias)

		n, err := redis.Int64(conn.Do("DEL", lookupKey, checkKey))
		if err != nil {
			return err
		}

		internalCount += int(n)
	}

	s.Log.Printf("%d removed", removedCount)
	s.Log.Printf("%d skipped", skippedCount)
	s.Log.Printf("%d conflicts", conflictCount)
	s.Log.Printf("%d internal", internalCount)

	return sr.Err()
}

func (s *Server) Gen(def *Def, r io.Reader, w io.Writer) error {
	conn := s.Pool.Get()
	defer conn.Close()

	// Generator for this line.
	gen := MakeGen(conn, def)

	sr := bufio.NewScanner(r)

	for sr.Scan() {
		key := sr.Text()

		lookupKey := mk(keyPrefix, def.ID, key)

		// Check if the key already exists. If so, just return it.
		alias, err := redis.String(conn.Do("GET", lookupKey))

		// Exists. Write it out and go to the next one.
		if err == nil {
			fmt.Fprintln(w, "0", alias)
			continue
		}

		if err != nil && err != redis.ErrNil {
			return err
		}

		var attempt int

		for {
			if attempt == MaxAttempts {
				s.Log.Printf("max attempts reached for '%s' in '%s'", lookupKey, def.Name)
				// TODO: auto-increase minlenth if this occurs.
				return ErrMaxAttemptsReached
			}

			attempt++

			// Generate new key.
			alias, err = gen.New()
			if err != nil {
				return err
			}

			// Check if it exists, otherwise set it.
			checkKey := mk(aliasPrefix, def.ID, alias)

			ok, err := redis.Bool(conn.Do("EXISTS", checkKey))
			if err != nil {
				return err
			}

			// Does not exist, set it.
			if !ok {
				_, err := conn.Do("MSET", lookupKey, alias, checkKey, true)
				if err != nil {
					return err
				}

				fmt.Fprintln(w, "1", alias)

				// TODO: add metric for number of attempts. this is an indicator
				// to whether the min length should be increased.
				break
			}
		}
	}

	return sr.Err()
}

func (s *Server) Get(def *Def, r io.Reader, w io.Writer) error {
	conn := s.Pool.Get()
	defer conn.Close()

	sr := bufio.NewScanner(r)

	for sr.Scan() {
		key := sr.Text()

		lookupKey := mk(keyPrefix, def.ID, key)

		// Check if the key already exists. If so, just return it.
		alias, err := redis.String(conn.Do("GET", lookupKey))

		// Exists. Write it out and go to the next one.
		if err == nil {
			fmt.Fprintln(w, "0", alias)
			continue
		}

		if err != nil && err != redis.ErrNil {
			return err
		}

		// Write an empty string.
		fmt.Fprintln(w, "")
	}

	return sr.Err()
}
<|endoftext|>


<commit_before>package imageproxy

import (
	"errors"
	"io/ioutil"
	"net/http"
	"regexp"
)

type Server struct {
	HttpServer    *http.Server
	RequestParser RequestParser
	Cache         Cache
	Converter     Converter
}

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) {
	parameters, err := server.RequestParser.ParseRequest(request)
	if err != nil {
		http.Error(writer, err.Error(), http.StatusBadRequest)
		return
	}

	err = parameters.Validate()
	if err != nil {
		http.Error(writer, err.Error(), http.StatusBadRequest)
		return
	}

	sourceImage, err := server.getSourceImage(parameters)
	if err != nil {
		http.Error(writer, err.Error(), http.StatusBadRequest)
		return
	}

	image, err := server.convertImage(sourceImage, parameters)
	if err != nil {
		http.Error(writer, err.Error(), http.StatusBadRequest)
		return
	}

	server.sendImage(writer, image)
}

func (server *Server) getSourceImage(parameters *Parameters) (image *Image, err error) {
	response, err := http.Get(parameters.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
}

func (server *Server) convertImage(sourceImage *Image, parameters *Parameters) (image *Image, err error) {
	if server.Converter != nil {
		image, err = server.Converter.Convert(sourceImage, parameters)
	} else {
		image = sourceImage
	}

	return
}

func (server *Server) sendImage(writer http.ResponseWriter, image *Image) {
	if len(image.Type) > 0 {
		writer.Header().Set("Content-Type", "image/"+image.Type)
	}

	writer.Write(image.Data)
}
<commit_msg>Refactor http<commit_after>package imageproxy

import (
	"errors"
	"io/ioutil"
	"net/http"
	"regexp"
)

type Server struct {
	HttpServer    *http.Server
	RequestParser RequestParser
	Cache         Cache
	Converter     Converter
}

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) {
	image, err := server.getImage(request)
	if err != nil {
		http.Error(writer, err.Error(), http.StatusBadRequest)
		return
	}

	server.sendImage(writer, image)
}

func (server *Server) getImage(request *http.Request) (image *Image, err error) {
	parameters, err := server.RequestParser.ParseRequest(request)
	if err != nil {
		return
	}
	err = parameters.Validate()
	if err != nil {
		return
	}

	sourceImage, err := server.getSourceImage(parameters)
	if err != nil {
		return
	}

	image, err = server.convertImage(sourceImage, parameters)
	if err != nil {
		return
	}

	return
}

func (server *Server) getSourceImage(parameters *Parameters) (image *Image, err error) {
	response, err := http.Get(parameters.Source.String())
	if err != nil {
		return
	}
	defer response.Body.Close()

	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]
		}
	}

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

	return
}

func (server *Server) convertImage(sourceImage *Image, parameters *Parameters) (image *Image, err error) {
	if server.Converter != nil {
		image, err = server.Converter.Convert(sourceImage, parameters)
	} else {
		image = sourceImage
	}

	return
}

func (server *Server) sendImage(writer http.ResponseWriter, image *Image) {
	if len(image.Type) > 0 {
		writer.Header().Set("Content-Type", "image/"+image.Type)
	}

	writer.Write(image.Data)
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/md5"
	"fmt"
	"net"
	"regexp"
	"strings"
	"sync"
	"syscall"
	"time"

	"golang.org/x/crypto/ssh"
)

const MAX_NAME_LENGTH = 32
const HISTORY_LEN = 20

const SYSTEM_MESSAGE_FORMAT string = "\033[1;3;90m"
const BEEP string = "\007"

var RE_STRIP_TEXT = regexp.MustCompile("[^0-9A-Za-z_.-]")

type Clients map[string]*Client

type Server struct {
	sshConfig *ssh.ServerConfig
	done      chan struct{}
	clients   Clients
	lock      sync.Mutex
	count     int
	history   *History
	admins    map[string]struct{}   // fingerprint lookup
	bannedPk  map[string]*time.Time // fingerprint lookup
	bannedIp  map[net.Addr]*time.Time
	started   time.Time
}

func NewServer(privateKey []byte) (*Server, error) {
	signer, err := ssh.ParsePrivateKey(privateKey)
	if err != nil {
		return nil, err
	}

	server := Server{
		done:     make(chan struct{}),
		clients:  Clients{},
		count:    0,
		history:  NewHistory(HISTORY_LEN),
		admins:   map[string]struct{}{},
		bannedPk: map[string]*time.Time{},
		bannedIp: map[net.Addr]*time.Time{},
		started:  time.Now(),
	}

	config := ssh.ServerConfig{
		NoClientAuth: false,
		// Auth-related things should be constant-time to avoid timing attacks.
		PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
			fingerprint := Fingerprint(key)
			if server.IsBanned(fingerprint) {
				return nil, fmt.Errorf("Banned.")
			}
			ip := strings.Split(conn.RemoteAddr().String(), ":")[0]
			if ip == "73.3.250.197" {
				// Can't believe I'm doing this...
				return nil, fmt.Errorf("Banned.")
			}
			perm := &ssh.Permissions{Extensions: map[string]string{"fingerprint": fingerprint}}
			return perm, nil
		},
	}
	config.AddHostKey(signer)

	server.sshConfig = &config

	return &server, nil
}

func (s *Server) Len() int {
	return len(s.clients)
}

func (s *Server) SysMsg(msg string, args ...interface{}) {
	s.Broadcast(ContinuousFormat(SYSTEM_MESSAGE_FORMAT, " * "+fmt.Sprintf(msg, args...)), nil)
}

func (s *Server) Broadcast(msg string, except *Client) {
	logger.Debugf("Broadcast to %d: %s", s.Len(), msg)
	s.history.Add(msg)

	for _, client := range s.clients {
		if except != nil && client == except {
			continue
		}
		/* Add an ascii BEL to ding clients when they're mentioned */
		if strings.HasPrefix(msg, fmt.Sprintf("%s:", client.Name)) {
			client.Msg <- msg + BEEP
		} else {
			client.Msg <- msg
		}
	}
}

/* Send a message to a particular nick, if it exists */
func (s *Server) Privmsg(nick, message string, sender *Client) error {
	/* Get the recipient */
	target, ok := s.clients[nick]
	if !ok {
		return fmt.Errorf("no client with that nick")
	}
	/* Send the message */
	target.Msg <- fmt.Sprintf(BEEP+"[PM from %v] %v", sender.Name, message)
	logger.Debugf("PM from %v to %v: %v", sender.Name, nick, message)
	return nil
}

func (s *Server) Add(client *Client) {
	go func() {
		client.WriteLines(s.history.Get(10))
		client.SysMsg2("Welcome to ssh-chat. Enter /help for more.")
	}()

	s.lock.Lock()
	s.count++

	newName, err := s.proposeName(client.Name)
	if err != nil {
		client.SysMsg("Your name '%s' is not available, renamed to '%s'. Use /nick <name> to change it.", client.ColoredName(), ColorString(client.Color, newName))
	}

	client.Rename(newName)
	s.clients[client.Name] = client
	num := len(s.clients)
	s.lock.Unlock()

	s.Broadcast(ContinuousFormat(SYSTEM_MESSAGE_FORMAT, fmt.Sprintf(" * %s joined. (Total connected: %d)", client.ColoredName(), num)), client)
}

func (s *Server) Remove(client *Client) {
	s.lock.Lock()
	delete(s.clients, client.Name)
	s.lock.Unlock()

	s.SysMsg("%s left.", client.ColoredName())
}

func (s *Server) proposeName(name string) (string, error) {
	// Assumes caller holds lock.
	var err error
	name = RE_STRIP_TEXT.ReplaceAllString(name, "")

	if len(name) > MAX_NAME_LENGTH {
		name = name[:MAX_NAME_LENGTH]
	} else if len(name) == 0 {
		name = fmt.Sprintf("Guest%d", s.count)
	}

	_, collision := s.clients[name]
	if collision {
		err = fmt.Errorf("%s is not available.", name)
		name = fmt.Sprintf("Guest%d", s.count)
	}

	return name, err
}

func (s *Server) Rename(client *Client, newName string) {
	s.lock.Lock()

	newName, err := s.proposeName(newName)
	if err != nil {
		client.SysMsg("%s", err)
		s.lock.Unlock()
		return
	}

	// TODO: Use a channel/goroutine for adding clients, rathern than locks?
	delete(s.clients, client.Name)
	oldName := client.Name
	client.Rename(newName)
	s.clients[client.Name] = client
	s.lock.Unlock()

	s.SysMsg("%s is now known as %s.", ColorString(client.Color, oldName), ColorString(client.Color, newName))
}

func (s *Server) List(prefix *string) []string {
	r := []string{}

	for name, _ := range s.clients {
		if prefix != nil && !strings.HasPrefix(name, *prefix) {
			continue
		}
		r = append(r, name)
	}

	return r
}

func (s *Server) Who(name string) *Client {
	return s.clients[name]
}

func (s *Server) Op(fingerprint string) {
	logger.Infof("Adding admin: %s", fingerprint)
	s.lock.Lock()
	s.admins[fingerprint] = struct{}{}
	s.lock.Unlock()
}

func (s *Server) Uptime() string {
	return time.Now().Sub(s.started).String()
}

func (s *Server) IsOp(client *Client) bool {
	_, r := s.admins[client.Fingerprint()]
	return r
}

func (s *Server) IsBanned(fingerprint string) bool {
	ban, hasBan := s.bannedPk[fingerprint]
	if !hasBan {
		return false
	}
	if ban == nil {
		return true
	}
	if ban.Before(time.Now()) {
		s.Unban(fingerprint)
		return false
	}
	return true
}

func (s *Server) Ban(fingerprint string, duration *time.Duration) {
	var until *time.Time
	s.lock.Lock()
	if duration != nil {
		when := time.Now().Add(*duration)
		until = &when
	}
	s.bannedPk[fingerprint] = until
	s.lock.Unlock()
}

func (s *Server) Unban(fingerprint string) {
	s.lock.Lock()
	delete(s.bannedPk, fingerprint)
	s.lock.Unlock()
}

func (s *Server) Start(laddr string) error {
	// Once a ServerConfig has been configured, connections can be
	// accepted.
	socket, err := net.Listen("tcp", laddr)
	if err != nil {
		return err
	}

	logger.Infof("Listening on %s", laddr)

	go func() {
		defer socket.Close()
		for {
			conn, err := socket.Accept()

			if err != nil {
				logger.Errorf("Failed to accept connection: %v", err)
				if err == syscall.EINVAL {
					// TODO: Handle shutdown more gracefully?
					return
				}
			}

			// Goroutineify to resume accepting sockets early.
			go func() {
				// From a standard TCP connection to an encrypted SSH connection
				sshConn, channels, requests, err := ssh.NewServerConn(conn, s.sshConfig)
				if err != nil {
					logger.Errorf("Failed to handshake: %v", err)
					return
				}

				version := RE_STRIP_TEXT.ReplaceAllString(string(sshConn.ClientVersion()), "")
				if len(version) > 100 {
					version = "Evil Jerk with a superlong string"
				}
				logger.Infof("Connection #%d from: %s, %s, %s", s.count+1, sshConn.RemoteAddr(), sshConn.User(), version)

				go ssh.DiscardRequests(requests)

				client := NewClient(s, sshConn)
				go client.handleChannels(channels)
			}()
		}
	}()

	go func() {
		<-s.done
		socket.Close()
	}()

	return nil
}

func (s *Server) AutoCompleteFunction(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
	if key == 9 {
		shortLine := strings.Split(line[:pos], " ")
		partialNick := shortLine[len(shortLine)-1]

		nicks := s.List(&partialNick)
		if len(nicks) > 0 {
			nick := nicks[len(nicks)-1]
			posPartialNick := pos - len(partialNick)

			newLine = strings.Replace(line[posPartialNick:],
				partialNick, nick, 1)
			newLine = line[:posPartialNick] + newLine
			newPos = pos + (len(nick) - len(partialNick))
			ok = true
		}
	} else {
		ok = false
	}
	return
}

func (s *Server) Stop() {
	for _, client := range s.clients {
		client.Conn.Close()
	}

	close(s.done)
}

func Fingerprint(k ssh.PublicKey) string {
	hash := md5.Sum(k.Marshal())
	r := fmt.Sprintf("% x", hash)
	return strings.Replace(r, " ", ":", -1)
}
<commit_msg>Removing beeps for now.<commit_after>package main

import (
	"crypto/md5"
	"fmt"
	"net"
	"regexp"
	"strings"
	"sync"
	"syscall"
	"time"

	"golang.org/x/crypto/ssh"
)

const MAX_NAME_LENGTH = 32
const HISTORY_LEN = 20

const SYSTEM_MESSAGE_FORMAT string = "\033[1;3;90m"
const BEEP string = "\007"

var RE_STRIP_TEXT = regexp.MustCompile("[^0-9A-Za-z_.-]")

type Clients map[string]*Client

type Server struct {
	sshConfig *ssh.ServerConfig
	done      chan struct{}
	clients   Clients
	lock      sync.Mutex
	count     int
	history   *History
	admins    map[string]struct{}   // fingerprint lookup
	bannedPk  map[string]*time.Time // fingerprint lookup
	bannedIp  map[net.Addr]*time.Time
	started   time.Time
}

func NewServer(privateKey []byte) (*Server, error) {
	signer, err := ssh.ParsePrivateKey(privateKey)
	if err != nil {
		return nil, err
	}

	server := Server{
		done:     make(chan struct{}),
		clients:  Clients{},
		count:    0,
		history:  NewHistory(HISTORY_LEN),
		admins:   map[string]struct{}{},
		bannedPk: map[string]*time.Time{},
		bannedIp: map[net.Addr]*time.Time{},
		started:  time.Now(),
	}

	config := ssh.ServerConfig{
		NoClientAuth: false,
		// Auth-related things should be constant-time to avoid timing attacks.
		PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
			fingerprint := Fingerprint(key)
			if server.IsBanned(fingerprint) {
				return nil, fmt.Errorf("Banned.")
			}
			ip := strings.Split(conn.RemoteAddr().String(), ":")[0]
			if ip == "73.3.250.197" {
				// Can't believe I'm doing this...
				return nil, fmt.Errorf("Banned.")
			}
			perm := &ssh.Permissions{Extensions: map[string]string{"fingerprint": fingerprint}}
			return perm, nil
		},
	}
	config.AddHostKey(signer)

	server.sshConfig = &config

	return &server, nil
}

func (s *Server) Len() int {
	return len(s.clients)
}

func (s *Server) SysMsg(msg string, args ...interface{}) {
	s.Broadcast(ContinuousFormat(SYSTEM_MESSAGE_FORMAT, " * "+fmt.Sprintf(msg, args...)), nil)
}

func (s *Server) Broadcast(msg string, except *Client) {
	logger.Debugf("Broadcast to %d: %s", s.Len(), msg)
	s.history.Add(msg)

	for _, client := range s.clients {
		if except != nil && client == except {
			continue
		}
		client.Msg <- msg
	}
}

/* Send a message to a particular nick, if it exists */
func (s *Server) Privmsg(nick, message string, sender *Client) error {
	/* Get the recipient */
	target, ok := s.clients[nick]
	if !ok {
		return fmt.Errorf("no client with that nick")
	}
	/* Send the message */
	target.Msg <- fmt.Sprintf(BEEP+"[PM from %v] %v", sender.Name, message)
	logger.Debugf("PM from %v to %v: %v", sender.Name, nick, message)
	return nil
}

func (s *Server) Add(client *Client) {
	go func() {
		client.WriteLines(s.history.Get(10))
		client.SysMsg2("Welcome to ssh-chat. Enter /help for more.")
	}()

	s.lock.Lock()
	s.count++

	newName, err := s.proposeName(client.Name)
	if err != nil {
		client.SysMsg("Your name '%s' is not available, renamed to '%s'. Use /nick <name> to change it.", client.ColoredName(), ColorString(client.Color, newName))
	}

	client.Rename(newName)
	s.clients[client.Name] = client
	num := len(s.clients)
	s.lock.Unlock()

	s.Broadcast(ContinuousFormat(SYSTEM_MESSAGE_FORMAT, fmt.Sprintf(" * %s joined. (Total connected: %d)", client.ColoredName(), num)), client)
}

func (s *Server) Remove(client *Client) {
	s.lock.Lock()
	delete(s.clients, client.Name)
	s.lock.Unlock()

	s.SysMsg("%s left.", client.ColoredName())
}

func (s *Server) proposeName(name string) (string, error) {
	// Assumes caller holds lock.
	var err error
	name = RE_STRIP_TEXT.ReplaceAllString(name, "")

	if len(name) > MAX_NAME_LENGTH {
		name = name[:MAX_NAME_LENGTH]
	} else if len(name) == 0 {
		name = fmt.Sprintf("Guest%d", s.count)
	}

	_, collision := s.clients[name]
	if collision {
		err = fmt.Errorf("%s is not available.", name)
		name = fmt.Sprintf("Guest%d", s.count)
	}

	return name, err
}

func (s *Server) Rename(client *Client, newName string) {
	s.lock.Lock()

	newName, err := s.proposeName(newName)
	if err != nil {
		client.SysMsg("%s", err)
		s.lock.Unlock()
		return
	}

	// TODO: Use a channel/goroutine for adding clients, rathern than locks?
	delete(s.clients, client.Name)
	oldName := client.Name
	client.Rename(newName)
	s.clients[client.Name] = client
	s.lock.Unlock()

	s.SysMsg("%s is now known as %s.", ColorString(client.Color, oldName), ColorString(client.Color, newName))
}

func (s *Server) List(prefix *string) []string {
	r := []string{}

	for name, _ := range s.clients {
		if prefix != nil && !strings.HasPrefix(name, *prefix) {
			continue
		}
		r = append(r, name)
	}

	return r
}

func (s *Server) Who(name string) *Client {
	return s.clients[name]
}

func (s *Server) Op(fingerprint string) {
	logger.Infof("Adding admin: %s", fingerprint)
	s.lock.Lock()
	s.admins[fingerprint] = struct{}{}
	s.lock.Unlock()
}

func (s *Server) Uptime() string {
	return time.Now().Sub(s.started).String()
}

func (s *Server) IsOp(client *Client) bool {
	_, r := s.admins[client.Fingerprint()]
	return r
}

func (s *Server) IsBanned(fingerprint string) bool {
	ban, hasBan := s.bannedPk[fingerprint]
	if !hasBan {
		return false
	}
	if ban == nil {
		return true
	}
	if ban.Before(time.Now()) {
		s.Unban(fingerprint)
		return false
	}
	return true
}

func (s *Server) Ban(fingerprint string, duration *time.Duration) {
	var until *time.Time
	s.lock.Lock()
	if duration != nil {
		when := time.Now().Add(*duration)
		until = &when
	}
	s.bannedPk[fingerprint] = until
	s.lock.Unlock()
}

func (s *Server) Unban(fingerprint string) {
	s.lock.Lock()
	delete(s.bannedPk, fingerprint)
	s.lock.Unlock()
}

func (s *Server) Start(laddr string) error {
	// Once a ServerConfig has been configured, connections can be
	// accepted.
	socket, err := net.Listen("tcp", laddr)
	if err != nil {
		return err
	}

	logger.Infof("Listening on %s", laddr)

	go func() {
		defer socket.Close()
		for {
			conn, err := socket.Accept()

			if err != nil {
				logger.Errorf("Failed to accept connection: %v", err)
				if err == syscall.EINVAL {
					// TODO: Handle shutdown more gracefully?
					return
				}
			}

			// Goroutineify to resume accepting sockets early.
			go func() {
				// From a standard TCP connection to an encrypted SSH connection
				sshConn, channels, requests, err := ssh.NewServerConn(conn, s.sshConfig)
				if err != nil {
					logger.Errorf("Failed to handshake: %v", err)
					return
				}

				version := RE_STRIP_TEXT.ReplaceAllString(string(sshConn.ClientVersion()), "")
				if len(version) > 100 {
					version = "Evil Jerk with a superlong string"
				}
				logger.Infof("Connection #%d from: %s, %s, %s", s.count+1, sshConn.RemoteAddr(), sshConn.User(), version)

				go ssh.DiscardRequests(requests)

				client := NewClient(s, sshConn)
				go client.handleChannels(channels)
			}()
		}
	}()

	go func() {
		<-s.done
		socket.Close()
	}()

	return nil
}

func (s *Server) AutoCompleteFunction(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
	if key == 9 {
		shortLine := strings.Split(line[:pos], " ")
		partialNick := shortLine[len(shortLine)-1]

		nicks := s.List(&partialNick)
		if len(nicks) > 0 {
			nick := nicks[len(nicks)-1]
			posPartialNick := pos - len(partialNick)

			newLine = strings.Replace(line[posPartialNick:],
				partialNick, nick, 1)
			newLine = line[:posPartialNick] + newLine
			newPos = pos + (len(nick) - len(partialNick))
			ok = true
		}
	} else {
		ok = false
	}
	return
}

func (s *Server) Stop() {
	for _, client := range s.clients {
		client.Conn.Close()
	}

	close(s.done)
}

func Fingerprint(k ssh.PublicKey) string {
	hash := md5.Sum(k.Marshal())
	r := fmt.Sprintf("% x", hash)
	return strings.Replace(r, " ", ":", -1)
}
<|endoftext|>


<commit_before>package main

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

	"github.com/gin-gonic/gin"
	"github.com/intervention-engine/fhir/server"
	"github.com/intervention-engine/ie/controllers"
	"github.com/intervention-engine/ie/huddles"
	"github.com/intervention-engine/ie/utilities"
	"github.com/robfig/cron"
)

// Setup the huddle flag to accumulate huddle config file paths
type huddleSlice []string

func (h *huddleSlice) String() string {
	return fmt.Sprint(*h)
}
func (h *huddleSlice) Set(value string) error {
	*h = append(*h, value)
	return nil
}

var huddleFlag huddleSlice

func init() {
	flag.Var(&huddleFlag, "huddle", "path to a huddle configuration file (repeat flag for multiple paths)")
}

func main() {
	codeLookupFlag := flag.Bool("loadCodes", false, "flag to enable download of icd-9 and icd-10 code lookup")
	icd9URL := flag.String("icd9URL", "https://www.cms.gov/Medicare/Coding/ICD9ProviderDiagnosticCodes/Downloads/ICD-9-CM-v32-master-descriptions.zip", "url for icd-9 code definition zip")
	icd10URL := flag.String("icd10URL", "https://www.cms.gov/Medicare/Coding/ICD10/Downloads/2016-Code-Descriptions-in-Tabular-Order.zip", "url for icd-10 code definition zip")
	subscriptionFlag := flag.Bool("subscriptions", false, "enables limited support for resource subscriptions (default: false)")
	reqLog := flag.Bool("reqlog", false, "Enables request logging -- do NOT use in production")
	logFileFlag := flag.String("logdir", "", "Path to a directory for ie and gin logs to be written to.")
	flag.Parse()

	lfpath := getConfigValue(logFileFlag, "IE_LOG_DIR", "")
	if lfpath != "" {
		err := os.Mkdir(lfpath, 0755)
		if err != nil && !os.IsExist(err) {
			fmt.Println("Error creating log directory:" + err.Error())
		}

		lf, err := os.OpenFile(lfpath+"/ie.log", os.O_RDWR|os.O_APPEND, 0755)
		if os.IsNotExist(err) {
			lf, err = os.Create(lfpath + "/ie.log")
		}
		if err != nil {
			fmt.Println("Unable to create ie log file:" + err.Error())
		} else {
			log.SetOutput(lf)
		}
	}

	mongoURL := os.Getenv("MONGO_URL")
	if mongoURL == "" {
		mongoURL = "mongodb://localhost:27017"
	}

	riskServiceURL := os.Getenv("RISK_SERVICE_URL")
	if riskServiceURL == "" {
		riskServiceURL = "http://localhost:9000"
	}

	huddleConfigs := huddleFlag
	if len(huddleConfigs) == 0 && os.Getenv("HUDDLE_CONFIGS") != "" {
		huddleConfigs = huddleSlice(strings.Split(os.Getenv("HUDDLE_CONFIGS"), ","))
	}

	if *codeLookupFlag {
		utilities.LoadICDFromCMS(mongoURL, *icd9URL, "ICD-9")
		utilities.LoadICDFromCMS(mongoURL, *icd10URL, "ICD-10")
	}

	var ip net.IP
	var selfURL string
	host, err := os.Hostname()
	if err != nil {
		panic(err)
	}
	addrs, err := net.LookupIP(host)
	if err != nil {
		fmt.Println("Unable to lookup IP based on hostname, defaulting to localhost.")
		selfURL = "http://localhost:3001"
	}
	for _, addr := range addrs {
		if ipv4 := addr.To4(); ipv4 != nil {
			ip = ipv4
			selfURL = "http://" + ip.String() + ":3001"
		}
	}

	s := server.NewServer(mongoURL)

	if *reqLog {
		s.Engine.Use(server.RequestLoggerHandler)
	}

	// Since the huddle controller needs info from the command line, set it up here.  When IE is refactored
	// to take out globals (and other stuff), this should be rethought.
	c := cron.New()
	huddleController := new(huddles.HuddleSchedulerController)
	for _, huddlePath := range huddleConfigs {
		data, err := ioutil.ReadFile(huddlePath)
		if err != nil {
			panic(err)
		}
		config := new(huddles.HuddleConfig)
		if err := json.Unmarshal(data, config); err != nil {
			panic(err)
		}
		huddleController.AddConfig(config)
		/*
			// Wait 1 minute before doing initial runs or setting up cron jobs.  This allows the server to get
			// started (since it needs to initiate the db connection, etc).
			time.AfterFunc(1*time.Minute, func() {
				// Do an initial run
				fmt.Println("Initial scheduling for huddle with name ", config.Name)
				huddles.ScheduleHuddles(config)

				// Set up the cron job for future runs
				if config.SchedulerCronSpec != "" {
					err := c.AddFunc(config.SchedulerCronSpec, func() {
						_, err := huddles.ScheduleHuddles(config)
						if err != nil {
							fmt.Printf("ERROR: Could not schedule huddles for huddle with name %s: %v", config.Name, err)
						}
					})
					if err != nil {
						panic(err)
					}
					fmt.Printf("Huddle with name %s scheduled with cron spec: %s\n", config.Name, config.SchedulerCronSpec)
				} else {
					fmt.Printf("Warning: Huddle with name %s is not configured with a scheduler cron job.\n", config.Name)
				}
			})
		*/
	}
	s.Engine.GET("/ScheduleHuddles", huddleController.ScheduleHandler)

	if lfpath != "" {
		glf, err := os.OpenFile(lfpath+"/gin.log", os.O_RDWR|os.O_APPEND, 0755)
		if os.IsNotExist(err) {
			glf, err = os.Create(lfpath + "/gin.log")
		}
		if err != nil {
			fmt.Println("Unable to create ie log file:" + err.Error())
		} else {
			s.Engine.Use(gin.LoggerWithWriter(glf))
		}
	}

	if len(huddleConfigs) > 0 {
		c.Start()
		defer c.Stop()
	}

	closer := controllers.RegisterRoutes(s, selfURL, riskServiceURL, *subscriptionFlag)
	defer closer()

	s.Run(server.DefaultConfig)
}

func getConfigValue(parsedFlag *string, envVar string, defaultVal string) string {
	val := *parsedFlag
	if val == "" {
		val = os.Getenv(envVar)
		if val == "" {
			val = defaultVal
		}
	}
	return val
}
<commit_msg>Re-enable huddle scheduling cron in server<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"os"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/intervention-engine/fhir/server"
	"github.com/intervention-engine/ie/controllers"
	"github.com/intervention-engine/ie/huddles"
	"github.com/intervention-engine/ie/utilities"
	"github.com/robfig/cron"
)

// Setup the huddle flag to accumulate huddle config file paths
type huddleSlice []string

func (h *huddleSlice) String() string {
	return fmt.Sprint(*h)
}
func (h *huddleSlice) Set(value string) error {
	*h = append(*h, value)
	return nil
}

var huddleFlag huddleSlice

func init() {
	flag.Var(&huddleFlag, "huddle", "path to a huddle configuration file (repeat flag for multiple paths)")
}

func main() {
	codeLookupFlag := flag.Bool("loadCodes", false, "flag to enable download of icd-9 and icd-10 code lookup")
	icd9URL := flag.String("icd9URL", "https://www.cms.gov/Medicare/Coding/ICD9ProviderDiagnosticCodes/Downloads/ICD-9-CM-v32-master-descriptions.zip", "url for icd-9 code definition zip")
	icd10URL := flag.String("icd10URL", "https://www.cms.gov/Medicare/Coding/ICD10/Downloads/2016-Code-Descriptions-in-Tabular-Order.zip", "url for icd-10 code definition zip")
	subscriptionFlag := flag.Bool("subscriptions", false, "enables limited support for resource subscriptions (default: false)")
	reqLog := flag.Bool("reqlog", false, "Enables request logging -- do NOT use in production")
	logFileFlag := flag.String("logdir", "", "Path to a directory for ie and gin logs to be written to.")
	flag.Parse()

	lfpath := getConfigValue(logFileFlag, "IE_LOG_DIR", "")
	if lfpath != "" {
		err := os.Mkdir(lfpath, 0755)
		if err != nil && !os.IsExist(err) {
			log.Println("Error creating log directory:" + err.Error())
		}

		lf, err := os.OpenFile(lfpath+"/ie.log", os.O_RDWR|os.O_APPEND, 0755)
		if os.IsNotExist(err) {
			lf, err = os.Create(lfpath + "/ie.log")
		}
		if err != nil {
			log.Println("Unable to create ie log file:" + err.Error())
		} else {
			log.SetOutput(lf)
		}
	}

	mongoURL := os.Getenv("MONGO_URL")
	if mongoURL == "" {
		mongoURL = "mongodb://localhost:27017"
	}

	riskServiceURL := os.Getenv("RISK_SERVICE_URL")
	if riskServiceURL == "" {
		riskServiceURL = "http://localhost:9000"
	}

	huddleConfigs := huddleFlag
	if len(huddleConfigs) == 0 && os.Getenv("HUDDLE_CONFIGS") != "" {
		huddleConfigs = huddleSlice(strings.Split(os.Getenv("HUDDLE_CONFIGS"), ","))
	}

	if *codeLookupFlag {
		utilities.LoadICDFromCMS(mongoURL, *icd9URL, "ICD-9")
		utilities.LoadICDFromCMS(mongoURL, *icd10URL, "ICD-10")
	}

	var ip net.IP
	var selfURL string
	host, err := os.Hostname()
	if err != nil {
		panic(err)
	}
	addrs, err := net.LookupIP(host)
	if err != nil {
		log.Println("Unable to lookup IP based on hostname, defaulting to localhost.")
		selfURL = "http://localhost:3001"
	}
	for _, addr := range addrs {
		if ipv4 := addr.To4(); ipv4 != nil {
			ip = ipv4
			selfURL = "http://" + ip.String() + ":3001"
		}
	}

	s := server.NewServer(mongoURL)

	if *reqLog {
		s.Engine.Use(server.RequestLoggerHandler)
	}

	// Since the huddle controller needs info from the command line, set it up here.  When IE is refactored
	// to take out globals (and other stuff), this should be rethought.
	c := cron.New()
	huddleController := new(huddles.HuddleSchedulerController)
	for _, huddlePath := range huddleConfigs {
		data, err := ioutil.ReadFile(huddlePath)
		if err != nil {
			panic(err)
		}
		config := new(huddles.HuddleConfig)
		if err := json.Unmarshal(data, config); err != nil {
			panic(err)
		}
		huddleController.AddConfig(config)
		scheduleIt := func() {
			_, err := huddles.NewHuddleScheduler(config).ScheduleHuddles()
			if err != nil {
				log.Printf("ERROR: Could not schedule huddles for huddle with name %s: %v", config.Name, err)
			}
		}

		// Wait 1 minute before doing initial runs or setting up cron jobs.  This allows the server to get
		// started (since it needs to initiate the db connection, etc).
		time.AfterFunc(1*time.Minute, func() {
			// Do an initial run
			log.Println("Initial scheduling for huddle with name ", config.Name)
			scheduleIt()

			// Set up the cron job for future runs
			if config.SchedulerCronSpec != "" {
				err := c.AddFunc(config.SchedulerCronSpec, scheduleIt)
				if err != nil {
					panic(err)
				}
				log.Printf("Huddle with name %s scheduled with cron spec: %s\n", config.Name, config.SchedulerCronSpec)
			} else {
				log.Printf("Warning: Huddle with name %s is not configured with a scheduler cron job.\n", config.Name)
			}
		})
	}
	s.Engine.GET("/ScheduleHuddles", huddleController.ScheduleHandler)

	if lfpath != "" {
		glf, err := os.OpenFile(lfpath+"/gin.log", os.O_RDWR|os.O_APPEND, 0755)
		if os.IsNotExist(err) {
			glf, err = os.Create(lfpath + "/gin.log")
		}
		if err != nil {
			log.Println("Unable to create ie log file:" + err.Error())
		} else {
			s.Engine.Use(gin.LoggerWithWriter(glf))
		}
	}

	if len(huddleConfigs) > 0 {
		c.Start()
		defer c.Stop()
	}

	closer := controllers.RegisterRoutes(s, selfURL, riskServiceURL, *subscriptionFlag)
	defer closer()

	s.Run(server.DefaultConfig)
}

func getConfigValue(parsedFlag *string, envVar string, defaultVal string) string {
	val := *parsedFlag
	if val == "" {
		val = os.Getenv(envVar)
		if val == "" {
			val = defaultVal
		}
	}
	return val
}
<|endoftext|>


<commit_before>package godge

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"sort"
	"sync"

	docker "github.com/fsouza/go-dockerclient"
)

type users struct {
	sync.RWMutex
	m map[string]string
}

func (u *users) get(username string) (string, bool) {
	u.RLock()
	defer u.RUnlock()
	ret, ok := u.m[username]
	return ret, ok
}

func (u *users) set(username, password string) {
	u.Lock()
	defer u.Unlock()
	u.m[username] = password
}

func (u *users) usernames() []string {
	u.RLock()
	defer u.RUnlock()
	var ret []string
	for k := range u.m {
		ret = append(ret, k)
	}
	return ret
}

type runningSubmissions struct {
	sync.RWMutex
	m map[string]*Submission
}

func (r *runningSubmissions) get(id string) (*Submission, bool) {
	r.RLock()
	defer r.RUnlock()
	ret, ok := r.m[id]
	return ret, ok
}

func (r *runningSubmissions) set(id string, sub *Submission) {
	r.Lock()
	defer r.Unlock()
	r.m[id] = sub
}

func (r *runningSubmissions) del(id string) {
	r.Lock()
	defer r.Unlock()
	delete(r.m, id)
}

type tasks struct {
	sync.RWMutex
	m map[string]Task
}

func (t *tasks) get(name string) (Task, bool) {
	t.RLock()
	defer t.RUnlock()
	ret, ok := t.m[name]
	return ret, ok
}

func (t *tasks) set(name string, task Task) {
	t.Lock()
	defer t.Unlock()
	t.m[name] = task
}

func (t *tasks) names() []string {
	t.RLock()
	defer t.RUnlock()
	var ret []string
	for k := range t.m {
		ret = append(ret, k)
	}
	return ret
}

// Server holds all the information related to a single instance of the judge. It's used to register Tasks and start the HTTP server.
type Server struct {
	address            string
	tasks              tasks
	pendingSubmissions chan submissionRequest
	requestErrorChan   chan error
	dockerClient       *docker.Client
	users              users
	scoreboard         scoreboard
	runningSubmissions runningSubmissions
}

// NewServer creates a new instance of the judge. It takes the address that the
// judge will listen to and the address of the address daemon (e.g. unix:///var/run/docker.sock).
// NewServer returns an error if it fails to connect to the docker daemon.
func NewServer(address string, dockerAddress string) (*Server, error) {
	dc, err := docker.NewClient(dockerAddress)
	if err != nil {
		return nil, fmt.Errorf("failed to connect to docker daemon: %v", err)
	}
	if err := dc.Ping(); err != nil {
		return nil, fmt.Errorf("failed to connect to docker daemon: %v", err)
	}
	return &Server{
		address: address,
		tasks: tasks{
			m: make(map[string]Task),
		},
		pendingSubmissions: make(chan submissionRequest),
		dockerClient:       dc,
		users: users{
			m: make(map[string]string),
		},
		scoreboard: scoreboard{
			m: make(map[string]map[string]string),
		},
		runningSubmissions: runningSubmissions{
			m: make(map[string]*Submission),
		},
	}, nil
}

// RegisterTask registers a new task in the server.
func (s *Server) RegisterTask(t Task) {
	s.tasks.set(t.Name, t)
}

// handleSubmission is used to handle a received submission by executing the tests of the
// submission's task against this submission.
func (s *Server) handleSubmission(sub *Submission) error {
	t, ok := s.tasks.get(sub.TaskName)
	if !ok {
		return fmt.Errorf("task %v not found", sub.TaskName)
	}
	s.runningSubmissions.set(sub.id, sub)
	defer s.runningSubmissions.del(sub.id)
	if err := t.execute(sub); err != nil {
		return fmt.Errorf("task %v failed: %v", sub.TaskName, err)
	}
	return nil
}

// A wrapper around the submission that's used for communication between
// the http handler and the server.
type submissionRequest struct {
	result     chan error
	submission *Submission
}

// Updates the scoreboard.
func (s *Server) reportResult(sub *Submission, err error) {
	log.Printf("%v submission for %v: %v", sub.Language, sub.TaskName, err)
	if err != nil {
		s.scoreboard.set(sub.Username, sub.TaskName, failedVerdict)
		return
	}
	s.scoreboard.set(sub.Username, sub.TaskName, passedVerdict)
}

// Executes the tests and report the result back to the http handler and the
// scoreboard.
func (s *Server) processSubmissions() {
	for sreq := range s.pendingSubmissions {
		err := s.handleSubmission(sreq.submission)
		sreq.result <- err
		s.reportResult(sreq.submission, err)
	}
}

// SubmissionResponse is the response returned back by the server in response
// to the submission request. It's exposed to be used by the command line client.
type SubmissionResponse struct {
	Passed bool   `json:"passed"`
	Error  string `json:"error"`
}

// The handler that handles submission requests.
func (s *Server) submitHTTPHandler(w http.ResponseWriter, req *http.Request) {
	if req.Method != http.MethodPost {
		httpJSONError(w, "Only POST requests are allowed", http.StatusMethodNotAllowed)
		return
	}
	// Authenticate the user
	username, password, ok := req.BasicAuth()
	if !ok {
		httpJSONError(w, "Wrong username or password", http.StatusUnauthorized)
		return
	}
	if u, _ := s.users.get(username); u != password {
		httpJSONError(w, "Wrong username or password", http.StatusUnauthorized)
		return
	}

	var sub Submission
	err := json.NewDecoder(req.Body).Decode(&sub)
	if err != nil {
		httpJSONError(w, fmt.Sprintf("Failed to decode request body: %v", err), http.StatusBadRequest)
		return
	}
	sub.Executor.setDockerClient(s.dockerClient)

	// Send the submission for the server to run the tests.
	res := make(chan error)
	s.pendingSubmissions <- submissionRequest{
		result:     res,
		submission: &sub,
	}

	// Wait for the submission results and prepare the response.
	result := <-res

	resp := SubmissionResponse{
		Passed: true,
		Error:  "",
	}

	if result != nil {
		resp = SubmissionResponse{
			Passed: false,
			Error:  result.Error(),
		}
	}

	w.WriteHeader(http.StatusOK)
	if err := json.NewEncoder(w).Encode(resp); err != nil {
		httpJSONError(w, "Failed to encode response", http.StatusInternalServerError)
		return
	}
}

// RegisterRequest represents the registeration request. It's exposed to be used by the
// command line client.
type RegisterRequest struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

// Handles registration requests.
func (s *Server) registerHTTPHandler(w http.ResponseWriter, req *http.Request) {
	if req.Method != http.MethodPost {
		httpJSONError(w, "Only POST requests are allowed", http.StatusMethodNotAllowed)
		return
	}

	var rreq RegisterRequest
	err := json.NewDecoder(req.Body).Decode(&rreq)
	if err != nil {
		httpJSONError(w, fmt.Sprintf("Failed to decode request body: %v", err), http.StatusBadRequest)
		return
	}

	// Make sure that the username is not empty.
	if len(rreq.Username) == 0 {
		httpJSONError(w, fmt.Sprintf("Username cannot be empty"), http.StatusBadRequest)
		return
	}

	// Make sure that the username is unique.
	if _, ok := s.users.get(rreq.Username); ok {
		httpJSONError(w, fmt.Sprintf("Username %v is already registered", rreq.Username), http.StatusBadRequest)
		return
	}

	s.users.set(rreq.Username, rreq.Password)
	log.Printf("User %v registered", rreq.Username)

	w.WriteHeader(http.StatusCreated)
}

// Handles tasks queries.
func (s *Server) tasksHTTPHandler(w http.ResponseWriter, req *http.Request) {
	if req.Method != http.MethodGet {
		httpJSONError(w, "Only GET requests are allowed", http.StatusMethodNotAllowed)
		return
	}

	ts := s.tasks.names()

	w.WriteHeader(http.StatusOK)

	if err := json.NewEncoder(w).Encode(ts); err != nil {
		httpJSONError(w, "Failed to encode tasks", http.StatusInternalServerError)
		return
	}
}

// Handles scoreboard requests.
func (s *Server) scoreboardHTTPHandler(w http.ResponseWriter, req *http.Request) {
	if req.Method != http.MethodGet {
		httpJSONError(w, "Only GET requests are allowed", http.StatusMethodNotAllowed)
		return
	}

	w.Header().Add("Content-Type", "text/html")

	ts := s.tasks.names()
	sort.Strings(ts)

	us := s.users.usernames()
	sort.Strings(us)

	scoreboard := s.scoreboard.toScoreboard(us, ts)

	scoreboardTmpl.Execute(w, map[string]interface{}{
		"Scoreboard": scoreboard,
	})
}

func (s *Server) proccessDockerEvents() {
	listener := make(chan *docker.APIEvents)
	if err := s.dockerClient.AddEventListener(listener); err != nil {
		log.Fatal(err)
	}
	for e := range listener {
		if e.Type != "container" || (e.Action != "start" && e.Action != "die") {
			continue
		}

		s.runningSubmissions.RLock()
		for _, s := range s.runningSubmissions.m {
			if s.Executor.containerID() == e.Actor.ID {
				switch e.Action {
				case "start":
					select {
					case s.Executor.StartEvent() <- struct{}{}:
					default:
					}
				case "die":
					select {
					case s.Executor.DieEvent() <- struct{}{}:
					default:
					}
				}
			}
		}
		s.runningSubmissions.RUnlock()
	}
}

// Start starts the http server and the goroutine responsible for processing
// the submissions.
func (s *Server) Start() error {
	go s.processSubmissions()
	go s.proccessDockerEvents()
	mux := http.NewServeMux()
	mux.HandleFunc("/submit", s.submitHTTPHandler)
	mux.HandleFunc("/register", s.registerHTTPHandler)
	mux.HandleFunc("/tasks", s.tasksHTTPHandler)
	mux.HandleFunc("/scoreboard", s.scoreboardHTTPHandler)
	return http.ListenAndServe(s.address, mux)
}
<commit_msg>Fixing tasks response<commit_after>package godge

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"sort"
	"sync"

	docker "github.com/fsouza/go-dockerclient"
)

type users struct {
	sync.RWMutex
	m map[string]string
}

func (u *users) get(username string) (string, bool) {
	u.RLock()
	defer u.RUnlock()
	ret, ok := u.m[username]
	return ret, ok
}

func (u *users) set(username, password string) {
	u.Lock()
	defer u.Unlock()
	u.m[username] = password
}

func (u *users) usernames() []string {
	u.RLock()
	defer u.RUnlock()
	var ret []string
	for k := range u.m {
		ret = append(ret, k)
	}
	return ret
}

type runningSubmissions struct {
	sync.RWMutex
	m map[string]*Submission
}

func (r *runningSubmissions) get(id string) (*Submission, bool) {
	r.RLock()
	defer r.RUnlock()
	ret, ok := r.m[id]
	return ret, ok
}

func (r *runningSubmissions) set(id string, sub *Submission) {
	r.Lock()
	defer r.Unlock()
	r.m[id] = sub
}

func (r *runningSubmissions) del(id string) {
	r.Lock()
	defer r.Unlock()
	delete(r.m, id)
}

type tasks struct {
	sync.RWMutex
	m map[string]Task
}

func (t *tasks) get(name string) (Task, bool) {
	t.RLock()
	defer t.RUnlock()
	ret, ok := t.m[name]
	return ret, ok
}

func (t *tasks) set(name string, task Task) {
	t.Lock()
	defer t.Unlock()
	t.m[name] = task
}

func (t *tasks) names() []string {
	t.RLock()
	defer t.RUnlock()
	var ret []string
	for k := range t.m {
		ret = append(ret, k)
	}
	return ret
}

func (t *tasks) tasks() []Task {
	t.RLock()
	defer t.RUnlock()
	var ret []Task
	for _, v := range t.m {
		ret = append(ret, v)
	}
	return ret
}

// Server holds all the information related to a single instance of the judge. It's used to register Tasks and start the HTTP server.
type Server struct {
	address            string
	tasks              tasks
	pendingSubmissions chan submissionRequest
	requestErrorChan   chan error
	dockerClient       *docker.Client
	users              users
	scoreboard         scoreboard
	runningSubmissions runningSubmissions
}

// NewServer creates a new instance of the judge. It takes the address that the
// judge will listen to and the address of the address daemon (e.g. unix:///var/run/docker.sock).
// NewServer returns an error if it fails to connect to the docker daemon.
func NewServer(address string, dockerAddress string) (*Server, error) {
	dc, err := docker.NewClient(dockerAddress)
	if err != nil {
		return nil, fmt.Errorf("failed to connect to docker daemon: %v", err)
	}
	if err := dc.Ping(); err != nil {
		return nil, fmt.Errorf("failed to connect to docker daemon: %v", err)
	}
	return &Server{
		address: address,
		tasks: tasks{
			m: make(map[string]Task),
		},
		pendingSubmissions: make(chan submissionRequest),
		dockerClient:       dc,
		users: users{
			m: make(map[string]string),
		},
		scoreboard: scoreboard{
			m: make(map[string]map[string]string),
		},
		runningSubmissions: runningSubmissions{
			m: make(map[string]*Submission),
		},
	}, nil
}

// RegisterTask registers a new task in the server.
func (s *Server) RegisterTask(t Task) {
	s.tasks.set(t.Name, t)
}

// handleSubmission is used to handle a received submission by executing the tests of the
// submission's task against this submission.
func (s *Server) handleSubmission(sub *Submission) error {
	t, ok := s.tasks.get(sub.TaskName)
	if !ok {
		return fmt.Errorf("task %v not found", sub.TaskName)
	}
	s.runningSubmissions.set(sub.id, sub)
	defer s.runningSubmissions.del(sub.id)
	if err := t.execute(sub); err != nil {
		return fmt.Errorf("task %v failed: %v", sub.TaskName, err)
	}
	return nil
}

// A wrapper around the submission that's used for communication between
// the http handler and the server.
type submissionRequest struct {
	result     chan error
	submission *Submission
}

// Updates the scoreboard.
func (s *Server) reportResult(sub *Submission, err error) {
	log.Printf("%v submission for %v: %v", sub.Language, sub.TaskName, err)
	if err != nil {
		s.scoreboard.set(sub.Username, sub.TaskName, failedVerdict)
		return
	}
	s.scoreboard.set(sub.Username, sub.TaskName, passedVerdict)
}

// Executes the tests and report the result back to the http handler and the
// scoreboard.
func (s *Server) processSubmissions() {
	for sreq := range s.pendingSubmissions {
		err := s.handleSubmission(sreq.submission)
		sreq.result <- err
		s.reportResult(sreq.submission, err)
	}
}

// SubmissionResponse is the response returned back by the server in response
// to the submission request. It's exposed to be used by the command line client.
type SubmissionResponse struct {
	Passed bool   `json:"passed"`
	Error  string `json:"error"`
}

// The handler that handles submission requests.
func (s *Server) submitHTTPHandler(w http.ResponseWriter, req *http.Request) {
	if req.Method != http.MethodPost {
		httpJSONError(w, "Only POST requests are allowed", http.StatusMethodNotAllowed)
		return
	}
	// Authenticate the user
	username, password, ok := req.BasicAuth()
	if !ok {
		httpJSONError(w, "Wrong username or password", http.StatusUnauthorized)
		return
	}
	if u, _ := s.users.get(username); u != password {
		httpJSONError(w, "Wrong username or password", http.StatusUnauthorized)
		return
	}

	var sub Submission
	err := json.NewDecoder(req.Body).Decode(&sub)
	if err != nil {
		httpJSONError(w, fmt.Sprintf("Failed to decode request body: %v", err), http.StatusBadRequest)
		return
	}
	sub.Executor.setDockerClient(s.dockerClient)

	// Send the submission for the server to run the tests.
	res := make(chan error)
	s.pendingSubmissions <- submissionRequest{
		result:     res,
		submission: &sub,
	}

	// Wait for the submission results and prepare the response.
	result := <-res

	resp := SubmissionResponse{
		Passed: true,
		Error:  "",
	}

	if result != nil {
		resp = SubmissionResponse{
			Passed: false,
			Error:  result.Error(),
		}
	}

	w.WriteHeader(http.StatusOK)
	if err := json.NewEncoder(w).Encode(resp); err != nil {
		httpJSONError(w, "Failed to encode response", http.StatusInternalServerError)
		return
	}
}

// RegisterRequest represents the registeration request. It's exposed to be used by the
// command line client.
type RegisterRequest struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

// Handles registration requests.
func (s *Server) registerHTTPHandler(w http.ResponseWriter, req *http.Request) {
	if req.Method != http.MethodPost {
		httpJSONError(w, "Only POST requests are allowed", http.StatusMethodNotAllowed)
		return
	}

	var rreq RegisterRequest
	err := json.NewDecoder(req.Body).Decode(&rreq)
	if err != nil {
		httpJSONError(w, fmt.Sprintf("Failed to decode request body: %v", err), http.StatusBadRequest)
		return
	}

	// Make sure that the username is not empty.
	if len(rreq.Username) == 0 {
		httpJSONError(w, fmt.Sprintf("Username cannot be empty"), http.StatusBadRequest)
		return
	}

	// Make sure that the username is unique.
	if _, ok := s.users.get(rreq.Username); ok {
		httpJSONError(w, fmt.Sprintf("Username %v is already registered", rreq.Username), http.StatusBadRequest)
		return
	}

	s.users.set(rreq.Username, rreq.Password)
	log.Printf("User %v registered", rreq.Username)

	w.WriteHeader(http.StatusCreated)
}

// Handles tasks queries.
func (s *Server) tasksHTTPHandler(w http.ResponseWriter, req *http.Request) {
	if req.Method != http.MethodGet {
		httpJSONError(w, "Only GET requests are allowed", http.StatusMethodNotAllowed)
		return
	}

	ts := s.tasks.tasks()

	w.WriteHeader(http.StatusOK)

	if err := json.NewEncoder(w).Encode(ts); err != nil {
		httpJSONError(w, "Failed to encode tasks", http.StatusInternalServerError)
		return
	}
}

// Handles scoreboard requests.
func (s *Server) scoreboardHTTPHandler(w http.ResponseWriter, req *http.Request) {
	if req.Method != http.MethodGet {
		httpJSONError(w, "Only GET requests are allowed", http.StatusMethodNotAllowed)
		return
	}

	w.Header().Add("Content-Type", "text/html")

	ts := s.tasks.names()
	sort.Strings(ts)

	us := s.users.usernames()
	sort.Strings(us)

	scoreboard := s.scoreboard.toScoreboard(us, ts)

	scoreboardTmpl.Execute(w, map[string]interface{}{
		"Scoreboard": scoreboard,
	})
}

func (s *Server) proccessDockerEvents() {
	listener := make(chan *docker.APIEvents)
	if err := s.dockerClient.AddEventListener(listener); err != nil {
		log.Fatal(err)
	}
	for e := range listener {
		if e.Type != "container" || (e.Action != "start" && e.Action != "die") {
			continue
		}

		s.runningSubmissions.RLock()
		for _, s := range s.runningSubmissions.m {
			if s.Executor.containerID() == e.Actor.ID {
				switch e.Action {
				case "start":
					select {
					case s.Executor.StartEvent() <- struct{}{}:
					default:
					}
				case "die":
					select {
					case s.Executor.DieEvent() <- struct{}{}:
					default:
					}
				}
			}
		}
		s.runningSubmissions.RUnlock()
	}
}

// Start starts the http server and the goroutine responsible for processing
// the submissions.
func (s *Server) Start() error {
	go s.processSubmissions()
	go s.proccessDockerEvents()
	mux := http.NewServeMux()
	mux.HandleFunc("/submit", s.submitHTTPHandler)
	mux.HandleFunc("/register", s.registerHTTPHandler)
	mux.HandleFunc("/tasks", s.tasksHTTPHandler)
	mux.HandleFunc("/scoreboard", s.scoreboardHTTPHandler)
	return http.ListenAndServe(s.address, mux)
}
<|endoftext|>