<commit_before>package web

import (
    "bytes"
    "code.google.com/p/go.net/websocket"
    "crypto/tls"
    "fmt"
    "log"
    "net"
    "net/http"
    "net/http/pprof"
    "os"
    "path"
    "reflect"
    "regexp"
    "runtime"
    "strconv"
    "strings"
    "time"
)

// ServerConfig is configuration for server objects.
type ServerConfig struct {
    StaticDir    string
    Addr         string
    Port         int
    CookieSecret string
    RecoverPanic bool
    Profiler     bool
}

// Server represents a web.go server.
type Server struct {
    Config *ServerConfig
    routes []route
    Logger *log.Logger
    Env    map[string]interface{}
    //save the listener so it can be closed
    l   net.Listener
}

func NewServer() *Server {
    return &Server{
        Config: Config,
        Logger: log.New(os.Stdout, "", log.Ldate|log.Ltime),
        Env:    map[string]interface{}{},
    }
}

func (s *Server) initServer() {
    if s.Config == nil {
        s.Config = &ServerConfig{}
    }

    if s.Logger == nil {
        s.Logger = log.New(os.Stdout, "", log.Ldate|log.Ltime)
    }
}

type route struct {
    r           string
    cr          *regexp.Regexp
    method      string
    handler     reflect.Value
    httpHandler http.Handler
}

func (s *Server) addRoute(r string, method string, handler interface{}) {
    cr, err := regexp.Compile(r)
    if err != nil {
        s.Logger.Printf("Error in route regex %q\n", r)
        return
    }

    switch handler.(type) {
    case http.Handler:
        s.routes = append(s.routes, route{r: r, cr: cr, method: method, httpHandler: handler.(http.Handler)})
    case reflect.Value:
        fv := handler.(reflect.Value)
        s.routes = append(s.routes, route{r: r, cr: cr, method: method, handler: fv})
    default:
        fv := reflect.ValueOf(handler)
        s.routes = append(s.routes, route{r: r, cr: cr, method: method, handler: fv})
    }
}

// ServeHTTP is the interface method for Go's http server package
func (s *Server) ServeHTTP(c http.ResponseWriter, req *http.Request) {
    s.Process(c, req)
}

// Process invokes the routing system for server s
func (s *Server) Process(c http.ResponseWriter, req *http.Request) {
    route := s.routeHandler(req, c)
    if route != nil {
        route.httpHandler.ServeHTTP(c, req)
    }
}

// Get adds a handler for the 'GET' http method for server s.
func (s *Server) Get(route string, handler interface{}) {
    s.addRoute(route, "GET", handler)
}

// Post adds a handler for the 'POST' http method for server s.
func (s *Server) Post(route string, handler interface{}) {
    s.addRoute(route, "POST", handler)
}

// Put adds a handler for the 'PUT' http method for server s.
func (s *Server) Put(route string, handler interface{}) {
    s.addRoute(route, "PUT", handler)
}

// Delete adds a handler for the 'DELETE' http method for server s.
func (s *Server) Delete(route string, handler interface{}) {
    s.addRoute(route, "DELETE", handler)
}

// Match adds a handler for an arbitrary http method for server s.
func (s *Server) Match(method string, route string, handler interface{}) {
    s.addRoute(route, method, handler)
}

//Adds a custom handler. Only for webserver mode. Will have no effect when running as FCGI or SCGI.
func (s *Server) Handler(route string, method string, httpHandler http.Handler) {
    s.addRoute(route, method, httpHandler)
}

//Adds a handler for websockets. Only for webserver mode. Will have no effect when running as FCGI or SCGI.
func (s *Server) Websocket(route string, httpHandler websocket.Handler) {
    s.addRoute(route, "GET", httpHandler)
}

// Run starts the web application and serves HTTP requests for s
func (s *Server) Run(addr string) {
    s.initServer()

    mux := http.NewServeMux()
    if s.Config.Profiler {
        mux.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline))
        mux.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile))
        mux.Handle("/debug/pprof/heap", pprof.Handler("heap"))
        mux.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol))
    }
    mux.Handle("/", s)

    s.Logger.Printf("web.go serving %s\n", addr)

    l, err := net.Listen("tcp", addr)
    if err != nil {
        log.Fatal("ListenAndServe:", err)
    }
    s.l = l
    err = http.Serve(s.l, mux)
    s.l.Close()
}

// RunFcgi starts the web application and serves FastCGI requests for s.
func (s *Server) RunFcgi(addr string) {
    s.initServer()
    s.Logger.Printf("web.go serving fcgi %s\n", addr)
    s.listenAndServeFcgi(addr)
}

// RunScgi starts the web application and serves SCGI requests for s.
func (s *Server) RunScgi(addr string) {
    s.initServer()
    s.Logger.Printf("web.go serving scgi %s\n", addr)
    s.listenAndServeScgi(addr)
}

// RunTLS starts the web application and serves HTTPS requests for s.
func (s *Server) RunTLS(addr string, config *tls.Config) error {
    s.initServer()
    mux := http.NewServeMux()
    mux.Handle("/", s)
    l, err := tls.Listen("tcp", addr, config)
    if err != nil {
        log.Fatal("Listen:", err)
        return err
    }

    s.l = l
    return http.Serve(s.l, mux)
}

// Close stops server s.
func (s *Server) Close() {
    if s.l != nil {
        s.l.Close()
    }
}

// safelyCall invokes `function` in recover block
func (s *Server) safelyCall(function reflect.Value, args []reflect.Value) (resp []reflect.Value, e interface{}) {
    defer func() {
        if err := recover(); err != nil {
            if !s.Config.RecoverPanic {
                // go back to panic
                panic(err)
            } else {
                e = err
                resp = nil
                s.Logger.Println("Handler crashed with error", err)
                for i := 1; ; i += 1 {
                    _, file, line, ok := runtime.Caller(i)
                    if !ok {
                        break
                    }
                    s.Logger.Println(file, line)
                }
            }
        }
    }()
    return function.Call(args), nil
}

// requiresContext determines whether 'handlerType' contains
// an argument to 'web.Ctx' as its first argument
func requiresContext(handlerType reflect.Type) bool {
    //if the method doesn't take arguments, no
    if handlerType.NumIn() == 0 {
        return false
    }

    //if the first argument is not a pointer, no
    a0 := handlerType.In(0)
    if a0.Kind() != reflect.Ptr {
        return false
    }
    //if the first argument is a context, yes
    if a0.Elem() == contextType {
        return true
    }

    return false
}

// tryServingFile attempts to serve a static file, and returns
// whether or not the operation is successful.
// It checks the following directories for the file, in order:
// 1) Config.StaticDir
// 2) The 'static' directory in the parent directory of the executable.
// 3) The 'static' directory in the current working directory
func (s *Server) tryServingFile(name string, req *http.Request, w http.ResponseWriter) bool {
    //try to serve a static file
    if s.Config.StaticDir != "" {
        staticFile := path.Join(s.Config.StaticDir, name)
        if fileExists(staticFile) {
            http.ServeFile(w, req, staticFile)
            return true
        }
    } else {
        for _, staticDir := range defaultStaticDirs {
            staticFile := path.Join(staticDir, name)
            if fileExists(staticFile) {
                http.ServeFile(w, req, staticFile)
                return true
            }
        }
    }
    return false
}

func (s *Server) logRequest(ctx Context, sTime time.Time) {
    //log the request
    var logEntry bytes.Buffer
    req := ctx.Request
    requestPath := req.URL.Path

    duration := time.Now().Sub(sTime)
    var client string

    // We suppose RemoteAddr is of the form Ip:Port as specified in the Request
    // documentation at http://golang.org/pkg/net/http/#Request
    pos := strings.LastIndex(req.RemoteAddr, ":")
    if pos > 0 {
        client = req.RemoteAddr[0:pos]
    } else {
        client = req.RemoteAddr
    }

    fmt.Fprintf(&logEntry, "%s - \033[32;1m %s %s\033[0m - %v", client, req.Method, requestPath, duration)

    if len(ctx.Params) > 0 {
        fmt.Fprintf(&logEntry, " - \033[37;1mParams: %v\033[0m\n", ctx.Params)
    }

    ctx.Server.Logger.Print(logEntry.String())

}

// the main route handler in web.go
// Tries to handle the given request.
// Finds the route matching the request, and execute the callback associated
// with it.  In case of custom http handlers, this function returns an "unused"
// route. The caller is then responsible for calling the httpHandler associated
// with the returned route.
func (s *Server) routeHandler(req *http.Request, w http.ResponseWriter) (unused *route) {
    requestPath := req.URL.Path
    ctx := Context{req, map[string]string{}, s, w}

    //set some default headers
    ctx.SetHeader("Server", "web.go", true)
    tm := time.Now().UTC()

    //ignore errors from ParseForm because it's usually harmless.
    req.ParseForm()
    if len(req.Form) > 0 {
        for k, v := range req.Form {
            ctx.Params[k] = v[0]
        }
    }

    defer s.logRequest(ctx, tm)

    ctx.SetHeader("Date", webTime(tm), true)

    if req.Method == "GET" || req.Method == "HEAD" {
        if s.tryServingFile(requestPath, req, w) {
            return
        }
    }

    //Set the default content-type
    ctx.SetHeader("Content-Type", "text/html; charset=utf-8", true)

    for i := 0; i < len(s.routes); i++ {
        route := s.routes[i]
        cr := route.cr
        //if the methods don't match, skip this handler (except HEAD can be used in place of GET)
        if req.Method != route.method && !(req.Method == "HEAD" && route.method == "GET") {
            continue
        }

        if !cr.MatchString(requestPath) {
            continue
        }
        match := cr.FindStringSubmatch(requestPath)

        if len(match[0]) != len(requestPath) {
            continue
        }

        if route.httpHandler != nil {
            unused = &route
            // We can not handle custom http handlers here, give back to the caller.
            return
        }

        var args []reflect.Value
        handlerType := route.handler.Type()
        if requiresContext(handlerType) {
            args = append(args, reflect.ValueOf(&ctx))
        }
        for _, arg := range match[1:] {
            args = append(args, reflect.ValueOf(arg))
        }

        ret, err := s.safelyCall(route.handler, args)
        if err != nil {
            //there was an error or panic while calling the handler
            ctx.Abort(500, "Server Error")
        }
        if len(ret) == 0 {
            return
        }

        sval := ret[0]

        var content []byte

        if sval.Kind() == reflect.String {
            content = []byte(sval.String())
        } else if sval.Kind() == reflect.Slice && sval.Type().Elem().Kind() == reflect.Uint8 {
            content = sval.Interface().([]byte)
        }
        ctx.SetHeader("Content-Length", strconv.Itoa(len(content)), true)
        _, err = ctx.ResponseWriter.Write(content)
        if err != nil {
            ctx.Server.Logger.Println("Error during write: ", err)
        }
        return
    }

    // try serving index.html or index.htm
    if req.Method == "GET" || req.Method == "HEAD" {
        if s.tryServingFile(path.Join(requestPath, "index.html"), req, w) {
            return
        } else if s.tryServingFile(path.Join(requestPath, "index.htm"), req, w) {
            return
        }
    }
    ctx.Abort(404, "Page not found")
    return
}

// SetLogger sets the logger for server s
func (s *Server) SetLogger(logger *log.Logger) {
    s.Logger = logger
}
<commit_msg>Added starting message to RunTLS function<commit_after>package web

import (
    "bytes"
    "code.google.com/p/go.net/websocket"
    "crypto/tls"
    "fmt"
    "log"
    "net"
    "net/http"
    "net/http/pprof"
    "os"
    "path"
    "reflect"
    "regexp"
    "runtime"
    "strconv"
    "strings"
    "time"
)

// ServerConfig is configuration for server objects.
type ServerConfig struct {
    StaticDir    string
    Addr         string
    Port         int
    CookieSecret string
    RecoverPanic bool
    Profiler     bool
}

// Server represents a web.go server.
type Server struct {
    Config *ServerConfig
    routes []route
    Logger *log.Logger
    Env    map[string]interface{}
    //save the listener so it can be closed
    l   net.Listener
}

func NewServer() *Server {
    return &Server{
        Config: Config,
        Logger: log.New(os.Stdout, "", log.Ldate|log.Ltime),
        Env:    map[string]interface{}{},
    }
}

func (s *Server) initServer() {
    if s.Config == nil {
        s.Config = &ServerConfig{}
    }

    if s.Logger == nil {
        s.Logger = log.New(os.Stdout, "", log.Ldate|log.Ltime)
    }
}

type route struct {
    r           string
    cr          *regexp.Regexp
    method      string
    handler     reflect.Value
    httpHandler http.Handler
}

func (s *Server) addRoute(r string, method string, handler interface{}) {
    cr, err := regexp.Compile(r)
    if err != nil {
        s.Logger.Printf("Error in route regex %q\n", r)
        return
    }

    switch handler.(type) {
    case http.Handler:
        s.routes = append(s.routes, route{r: r, cr: cr, method: method, httpHandler: handler.(http.Handler)})
    case reflect.Value:
        fv := handler.(reflect.Value)
        s.routes = append(s.routes, route{r: r, cr: cr, method: method, handler: fv})
    default:
        fv := reflect.ValueOf(handler)
        s.routes = append(s.routes, route{r: r, cr: cr, method: method, handler: fv})
    }
}

