diff --git a/providers/http/http.go b/providers/http/http.go index 62ecbc8..1b2a9fa 100644 --- a/providers/http/http.go +++ b/providers/http/http.go @@ -1,16 +1,21 @@ package http +import "log" +import "time" import "net/url" import "net/http" import "html/template" import "git.tebibyte.media/sashakoshka/step" import shttp "git.tebibyte.media/sashakoshka/step/http" +const defaultLifetime = 24 * 7 * time.Hour + var _ step.FuncProvider = new(Provider) // Provider provides HTTP and URL functions. type Provider struct { - + InsecureCookie bool + CookieDomain string } // Package fulfills the step.Provider interface. @@ -18,17 +23,36 @@ func (this *Provider) Package () string { return "http" } +func (this *Provider) Configure (config step.Meta) error { + if insecureCookieStr := config.Get("http.insecure-cookie"); insecureCookieStr != "" { + if insecureCookieStr != "true" && insecureCookieStr != "false" { + return step.ErrTypeMismatch + } + this.InsecureCookie = insecureCookieStr == "true" + if this.InsecureCookie { + log.Println("!!! http.insecure-cookie is active, this is not recommended") + } + } + if cookieDomainStr := config.Get("http.cookie-domain"); cookieDomainStr != "" { + this.CookieDomain = cookieDomainStr + } + return nil +} + // FuncMap fulfills the step.FuncProvider interface. func (this *Provider) FuncMap () template.FuncMap { return template.FuncMap { - "statusText": http.StatusText, - "parseQuery": funcParseQuery, - "parseForm": funcParseForm, - "error": funcError, - "redirect": funcRedirect, - "queryEscape": url.QueryEscape, - "queryUnescape": url.QueryUnescape, - "unsafe": funcUnsafe, + "statusText": http.StatusText, + "parseQuery": funcParseQuery, + "parseForm": funcParseForm, + "error": funcError, + "redirect": funcRedirect, + "queryEscape": url.QueryEscape, + "queryUnescape": url.QueryUnescape, + "unsafe": funcUnsafe, + "getCookie": this.funcGetCookie, + "setCookie": this.funcSetCookie, + "setCookieExpires": this.funcSetCookieExpires, } } @@ -64,3 +88,58 @@ func funcParseForm (req *http.Request) url.Values { func funcUnsafe(text string) template.HTML { return template.HTML(text) } + +func (this *Provider) funcGetCookie( + req *http.Request, + name string, +) string { + cookie, err := req.Cookie(name) + if err != nil { return "" } + return cookie.Value +} + +func (this *Provider) funcSetCookie( + res shttp.WrappedResponseWriter, + name string, + value string, +) error { + return this.funcSetCookieExpires(res, name, value, 0) +} + +func (this *Provider) funcSetCookieExpires( + res shttp.WrappedResponseWriter, + name string, + value string, + lifetime time.Duration, +) error { + if !this.InsecureCookie { + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie + // __Host- prefix: Cookies with names starting with __Host- are + // sent only to the host subdomain or domain that set them, and + // not to any other host. They must be set with the secure flag + // must be from a secure page (HTTPS), must not have a domain + // specified, and the path must be / + name = "__Host-" + name + } + var expiration time.Time + if lifetime == 0 { + expiration = time.Now().Add(defaultLifetime) + } else { + expiration = time.Now().Add(lifetime) + } + cookie := &http.Cookie { + Name: name, + Value: value, + Expires: expiration, + SameSite: http.SameSiteStrictMode, + Path: "/", + Domain: this.CookieDomain, + } + if !this.InsecureCookie { + cookie.Secure = true + cookie.HttpOnly = true + } + underlyingRes := shttp.UnderlyingResponseWriter(res) + http.SetCookie(underlyingRes, cookie) + return nil +}