--- /dev/null
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package session
+
+import (
+ "encoding/hex"
+ "encoding/json"
+ "errors"
+ "github.com/twinj/uuid"
+ "reflect"
+ "strconv"
+ "strings"
+ "time"
+)
+
+const (
+ // The key for the identity of the session
+ SessionIDKey = "_ID"
+ // The expiration date of the session
+ TimestampKey = "_TS"
+ // The value name indicating how long the session should persist - ie should it persist after the browser closes
+ // this is set under the TimestampKey if the session data should expire immediately
+ SessionValueName = "session"
+ // The key container for the json objects of the data, any non strings found in the map will be placed in here
+ // serialized by key using JSON
+ SessionObjectKeyName = "_object_"
+ // The mapped session object
+ SessionMapKeyName = "_map_"
+ // The suffix of the session cookie
+ SessionCookieSuffix = "_SESSION"
+)
+
+// Session data, can be any data, there are reserved keywords used by the storage data
+// SessionIDKey Is the key name for the session
+// TimestampKey Is the time that the session should expire
+//
+type Session map[string]interface{}
+
+func NewSession() Session {
+ return Session{}
+}
+
+// ID retrieves from the cookie or creates a time-based UUID identifying this
+// session.
+func (s Session) ID() string {
+ if sessionIDStr, ok := s[SessionIDKey]; ok {
+ return sessionIDStr.(string)
+ }
+
+ buffer := uuid.NewV4()
+
+ s[SessionIDKey] = hex.EncodeToString(buffer.Bytes())
+ return s[SessionIDKey].(string)
+}
+
+// getExpiration return a time.Time with the session's expiration date.
+// It uses the passed in expireAfterDuration to add with the current time if the timeout is not
+// browser dependent (ie session). If previous session has set to "session", the time returned is time.IsZero()
+func (s Session) GetExpiration(expireAfterDuration time.Duration) time.Time {
+ if expireAfterDuration == 0 || s[TimestampKey] == SessionValueName {
+ // Expire after closing browser
+ return time.Time{}
+ }
+ return time.Now().Add(expireAfterDuration)
+}
+
+// SetNoExpiration sets session to expire when browser session ends
+func (s Session) SetNoExpiration() {
+ s[TimestampKey] = SessionValueName
+}
+
+// SetDefaultExpiration sets session to expire after default duration
+func (s Session) SetDefaultExpiration() {
+ delete(s, TimestampKey)
+}
+
+// sessionTimeoutExpiredOrMissing returns a boolean of whether the session
+// cookie is either not present or present but beyond its time to live; i.e.,
+// whether there is not a valid session.
+func (s Session) SessionTimeoutExpiredOrMissing() bool {
+ if exp, present := s[TimestampKey]; !present {
+ return true
+ } else if exp == SessionValueName {
+ return false
+ } else if expInt, _ := strconv.Atoi(exp.(string)); int64(expInt) < time.Now().Unix() {
+ return true
+ }
+ return false
+}
+
+// Constant error if session value is not found
+var SESSION_VALUE_NOT_FOUND = errors.New("Session value not found")
+
+// Get an object or property from the session
+// it may be embedded inside the session.
+func (s Session) Get(key string) (newValue interface{}, err error) {
+ // First check to see if it is in the session
+ if v, found := s[key]; found {
+ return v, nil
+ }
+ return s.GetInto(key, nil, false)
+}
+
+// Get into the specified value.
+// If value exists in the session it will just return the value
+func (s Session) GetInto(key string, target interface{}, force bool) (result interface{}, err error) {
+ if v, found := s[key]; found && !force {
+ return v, nil
+ }
+ splitKey := strings.Split(key, ".")
+ rootKey := splitKey[0]
+
+ // Force always recreates the object from the session data map
+ if force {
+ if target == nil {
+ if result, err = s.sessionDataFromMap(key); err != nil {
+ return
+ }
+ } else if result, err = s.sessionDataFromObject(rootKey, target); err != nil {
+ return
+ }
+
+ return s.getNestedProperty(splitKey, result)
+ }
+
+ // Attempt to find the key in the session, this is the most generalized form
+ v, found := s[rootKey]
+ if !found {
+ if target == nil {
+ // Try to fetch it from the session
+
+ if v, err = s.sessionDataFromMap(rootKey); err != nil {
+ return
+ }
+ } else if v, err = s.sessionDataFromObject(rootKey, target); err != nil {
+ return
+ }
+ }
+
+ return s.getNestedProperty(splitKey, v)
+}
+
+// Returns the default value if the key is not found
+func (s Session) GetDefault(key string, value interface{}, defaultValue interface{}) interface{} {
+ v, e := s.GetInto(key, value, false)
+ if e != nil {
+ v = defaultValue
+ }
+ return v
+}
+
+// Extract the values from the session
+func (s Session) GetProperty(key string, value interface{}) (interface{}, error) {
+ // Capitalize the first letter
+ key = strings.Title(key)
+
+ sessionLog.Info("getProperty", "key", key, "value", value)
+
+ // For a map it is easy
+ if reflect.TypeOf(value).Kind() == reflect.Map {
+ val := reflect.ValueOf(value)
+ valueOf := val.MapIndex(reflect.ValueOf(key))
+ if valueOf == reflect.Zero(reflect.ValueOf(value).Type()) {
+ return nil, nil
+ }
+ //idx := val.MapIndex(reflect.ValueOf(key))
+ if !valueOf.IsValid() {
+ return nil, nil
+ }
+
+ return valueOf.Interface(), nil
+ }
+
+ objValue := s.reflectValue(value)
+ field := objValue.FieldByName(key)
+ if !field.IsValid() {
+ return nil, SESSION_VALUE_NOT_FOUND
+ }
+
+ return field.Interface(), nil
+}
+
+// Places the object into the session, a nil value will cause remove the key from the session
+// (or you can use the Session.Del(key) function
+func (s Session) Set(key string, value interface{}) error {
+ if value == nil {
+ s.Del(key)
+ return nil
+ }
+
+ s[key] = value
+ return nil
+}
+
+// Delete the key from the sessionObjects and Session
+func (s Session) Del(key string) {
+ sessionJsonMap := s.getSessionJsonMap()
+ delete(sessionJsonMap, key)
+ delete(s, key)
+}
+
+// Extracts the session as a map of [string keys] and json values
+func (s Session) getSessionJsonMap() map[string]string {
+ if sessionJson, found := s[SessionObjectKeyName]; found {
+ if _, valid := sessionJson.(map[string]string); !valid {
+ sessionLog.Error("Session object key corrupted, reset", "was", sessionJson)
+ s[SessionObjectKeyName] = map[string]string{}
+ }
+ // serialized data inside the session _objects
+ } else {
+ s[SessionObjectKeyName] = map[string]string{}
+ }
+
+ return s[SessionObjectKeyName].(map[string]string)
+}
+
+// Convert the map to a simple map[string]string map
+// this will marshal any non string objects encountered and store them the the jsonMap
+// The expiration time will also be assigned
+func (s Session) Serialize() map[string]string {
+ sessionJsonMap := s.getSessionJsonMap()
+ newMap := map[string]string{}
+ newObjectMap := map[string]string{}
+ for key, value := range sessionJsonMap {
+ newObjectMap[key] = value
+ }
+ for key, value := range s {
+ if key == SessionObjectKeyName || key == SessionMapKeyName {
+ continue
+ }
+ if reflect.ValueOf(value).Kind() == reflect.String {
+ newMap[key] = value.(string)
+ continue
+ }
+ println("Serialize the data for", key)
+ if data, err := json.Marshal(value); err != nil {
+ sessionLog.Error("Unable to marshal session ", "key", key, "error", err)
+ continue
+ } else {
+ newObjectMap[key] = string(data)
+ }
+ }
+ if len(newObjectMap) > 0 {
+ if data, err := json.Marshal(newObjectMap); err != nil {
+ sessionLog.Error("Unable to marshal session ", "key", SessionObjectKeyName, "error", err)
+
+ } else {
+ newMap[SessionObjectKeyName] = string(data)
+ }
+ }
+
+ return newMap
+}
+
+// Set the session object from the loaded data
+func (s Session) Load(data map[string]string) {
+ for key, value := range data {
+ if key == SessionObjectKeyName {
+ target := map[string]string{}
+ if err := json.Unmarshal([]byte(value), &target); err != nil {
+ sessionLog.Error("Unable to unmarshal session ", "key", SessionObjectKeyName, "error", err)
+ } else {
+ s[key] = target
+ }
+ } else {
+ s[key] = value
+ }
+
+ }
+}
+
+// Checks to see if the session is empty
+func (s Session) Empty() bool {
+ i := 0
+ for k := range s {
+ i++
+ if k == SessionObjectKeyName || k == SessionMapKeyName {
+ continue
+ }
+ }
+ return i == 0
+}
+
+func (s *Session) reflectValue(obj interface{}) reflect.Value {
+ var val reflect.Value
+
+ if reflect.TypeOf(obj).Kind() == reflect.Ptr {
+ val = reflect.ValueOf(obj).Elem()
+ } else {
+ val = reflect.ValueOf(obj)
+ }
+
+ return val
+}
+
+// Starting at position 1 drill into the object
+func (s Session) getNestedProperty(keys []string, newValue interface{}) (result interface{}, err error) {
+ for x := 1; x < len(keys); x++ {
+ newValue, err = s.GetProperty(keys[x], newValue)
+ if err != nil || newValue == nil {
+ return newValue, err
+ }
+ }
+ return newValue, nil
+}
+
+// Always converts the data from the session mapped objects into the target,
+// it will store the results under the session key name SessionMapKeyName
+func (s Session) sessionDataFromMap(key string) (result interface{}, err error) {
+ var mapValue map[string]interface{}
+ uncastMapValue, found := s[SessionMapKeyName]
+ if !found {
+ mapValue = map[string]interface{}{}
+ s[SessionMapKeyName] = mapValue
+ } else if mapValue, found = uncastMapValue.(map[string]interface{}); !found {
+ // Unusual means that the value in the session was not expected
+ sessionLog.Errorf("Unusual means that the value in the session was not expected", "session", uncastMapValue)
+ mapValue = map[string]interface{}{}
+ s[SessionMapKeyName] = mapValue
+ }
+
+ // Try to extract the key from the map
+ result, found = mapValue[key]
+ if !found {
+ result, err = s.convertSessionData(key, nil)
+ if err == nil {
+ mapValue[key] = result
+ }
+ }
+ return
+}
+
+// Unpack the object from the session map and store it in the session when done, if no error occurs
+func (s Session) sessionDataFromObject(key string, newValue interface{}) (result interface{}, err error) {
+ result, err = s.convertSessionData(key, newValue)
+ if err != nil {
+ return
+ }
+ s[key] = result
+ return
+}
+
+// Converts from the session json map into the target,
+func (s Session) convertSessionData(key string, target interface{}) (result interface{}, err error) {
+ sessionJsonMap := s.getSessionJsonMap()
+ v, found := sessionJsonMap[key]
+ if !found {
+ return target, SESSION_VALUE_NOT_FOUND
+ }
+
+ // Create a target if needed
+ if target == nil {
+ target = map[string]interface{}{}
+ if err := json.Unmarshal([]byte(v), &target); err != nil {
+ return target, err
+ }
+ } else if err := json.Unmarshal([]byte(v), target); err != nil {
+ return target, err
+ }
+ result = target
+ return
+}