// ServeHTTP is the interface method for Go's http server package
func (s *Server) ServeHTTP(c http.ResponseWriter, req *http.Request) {
    s.Process(c, req)
}

// Process invokes the routing system for server s
func (s *Server) Process(c http.ResponseWriter, req *http.Request) {
    route := s.routeHandler(req, c)
    if route != nil {
        route.httpHandler.ServeHTTP(c, req)
    }
}

// Get adds a handler for the 'GET' http method for server s.
func (s *Server) Get(route string, handler interface{}) {
    s.addRoute(route, "GET", handler)
}

// Post adds a handler for the 'POST' http method for server s.
func (s *Server) Post(route string, handler interface{}) {
    s.addRoute(route, "POST", handler)
}

// Put adds a handler for the 'PUT' http method for server s.
func (s *Server) Put(route string, handler interface{}) {
    s.addRoute(route, "PUT", handler)
}

// Delete adds a handler for the 'DELETE' http method for server s.
func (s *Server) Delete(route string, handler interface{}) {
    s.addRoute(route, "DELETE", handler)
}

// Match adds a handler for an arbitrary http method for server s.
func (s *Server) Match(method string, route string, handler interface{}) {
    s.addRoute(route, method, handler)
}

//Adds a custom handler. Only for webserver mode. Will have no effect when running as FCGI or SCGI.
func (s *Server) Handler(route string, method string, httpHandler http.Handler) {
    s.addRoute(route, method, httpHandler)
}

//Adds a handler for websockets. Only for webserver mode. Will have no effect when running as FCGI or SCGI.
func (s *Server) Websocket(route string, httpHandler websocket.Handler) {
    s.addRoute(route, "GET", httpHandler)
}

// Run starts the web application and serves HTTP requests for s
func (s *Server) Run(addr string) {
    s.initServer()

    mux := http.NewServeMux()
    if s.Config.Profiler {
        mux.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline))
        mux.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile))
        mux.Handle("/debug/pprof/heap", pprof.Handler("heap"))
        mux.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol))
    }
    mux.Handle("/", s)

    s.Logger.Printf("web.go serving %s\n", addr)

    l, err := net.Listen("tcp", addr)
    if err != nil {
        log.Fatal("ListenAndServe:", err)
    }
    s.l = l
    err = http.Serve(s.l, mux)
    s.l.Close()
}

// RunFcgi starts the web application and serves FastCGI requests for s.
func (s *Server) RunFcgi(addr string) {
    s.initServer()
    s.Logger.Printf("web.go serving fcgi %s\n", addr)
    s.listenAndServeFcgi(addr)
}

// RunScgi starts the web application and serves SCGI requests for s.
func (s *Server) RunScgi(addr string) {
    s.initServer()
    s.Logger.Printf("web.go serving scgi %s\n", addr)
    s.listenAndServeScgi(addr)
}

// RunTLS starts the web application and serves HTTPS requests for s.
func (s *Server) RunTLS(addr string, config *tls.Config) error {
    s.initServer()
    mux := http.NewServeMux()
    mux.Handle("/", s)

    s.Logger.Printf("web.go serving %s\n", addr)

    l, err := tls.Listen("tcp", addr, config)
    if err != nil {
        log.Fatal("Listen:", err)
        return err
    }

    s.l = l
    return http.Serve(s.l, mux)
}

// Close stops server s.
func (s *Server) Close() {
    if s.l != nil {
        s.l.Close()
    }
}

// safelyCall invokes `function` in recover block
func (s *Server) safelyCall(function reflect.Value, args []reflect.Value) (resp []reflect.Value, e interface{}) {
    defer func() {
        if err := recover(); err != nil {
            if !s.Config.RecoverPanic {
                // go back to panic
                panic(err)
            } else {
                e = err
                resp = nil
                s.Logger.Println("Handler crashed with error", err)
                for i := 1; ; i += 1 {
                    _, file, line, ok := runtime.Caller(i)
                    if !ok {
                        break
                    }
                    s.Logger.Println(file, line)
                }
            }
        }
    }()
    return function.Call(args), nil
}

// requiresContext determines whether 'handlerType' contains
// an argument to 'web.Ctx' as its first argument
func requiresContext(handlerType reflect.Type) bool {
    //if the method doesn't take arguments, no
    if handlerType.NumIn() == 0 {
        return false
    }

    //if the first argument is not a pointer, no
    a0 := handlerType.In(0)
    if a0.Kind() != reflect.Ptr {
        return false
    }
    //if the first argument is a context, yes
    if a0.Elem() == contextType {
        return true
    }

    return false
}

// tryServingFile attempts to serve a static file, and returns
// whether or not the operation is successful.
// It checks the following directories for the file, in order:
// 1) Config.StaticDir
// 2) The 'static' directory in the parent directory of the executable.
// 3) The 'static' directory in the current working directory
func (s *Server) tryServingFile(name string, req *http.Request, w http.ResponseWriter) bool {
    //try to serve a static file
    if s.Config.StaticDir != "" {
        staticFile := path.Join(s.Config.StaticDir, name)
        if fileExists(staticFile) {
            http.ServeFile(w, req, staticFile)
            return true
        }
    } else {
        for _, staticDir := range defaultStaticDirs {
            staticFile := path.Join(staticDir, name)
            if fileExists(staticFile) {
                http.ServeFile(w, req, staticFile)
                return true
            }
        }
    }
    return false
}

func (s *Server) logRequest(ctx Context, sTime time.Time) {
    //log the request
    var logEntry bytes.Buffer
    req := ctx.Request
    requestPath := req.URL.Path

    duration := time.Now().Sub(sTime)
    var client string

    // We suppose RemoteAddr is of the form Ip:Port as specified in the Request
    // documentation at http://golang.org/pkg/net/http/#Request
    pos := strings.LastIndex(req.RemoteAddr, ":")
    if pos > 0 {
        client = req.RemoteAddr[0:pos]
    } else {
        client = req.RemoteAddr
    }

    fmt.Fprintf(&logEntry, "%s - \033[32;1m %s %s\033[0m - %v", client, req.Method, requestPath, duration)

    if len(ctx.Params) > 0 {
        fmt.Fprintf(&logEntry, " - \033[37;1mParams: %v\033[0m\n", ctx.Params)
    }

    ctx.Server.Logger.Print(logEntry.String())

}

// the main route handler in web.go
// Tries to handle the given request.
// Finds the route matching the request, and execute the callback associated
// with it.  In case of custom http handlers, this function returns an "unused"
// route. The caller is then responsible for calling the httpHandler associated
// with the returned route.
func (s *Server) routeHandler(req *http.Request, w http.ResponseWriter) (unused *route) {
    requestPath := req.URL.Path
    ctx := Context{req, map[string]string{}, s, w}

    //set some default headers
    ctx.SetHeader("Server", "web.go", true)
    tm := time.Now().UTC()

    //ignore errors from ParseForm because it's usually harmless.
    req.ParseForm()
    if len(req.Form) > 0 {
        for k, v := range req.Form {
            ctx.Params[k] = v[0]
        }
    }

    defer s.logRequest(ctx, tm)

    ctx.SetHeader("Date", webTime(tm), true)

    if req.Method == "GET" || req.Method == "HEAD" {
        if s.tryServingFile(requestPath, req, w) {
            return
        }
    }

    //Set the default content-type
    ctx.SetHeader("Content-Type", "text/html; charset=utf-8", true)

    for i := 0; i < len(s.routes); i++ {
        route := s.routes[i]
        cr := route.cr
        //if the methods don't match, skip this handler (except HEAD can be used in place of GET)
        if req.Method != route.method && !(req.Method == "HEAD" && route.method == "GET") {
            continue
        }

        if !cr.MatchString(requestPath) {
            continue
        }
        match := cr.FindStringSubmatch(requestPath)

        if len(match[0]) != len(requestPath) {
            continue
        }

        if route.httpHandler != nil {
            unused = &route
            // We can not handle custom http handlers here, give back to the caller.
            return
        }

        var args []reflect.Value
        handlerType := route.handler.Type()
        if requiresContext(handlerType) {
            args = append(args, reflect.ValueOf(&ctx))
        }
        for _, arg := range match[1:] {
            args = append(args, reflect.ValueOf(arg))
        }

        ret, err := s.safelyCall(route.handler, args)
        if err != nil {
            //there was an error or panic while calling the handler
            ctx.Abort(500, "Server Error")
        }
        if len(ret) == 0 {
            return
        }

        sval := ret[0]

        var content []byte

        if sval.Kind() == reflect.String {
            content = []byte(sval.String())
        } else if sval.Kind() == reflect.Slice && sval.Type().Elem().Kind() == reflect.Uint8 {
            content = sval.Interface().([]byte)
        }
        ctx.SetHeader("Content-Length", strconv.Itoa(len(content)), true)
        _, err = ctx.ResponseWriter.Write(content)
        if err != nil {
            ctx.Server.Logger.Println("Error during write: ", err)
        }
        return
    }

    // try serving index.html or index.htm
    if req.Method == "GET" || req.Method == "HEAD" {
        if s.tryServingFile(path.Join(requestPath, "index.html"), req, w) {
            return
        } else if s.tryServingFile(path.Join(requestPath, "index.htm"), req, w) {
            return
        }
    }
    ctx.Abort(404, "Page not found")
    return
}

// SetLogger sets the logger for server s
func (s *Server) SetLogger(logger *log.Logger) {
    s.Logger = logger
}
<|endoftext|>


<commit_before>package air

import (
	"context"
	"net/http"
	"time"
)

// server represents the HTTP server.
type server struct {
	air    *Air
	server *http.Server
}

// newServer returns a pointer of a new instance of the `server`.
func newServer(a *Air) *server {
	return &server{
		air:    a,
		server: &http.Server{},
	}
}

// serve starts the HTTP server.
func (s *server) serve() error {
	s.server.Addr = s.air.Address
	s.server.Handler = s
	s.server.ReadTimeout = s.air.ReadTimeout
	s.server.WriteTimeout = s.air.WriteTimeout
	s.server.MaxHeaderBytes = s.air.MaxHeaderBytes

	if err := s.air.minifier.init(); err != nil {
		panic(err)
	} else if err := s.air.renderer.init(); err != nil {
		panic(err)
	} else if err := s.air.coffer.init(); err != nil {
		panic(err)
	}

	if s.air.DebugMode {
		s.air.LoggerEnabled = true
		s.air.Logger.Debug("serving in debug mode")
	}

	if s.air.TLSCertFile != "" && s.air.TLSKeyFile != "" {
		return s.server.ListenAndServeTLS(s.air.TLSCertFile, s.air.TLSKeyFile)
	}

	return s.server.ListenAndServe()
}

// close closes the HTTP server immediately.
func (s *server) close() error {
	return s.server.Close()
}

// shutdown gracefully shuts down the HTTP server without
// interrupting any active connections until timeout. It waits
// indefinitely for connections to return to idle and then shut
// down when the timeout is negative.
func (s *server) shutdown(timeout time.Duration) error {
	if timeout < 0 {
		return s.server.Shutdown(context.Background())
	}

	c, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()

	return s.server.Shutdown(c)
}

// ServeHTTP implements the `http.Handler`.
func (s *server) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
	req := newRequest(s.air, r)
	res := newResponse(req, rw)

	// Gases
	h := func(req *Request, res *Response) error {
		h := s.air.router.route(req)
		for i := len(s.air.Gases) - 1; i >= 0; i-- {
			h = s.air.Gases[i](h)
		}

		return h(req, res)
	}

	// Pregases
	for i := len(s.air.PreGases) - 1; i >= 0; i-- {
		h = s.air.PreGases[i](h)
	}

	// Execute chain
	if err := h(req, res); err != nil {
		s.air.ErrorHandler(err, req, res)
	}
}
<commit_msg>refactor: unify server.go<commit_after>package air

import (
	"context"
	"net/http"
	"time"
)

// server represents the HTTP server.
type server struct {
	air    *Air
	server *http.Server
}

// newServer returns a new instance of the `server`.
func newServer(a *Air) *server {
	return &server{
		air:    a,
		server: &http.Server{},
	}
}

// serve starts the s.
func (s *server) serve() error {
	s.server.Addr = s.air.Address
	s.server.Handler = s
	s.server.ReadTimeout = s.air.ReadTimeout
	s.server.WriteTimeout = s.air.WriteTimeout
	s.server.MaxHeaderBytes = s.air.MaxHeaderBytes

	go func() {
		if err := s.air.minifier.init(); err != nil {
			s.air.Logger.Error(err)
		}
		if err := s.air.renderer.init(); err != nil {
			s.air.Logger.Error(err)
		}
		if err := s.air.coffer.init(); err != nil {
			s.air.Logger.Error(err)
		}
	}()

	if s.air.DebugMode {
		s.air.LoggerEnabled = true
		s.air.Logger.Debug("serving in debug mode")
	}

	if s.air.TLSCertFile != "" && s.air.TLSKeyFile != "" {
		return s.server.ListenAndServeTLS(
			s.air.TLSCertFile,
			s.air.TLSKeyFile,
		)
	}

	return s.server.ListenAndServe()
}

