From 66a66fdfa0901398f058b426c95365446904e4ca Mon Sep 17 00:00:00 2001 From: Makis Maropoulos Date: Thu, 30 Jun 2016 05:58:04 +0300 Subject: [PATCH] Fix https://github.com/kataras/iris/issues/231 --- config/sessions.go | 13 ++++--- context.go | 22 ++++++++++-- context/context.go | 1 + iris.go | 88 ++++++++++++++++++++++----------------------- sessions/manager.go | 51 ++++++++++++++++++++------ 5 files changed, 114 insertions(+), 61 deletions(-) diff --git a/config/sessions.go b/config/sessions.go index f2299b44..0c73b9cd 100644 --- a/config/sessions.go +++ b/config/sessions.go @@ -71,16 +71,21 @@ type ( // // Default 2 hours GcDuration time.Duration + + // DisableSubdomainPersistance set it to dissallow your iris subdomains to have access to the session cookie + // defaults to false + DisableSubdomainPersistance bool } ) // DefaultSessions the default configs for Sessions func DefaultSessions() Sessions { return Sessions{ - Provider: "memory", // the default provider is "memory", if you set it to "" means that sessions are disabled. - Cookie: DefaultCookieName, - Expires: CookieExpireNever, - GcDuration: DefaultSessionGcDuration, + Provider: "memory", // the default provider is "memory", if you set it to "" means that sessions are disabled. + Cookie: DefaultCookieName, + Expires: CookieExpireNever, + GcDuration: DefaultSessionGcDuration, + DisableSubdomainPersistance: false, } } diff --git a/context.go b/context.go index cbcb5224..f34372bf 100644 --- a/context.go +++ b/context.go @@ -215,12 +215,21 @@ func (ctx *Context) HostString() string { // VirtualHostname returns the hostname that user registers, host path maybe differs from the real which is HostString, which taken from a net.listener func (ctx *Context) VirtualHostname() string { realhost := ctx.HostString() + hostname := realhost virtualhost := ctx.framework.HTTPServer.VirtualHostname() - hostname := strings.Replace(realhost, "127.0.0.1", virtualhost, 1) - hostname = strings.Replace(realhost, "localhost", virtualhost, 1) + if portIdx := strings.IndexByte(hostname, ':'); portIdx > 0 { hostname = hostname[0:portIdx] } + if idxDotAnd := strings.LastIndexByte(hostname, '.'); idxDotAnd > 0 { + s := hostname[idxDotAnd:] + if s == ".1" { + hostname = strings.Replace(hostname, "127.0.0.1", virtualhost, 1) + } + } else { + hostname = strings.Replace(hostname, "localhost", virtualhost, 1) + } + return hostname } @@ -288,6 +297,15 @@ func (ctx *Context) PostFormMulti(name string) []string { return arrStr } +// Domain same as VirtualHostname but without the :port part (if any) +func (ctx *Context) Domain() string { + domain := ctx.VirtualHostname() + if idx := strings.IndexByte(domain, ':'); idx > 0 { + domain = domain[0 : idx-1] + } + return domain +} + // Subdomain returns the subdomain (string) of this request, if any func (ctx *Context) Subdomain() (subdomain string) { host := ctx.HostString() diff --git a/context/context.go b/context/context.go index b155ac86..6900ce85 100644 --- a/context/context.go +++ b/context/context.go @@ -23,6 +23,7 @@ type ( URLParams() map[string]string MethodString() string HostString() string + Domain() string Subdomain() string PathString() string RequestPath(bool) string diff --git a/iris.go b/iris.go index 381f88de..ef61f668 100644 --- a/iris.go +++ b/iris.go @@ -89,8 +89,8 @@ type ( ListenTLS(string, string, string) ListenUNIXWithErr(string, os.FileMode) error ListenUNIX(string, os.FileMode) - NoListen() *Server SecondaryListen(config.Server) *Server + NoListen() *Server Close() // global middleware prepending, registers to all subdomains, to all parties, you can call it at the last also MustUse(...Handler) @@ -292,6 +292,49 @@ func (s *Framework) ListenUNIX(addr string, mode os.FileMode) { s.Must(s.ListenUNIXWithErr(addr, mode)) } +// SecondaryListen starts a server which listens to this station +// Note that the view engine's functions {{ url }} and {{ urlpath }} will return the first's registered server's scheme (http/https) +// +// this is useful only when you want to have two or more listening ports ( two or more servers ) for the same station +// +// receives one parameter which is the config.Server for the new server +// returns the new standalone server( you can close this server by the returning reference) +// +// If you need only one server this function is not for you, instead you must use the normal .Listen/ListenTLS functions. +// +// this is a NOT A BLOCKING version, the main iris.Listen should be always executed LAST, so this function goes before the main .Listen. +func SecondaryListen(cfg config.Server) *Server { + return Default.SecondaryListen(cfg) +} + +// SecondaryListen starts a server which listens to this station +// Note that the view engine's functions {{ url }} and {{ urlpath }} will return the first's registered server's scheme (http/https) +// +// this is useful only when you want to have two or more listening ports ( two or more servers ) for the same station +// +// receives one parameter which is the config.Server for the new server +// returns the new standalone server( you can close this server by the returning reference) +// +// If you need only one server this function is not for you, instead you must use the normal .Listen/ListenTLS functions. +// +// this is a NOT A BLOCKING version, the main iris.Listen should be always executed LAST, so this function goes before the main .Listen. +func (s *Framework) SecondaryListen(cfg config.Server) *Server { + srv := newServer(&cfg) + // add a post listen event to start this server after the previous started + s.Plugins.Add(PostListenFunc(func(*Framework) { + go func() { // goroutine in order to not block any runtime post listeners + srv.Handler = s.HTTPServer.Handler + if err := srv.Open(); err == nil { + ch := make(chan os.Signal) + <-ch + srv.Close() + } + }() + })) + + return srv +} + // NoListen is useful only when you want to test Iris, it doesn't starts the server but it configures and returns it func NoListen() *Server { return Default.NoListen() @@ -558,49 +601,6 @@ func (s *Framework) TemplateString(templateFile string, pageContext interface{}, return res } -// SecondaryListen starts a server which listens to this station -// Note that the view engine's functions {{ url }} and {{ urlpath }} will return the first's registered server's scheme (http/https) -// -// this is useful only when you want to have two or more listening ports ( two or more servers ) for the same station -// -// receives one parameter which is the config.Server for the new server -// returns the new standalone server( you can close this server by the returning reference) -// -// If you need only one server this function is not for you, instead you must use the normal .Listen/ListenTLS functions. -// -// this is a NOT A BLOCKING version, the main iris.Listen should be always executed LAST, so this function goes before the main .Listen. -func SecondaryListen(cfg config.Server) *Server { - return Default.SecondaryListen(cfg) -} - -// SecondaryListen starts a server which listens to this station -// Note that the view engine's functions {{ url }} and {{ urlpath }} will return the first's registered server's scheme (http/https) -// -// this is useful only when you want to have two or more listening ports ( two or more servers ) for the same station -// -// receives one parameter which is the config.Server for the new server -// returns the new standalone server( you can close this server by the returning reference) -// -// If you need only one server this function is not for you, instead you must use the normal .Listen/ListenTLS functions. -// -// this is a NOT A BLOCKING version, the main iris.Listen should be always executed LAST, so this function goes before the main .Listen. -func (s *Framework) SecondaryListen(cfg config.Server) *Server { - srv := newServer(&cfg) - // add a post listen event to start this server after the previous started - s.Plugins.Add(PostListenFunc(func(*Framework) { - go func() { // goroutine in order to not block any runtime post listeners - srv.Handler = s.HTTPServer.Handler - if err := srv.Open(); err == nil { - ch := make(chan os.Signal) - <-ch - srv.Close() - } - }() - })) - - return srv -} - // ------------------------------------------------------------------------------------- // ------------------------------------------------------------------------------------- // ----------------------------------MuxAPI implementation------------------------------ diff --git a/sessions/manager.go b/sessions/manager.go index 407b43c7..ee219556 100644 --- a/sessions/manager.go +++ b/sessions/manager.go @@ -2,7 +2,7 @@ package sessions import ( "encoding/base64" - "net/url" + "strings" "sync" "time" @@ -23,9 +23,10 @@ type ( // Manager implements the IManager interface // contains the cookie's name, the provider and a duration for GC and cookie life expire Manager struct { - config *config.Sessions - provider IProvider - mu sync.Mutex + config *config.Sessions + provider IProvider + mu sync.Mutex + compiledCookie string } ) @@ -46,7 +47,7 @@ func newManager(c config.Sessions) (*Manager, error) { manager := &Manager{} manager.config = &c manager.provider = provider - + manager.compiledCookie = base64.URLEncoding.EncodeToString([]byte(c.Cookie)) return manager, nil } @@ -74,29 +75,57 @@ func (m *Manager) generateSessionID() string { return base64.URLEncoding.EncodeToString(utils.Random(32)) } +var dotB = byte('.') + // Start starts the session func (m *Manager) Start(ctx context.IContext) store.IStore { m.mu.Lock() var store store.IStore requestCtx := ctx.GetRequestCtx() - cookieValue := string(requestCtx.Request.Header.Cookie(m.config.Cookie)) + cookieValue := string(requestCtx.Request.Header.Cookie(m.compiledCookie)) if cookieValue == "" { // cookie doesn't exists, let's generate a session and add set a cookie sid := m.generateSessionID() store, _ = m.provider.Init(sid) cookie := fasthttp.AcquireCookie() - cookie.SetKey(m.config.Cookie) - cookie.SetValue(url.QueryEscape(sid)) + // The RFC makes no mention of encoding url value, so here I think to encode both sessionid key and the value using the safe(to put and to use as cookie) url-encoding + cookie.SetKey(m.compiledCookie) + cookie.SetValue(base64.URLEncoding.EncodeToString([]byte(sid))) cookie.SetPath("/") + if !m.config.DisableSubdomainPersistance { + requestDomain := ctx.HostString() // we don't use the ctx.Domain because it gives the virtual domain, which is correct on some cases but not here. + + if portIdx := strings.IndexByte(requestDomain, ':'); portIdx > 0 { + requestDomain = requestDomain[0:portIdx] + } + // RFC2109, we allow level 1 subdomains, but no further + // if we have localhost.com , we want the localhost.com. + // so if we have something like: mysubdomain.localhost.com we want the localhost here + // if we have mysubsubdomain.mysubdomain.localhost.com we want the .mysubdomain.localhost.com here + // slow things here, especially the 'replace' but this is a good and understable( I hope) way to get the be able to set cookies from subdomains & domain with 1-level limit + if dotIdx := strings.LastIndexByte(requestDomain, dotB); dotIdx > 0 { + // is mysubdomain.localhost.com || mysubsubdomain.mysubdomain.localhost.com + s := requestDomain[0:dotIdx] // set mysubdomain.localhost || mysubsubdomain.mysubdomain.localhost + if secondDotIdx := strings.LastIndexByte(s, dotB); secondDotIdx > 0 { + //is mysubdomain.localhost || mysubsubdomain.mysubdomain.localhost + s = s[secondDotIdx+1:] // set to localhost || mysubdomain.localhost + } + // replace the s with the requestDomain before the domain's siffux + subdomainSuff := strings.LastIndexByte(requestDomain, dotB) + if subdomainSuff > len(s) { // if it is actual exists as subdomain suffix + requestDomain = strings.Replace(requestDomain, requestDomain[0:subdomainSuff], s, 1) // set to localhost.com || mysubdomain.localhost.com + } + } + cookie.SetDomain("." + requestDomain) // . to allow persistance + } cookie.SetHTTPOnly(true) cookie.SetExpire(m.config.Expires) requestCtx.Response.Header.SetCookie(cookie) fasthttp.ReleaseCookie(cookie) - //println("manager.go:156-> Setting cookie with lifetime: ", m.lifeDuration.Seconds()) } else { - sid, _ := url.QueryUnescape(cookieValue) - store, _ = m.provider.Read(sid) + sid, _ := base64.URLEncoding.DecodeString(cookieValue) + store, _ = m.provider.Read(string(sid)) } m.mu.Unlock()