package sessions import ( "encoding/base64" "strings" "sync" "time" "github.com/kataras/iris/config" "github.com/kataras/iris/context" "github.com/kataras/iris/sessions/store" "github.com/kataras/iris/utils" "github.com/valyala/fasthttp" ) type ( // IManager is the interface which Manager should implement IManager interface { Start(context.IContext) store.IStore Destroy(context.IContext) GC() } // 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 } ) var _ IManager = &Manager{} var ( continueOnError = true providers = make(map[string]IProvider) ) // newManager creates & returns a new Manager func newManager(c config.Sessions) (*Manager, error) { provider, found := providers[c.Provider] if !found { return nil, ErrProviderNotFound.Format(c.Provider) } if c.DecodeCookie { c.Cookie = base64.URLEncoding.EncodeToString([]byte(c.Cookie)) // change the cookie's name/key to a more safe(?) // get the real value for your tests by: //sessIdKey := url.QueryEscape(base64.URLEncoding.EncodeToString([]byte(iris.Config.Sessions.Cookie))) } manager := &Manager{} manager.config = &c manager.provider = provider return manager, nil } // Register registers a provider func Register(provider IProvider) { if provider == nil { ErrProviderRegister.Panic() } providerName := provider.Name() if _, exists := providers[providerName]; exists { if !continueOnError { ErrProviderAlreadyExists.Panicf(providerName) } else { // do nothing it's a map it will overrides the existing provider. } } providers[providerName] = provider } // Manager implementation 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)) 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() // 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.config.Cookie) cookie.SetValue(sid) cookie.SetPath("/") if !m.config.DisableSubdomainPersistence { requestDomain := ctx.HostString() if portIdx := strings.IndexByte(requestDomain, ':'); portIdx > 0 { requestDomain = requestDomain[0:portIdx] } if requestDomain == "0.0.0.0" || requestDomain == "127.0.0.1" { // for these type of hosts, we can't allow subdomains persistance, // the web browser doesn't understand the mysubdomain.0.0.0.0 and mysubdomain.127.0.0.1 as scorrectly ubdomains because of the many dots cookie.SetDomain(requestDomain) } else if strings.Count(requestDomain, ".") > 0 { // there is a problem with .localhost setted as the domain, so we check that first // 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 } } // finally set the .localhost.com (for(1-level) || .mysubdomain.localhost.com (for 2-level subdomain allow) cookie.SetDomain("." + requestDomain) // . to allow persistance } } cookie.SetHTTPOnly(true) cookie.SetExpire(m.config.Expires) requestCtx.Response.Header.SetCookie(cookie) fasthttp.ReleaseCookie(cookie) } else { store, _ = m.provider.Read(cookieValue) } m.mu.Unlock() return store } // Destroy kills the session and remove the associated cookie func (m *Manager) Destroy(ctx context.IContext) { cookieValue := string(ctx.GetRequestCtx().Request.Header.Cookie(m.config.Cookie)) if cookieValue == "" { // nothing to destroy return } m.mu.Lock() m.provider.Destroy(cookieValue) ctx.RemoveCookie(m.config.Cookie) m.mu.Unlock() } // GC tick-tock for the store cleanup // it's a blocking function, so run it with go routine, it's totally safe func (m *Manager) GC() { m.mu.Lock() m.provider.GC(m.config.GcDuration) // set a timer for the next GC time.AfterFunc(m.config.GcDuration, func() { m.GC() }) // or m.expire.Unix() if Nanosecond() doesn't works here m.mu.Unlock() }