// close closes the s immediately.
func (s *server) close() error {
	return s.server.Close()
}

// shutdown gracefully shuts down the s without interrupting any active
// connections until timeout. It waits indefinitely for connections to return to
// idle and then shut down when the timeout is negative.
func (s *server) shutdown(timeout time.Duration) error {
	if timeout < 0 {
		return s.server.Shutdown(context.Background())
	}

	c, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()

	return s.server.Shutdown(c)
}

// ServeHTTP implements the `http.Handler`.
func (s *server) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
	req := newRequest(s.air, r)
	res := newResponse(req, rw)

	// Gases
	h := func(req *Request, res *Response) error {
		h := s.air.router.route(req)
		for i := len(s.air.Gases) - 1; i >= 0; i-- {
			h = s.air.Gases[i](h)
		}

		return h(req, res)
	}

	// Pregases
	for i := len(s.air.PreGases) - 1; i >= 0; i-- {
		h = s.air.PreGases[i](h)
	}

	// Execute chain
	if err := h(req, res); err != nil {
		s.air.ErrorHandler(err, req, res)
	}
}
<|endoftext|>


<commit_before>package gatt

import (
	"errors"
	"fmt"
	"net"
	"strconv"
	"strings"
	"sync"
)

// MaxEIRPacketLength is the maximum allowed AdvertisingPacket
// and ScanResponsePacket length.
const MaxEIRPacketLength = 31

// ErrEIRPacketTooLong is the error returned when an AdvertisingPacket
// or ScanResponsePacket is too long.
var ErrEIRPacketTooLong = errors.New("max packet length is 31")

// A Server is a GATT server. Servers are single-shot types; once
// a Server has been closed, it cannot be restarted. Instead, create
// a new Server. Only one server may be running at a time.
type Server struct {
	// Name is the device name, exposed via the Generic Access Service (0x1800).
	// Name may not be changed while serving.
	Name string

	// HCI is the hci device to use, e.g. "hci1".
	// If HCI is "", an hci device will be selected
	// automatically.
	HCI string

	// AdvertisingPacket is an optional custom advertising packet.
	// If nil, the advertising packet will constructed to advertise
	// as many services as possible. AdvertisingPacket must be set,
	// if at all, before starting the server. The AdvertisingPacket
	// must be no longer than MaxAdvertisingPacketLength.
	AdvertisingPacket []byte

	// ScanResponsePacket is an optional custom scan response packet.
	// If nil, the scan response packet will set to return the server
	// name, truncated if necessary. ScanResponsePacket must be set,
	// if at all, before starting the server. The ScanResponsePacket
	// must be no longer than MaxAdvertisingPacketLength.
	ScanResponsePacket []byte

	// TODO: Add a way to disable connections? The iBeacon advertising
	// packet will advertise that the device is not connectable. Do
	// we also need to enforce that?
	// AdvertiseOnly bool

	// Connect is an optional callback function that will be called
	// when a device has connected to the server.
	Connect func(c Conn)

	// Disconnect is an optional callback function that will be called
	// when a device has disconnected from the server.
	Disconnect func(c Conn)

	// ReceiveRSSI is an optional callback function that will be called
	// when an RSSI measurement has been received for a connection.
	ReceiveRSSI func(c Conn, rssi int)

	// Closed is an optional callback function that will be called
	// when the server is closed. err will be any associated error.
	// If the server was closed by calling Close, err may be nil.
	Closed func(error)

	// StateChange is an optional callback function that will be called
	// when the server changes states.
	// TODO: Break these states out into separate, meaningful methods?
	// At least document them.
	StateChange func(newState string)

	hci   *hci
	l2cap *l2cap

	addr BDAddr

	// For now, there is one active conn per server; stash it here.
	// The conn part of the API is for forward-compatibility.
	// When Bluetooth 4.1 hits, there may be multiple active
	// connections per server, at which point, we'll need to
	// thread the connection through at each event. We won't
	// be able to do that without l2cap/BlueZ support, though.
	connmu sync.RWMutex
	conn   *conn

	services []*Service

	quitonce sync.Once
	quit     chan struct{}
	err      error
}

// AddService registers a new Service with the server.
// All services must be added before starting the server.
func (s *Server) AddService(u UUID) *Service {
	if serving() {
		return nil
	}
	svc := &Service{uuid: u}
	s.services = append(s.services, svc)
	return svc
}

// TODO: Helper function to construct iBeacon advertising packet.
// See e.g. http://stackoverflow.com/questions/18906988.

func (s *Server) startAdvertising() error {
	return s.hci.advertiseEIR(s.AdvertisingPacket, s.ScanResponsePacket)
}

// serverRunning prevents multiple servers from being started concurrently.
var (
	serverRunningMu sync.RWMutex
	serverRunning   bool
)

func serving() bool {
	serverRunningMu.RLock()
	defer serverRunningMu.RUnlock()
	return serverRunning
}

func (s *Server) AdvertiseAndServe() error {
	serverRunningMu.Lock()
	defer serverRunningMu.Unlock()
	if serverRunning {
		return errors.New("a server is already running")
	}

	if len(s.AdvertisingPacket) > MaxEIRPacketLength || len(s.ScanResponsePacket) > MaxEIRPacketLength {
		return ErrEIRPacketTooLong
	}

	if s.ScanResponsePacket == nil && s.Name != "" {
		s.ScanResponsePacket = nameScanResponsePacket(s.Name)
	}

	if s.AdvertisingPacket == nil {
		uuids := make([]UUID, len(s.services))
		for i, svc := range s.services {
			uuids[i] = svc.UUID()
		}
		s.AdvertisingPacket, _ = serviceAdvertisingPacket(uuids)
	}

	if err := s.start(); err != nil {
		return err
	}

	select {
	case <-s.quit:
		return s.err
	default:
	}

	serverRunning = true

	if err := s.l2cap.setServices(s.Name, s.services); err != nil {
		return err
	}
	if err := s.startAdvertising(); err != nil {
		return err
	}

	return s.l2cap.listenAndServe()
}

// cleanHCIDevice converts hci (user-provided)
// into a format safe to pass to the c shims.
func cleanHCIDevice(hci string) string {
	if hci == "" {
		return ""
	}
	if strings.HasPrefix(hci, "hci") {
		hci = hci[len("hci"):]
	}
	if n, err := strconv.Atoi(hci); err != nil || n < 0 {
		return ""
	}
	return hci
}

func (s *Server) start() error {
	hciDevice := cleanHCIDevice(s.HCI)

	hciShim, err := newCShim("hci-ble", hciDevice)
	if err != nil {
		return err
	}

	s.quit = make(chan struct{})

	s.hci = newHCI(hciShim)
	event, err := s.hci.event()
	if err != nil {
		return err
	}
	if event == "unauthorized" {
		return errors.New("unauthorized; does l2cap-ble have the correct permissions?")
	}
	if event != "poweredOn" {
		return fmt.Errorf("unexpected hci event: %q", event)
	}
	// TODO: If you kill and restart the server quickly, you get event
	// "unsupported". Waiting and then starting again fixes it.
	// Figure out why, and handle it automatically.

	go func() {
		for {
			// No need to check s.quit here; if the users closes the server,
			// hci will get killed, which'll cause an error to be returned here.
			event, err := s.hci.event()
			if err != nil {
				break
			}
			if s.StateChange != nil {
				s.StateChange(event)
			}
		}
		s.close(err)
	}()

	if s.Closed != nil {
		go func() {
			<-s.quit
			s.Closed(s.err)
		}()
	}

	l2capShim, err := newCShim("l2cap-ble", hciDevice)
	if err != nil {
		s.close(err)
		return err
	}

	s.l2cap = newL2cap(l2capShim, s)
	return nil
}

// Close stops a Server.
func (s *Server) Close() error {
	if !serving() {
		return errors.New("not serving")
	}
	err := s.hci.Close()
	l2caperr := s.l2cap.close()
	if err == nil {
		err = l2caperr
	}
	s.close(err)
	serverRunningMu.Lock()
	serverRunning = false
	serverRunningMu.Unlock()
	return err
}

// A BDAddr (Bluetooth Device Address) is a
// hardware-addressed-based net.Addr.
type BDAddr struct {
	net.HardwareAddr
}

func (a BDAddr) Network() string { return "BLE" }

// Conn is a BLE connection. Due to the limitations of Bluetooth 4.0,
// there is only one active connection at a time; this will change in
// Bluetooth 4.1.
type Conn interface {
	// LocalAddr returns the address of the connected device (central).
	LocalAddr() BDAddr

	// LocalAddr returns the address of the local device (peripheral).
	RemoteAddr() BDAddr

	// Close disconnects the connection.
	Close() error

	// RSSI returns the last RSSI measurement, or -1 if there have not been any.
	RSSI() int

	// UpdateRSSI requests an RSSI update and blocks until one has been received.
	// TODO: Implement.
	UpdateRSSI() (rssi int, err error)

	// MTU returns the current connection mtu.
	MTU() int
}

func (s *Server) close(err error) {
	s.quitonce.Do(func() {
		s.err = err
		close(s.quit)
	})
}

// l2capHandler methods

func (s *Server) receivedBDAddr(bdaddr string) {
	hwaddr, err := net.ParseMAC(bdaddr)
	if err != nil {
		s.addr = BDAddr{hwaddr}
	}
}

func (s *Server) request(c *Characteristic) Request {
	s.connmu.RLock()
	r := Request{
		Server:         s,
		Service:        c.service,
		Characteristic: c,
		Conn:           s.conn,
	}
	s.connmu.RUnlock()
	return r
}

func (s *Server) readChar(c *Characteristic, maxlen int, offset int) (data []byte, status byte) {
	req := &ReadRequest{Request: s.request(c), Cap: maxlen, Offset: offset}
	resp := newReadResponseWriter(maxlen)
	c.rhandler.ServeRead(resp, req)
	return resp.bytes(), resp.status
}

func (s *Server) writeChar(c *Characteristic, data []byte, noResponse bool) (status byte) {
	return c.whandler.ServeWrite(s.request(c), data)
}

func (s *Server) startNotify(c *Characteristic, maxlen int) {
	if c.notifier != nil {
		return
	}
	c.notifier = newNotifier(s.l2cap, c, maxlen)
	c.nhandler.ServeNotify(s.request(c), c.notifier)
}

func (s *Server) stopNotify(c *Characteristic) {
	c.notifier.stop()
	c.notifier = nil
}

func (s *Server) connected(addr net.HardwareAddr) {
	s.connmu.Lock()
	s.conn = newConn(s, BDAddr{addr})
	s.connmu.Unlock()
	s.connmu.RLock()
	defer s.connmu.RUnlock()
	if s.Connect != nil {
		s.Connect(s.conn)
	}
}

func (s *Server) disconnected(hw net.HardwareAddr) {
	// Stop all notifiers
	// TODO: Clear all descriptor CCC values?
	for _, svc := range s.services {
		for _, char := range svc.chars {
			if char.notifier != nil {
				char.notifier.stop()
				char.notifier = nil
			}
		}
	}

	if s.Disconnect != nil {
		s.Disconnect(s.conn)
	}
	s.connmu.Lock()
	s.conn = nil
	s.connmu.Unlock()
	if err := s.startAdvertising(); err != nil {
		s.close(err)
	}
}

func (s *Server) receivedRSSI(rssi int) {
	s.connmu.RLock()
	defer s.connmu.RUnlock()
	if s.conn != nil {
		s.conn.rssi = rssi
		if s.ReceiveRSSI != nil {
			s.ReceiveRSSI(s.conn, rssi)
		}
	}
}

func (s *Server) disconnect(c *conn) error {
	s.connmu.RLock()
	defer s.connmu.RUnlock()
	if s.conn != c {
		return errors.New("already disconnected")
	}
	return c.server.l2cap.disconnect()
}

type conn struct {
	server     *Server
	localAddr  BDAddr
	remoteAddr BDAddr
	rssi       int
}

func newConn(server *Server, addr BDAddr) *conn {
	return &conn{
		server:     server,
		rssi:       -1,
		localAddr:  server.addr,
		remoteAddr: addr,
	}
}

func (c *conn) String() string     { return c.remoteAddr.String() }
func (c *conn) LocalAddr() BDAddr  { return c.localAddr }
func (c *conn) RemoteAddr() BDAddr { return c.remoteAddr }
func (c *conn) Close() error       { return c.server.disconnect(c) }
func (c *conn) RSSI() int          { return c.rssi }
func (c *conn) MTU() int           { return int(c.server.l2cap.mtu) }

