5 "github.com/revel/revel/session"
15 // The session cookie engine
16 SessionCookieEngine struct {
17 ExpireAfterDuration time.Duration
21 // A logger for the session engine
22 var sessionEngineLog = RevelLog.New("section", "session-engine")
24 // Create a new instance to test
26 RegisterSessionEngine(initCookieEngine, "revel-cookie")
29 // For testing purposes this engine is used
30 func NewSessionCookieEngine() *SessionCookieEngine {
31 ce := &SessionCookieEngine{}
35 // Called when the the application starts, retrieves data from the app config so cannot be run until then
36 func initCookieEngine() SessionEngine {
37 ce := &SessionCookieEngine{}
40 if expiresString, ok := Config.String("session.expires"); !ok {
41 ce.ExpireAfterDuration = 30 * 24 * time.Hour
42 } else if expiresString == session.SessionValueName {
43 ce.ExpireAfterDuration = 0
44 } else if ce.ExpireAfterDuration, err = time.ParseDuration(expiresString); err != nil {
45 panic(fmt.Errorf("session.expires invalid: %s", err))
51 // Decode the session information from the cookie retrieved from the controller request
52 func (cse *SessionCookieEngine) Decode(c *Controller) {
53 // Decode the session from a cookie.
54 c.Session = map[string]interface{}{}
55 sessionMap := c.Session
56 if cookie, err := c.Request.Cookie(CookiePrefix + session.SessionCookieSuffix); err != nil {
59 cse.DecodeCookie(cookie, sessionMap)
60 c.Session = sessionMap
64 // Encode the session information to the cookie, set the cookie on the controller
65 func (cse *SessionCookieEngine) Encode(c *Controller) {
67 c.SetCookie(cse.GetCookie(c.Session))
70 // Exposed only for testing purposes
71 func (cse *SessionCookieEngine) DecodeCookie(cookie ServerCookie, s session.Session) {
72 // Decode the session from a cookie.
73 // Separate the data from the signature.
74 cookieValue := cookie.GetValue()
75 hyphen := strings.Index(cookieValue, "-")
76 if hyphen == -1 || hyphen >= len(cookieValue)-1 {
79 sig, data := cookieValue[:hyphen], cookieValue[hyphen+1:]
81 // Verify the signature.
82 if !Verify(data, sig) {
83 sessionEngineLog.Warn("Session cookie signature failed")
87 // Parse the cookie into a temp map, and then load it into the session object
88 tempMap := map[string]string{}
89 ParseKeyValueCookie(data, func(key, val string) {
94 // Check timeout after unpacking values - if timeout missing (or removed) destroy all session
96 if s.SessionTimeoutExpiredOrMissing() {
97 // If this fails we need to delete all the keys from the session
104 // Convert session to cookie
105 func (cse *SessionCookieEngine) GetCookie(s session.Session) *http.Cookie {
106 var sessionValue string
107 ts := s.GetExpiration(cse.ExpireAfterDuration)
109 s[session.TimestampKey] = session.SessionValueName
111 s[session.TimestampKey] = strconv.FormatInt(ts.Unix(), 10)
114 // Convert the key to a string map
115 stringMap := s.Serialize()
117 for key, value := range stringMap {
118 if strings.ContainsAny(key, ":\x00") {
119 panic("Session keys may not have colons or null bytes")
121 if strings.Contains(value, "\x00") {
122 panic("Session values may not have null bytes")
124 sessionValue += "\x00" + key + ":" + value + "\x00"
127 if len(sessionValue) > 1024*4 {
128 sessionEngineLog.Error("SessionCookieEngine.Cookie, session data has exceeded 4k limit (%d) cookie data will not be reliable", "length", len(sessionValue))
131 sessionData := url.QueryEscape(sessionValue)
132 sessionCookie := &http.Cookie{
133 Name: CookiePrefix + session.SessionCookieSuffix,
134 Value: Sign(sessionData) + "-" + sessionData,
135 Domain: CookieDomain,
138 Secure: CookieSecure,
140 MaxAge: int(cse.ExpireAfterDuration.Seconds()),