// Copyright (C) 2017 Marius Schellenberger package server import ( "crypto/subtle" "encoding/base64" "errors" "git.giftfish.de/ston1th/gowiki/pkg/log" "github.com/gorilla/mux" "html/template" "net/http" "time" ) func newContext(w http.ResponseWriter, r *http.Request, s *HTTPServer) *Context { h := w.Header() h.Set("X-Frame-Options", "DENY") h.Set("X-Content-Type-Options", "nosniff") h.Set("X-XSS-Protection", "1; mode=block") h.Set("Content-Security-Policy", "default-src 'none';style-src 'self' 'unsafe-inline';frame-ancestors 'none'") return &Context{ Request: r, Response: w, Srv: s, Time: time.Now(), } } type Context struct { Request *http.Request Response http.ResponseWriter Srv *HTTPServer T *template.Template Status int Err error Time time.Time Data webData } type webData struct { Version string Time int64 Title string BodyTitle string Admin bool Login bool User string Token string Msg string Search string Data interface{} } func (c *Context) Exec() { defer c.log() if c.T == nil { c.Status = http.StatusInternalServerError c.Err = errors.New("template is nil") return } c.newToken() c.Data.Version = c.Srv.Version if c.Data.BodyTitle == "" { c.Data.BodyTitle = c.Data.Title } c.Data.Admin = c.GetAdmin() c.Data.User = c.GetUser() c.Data.Login = c.LoggedOn() c.Data.Time = time.Since(c.Time).Nanoseconds() / 1e6 c.Err = c.T.Execute(c.Response, c.Data) if c.Err != nil { c.Status = http.StatusInternalServerError } if c.Status == 0 { c.Status = http.StatusOK } } func (c *Context) log() { if c.Err != nil { log.Printf("%s %s %s %d error: %s\n", c.Request.RemoteAddr, c.Request.Method, c.Request.URL, c.Status, c.Err) return } log.Printf("%s %s %s %d\n", c.Request.RemoteAddr, c.Request.Method, c.Request.URL, c.Status) } func (c *Context) NotFound() { c.Template("notFoundHandler") c.Data = webData{Title: "404"} c.Status = http.StatusNotFound c.Response.WriteHeader(http.StatusNotFound) c.Exec() } func (c *Context) Template(name string) { c.T = c.Srv.templ[name] } func (c *Context) Write(buf []byte) (err error) { _, err = c.Response.Write(buf) return } func (c *Context) SetHeader(name, value string) { c.Response.Header().Set(name, value) } func (c *Context) Redirect(uri string, code int) { log.Printf("%s %s %s %d %s\n", c.Request.RemoteAddr, c.Request.Method, c.Request.URL, code, uri) http.Redirect(c.Response, c.Request, uri, code) } func (c *Context) Method() string { return c.Request.Method } func (c *Context) Path() string { return c.Request.URL.Path } func (c *Context) FormSlice(name string) []string { // does nothing if called twice c.Request.ParseForm() return c.Request.PostForm[name] } func (c *Context) Form(name string) (ret string) { ret = c.Request.PostFormValue(name) return } func (c *Context) Var(name string) (ret string) { ret, _ = mux.Vars(c.Request)[name] return } func (c *Context) initToken() { token := c.getToken() if token == nil || len(token) != keyLen { s, _ := c.Srv.Store.Get(c.Request, cookieName) s.Values["token"] = genKey(keyLen) s.Options = c.Srv.Store.Options err := s.Save(c.Request, c.Response) if err != nil { log.Println("InitToken: ", err) } } } func (c *Context) newToken() { c.initToken() rnd := genKey(keyLen) c.Data.Token = base64.StdEncoding.EncodeToString(append(rnd, xor(rnd, c.getToken())...)) } // CheckToken validates the xsrf token func (c *Context) CheckToken(f func()) bool { token, err := base64.StdEncoding.DecodeString(c.Form("token")) if err != nil { log.Println("CheckToken: ", err) return false } if len(token) != authLen { return false } ok := subtle.ConstantTimeCompare(c.getToken(), xor(token[keyLen:], token[:keyLen])) == 1 if !ok { if f != nil { f() return ok } c.Data.Msg = "wrong csrf token" c.Exec() } return ok } func (c *Context) LoggedOn() (ok bool) { s, _ := c.Srv.Store.Get(c.Request, cookieName) _, ok = s.Values["user"] return } func (c *Context) SetUdata(user string, admin bool) { s, _ := c.Srv.Store.Get(c.Request, cookieName) s.Values["user"] = user s.Values["admin"] = admin s.Options = c.Srv.Store.Options if err := s.Save(c.Request, c.Response); err != nil { log.Println(err) } } func (c *Context) UnsetUdata() { s, _ := c.Srv.Store.Get(c.Request, cookieName) s.Values = make(map[interface{}]interface{}) s.Options = c.Srv.Store.Options if err := s.Save(c.Request, c.Response); err != nil { log.Println(err) } } func (c *Context) GetUser() string { s, _ := c.Srv.Store.Get(c.Request, cookieName) if ret, ok := s.Values["user"].(string); ok { return ret } return "" } func (c *Context) GetAdmin() bool { s, _ := c.Srv.Store.Get(c.Request, cookieName) if ret, ok := s.Values["admin"].(bool); ok { return ret } return false } func (c *Context) getToken() []byte { s, _ := c.Srv.Store.Get(c.Request, cookieName) t, ok := s.Values["token"] if !ok { return nil } b, ok := t.([]byte) if !ok { return nil } return b } func xor(a, b []byte) (c []byte) { n := len(a) if len(b) != n { return } c = make([]byte, n) for i := 0; i < n; i++ { c[i] = a[i] ^ b[i] } return } type ctxHandler func(*Context)