func (c *conn) UpdateRSSI() (rssi int, err error) {
	// TODO
	return 0, errors.New("not implemented yet")
}
<commit_msg>server: remove singleton constraint from Server for now<commit_after>package gatt

import (
	"errors"
	"fmt"
	"net"
	"strconv"
	"strings"
	"sync"
)

// MaxEIRPacketLength is the maximum allowed AdvertisingPacket
// and ScanResponsePacket length.
const MaxEIRPacketLength = 31

// ErrEIRPacketTooLong is the error returned when an AdvertisingPacket
// or ScanResponsePacket is too long.
var ErrEIRPacketTooLong = errors.New("max packet length is 31")

// A Server is a GATT server. Servers are single-shot types; once
// a Server has been closed, it cannot be restarted. Instead, create
// a new Server. Only one server may be running at a time.
type Server struct {
	// Name is the device name, exposed via the Generic Access Service (0x1800).
	// Name may not be changed while serving.
	Name string

	// HCI is the hci device to use, e.g. "hci1".
	// If HCI is "", an hci device will be selected
	// automatically.
	HCI string

	// AdvertisingPacket is an optional custom advertising packet.
	// If nil, the advertising packet will constructed to advertise
	// as many services as possible. AdvertisingPacket must be set,
	// if at all, before starting the server. The AdvertisingPacket
	// must be no longer than MaxAdvertisingPacketLength.
	AdvertisingPacket []byte

	// ScanResponsePacket is an optional custom scan response packet.
	// If nil, the scan response packet will set to return the server
	// name, truncated if necessary. ScanResponsePacket must be set,
	// if at all, before starting the server. The ScanResponsePacket
	// must be no longer than MaxAdvertisingPacketLength.
	ScanResponsePacket []byte

	// TODO: Add a way to disable connections? The iBeacon advertising
	// packet will advertise that the device is not connectable. Do
	// we also need to enforce that?
	// AdvertiseOnly bool

	// Connect is an optional callback function that will be called
	// when a device has connected to the server.
	Connect func(c Conn)

	// Disconnect is an optional callback function that will be called
	// when a device has disconnected from the server.
	Disconnect func(c Conn)

	// ReceiveRSSI is an optional callback function that will be called
	// when an RSSI measurement has been received for a connection.
	ReceiveRSSI func(c Conn, rssi int)

	// Closed is an optional callback function that will be called
	// when the server is closed. err will be any associated error.
	// If the server was closed by calling Close, err may be nil.
	Closed func(error)

	// StateChange is an optional callback function that will be called
	// when the server changes states.
	// TODO: Break these states out into separate, meaningful methods?
	// At least document them.
	StateChange func(newState string)

	serving bool

	hci   *hci
	l2cap *l2cap

	addr BDAddr

	// For now, there is one active conn per server; stash it here.
	// The conn part of the API is for forward-compatibility.
	// When Bluetooth 4.1 hits, there may be multiple active
	// connections per server, at which point, we'll need to
	// thread the connection through at each event. We won't
	// be able to do that without l2cap/BlueZ support, though.
	connmu sync.RWMutex
	conn   *conn

	services []*Service

	quitonce sync.Once
	quit     chan struct{}
	err      error
}

// AddService registers a new Service with the server.
// All services must be added before starting the server.
func (s *Server) AddService(u UUID) *Service {
	if s.serving {
		return nil
	}
	svc := &Service{uuid: u}
	s.services = append(s.services, svc)
	return svc
}

// TODO: Helper function to construct iBeacon advertising packet.
// See e.g. http://stackoverflow.com/questions/18906988.

func (s *Server) startAdvertising() error {
	return s.hci.advertiseEIR(s.AdvertisingPacket, s.ScanResponsePacket)
}

func (s *Server) AdvertiseAndServe() error {
	if len(s.AdvertisingPacket) > MaxEIRPacketLength || len(s.ScanResponsePacket) > MaxEIRPacketLength {
		return ErrEIRPacketTooLong
	}

	if s.ScanResponsePacket == nil && s.Name != "" {
		s.ScanResponsePacket = nameScanResponsePacket(s.Name)
	}

	if s.AdvertisingPacket == nil {
		uuids := make([]UUID, len(s.services))
		for i, svc := range s.services {
			uuids[i] = svc.UUID()
		}
		s.AdvertisingPacket, _ = serviceAdvertisingPacket(uuids)
	}

	if err := s.start(); err != nil {
		return err
	}

	select {
	case <-s.quit:
		return s.err
	default:
	}

	s.serving = true

	if err := s.l2cap.setServices(s.Name, s.services); err != nil {
		return err
	}
	if err := s.startAdvertising(); err != nil {
		return err
	}

	return s.l2cap.listenAndServe()
}

// cleanHCIDevice converts hci (user-provided)
// into a format safe to pass to the c shims.
func cleanHCIDevice(hci string) string {
	if hci == "" {
		return ""
	}
	if strings.HasPrefix(hci, "hci") {
		hci = hci[len("hci"):]
	}
	if n, err := strconv.Atoi(hci); err != nil || n < 0 {
		return ""
	}
	return hci
}

func (s *Server) start() error {
	hciDevice := cleanHCIDevice(s.HCI)

	hciShim, err := newCShim("hci-ble", hciDevice)
	if err != nil {
		return err
	}

	s.quit = make(chan struct{})

	s.hci = newHCI(hciShim)
	event, err := s.hci.event()
	if err != nil {
		return err
	}
	if event == "unauthorized" {
		return errors.New("unauthorized; does l2cap-ble have the correct permissions?")
	}
	if event != "poweredOn" {
		return fmt.Errorf("unexpected hci event: %q", event)
	}
	// TODO: If you kill and restart the server quickly, you get event
	// "unsupported". Waiting and then starting again fixes it.
	// Figure out why, and handle it automatically.

	go func() {
		for {
			// No need to check s.quit here; if the users closes the server,
			// hci will get killed, which'll cause an error to be returned here.
			event, err := s.hci.event()
			if err != nil {
				break
			}
			if s.StateChange != nil {
				s.StateChange(event)
			}
		}
		s.close(err)
	}()

	if s.Closed != nil {
		go func() {
			<-s.quit
			s.Closed(s.err)
		}()
	}

	l2capShim, err := newCShim("l2cap-ble", hciDevice)
	if err != nil {
		s.close(err)
		return err
	}

	s.l2cap = newL2cap(l2capShim, s)
	return nil
}

// Close stops a Server.
func (s *Server) Close() error {
	if !s.serving {
		return errors.New("not serving")
	}
	err := s.hci.Close()
	l2caperr := s.l2cap.close()
	if err == nil {
		err = l2caperr
	}
	s.close(err)
	return err
}

// A BDAddr (Bluetooth Device Address) is a
// hardware-addressed-based net.Addr.
type BDAddr struct {
	net.HardwareAddr
}

func (a BDAddr) Network() string { return "BLE" }

// Conn is a BLE connection. Due to the limitations of Bluetooth 4.0,
// there is only one active connection at a time; this will change in
// Bluetooth 4.1.
type Conn interface {
	// LocalAddr returns the address of the connected device (central).
	LocalAddr() BDAddr

	// LocalAddr returns the address of the local device (peripheral).
	RemoteAddr() BDAddr

	// Close disconnects the connection.
	Close() error

	// RSSI returns the last RSSI measurement, or -1 if there have not been any.
	RSSI() int

	// UpdateRSSI requests an RSSI update and blocks until one has been received.
	// TODO: Implement.
	UpdateRSSI() (rssi int, err error)

	// MTU returns the current connection mtu.
	MTU() int
}

func (s *Server) close(err error) {
	s.quitonce.Do(func() {
		s.err = err
		close(s.quit)
	})
}

// l2capHandler methods

func (s *Server) receivedBDAddr(bdaddr string) {
	hwaddr, err := net.ParseMAC(bdaddr)
	if err != nil {
		s.addr = BDAddr{hwaddr}
	}
}

func (s *Server) request(c *Characteristic) Request {
	s.connmu.RLock()
	r := Request{
		Server:         s,
		Service:        c.service,
		Characteristic: c,
		Conn:           s.conn,
	}
	s.connmu.RUnlock()
	return r
}

func (s *Server) readChar(c *Characteristic, maxlen int, offset int) (data []byte, status byte) {
	req := &ReadRequest{Request: s.request(c), Cap: maxlen, Offset: offset}
	resp := newReadResponseWriter(maxlen)
	c.rhandler.ServeRead(resp, req)
	return resp.bytes(), resp.status
}

func (s *Server) writeChar(c *Characteristic, data []byte, noResponse bool) (status byte) {
	return c.whandler.ServeWrite(s.request(c), data)
}

func (s *Server) startNotify(c *Characteristic, maxlen int) {
	if c.notifier != nil {
		return
	}
	c.notifier = newNotifier(s.l2cap, c, maxlen)
	c.nhandler.ServeNotify(s.request(c), c.notifier)
}

func (s *Server) stopNotify(c *Characteristic) {
	c.notifier.stop()
	c.notifier = nil
}

func (s *Server) connected(addr net.HardwareAddr) {
	s.connmu.Lock()
	s.conn = newConn(s, BDAddr{addr})
	s.connmu.Unlock()
	s.connmu.RLock()
	defer s.connmu.RUnlock()
	if s.Connect != nil {
		s.Connect(s.conn)
	}
}

func (s *Server) disconnected(hw net.HardwareAddr) {
	// Stop all notifiers
	// TODO: Clear all descriptor CCC values?
	for _, svc := range s.services {
		for _, char := range svc.chars {
			if char.notifier != nil {
				char.notifier.stop()
				char.notifier = nil
			}
		}
	}

	if s.Disconnect != nil {
		s.Disconnect(s.conn)
	}
	s.connmu.Lock()
	s.conn = nil
	s.connmu.Unlock()
	if err := s.startAdvertising(); err != nil {
		s.close(err)
	}
}

func (s *Server) receivedRSSI(rssi int) {
	s.connmu.RLock()
	defer s.connmu.RUnlock()
	if s.conn != nil {
		s.conn.rssi = rssi
		if s.ReceiveRSSI != nil {
			s.ReceiveRSSI(s.conn, rssi)
		}
	}
}

func (s *Server) disconnect(c *conn) error {
	s.connmu.RLock()
	defer s.connmu.RUnlock()
	if s.conn != c {
		return errors.New("already disconnected")
	}
	return c.server.l2cap.disconnect()
}

type conn struct {
	server     *Server
	localAddr  BDAddr
	remoteAddr BDAddr
	rssi       int
}

func newConn(server *Server, addr BDAddr) *conn {
	return &conn{
		server:     server,
		rssi:       -1,
		localAddr:  server.addr,
		remoteAddr: addr,
	}
}

func (c *conn) String() string     { return c.remoteAddr.String() }
func (c *conn) LocalAddr() BDAddr  { return c.localAddr }
func (c *conn) RemoteAddr() BDAddr { return c.remoteAddr }
func (c *conn) Close() error       { return c.server.disconnect(c) }
func (c *conn) RSSI() int          { return c.rssi }
func (c *conn) MTU() int           { return int(c.server.l2cap.mtu) }

func (c *conn) UpdateRSSI() (rssi int, err error) {
	// TODO
	return 0, errors.New("not implemented yet")
}
<|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.3"

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()
}
<commit_msg>Better error handling - document is not available for download - command fails to run<commit_after>package main

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"

	"github.com/codegangsta/martini"
	"github.com/codegangsta/martini-contrib/render"
)

const version = "0.1.3"

