--- /dev/null
+package revel
+
+import (
+ "fmt"
+ "github.com/revel/revel/session"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+ "time"
+)
+
+
+type (
+ // The session cookie engine
+ SessionCookieEngine struct {
+ ExpireAfterDuration time.Duration
+ }
+)
+
+// A logger for the session engine
+var sessionEngineLog = RevelLog.New("section", "session-engine")
+
+// Create a new instance to test
+func init() {
+ RegisterSessionEngine(initCookieEngine, "revel-cookie")
+}
+
+// For testing purposes this engine is used
+func NewSessionCookieEngine() *SessionCookieEngine {
+ ce := &SessionCookieEngine{}
+ return ce
+}
+
+// Called when the the application starts, retrieves data from the app config so cannot be run until then
+func initCookieEngine() SessionEngine {
+ ce := &SessionCookieEngine{}
+
+ var err error
+ if expiresString, ok := Config.String("session.expires"); !ok {
+ ce.ExpireAfterDuration = 30 * 24 * time.Hour
+ } else if expiresString == session.SessionValueName {
+ ce.ExpireAfterDuration = 0
+ } else if ce.ExpireAfterDuration, err = time.ParseDuration(expiresString); err != nil {
+ panic(fmt.Errorf("session.expires invalid: %s", err))
+ }
+
+ return ce
+}
+
+// Decode the session information from the cookie retrieved from the controller request
+func (cse *SessionCookieEngine) Decode(c *Controller) {
+ // Decode the session from a cookie.
+ c.Session = map[string]interface{}{}
+ sessionMap := c.Session
+ if cookie, err := c.Request.Cookie(CookiePrefix + session.SessionCookieSuffix); err != nil {
+ return
+ } else {
+ cse.DecodeCookie(cookie, sessionMap)
+ c.Session = sessionMap
+ }
+}
+
+// Encode the session information to the cookie, set the cookie on the controller
+func (cse *SessionCookieEngine) Encode(c *Controller) {
+
+ c.SetCookie(cse.GetCookie(c.Session))
+}
+
+// Exposed only for testing purposes
+func (cse *SessionCookieEngine) DecodeCookie(cookie ServerCookie, s session.Session) {
+ // Decode the session from a cookie.
+ // Separate the data from the signature.
+ cookieValue := cookie.GetValue()
+ hyphen := strings.Index(cookieValue, "-")
+ if hyphen == -1 || hyphen >= len(cookieValue)-1 {
+ return
+ }
+ sig, data := cookieValue[:hyphen], cookieValue[hyphen+1:]
+
+ // Verify the signature.
+ if !Verify(data, sig) {
+ sessionEngineLog.Warn("Session cookie signature failed")
+ return
+ }
+
+ // Parse the cookie into a temp map, and then load it into the session object
+ tempMap := map[string]string{}
+ ParseKeyValueCookie(data, func(key, val string) {
+ tempMap[key] = val
+ })
+ s.Load(tempMap)
+
+ // Check timeout after unpacking values - if timeout missing (or removed) destroy all session
+ // objects
+ if s.SessionTimeoutExpiredOrMissing() {
+ // If this fails we need to delete all the keys from the session
+ for key := range s {
+ delete(s, key)
+ }
+ }
+}
+
+// Convert session to cookie
+func (cse *SessionCookieEngine) GetCookie(s session.Session) *http.Cookie {
+ var sessionValue string
+ ts := s.GetExpiration(cse.ExpireAfterDuration)
+ if ts.IsZero() {
+ s[session.TimestampKey] = session.SessionValueName
+ } else {
+ s[session.TimestampKey] = strconv.FormatInt(ts.Unix(), 10)
+ }
+
+ // Convert the key to a string map
+ stringMap := s.Serialize()
+
+ for key, value := range stringMap {
+ if strings.ContainsAny(key, ":\x00") {
+ panic("Session keys may not have colons or null bytes")
+ }
+ if strings.Contains(value, "\x00") {
+ panic("Session values may not have null bytes")
+ }
+ sessionValue += "\x00" + key + ":" + value + "\x00"
+ }
+
+ if len(sessionValue) > 1024*4 {
+ sessionEngineLog.Error("SessionCookieEngine.Cookie, session data has exceeded 4k limit (%d) cookie data will not be reliable", "length", len(sessionValue))
+ }
+
+ sessionData := url.QueryEscape(sessionValue)
+ sessionCookie := &http.Cookie{
+ Name: CookiePrefix + session.SessionCookieSuffix,
+ Value: Sign(sessionData) + "-" + sessionData,
+ Domain: CookieDomain,
+ Path: "/",
+ HttpOnly: true,
+ Secure: CookieSecure,
+ Expires: ts.UTC(),
+ MaxAge: int(cse.ExpireAfterDuration.Seconds()),
+ }
+ return sessionCookie
+
+}