func printDocument(res render.Render, req *http.Request, w http.ResponseWriter) {
	fetchDocument := func(url string) (*os.File, error) {
		if url == "" {
			err := fmt.Errorf("URL is blank")
			return nil, err
		}

		os.Mkdir("./tmp", 0700)

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

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

		io.Copy(out, resp.Body)

		return out, err
	}

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

	sendToPrinter := func(url string, printer string, orientation string, media string) ([]string, string, error) {
		document, err := fetchDocument(url)
		if err != nil {
			log.Println(err)
			return nil, "", err
		}
		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
		err = cmd.Run()
		if err != nil {
			log.Println(err)
			return nil, "", err
		}
		return printOptions, out.String(), nil
	}

	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 (
	"crypto/hmac"
	"crypto/sha1"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"os"
	"sort"
	"strings"
	"time"

	"github.com/codegangsta/martini"
	"github.com/mbanzon/mailgun"
	"github.com/subosito/twilio"
)

var twilioAccount = os.Getenv("TWILIO_ACCOUNT")
var twilioKey = os.Getenv("TWILIO_KEY")
var mailgunPublicKey = os.Getenv("MAILGUN_PUBLIC_KEY")
var mailgunKey = os.Getenv("MAILGUN_KEY")
var smsFrom = os.Getenv("FROM_NUMBER")
var emailDomain = os.Getenv("FROM_DOMAIN")
var emailTo = os.Getenv("TO_EMAIL")

func main() {
	tc := twilio.NewClient(twilioAccount, twilioKey, nil)
	mc := mailgun.NewMailgun(emailDomain, mailgunKey, mailgunPublicKey)

	r := martini.NewRouter()
	m := martini.New()
	m.Use(martini.Logger())
	m.Use(martini.Recovery())
	m.Action(r.Handle)

	m.Map(tc)
	m.MapTo(mc, (*mailgun.Mailgun)(nil))

	r.Post("/sms", incomingSMS)
	r.Post("/email", incomingEmail)
	r.Get("/ping", func() {})

	go pinger()
	m.Run()
}

func pinger() {
	for _ = range time.Tick(time.Minute) {
		http.Get(os.Getenv("BASE_URL") + "/ping")
	}
}

// incoming call -> forward -> voicemail
// incoming voicemail -> email

func incomingCall() {
	// dial number
	// action=dialfinished
}

func dialFinished() {
	// check dialstatus
	// record transcribeAction=sendVoicemail
}

func sendVoicemail() {
	// send email
}

func incomingSMS(m mailgun.Mailgun, req *http.Request, log *log.Logger) {
	if err := verifyTwilioReq(req); err != nil {
		log.Print("Twilio request verification failed: ", err)
		return
	}

	log.Println("Got message from", req.FormValue("From"))
	msg := mailgun.NewMessage(
		req.FormValue("From")+"@"+emailDomain,
		"New text from "+req.FormValue("From"),
		req.FormValue("Body"),
		emailTo,
	)
	msg.SetDKIM(true)
	_, _, err := m.Send(msg)
	if err != nil {
		log.Println("Email send error:", err)
		return
	}
	log.Println("Email sent to", emailTo)
}

func incomingEmail(tc *twilio.Client, req *http.Request, log *log.Logger) {
	if err := verifyMailgunSig(
		req.FormValue("token"),
		req.FormValue("timestamp"),
		req.FormValue("signature"),
	); err != nil {
		log.Println("Mailgun request verification failed:", err)
		return
	}

	dkim := req.FormValue("X-Mailgun-Spf")
	spf := req.FormValue("X-Mailgun-Dkim-Check-Result")
	sender := req.FormValue("sender")
	if dkim != "Pass" || spf != "Pass" || sender != emailTo {
		log.Printf("Email verification failed: SPF: %s, DKIM: %s, addr: %s", spf, dkim, sender)
		return
	}

	params := twilio.MessageParams{Body: req.FormValue("stripped-text")}
	dest := strings.SplitN(req.FormValue("recipient"), "@", 2)[0]
	_, _, err := tc.Messages.Send(smsFrom, dest, params)
	if err != nil {
		log.Println("SMS send failed:", err)
		return
	}
	log.Println("SMS sent to", dest)
}

func requestURL(req *http.Request) string {
	return "https://" + req.Host + req.RequestURI
}

func verifyTwilioReq(req *http.Request) error {
	req.ParseForm()
	return verifyTwilioSig(requestURL(req), req.PostForm, req.Header.Get("X-Twilio-Signature"))
}

func verifyTwilioSig(url string, data url.Values, signature string) error {
	sig, err := base64.StdEncoding.DecodeString(signature)
	if err != nil {
		return err
	}

	params := make([]string, 0, len(data))
	for k, vs := range data {
		for _, v := range vs {
			params = append(params, k+v)
		}
	}
	sort.Strings(params)

	h := hmac.New(sha1.New, []byte(twilioKey))
	h.Write([]byte(url + strings.Join(params, "")))
	if res := h.Sum(nil); !hmac.Equal(res, sig) {
		return fmt.Errorf("invalid signature: got %x, expected %x", res, sig)
	}
	return nil
}

func verifyMailgunSig(token, timestamp, signature string) error {
	sig, err := hex.DecodeString(signature)
	if err != nil {
		return err
	}

	h := hmac.New(sha256.New, []byte(mailgunKey))
	h.Write([]byte(timestamp + token))
	if res := h.Sum(nil); !hmac.Equal(res, sig) {
		return fmt.Errorf("invalid signature: go %x, expected %x", res, sig)
	}
	return nil
}
<commit_msg>Make twilio req verification middleware<commit_after>package main

import (
	"crypto/hmac"
	"crypto/sha1"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"os"
	"sort"
	"strings"
	"time"

	"github.com/codegangsta/martini"
	"github.com/mbanzon/mailgun"
	"github.com/subosito/twilio"
)

var twilioAccount = os.Getenv("TWILIO_ACCOUNT")
var twilioKey = os.Getenv("TWILIO_KEY")
var mailgunPublicKey = os.Getenv("MAILGUN_PUBLIC_KEY")
var mailgunKey = os.Getenv("MAILGUN_KEY")
var smsFrom = os.Getenv("FROM_NUMBER")
var emailDomain = os.Getenv("FROM_DOMAIN")
var emailTo = os.Getenv("TO_EMAIL")

func main() {
	tc := twilio.NewClient(twilioAccount, twilioKey, nil)
	mc := mailgun.NewMailgun(emailDomain, mailgunKey, mailgunPublicKey)

	r := martini.NewRouter()
	m := martini.New()
	m.Use(martini.Logger())
	m.Use(martini.Recovery())
	m.Action(r.Handle)

	m.Map(tc)
	m.MapTo(mc, (*mailgun.Mailgun)(nil))

	r.Post("/sms", verifyTwilioReq, incomingSMS)
	r.Post("/email", incomingEmail)
	r.Get("/ping", func() {})

	go pinger()
	m.Run()
}

func pinger() {
	for _ = range time.Tick(time.Minute) {
		http.Get(os.Getenv("BASE_URL") + "/ping")
	}
}

// incoming call -> forward -> voicemail
// incoming voicemail -> email

func incomingCall() {
	// dial number
	// action=dialfinished
}

func dialFinished() {
	// check dialstatus
	// record transcribeAction=sendVoicemail
}

func sendVoicemail() {
	// send email
}

func incomingSMS(m mailgun.Mailgun, req *http.Request, log *log.Logger) {
	log.Println("Got message from", req.FormValue("From"))
	msg := mailgun.NewMessage(
		req.FormValue("From")+"@"+emailDomain,
		"New text from "+req.FormValue("From"),
		req.FormValue("Body"),
		emailTo,
	)
	msg.SetDKIM(true)
	_, _, err := m.Send(msg)
	if err != nil {
		log.Println("Email send error:", err)
		return
	}
	log.Println("Email sent to", emailTo)
}

func incomingEmail(tc *twilio.Client, req *http.Request, log *log.Logger) {
	if err := verifyMailgunSig(
		req.FormValue("token"),
		req.FormValue("timestamp"),
		req.FormValue("signature"),
	); err != nil {
		log.Println("Mailgun request verification failed:", err)
		return
	}

	dkim := req.FormValue("X-Mailgun-Spf")
	spf := req.FormValue("X-Mailgun-Dkim-Check-Result")
	sender := req.FormValue("sender")
	if dkim != "Pass" || spf != "Pass" || sender != emailTo {
		log.Printf("Email verification failed: SPF: %s, DKIM: %s, addr: %s", spf, dkim, sender)
		return
	}

	params := twilio.MessageParams{Body: req.FormValue("stripped-text")}
	dest := strings.SplitN(req.FormValue("recipient"), "@", 2)[0]
	_, _, err := tc.Messages.Send(smsFrom, dest, params)
	if err != nil {
		log.Println("SMS send failed:", err)
		return
	}
	log.Println("SMS sent to", dest)
}

func requestURL(req *http.Request) string {
	return "https://" + req.Host + req.RequestURI
}

func verifyTwilioReq(w http.ResponseWriter, req *http.Request, log *log.Logger) {
	req.ParseForm()
	err := verifyTwilioSig(requestURL(req), req.PostForm, req.Header.Get("X-Twilio-Signature"))
	if err != nil {
		log.Println("Twilio request verification failed:", err)
		w.WriteHeader(403)
		return
	}
}

func verifyTwilioSig(url string, data url.Values, signature string) error {
	sig, err := base64.StdEncoding.DecodeString(signature)
	if err != nil {
		return err
	}

	params := make([]string, 0, len(data))
	for k, vs := range data {
		for _, v := range vs {
			params = append(params, k+v)
		}
	}
	sort.Strings(params)

	h := hmac.New(sha1.New, []byte(twilioKey))
	h.Write([]byte(url + strings.Join(params, "")))
	if res := h.Sum(nil); !hmac.Equal(res, sig) {
		return fmt.Errorf("invalid signature: got %x, expected %x", res, sig)
	}
	return nil
}

func verifyMailgunSig(token, timestamp, signature string) error {
	sig, err := hex.DecodeString(signature)
	if err != nil {
		return err
	}

	h := hmac.New(sha256.New, []byte(mailgunKey))
	h.Write([]byte(timestamp + token))
	if res := h.Sum(nil); !hmac.Equal(res, sig) {
		return fmt.Errorf("invalid signature: go %x, expected %x", res, sig)
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/http"
//	"log"
	"time"
	"flag"

	"code.google.com/p/go.net/websocket"
)

const SERVICE = 8080
const HOST = ""

var p = Pool {
	connections: make(map[*Connection]bool),
	subscribe: make(chan *Connection),
	unsubscribe: make(chan *Connection),
}

func feed(conn *Connection, n int) {
	message := fmt.Sprintf("Chunk: %d", n)
	err := websocket.Message.Send(conn.ws, message)
	if err == nil {
		time.Sleep(10 * time.Second)
		feed(conn, n+1)
	}
}

func socketHandler(ws *websocket.Conn) {
	var conn = &Connection {
		ws: ws,
	}

	p.subscribe <- conn
	feed(conn, 1)
	p.unsubscribe <- conn
}

func displayStats() {
	for {
                fmt.Printf("open connections: %d\n", len(p.connections))
		time.Sleep(1 * time.Second)
        }
}

func main() {
	var host = flag.String("host", HOST, "Server listen host")
	var port = flag.Int("port", SERVICE, "Server listen port")

	flag.Parse()

	go p.Dispatch()
	go displayStats()

	http.Handle("/", websocket.Handler(socketHandler))
	err := http.ListenAndServe(fmt.Sprintf("%s:%d", *host, *port), nil)
	if err != nil {
		panic("ListenAndServe: " + err.Error())
	}
}
<commit_msg>Removes the recursive function to avoid memory increase<commit_after>package main

import (
	"fmt"
	"net/http"
//	"log"
	"time"
	"flag"

	"code.google.com/p/go.net/websocket"
)

const SERVICE = 8080
const HOST = ""

var p = Pool {
	connections: make(map[*Connection]bool),
	subscribe: make(chan *Connection),
	unsubscribe: make(chan *Connection),
}

func feed(conn *Connection) {
	n := 0
	for {
		message := fmt.Sprintf("Chunk: %d", n)
		err := websocket.Message.Send(conn.ws, message)
		if err != nil {
			break
		}
		time.Sleep(10 * time.Second)
		n++
	}
}

func socketHandler(ws *websocket.Conn) {
	var conn = &Connection {
		ws: ws,
	}

	p.subscribe <- conn
	feed(conn)
	p.unsubscribe <- conn
}

func displayStats() {
	for {
                fmt.Printf("open connections: %d\n", len(p.connections))
		time.Sleep(1 * time.Second)
        }
}

func main() {
	var host = flag.String("host", HOST, "Server listen host")
	var port = flag.Int("port", SERVICE, "Server listen port")

	flag.Parse()

	go p.Dispatch()
	go displayStats()

	http.Handle("/", websocket.Handler(socketHandler))
	err := http.ListenAndServe(fmt.Sprintf("%s:%d", *host, *port), nil)
	if err != nil {
		panic("ListenAndServe: " + err.Error())
	}
}
<|endoftext|>


<commit_before>// Package bla provides ...
package bla

import (
	"bytes"
	"flag"
	"io"
	"log"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"text/template"
	"time"

	"github.com/PuerkitoBio/goquery"
)

const Version = "0.1 alpha"

func New() {

	flag.Parse()
	LoadConfig(*configPath)

	server := &Server{
		Docs:        make(map[string]*Doc, 0),
		sortedDocs:  make([]*Doc, 0),
		DocLock:     &sync.RWMutex{},
		Tags:        make(map[string][]*Doc, 0),
		TagLock:     &sync.RWMutex{},
		StaticFiles: make([]string, 0),
		Version:     Version,
		StopWatch:   make(chan bool)}

	server.Reset()
	if err := server.LoadTempalte(); err != nil {
		log.Fatal(err)
	}
	server.LoadStaticFiles()
	server.LoadAllDocs()
	if err := server.SaveAllDocs(); err != nil {
		log.Fatal(err)
	}
	server.MakeHome()
	server.MakeSitemap()
	log.Print(Cfg)
	server.Watch()
	server.ConfigWatch()

	admin := map[string]http.HandlerFunc{
		".add":    server.Add,
		".edit":   server.Edit,
		".quit":   server.Quit,
		".upload": server.UploadMedia,
	}
	for k, v := range admin {
		http.HandleFunc(path.Join(Cfg.BasePath, k), server.auth(v))
	}

	http.Handle("/", http.FileServer(http.Dir(Cfg.PublicPath)))

	if Cfg.TLSKeyFile != "" && Cfg.TLSCertFile != "" {
		log.Fatal(http.ListenAndServeTLS(Cfg.Addr, Cfg.TLSCertFile, Cfg.TLSKeyFile, nil))
	} else {
		log.Fatal(http.ListenAndServe(Cfg.Addr, nil))
	}
}

func (s *Server) auth(orig http.HandlerFunc) http.HandlerFunc {

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

		if u, p, ok := r.BasicAuth(); ok {
			if u == Cfg.Username && p == Cfg.Password {
				orig(w, r)
				return
			}
		}

		w.Header().Set("WWW-Authenticate", `Basic realm="`+Cfg.BaseURL+`"`)
		w.WriteHeader(401)
		w.Write([]byte("401 Unauthorized\n"))
	}
}

func (s *Server) Quit(w http.ResponseWriter, r *http.Request) {

	w.Header().Set("Location", strings.Replace(Cfg.BaseURL, "://", "://log:out@", 1))
	w.WriteHeader(301)
}

func (s *Server) Add(w http.ResponseWriter, r *http.Request) {

	switch r.Method {
	case "GET":
		rd := s.newRootData()
		if err := s.template.add.ExecuteTemplate(w, "root", rd); err != nil {
			log.Print(err)
			w.Write([]byte(err.Error()))
			w.WriteHeader(500)
		}
	case "POST":
		r.ParseForm()
		content := r.FormValue("mce_0")
		parsed, err := goquery.NewDocumentFromReader(bytes.NewBuffer([]byte(content)))
		if err != nil {
			log.Printf("Parse failed:%v", err)
			w.WriteHeader(500)
			return
		}
		header := parsed.Find("h1").First().Text()
		if header == "" {
			log.Print("Header not existed", header)
			w.WriteHeader(500)
			return

		}

		f, err := os.Create(filepath.Join(Cfg.ContentPath, santiSpace(header)+".html"))
		if err != nil {
			log.Print(err)
			w.WriteHeader(500)
			return
		}

		f.WriteString(content)
		f.Close()

		w.Header().Set("Location", Cfg.BasePath)
		w.WriteHeader(301)

	default:
		w.WriteHeader(403)

	}
}

func (s *Server) Edit(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case "GET":
		doc := r.URL.Query().Get("doc")
		if doc == "" {

			w.Write([]byte("no doc query"))
			w.WriteHeader(403)
			return
		}

		fp := filepath.Join(Cfg.ContentPath, filepath.Base(doc)+".html")
		if _, err := os.Stat(fp); os.IsNotExist(err) {
			w.Write([]byte("no such doc"))
			w.WriteHeader(404)
			return
		}

		f, err := os.Open(fp)
		if err != nil {
			w.Write([]byte(err.Error()))
			w.WriteHeader(500)
			return
		}
		defer f.Close()

		rd := s.newRootData()
		parsed, _ := goquery.NewDocumentFromReader(f)

		rd.Docs = append(rd.Docs, &Doc{Parsed: parsed})
		if err := s.template.add.ExecuteTemplate(w, "root", rd); err != nil {
			log.Print(err)
			w.Write([]byte(err.Error()))
			w.WriteHeader(500)
			f.Close()
			return
		}

		bak, err := os.Create(fp + ".bak")
		io.Copy(bak, f)
		bak.Close()
		f.Close()
		os.Remove(fp)
	}
}

func (s *Server) Reset() {

	if err := os.RemoveAll(Cfg.PublicPath); err != nil {
		log.Print(err)
	}
	os.MkdirAll(Cfg.PublicPath, 0755)

	if Cfg.Favicon != "" {
		Link(Cfg.Favicon, filepath.Join(Cfg.PublicPath, "favicon.ico"))
	}

	for _, p := range Cfg.LinkFiles {
		Link(p, filepath.Join(Cfg.PublicPath, filepath.Base(p)))
	}

	uploads, _ := filepath.Abs(Cfg.UploadPath)
	uploadBase := filepath.Base(uploads)

	os.MkdirAll(filepath.Join(Cfg.PublicPath, Cfg.BasePath), 0755)
	Link(Cfg.UploadPath, filepath.Join(Cfg.PublicPath, Cfg.BasePath, uploadBase))
}

func (s *Server) LoadStaticFiles() {

	log.Print("Rebuild from: ", Cfg.TemplatePath)
	Link(filepath.Join(Cfg.TemplatePath, "asset"),
		filepath.Join(Cfg.PublicPath, Cfg.BasePath, "asset"))

	log.Print("Loading Asset files")

	orig, err := filepath.Abs(filepath.Join(Cfg.TemplatePath, "asset"))
	if err != nil {
		log.Fatal(err)
	}

	filepath.Walk(orig,
		func(path string, info os.FileInfo, e error) (err error) {

			if info == nil || !info.Mode().IsRegular() {
				return
			}
			if !(filepath.Ext(path) == ".css" || filepath.Ext(path) == ".js") {
				return
			}
			log.Print("|- ", path)
			s.StaticFiles = append(s.StaticFiles, path)
			return
		})
}

func (s *Server) LoadTempalte() (err error) {

	var funcMap = template.FuncMap{
		"base": path.Base,
		"ext":  filepath.Ext,
		"now":  time.Now,
		"join": filepath.Join,
	}
	root := filepath.Join(Cfg.TemplatePath, "root.tmpl")
	parsed := func(fname string) (t *template.Template) {
		t, err = template.New("").Funcs(funcMap).ParseFiles(root, filepath.Join(Cfg.TemplatePath, fname))
		if err != nil {
			log.Fatal(err)
		}
		return t
	}

	s.template.home = parsed("home.tmpl")
	s.template.doc = parsed("doc.tmpl")
	s.template.index = parsed("index.tmpl")

	s.template.add = parsed("add.tmpl")
	return
}

type Server struct {
	Docs        map[string]*Doc
	sortedDocs  []*Doc
	Tags        map[string][]*Doc
	StaticFiles []string
	template    struct{ home, doc, index, add *template.Template }
	Version     string
	StopWatch   chan bool
	DocLock     *sync.RWMutex
	TagLock     *sync.RWMutex
}

func Link(orig, dest string) error {

	absOrig, err := filepath.Abs(orig)
	if err != nil {
		return err
	}
	absDest, err := filepath.Abs(dest)
	if err != nil {
		return err
	}

	if err := os.Symlink(absOrig, absDest); err != nil {
		log.Print(err)
		return err
	} else {
		log.Print("Rebuild link:", absOrig, " -> ", absDest)
	}
	return nil
}
<commit_msg>log sitemap<commit_after>// Package bla provides ...
package bla

import (
	"bytes"
	"flag"
	"io"
	"log"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"text/template"
	"time"

	"github.com/PuerkitoBio/goquery"
)

const Version = "0.1 alpha"

func New() {

	flag.Parse()
	LoadConfig(*configPath)

	server := &Server{
		Docs:        make(map[string]*Doc, 0),
		sortedDocs:  make([]*Doc, 0),
		DocLock:     &sync.RWMutex{},
		Tags:        make(map[string][]*Doc, 0),
		TagLock:     &sync.RWMutex{},
		StaticFiles: make([]string, 0),
		Version:     Version,
		StopWatch:   make(chan bool)}

	server.Reset()
	if err := server.LoadTempalte(); err != nil {
		log.Fatal(err)
	}
	server.LoadStaticFiles()
	server.LoadAllDocs()
	if err := server.SaveAllDocs(); err != nil {
		log.Fatal(err)
	}
	server.MakeHome()
	log.Print(server.MakeSitemap())
	log.Print(Cfg)
	server.Watch()
	server.ConfigWatch()

	admin := map[string]http.HandlerFunc{
		".add":    server.Add,
		".edit":   server.Edit,
		".quit":   server.Quit,
		".upload": server.UploadMedia,
	}
	for k, v := range admin {
		http.HandleFunc(path.Join(Cfg.BasePath, k), server.auth(v))
	}

	http.Handle("/", http.FileServer(http.Dir(Cfg.PublicPath)))

	if Cfg.TLSKeyFile != "" && Cfg.TLSCertFile != "" {
		log.Fatal(http.ListenAndServeTLS(Cfg.Addr, Cfg.TLSCertFile, Cfg.TLSKeyFile, nil))
	} else {
		log.Fatal(http.ListenAndServe(Cfg.Addr, nil))
	}
}

func (s *Server) auth(orig http.HandlerFunc) http.HandlerFunc {

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

		if u, p, ok := r.BasicAuth(); ok {
			if u == Cfg.Username && p == Cfg.Password {
				orig(w, r)
				return
			}
		}

		w.Header().Set("WWW-Authenticate", `Basic realm="`+Cfg.BaseURL+`"`)
		w.WriteHeader(401)
		w.Write([]byte("401 Unauthorized\n"))
	}
}

func (s *Server) Quit(w http.ResponseWriter, r *http.Request) {

	w.Header().Set("Location", strings.Replace(Cfg.BaseURL, "://", "://log:out@", 1))
	w.WriteHeader(301)
}

func (s *Server) Add(w http.ResponseWriter, r *http.Request) {

	switch r.Method {
	case "GET":
		rd := s.newRootData()
		if err := s.template.add.ExecuteTemplate(w, "root", rd); err != nil {
			log.Print(err)
			w.Write([]byte(err.Error()))
			w.WriteHeader(500)
		}
	case "POST":
		r.ParseForm()
		content := r.FormValue("mce_0")
		parsed, err := goquery.NewDocumentFromReader(bytes.NewBuffer([]byte(content)))
		if err != nil {
			log.Printf("Parse failed:%v", err)
			w.WriteHeader(500)
			return
		}
		header := parsed.Find("h1").First().Text()
		if header == "" {
			log.Print("Header not existed", header)
			w.WriteHeader(500)
			return

		}

		f, err := os.Create(filepath.Join(Cfg.ContentPath, santiSpace(header)+".html"))
		if err != nil {
			log.Print(err)
			w.WriteHeader(500)
			return
		}

		f.WriteString(content)
		f.Close()

		w.Header().Set("Location", Cfg.BasePath)
		w.WriteHeader(301)

	default:
		w.WriteHeader(403)

	}
}

func (s *Server) Edit(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case "GET":
		doc := r.URL.Query().Get("doc")
		if doc == "" {

			w.Write([]byte("no doc query"))
			w.WriteHeader(403)
			return
		}

		fp := filepath.Join(Cfg.ContentPath, filepath.Base(doc)+".html")
		if _, err := os.Stat(fp); os.IsNotExist(err) {
			w.Write([]byte("no such doc"))
			w.WriteHeader(404)
			return
		}

		f, err := os.Open(fp)
		if err != nil {
			w.Write([]byte(err.Error()))
			w.WriteHeader(500)
			return
		}
		defer f.Close()

		rd := s.newRootData()
		parsed, _ := goquery.NewDocumentFromReader(f)

		rd.Docs = append(rd.Docs, &Doc{Parsed: parsed})
		if err := s.template.add.ExecuteTemplate(w, "root", rd); err != nil {
			log.Print(err)
			w.Write([]byte(err.Error()))
			w.WriteHeader(500)
			f.Close()
			return
		}

		bak, err := os.Create(fp + ".bak")
		io.Copy(bak, f)
		bak.Close()
		f.Close()
		os.Remove(fp)
	}
}

func (s *Server) Reset() {

	if err := os.RemoveAll(Cfg.PublicPath); err != nil {
		log.Print(err)
	}
	os.MkdirAll(Cfg.PublicPath, 0755)

	if Cfg.Favicon != "" {
		Link(Cfg.Favicon, filepath.Join(Cfg.PublicPath, "favicon.ico"))
	}

	for _, p := range Cfg.LinkFiles {
		Link(p, filepath.Join(Cfg.PublicPath, filepath.Base(p)))
	}

	uploads, _ := filepath.Abs(Cfg.UploadPath)
	uploadBase := filepath.Base(uploads)

	os.MkdirAll(filepath.Join(Cfg.PublicPath, Cfg.BasePath), 0755)
	Link(Cfg.UploadPath, filepath.Join(Cfg.PublicPath, Cfg.BasePath, uploadBase))
}

func (s *Server) LoadStaticFiles() {

	log.Print("Rebuild from: ", Cfg.TemplatePath)
	Link(filepath.Join(Cfg.TemplatePath, "asset"),
		filepath.Join(Cfg.PublicPath, Cfg.BasePath, "asset"))

	log.Print("Loading Asset files")

	orig, err := filepath.Abs(filepath.Join(Cfg.TemplatePath, "asset"))
	if err != nil {
		log.Fatal(err)
	}

	filepath.Walk(orig,
		func(path string, info os.FileInfo, e error) (err error) {

			if info == nil || !info.Mode().IsRegular() {
				return
			}
			if !(filepath.Ext(path) == ".css" || filepath.Ext(path) == ".js") {
				return
			}
			log.Print("|- ", path)
			s.StaticFiles = append(s.StaticFiles, path)
			return
		})
}

func (s *Server) LoadTempalte() (err error) {

	var funcMap = template.FuncMap{
		"base": path.Base,
		"ext":  filepath.Ext,
		"now":  time.Now,
		"join": filepath.Join,
	}
	root := filepath.Join(Cfg.TemplatePath, "root.tmpl")
	parsed := func(fname string) (t *template.Template) {
		t, err = template.New("").Funcs(funcMap).ParseFiles(root, filepath.Join(Cfg.TemplatePath, fname))
		if err != nil {
			log.Fatal(err)
		}
		return t
	}

	s.template.home = parsed("home.tmpl")
	s.template.doc = parsed("doc.tmpl")
	s.template.index = parsed("index.tmpl")

	s.template.add = parsed("add.tmpl")
	return
}

type Server struct {
	Docs        map[string]*Doc
	sortedDocs  []*Doc
	Tags        map[string][]*Doc
	StaticFiles []string
	template    struct{ home, doc, index, add *template.Template }
	Version     string
	StopWatch   chan bool
	DocLock     *sync.RWMutex
	TagLock     *sync.RWMutex
}

func Link(orig, dest string) error {

	absOrig, err := filepath.Abs(orig)
	if err != nil {
		return err
	}
	absDest, err := filepath.Abs(dest)
	if err != nil {
		return err
	}

	if err := os.Symlink(absOrig, absDest); err != nil {
		log.Print(err)
		return err
	} else {
		log.Print("Rebuild link:", absOrig, " -> ", absDest)
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/garyburd/redigo/redis"
	"github.com/go-martini/martini"
	"log"
	"net/http"
)

var (
	listenstring = ":5596"
	pool         *redis.Pool
)

func main() {
	pool = newPool()
	m := martini.Classic()
	m.Get("/api/add/**", ApiAddURLHandler)
	m.Get("/add", WebAddHandler)
	m.Get("/:id", GetURLAndRedirect)
	log.Println("Listening on " + listenstring)
	log.Fatal(http.ListenAndServe(listenstring, m))
}

func WebAddHandler(w http.ResponseWriter, r *http.Request) {
	k := r.URL.Query()["url"][0]
	if k == "" {
		http.Redirect(w, r, "/", 302)
	} else {
		w.Write([]byte(k))
	}
}

func GetURLAndRedirect(params martini.Params, w http.ResponseWriter, r *http.Request) {
	k, err := GetUrlById(params["id"])
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	if k != nil {
		http.Redirect(w, r, k.link, http.StatusMovedPermanently)
	} else {
		http.Error(w, fmt.Sprintf("/%s not found", params["id"]), 404)
	}
}

func ApiAddURLHandler(params martini.Params, w http.ResponseWriter, r *http.Request) {
	k, err := GetNewUrl(params["_1"])
	if err != nil {
		http.Error(w, err.Error(), 500)
	} else {
		l := r.URL.Host
		w.Write([]byte(l + "/" + k.id))
	}
}
<commit_msg>add host<commit_after>package main

import (
	"fmt"
	"github.com/garyburd/redigo/redis"
	"github.com/go-martini/martini"
	"log"
	"net/http"
)

var (
	listenstring = ":5596"
	pool         *redis.Pool
)

func main() {
	pool = newPool()
	m := martini.Classic()
	m.Get("/api/add/**", ApiAddURLHandler)
	m.Get("/add", WebAddHandler)
	m.Get("/:id", GetURLAndRedirect)
	log.Println("Listening on " + listenstring)
	log.Fatal(http.ListenAndServe(listenstring, m))
}

func WebAddHandler(w http.ResponseWriter, r *http.Request) {
	k := r.URL.Query()["url"][0]
	if k == "" {
		http.Redirect(w, r, "/", 302)
	} else {
		w.Write([]byte(k))
	}
}

func GetURLAndRedirect(params martini.Params, w http.ResponseWriter, r *http.Request) {
	k, err := GetUrlById(params["id"])
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	if k != nil {
		http.Redirect(w, r, k.link, http.StatusMovedPermanently)
	} else {
		http.Error(w, fmt.Sprintf("/%s not found", params["id"]), 404)
	}
}

func ApiAddURLHandler(params martini.Params, w http.ResponseWriter, r *http.Request) {
	k, err := GetNewUrl(params["_1"])
	if err != nil {
		http.Error(w, err.Error(), 500)
	} else {
		w.Write([]byte(r.URL.Host + "/" + k.id))
	}
}
<|endoftext|>


<commit_before>package webfist

import (
	"crypto/sha1"
	"io"
	"sync"

	"crypto/aes"
	"crypto/cipher"
	"fmt"

	"code.google.com/p/go.crypto/scrypt"
)

var (
	dummyIV  = make([]byte, 16) // all zeros
	fistSalt = []byte("WebFist salt.")
)

// EmailAddr provides utility functions on a wrapped email address.
type EmailAddr struct {
	email string // canonical

	keyOnce sync.Once
	lazyKey []byte // scrypt
}

// NewEmailAddr returns a EmailAddr wrapper around an email address string.
// The incoming email address does not need to be canonicalized.
func NewEmailAddr(addr string) *EmailAddr {
	return &EmailAddr{
		email: canonicalEmail(addr),
	}
}

// Canonical returns the canonical version of the email address.
func (e *EmailAddr) Canonical() string {
	return e.email
}

// HexKey returns the human-readable, lowercase hex version of
// the email address's key.
func (e *EmailAddr) HexKey() string {
	return fmt.Sprintf("%x", e.getKey())
}

func (e *EmailAddr) getKey() []byte {
	e.keyOnce.Do(e.initLazyKey)
	return e.lazyKey
}

func (e *EmailAddr) initLazyKey() {
	key, err := scrypt.Key([]byte(e.Canonical()), fistSalt, 16384*8, 8, 1, 32)
	if err != nil {
		panic(err)
	}
	e.lazyKey = key
}

func (e *EmailAddr) block() cipher.Block {
	block, err := aes.NewCipher(e.encryptionKey())
	if err != nil {
		panic(err)
	}
	return block
}

// encryptionKey returns the AES-128 key for this email address.
func (e *EmailAddr) encryptionKey() []byte {
	s1 := sha1.New()
	io.WriteString(s1, e.email)
	s1.Write(e.getKey())
	return s1.Sum(nil)[:16]
}

// canonicalEmail returns the canonicalized version of the provided
// email address.
func canonicalEmail(email string) string {
	// TODO
	return email
}

func (e *EmailAddr) Encrypter(w io.Writer) io.Writer {
	return cipher.StreamWriter{
		S: cipher.NewCTR(e.block(), dummyIV),
		W: w,
	}
}

func (e *EmailAddr) Decrypter(r io.Reader) io.Reader {
	return cipher.StreamReader{
		S: cipher.NewCTR(e.block(), dummyIV),
		R: r,
	}
}
<commit_msg>Shorten human-readable hex key of scrypt fo email.<commit_after>package webfist

import (
	"crypto/sha1"
	"io"
	"sync"

	"crypto/aes"
	"crypto/cipher"
	"fmt"

	"code.google.com/p/go.crypto/scrypt"
)

var (
	dummyIV  = make([]byte, 16) // all zeros
	fistSalt = []byte("WebFist salt.")
)

// EmailAddr provides utility functions on a wrapped email address.
type EmailAddr struct {
	email string // canonical

	keyOnce sync.Once
	lazyKey []byte // scrypt
}

// NewEmailAddr returns a EmailAddr wrapper around an email address string.
// The incoming email address does not need to be canonicalized.
func NewEmailAddr(addr string) *EmailAddr {
	return &EmailAddr{
		email: canonicalEmail(addr),
	}
}

// Canonical returns the canonical version of the email address.
func (e *EmailAddr) Canonical() string {
	return e.email
}

// HexKey returns the human-readable, lowercase hex version of
// the email address's key.
func (e *EmailAddr) HexKey() string {
	return fmt.Sprintf("%x", e.getKey()[:20])
}

func (e *EmailAddr) getKey() []byte {
	e.keyOnce.Do(e.initLazyKey)
	return e.lazyKey
}

func (e *EmailAddr) initLazyKey() {
	key, err := scrypt.Key([]byte(e.Canonical()), fistSalt, 16384*8, 8, 1, 32)
	if err != nil {
		panic(err)
	}
	e.lazyKey = key
}

func (e *EmailAddr) block() cipher.Block {
	block, err := aes.NewCipher(e.encryptionKey())
	if err != nil {
		panic(err)
	}
	return block
}

// encryptionKey returns the AES-128 key for this email address.
func (e *EmailAddr) encryptionKey() []byte {
	s1 := sha1.New()
	io.WriteString(s1, e.email)
	s1.Write(e.getKey())
	return s1.Sum(nil)[:16]
}

// canonicalEmail returns the canonicalized version of the provided
// email address.
func canonicalEmail(email string) string {
	// TODO
	return email
}

func (e *EmailAddr) Encrypter(w io.Writer) io.Writer {
	return cipher.StreamWriter{
		S: cipher.NewCTR(e.block(), dummyIV),
		W: w,
	}
}

func (e *EmailAddr) Decrypter(r io.Reader) io.Reader {
	return cipher.StreamReader{
		S: cipher.NewCTR(e.block(), dummyIV),
		R: r,
	}
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/sha256"
	"database/sql"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"os"
	"regexp"
	"strings"

	"github.com/flynn/subdomainer/Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws"
	"github.com/flynn/subdomainer/Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/gen/route53"
	hh "github.com/flynn/subdomainer/Godeps/_workspace/src/github.com/flynn/flynn/pkg/httphelper"
	"github.com/flynn/subdomainer/Godeps/_workspace/src/github.com/flynn/flynn/pkg/random"
	"github.com/flynn/subdomainer/Godeps/_workspace/src/github.com/julienschmidt/httprouter"
	_ "github.com/flynn/subdomainer/Godeps/_workspace/src/github.com/lib/pq"
	"github.com/flynn/subdomainer/Godeps/_workspace/src/github.com/speps/go-hashids"
	"github.com/flynn/subdomainer/Godeps/_workspace/src/gopkg.in/macaroon.v1"
)

func main() {
	awsCreds, err := aws.EnvCreds()
	if err != nil {
		panic(err)
	}
	db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
	if err != nil {
		panic(err)
	}

	router := httprouter.New()
	api := &API{
		db:     db,
		domain: os.Getenv("DOMAIN"),
		domainHash: hashids.NewWithData(&hashids.HashIDData{
			Salt:      os.Getenv("DOMAIN_SALT"),
			Alphabet:  "abcdefghijklmnopqrstuvwxyz0123456789",
			MinLength: 2,
		}),
		zoneID: aws.String(os.Getenv("ZONE_ID")),
		r53:    route53.New(awsCreds, "us-east-1", nil),
	}

	router.POST("/domains", api.AllocateDomain)
	router.PUT("/domains/:id", api.AuthHandler(api.ProvisionDomain))
	router.GET("/domains/:id/status", api.AuthHandler(api.GetStatus))

	http.ListenAndServe(":"+os.Getenv("PORT"), router)
}

type API struct {
	db         *sql.DB
	domain     string
	domainHash *hashids.HashID
	zoneID     aws.StringValue
	r53        *route53.Route53
}

type DomainCreateRes struct {
	Domain string `json:"domain"`
	Token  []byte `json:"token"`
}

func (a *API) AllocateDomain(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	var id int
	if err := a.db.QueryRow("SELECT nextval('domain_seeds')").Scan(&id); err != nil {
		hh.Error(w, err)
		return
	}
	sub, _ := a.domainHash.Encode([]int{id})
	domain := fmt.Sprintf("%s.%s", sub, a.domain)
	key := random.Bytes(sha256.Size)

	var domainID string
	const insert = "INSERT INTO domains (domain, access_key, creator_ip) VALUES ($1, $2, $3) RETURNING domain_id"
	if err := a.db.QueryRow(insert, domain, key, sourceIP(req)).Scan(&domainID); err != nil {
		hh.Error(w, err)
		return
	}

	m, err := macaroon.New(key, domainID, "")
	if err != nil {
		hh.Error(w, err)
		return
	}
	res := &DomainCreateRes{Domain: domain}
	res.Token, err = m.MarshalBinary()
	if err != nil {
		hh.Error(w, err)
		return
	}

	hh.JSON(w, 200, res)
}

func nopChecker(caveat string) error {
	return fmt.Errorf("unexpected caveat %s", caveat)
}

func (a *API) AuthHandler(h httprouter.Handle) httprouter.Handle {
	return func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
		var domainID string
		var key []byte
		const get = "SELECT domain_id, access_key FROM domains WHERE domain = $1"
		if err := a.db.QueryRow(get, params.ByName("id")).Scan(&domainID, &key); err != nil {
			hh.Error(w, err)
			return
		}

		auth := req.Header.Get("Authorization")
		if !strings.HasPrefix(auth, "Token ") {
			hh.JSON(w, 401, struct{}{})
			return
		}
		serialized, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(auth, "Token "))
		if err != nil {
			hh.JSON(w, 400, struct{}{})
			return
		}

		var m macaroon.Macaroon
		if err := m.UnmarshalBinary(serialized); err != nil {
			hh.JSON(w, 400, struct{}{})
			return
		}
		if err := m.Verify(key, nopChecker, nil); err != nil {
			hh.JSON(w, 400, struct{}{})
			return
		}

		h(w, req, params)
	}
}

type ProvisionReq struct {
	Nameservers []string `json:"nameservers"`
}

var nameserverPattern = regexp.MustCompile(`\Ans\d*-?\d+\.((awsdns-\d+(\.[a-z]{1,3}){1,2})|(digitalocean\.com)|(azure-dns\.com))\z`)

func (a *API) ProvisionDomain(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
	domain := params.ByName("id")

	var reqData ProvisionReq
	if err := hh.DecodeJSON(req, &reqData); err != nil {
		hh.Error(w, err)
		return
	}
	if len(reqData.Nameservers) < 3 || len(reqData.Nameservers) > 10 {
		// TODO: log it
		hh.JSON(w, 400, struct{}{})
		return
	}
	for _, n := range reqData.Nameservers {
		if !nameserverPattern.MatchString(n) {
			// TODO: log it
			hh.JSON(w, 400, struct{}{})
			return
		}
	}

	nsData, _ := json.Marshal(reqData.Nameservers)
	const nsUpdate = "UPDATE domains SET nameservers = $2 WHERE domain = $1 AND nameservers IS NULL"
	updateRes, err := a.db.Exec(nsUpdate, domain, nsData)
	if err != nil {
		hh.Error(w, err)
		return
	}
	if n, _ := updateRes.RowsAffected(); n != 1 {
		// TODO(titanous): don't error if the nameservers are the same and there is no route
		hh.JSON(w, 400, struct{}{})
		return
	}

	records := make([]route53.ResourceRecord, len(reqData.Nameservers))
	for i, ns := range reqData.Nameservers {
		records[i].Value = aws.String(ns)
	}

	dnsReq := &route53.ChangeResourceRecordSetsRequest{
		HostedZoneID: a.zoneID,
		ChangeBatch: &route53.ChangeBatch{
			Changes: []route53.Change{{
				Action: aws.String(route53.ChangeActionCreate),
				ResourceRecordSet: &route53.ResourceRecordSet{
					Name:            aws.String(domain + "."),
					TTL:             aws.Long(3600),
					Type:            aws.String(route53.RRTypeNs),
					ResourceRecords: records,
				},
			}},
		},
	}
	dnsRes, err := a.r53.ChangeResourceRecordSets(dnsReq)
	if err != nil {
		hh.Error(w, err)
		return
	}

	const updateChange = "UPDATE domains SET external_change_id = $2 WHERE domain = $1"
	if _, err := a.db.Exec(updateChange, domain, dnsRes.ChangeInfo.ID); err != nil {
		hh.Error(w, err)
		return
	}

	hh.JSON(w, 200, struct{}{})
}

type StatusResponse struct {
	Status string `json:"status"`
}

var statusApplied = &StatusResponse{"applied"}
var statusPending = &StatusResponse{"pending"}

func (a *API) GetStatus(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
	var changeID *string
	var applied bool
	domain := params.ByName("id")
	const get = "SELECT external_change_id, external_change_applied FROM domains WHERE domain = $1"
	if err := a.db.QueryRow(get, domain).Scan(&changeID, &applied); err != nil {
		hh.Error(w, err)
		return
	}
	if changeID == nil || *changeID == "" {
		hh.JSON(w, 404, struct{}{})
		return
	}
	if applied {
		hh.JSON(w, 200, statusApplied)
		return
	}

	*changeID = strings.TrimPrefix(*changeID, "/change/")
	res, err := a.r53.GetChange(&route53.GetChangeRequest{ID: changeID})
	if err != nil {
		// TODO(titanous): check NoSuchChange
		hh.Error(w, err)
		return
	}

	if *res.ChangeInfo.Status != route53.ChangeStatusInsync {
		hh.JSON(w, 200, statusPending)
		return
	}
	const update = "UPDATE domains SET external_change_applied = true WHERE domain = $1"
	if _, err := a.db.Exec(update, domain); err != nil {
		hh.Error(w, err)
		return
	}
	hh.JSON(w, 200, statusApplied)
}

func sourceIP(req *http.Request) string {
	if xff := req.Header.Get("X-Forwarded-For"); xff != "" {
		ips := strings.Split(xff, ",")
		return strings.TrimSpace(ips[len(ips)-1])
	}
	ip, _, _ := net.SplitHostPort(req.RemoteAddr)
	return ip
}
<commit_msg>Fix matching azure name servers<commit_after>package main

import (
	"crypto/sha256"
	"database/sql"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"os"
	"regexp"
	"strings"

	"github.com/flynn/subdomainer/Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws"
	"github.com/flynn/subdomainer/Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/gen/route53"
	hh "github.com/flynn/subdomainer/Godeps/_workspace/src/github.com/flynn/flynn/pkg/httphelper"
	"github.com/flynn/subdomainer/Godeps/_workspace/src/github.com/flynn/flynn/pkg/random"
	"github.com/flynn/subdomainer/Godeps/_workspace/src/github.com/julienschmidt/httprouter"
	_ "github.com/flynn/subdomainer/Godeps/_workspace/src/github.com/lib/pq"
	"github.com/flynn/subdomainer/Godeps/_workspace/src/github.com/speps/go-hashids"
	"github.com/flynn/subdomainer/Godeps/_workspace/src/gopkg.in/macaroon.v1"
)

func main() {
	awsCreds, err := aws.EnvCreds()
	if err != nil {
		panic(err)
	}
	db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
	if err != nil {
		panic(err)
	}

	router := httprouter.New()
	api := &API{
		db:     db,
		domain: os.Getenv("DOMAIN"),
		domainHash: hashids.NewWithData(&hashids.HashIDData{
			Salt:      os.Getenv("DOMAIN_SALT"),
			Alphabet:  "abcdefghijklmnopqrstuvwxyz0123456789",
			MinLength: 2,
		}),
		zoneID: aws.String(os.Getenv("ZONE_ID")),
		r53:    route53.New(awsCreds, "us-east-1", nil),
	}

	router.POST("/domains", api.AllocateDomain)
	router.PUT("/domains/:id", api.AuthHandler(api.ProvisionDomain))
	router.GET("/domains/:id/status", api.AuthHandler(api.GetStatus))

	http.ListenAndServe(":"+os.Getenv("PORT"), router)
}

type API struct {
	db         *sql.DB
	domain     string
	domainHash *hashids.HashID
	zoneID     aws.StringValue
	r53        *route53.Route53
}

type DomainCreateRes struct {
	Domain string `json:"domain"`
	Token  []byte `json:"token"`
}

func (a *API) AllocateDomain(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	var id int
	if err := a.db.QueryRow("SELECT nextval('domain_seeds')").Scan(&id); err != nil {
		hh.Error(w, err)
		return
	}
	sub, _ := a.domainHash.Encode([]int{id})
	domain := fmt.Sprintf("%s.%s", sub, a.domain)
	key := random.Bytes(sha256.Size)

	var domainID string
	const insert = "INSERT INTO domains (domain, access_key, creator_ip) VALUES ($1, $2, $3) RETURNING domain_id"
	if err := a.db.QueryRow(insert, domain, key, sourceIP(req)).Scan(&domainID); err != nil {
		hh.Error(w, err)
		return
	}

	m, err := macaroon.New(key, domainID, "")
	if err != nil {
		hh.Error(w, err)
		return
	}
	res := &DomainCreateRes{Domain: domain}
	res.Token, err = m.MarshalBinary()
	if err != nil {
		hh.Error(w, err)
		return
	}

	hh.JSON(w, 200, res)
}

func nopChecker(caveat string) error {
	return fmt.Errorf("unexpected caveat %s", caveat)
}

func (a *API) AuthHandler(h httprouter.Handle) httprouter.Handle {
	return func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
		var domainID string
		var key []byte
		const get = "SELECT domain_id, access_key FROM domains WHERE domain = $1"
		if err := a.db.QueryRow(get, params.ByName("id")).Scan(&domainID, &key); err != nil {
			hh.Error(w, err)
			return
		}

		auth := req.Header.Get("Authorization")
		if !strings.HasPrefix(auth, "Token ") {
			hh.JSON(w, 401, struct{}{})
			return
		}
		serialized, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(auth, "Token "))
		if err != nil {
			hh.JSON(w, 400, struct{}{})
			return
		}

		var m macaroon.Macaroon
		if err := m.UnmarshalBinary(serialized); err != nil {
			hh.JSON(w, 400, struct{}{})
			return
		}
		if err := m.Verify(key, nopChecker, nil); err != nil {
			hh.JSON(w, 400, struct{}{})
			return
		}

		h(w, req, params)
	}
}

type ProvisionReq struct {
	Nameservers []string `json:"nameservers"`
}

var nameserverPattern = regexp.MustCompile(`\Ans\d*-?\d+\.((awsdns-\d+(\.[a-z]{1,3}){1,2})|(digitalocean\.com)|(azure-dns\.(com|info|org|net)))\z`)

func (a *API) ProvisionDomain(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
	domain := params.ByName("id")

	var reqData ProvisionReq
	if err := hh.DecodeJSON(req, &reqData); err != nil {
		hh.Error(w, err)
		return
	}
	if len(reqData.Nameservers) < 3 || len(reqData.Nameservers) > 10 {
		// TODO: log it
		hh.JSON(w, 400, struct{}{})
		return
	}
	for _, n := range reqData.Nameservers {
		if !nameserverPattern.MatchString(n) {
			// TODO: log it
			hh.JSON(w, 400, struct{}{})
			return
		}
	}

	nsData, _ := json.Marshal(reqData.Nameservers)
	const nsUpdate = "UPDATE domains SET nameservers = $2 WHERE domain = $1 AND nameservers IS NULL"
	updateRes, err := a.db.Exec(nsUpdate, domain, nsData)
	if err != nil {
		hh.Error(w, err)
		return
	}
	if n, _ := updateRes.RowsAffected(); n != 1 {
		// TODO(titanous): don't error if the nameservers are the same and there is no route
		hh.JSON(w, 400, struct{}{})
		return
	}

	records := make([]route53.ResourceRecord, len(reqData.Nameservers))
	for i, ns := range reqData.Nameservers {
		records[i].Value = aws.String(ns)
	}

	dnsReq := &route53.ChangeResourceRecordSetsRequest{
		HostedZoneID: a.zoneID,
		ChangeBatch: &route53.ChangeBatch{
			Changes: []route53.Change{{
				Action: aws.String(route53.ChangeActionCreate),
				ResourceRecordSet: &route53.ResourceRecordSet{
					Name:            aws.String(domain + "."),
					TTL:             aws.Long(3600),
					Type:            aws.String(route53.RRTypeNs),
					ResourceRecords: records,
				},
			}},
		},
	}
	dnsRes, err := a.r53.ChangeResourceRecordSets(dnsReq)
	if err != nil {
		hh.Error(w, err)
		return
	}

	const updateChange = "UPDATE domains SET external_change_id = $2 WHERE domain = $1"
	if _, err := a.db.Exec(updateChange, domain, dnsRes.ChangeInfo.ID); err != nil {
		hh.Error(w, err)
		return
	}

	hh.JSON(w, 200, struct{}{})
}

type StatusResponse struct {
	Status string `json:"status"`
}

var statusApplied = &StatusResponse{"applied"}
var statusPending = &StatusResponse{"pending"}

func (a *API) GetStatus(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
	var changeID *string
	var applied bool
	domain := params.ByName("id")
	const get = "SELECT external_change_id, external_change_applied FROM domains WHERE domain = $1"
	if err := a.db.QueryRow(get, domain).Scan(&changeID, &applied); err != nil {
		hh.Error(w, err)
		return
	}
	if changeID == nil || *changeID == "" {
		hh.JSON(w, 404, struct{}{})
		return
	}
	if applied {
		hh.JSON(w, 200, statusApplied)
		return
	}

	*changeID = strings.TrimPrefix(*changeID, "/change/")
	res, err := a.r53.GetChange(&route53.GetChangeRequest{ID: changeID})
	if err != nil {
		// TODO(titanous): check NoSuchChange
		hh.Error(w, err)
		return
	}

	if *res.ChangeInfo.Status != route53.ChangeStatusInsync {
		hh.JSON(w, 200, statusPending)
		return
	}
	const update = "UPDATE domains SET external_change_applied = true WHERE domain = $1"
	if _, err := a.db.Exec(update, domain); err != nil {
		hh.Error(w, err)
		return
	}
	hh.JSON(w, 200, statusApplied)
}

func sourceIP(req *http.Request) string {
	if xff := req.Header.Get("X-Forwarded-For"); xff != "" {
		ips := strings.Split(xff, ",")
		return strings.TrimSpace(ips[len(ips)-1])
	}
	ip, _, _ := net.SplitHostPort(req.RemoteAddr)
	return ip
}
<|endoftext|>