...

Source file src/github.com/gorilla/sessions/store.go

Documentation: github.com/gorilla/sessions

     1  // Copyright 2012 The Gorilla Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package sessions
     6  
     7  import (
     8  	"encoding/base32"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  	"sync"
    15  
    16  	"github.com/gorilla/securecookie"
    17  )
    18  
    19  // Store is an interface for custom session stores.
    20  //
    21  // See CookieStore and FilesystemStore for examples.
    22  type Store interface {
    23  	// Get should return a cached session.
    24  	Get(r *http.Request, name string) (*Session, error)
    25  
    26  	// New should create and return a new session.
    27  	//
    28  	// Note that New should never return a nil session, even in the case of
    29  	// an error if using the Registry infrastructure to cache the session.
    30  	New(r *http.Request, name string) (*Session, error)
    31  
    32  	// Save should persist session to the underlying store implementation.
    33  	Save(r *http.Request, w http.ResponseWriter, s *Session) error
    34  }
    35  
    36  // CookieStore ----------------------------------------------------------------
    37  
    38  // NewCookieStore returns a new CookieStore.
    39  //
    40  // Keys are defined in pairs to allow key rotation, but the common case is
    41  // to set a single authentication key and optionally an encryption key.
    42  //
    43  // The first key in a pair is used for authentication and the second for
    44  // encryption. The encryption key can be set to nil or omitted in the last
    45  // pair, but the authentication key is required in all pairs.
    46  //
    47  // It is recommended to use an authentication key with 32 or 64 bytes.
    48  // The encryption key, if set, must be either 16, 24, or 32 bytes to select
    49  // AES-128, AES-192, or AES-256 modes.
    50  func NewCookieStore(keyPairs ...[]byte) *CookieStore {
    51  	cs := &CookieStore{
    52  		Codecs: securecookie.CodecsFromPairs(keyPairs...),
    53  		Options: &Options{
    54  			Path:   "/",
    55  			MaxAge: 86400 * 30,
    56  		},
    57  	}
    58  
    59  	cs.MaxAge(cs.Options.MaxAge)
    60  	return cs
    61  }
    62  
    63  // CookieStore stores sessions using secure cookies.
    64  type CookieStore struct {
    65  	Codecs  []securecookie.Codec
    66  	Options *Options // default configuration
    67  }
    68  
    69  // Get returns a session for the given name after adding it to the registry.
    70  //
    71  // It returns a new session if the sessions doesn't exist. Access IsNew on
    72  // the session to check if it is an existing session or a new one.
    73  //
    74  // It returns a new session and an error if the session exists but could
    75  // not be decoded.
    76  func (s *CookieStore) Get(r *http.Request, name string) (*Session, error) {
    77  	return GetRegistry(r).Get(s, name)
    78  }
    79  
    80  // New returns a session for the given name without adding it to the registry.
    81  //
    82  // The difference between New() and Get() is that calling New() twice will
    83  // decode the session data twice, while Get() registers and reuses the same
    84  // decoded session after the first call.
    85  func (s *CookieStore) New(r *http.Request, name string) (*Session, error) {
    86  	session := NewSession(s, name)
    87  	opts := *s.Options
    88  	session.Options = &opts
    89  	session.IsNew = true
    90  	var err error
    91  	if c, errCookie := r.Cookie(name); errCookie == nil {
    92  		err = securecookie.DecodeMulti(name, c.Value, &session.Values,
    93  			s.Codecs...)
    94  		if err == nil {
    95  			session.IsNew = false
    96  		}
    97  	}
    98  	return session, err
    99  }
   100  
   101  // Save adds a single session to the response.
   102  func (s *CookieStore) Save(r *http.Request, w http.ResponseWriter,
   103  	session *Session) error {
   104  	encoded, err := securecookie.EncodeMulti(session.Name(), session.Values,
   105  		s.Codecs...)
   106  	if err != nil {
   107  		return err
   108  	}
   109  	http.SetCookie(w, NewCookie(session.Name(), encoded, session.Options))
   110  	return nil
   111  }
   112  
   113  // MaxAge sets the maximum age for the store and the underlying cookie
   114  // implementation. Individual sessions can be deleted by setting Options.MaxAge
   115  // = -1 for that session.
   116  func (s *CookieStore) MaxAge(age int) {
   117  	s.Options.MaxAge = age
   118  
   119  	// Set the maxAge for each securecookie instance.
   120  	for _, codec := range s.Codecs {
   121  		if sc, ok := codec.(*securecookie.SecureCookie); ok {
   122  			sc.MaxAge(age)
   123  		}
   124  	}
   125  }
   126  
   127  // FilesystemStore ------------------------------------------------------------
   128  
   129  var fileMutex sync.RWMutex
   130  
   131  // NewFilesystemStore returns a new FilesystemStore.
   132  //
   133  // The path argument is the directory where sessions will be saved. If empty
   134  // it will use os.TempDir().
   135  //
   136  // See NewCookieStore() for a description of the other parameters.
   137  func NewFilesystemStore(path string, keyPairs ...[]byte) *FilesystemStore {
   138  	if path == "" {
   139  		path = os.TempDir()
   140  	}
   141  	fs := &FilesystemStore{
   142  		Codecs: securecookie.CodecsFromPairs(keyPairs...),
   143  		Options: &Options{
   144  			Path:   "/",
   145  			MaxAge: 86400 * 30,
   146  		},
   147  		path: path,
   148  	}
   149  
   150  	fs.MaxAge(fs.Options.MaxAge)
   151  	return fs
   152  }
   153  
   154  // FilesystemStore stores sessions in the filesystem.
   155  //
   156  // It also serves as a reference for custom stores.
   157  //
   158  // This store is still experimental and not well tested. Feedback is welcome.
   159  type FilesystemStore struct {
   160  	Codecs  []securecookie.Codec
   161  	Options *Options // default configuration
   162  	path    string
   163  }
   164  
   165  // MaxLength restricts the maximum length of new sessions to l.
   166  // If l is 0 there is no limit to the size of a session, use with caution.
   167  // The default for a new FilesystemStore is 4096.
   168  func (s *FilesystemStore) MaxLength(l int) {
   169  	for _, c := range s.Codecs {
   170  		if codec, ok := c.(*securecookie.SecureCookie); ok {
   171  			codec.MaxLength(l)
   172  		}
   173  	}
   174  }
   175  
   176  // Get returns a session for the given name after adding it to the registry.
   177  //
   178  // See CookieStore.Get().
   179  func (s *FilesystemStore) Get(r *http.Request, name string) (*Session, error) {
   180  	return GetRegistry(r).Get(s, name)
   181  }
   182  
   183  // New returns a session for the given name without adding it to the registry.
   184  //
   185  // See CookieStore.New().
   186  func (s *FilesystemStore) New(r *http.Request, name string) (*Session, error) {
   187  	session := NewSession(s, name)
   188  	opts := *s.Options
   189  	session.Options = &opts
   190  	session.IsNew = true
   191  	var err error
   192  	if c, errCookie := r.Cookie(name); errCookie == nil {
   193  		err = securecookie.DecodeMulti(name, c.Value, &session.ID, s.Codecs...)
   194  		if err == nil {
   195  			err = s.load(session)
   196  			if err == nil {
   197  				session.IsNew = false
   198  			}
   199  		}
   200  	}
   201  	return session, err
   202  }
   203  
   204  // Save adds a single session to the response.
   205  //
   206  // If the Options.MaxAge of the session is <= 0 then the session file will be
   207  // deleted from the store path. With this process it enforces the properly
   208  // session cookie handling so no need to trust in the cookie management in the
   209  // web browser.
   210  func (s *FilesystemStore) Save(r *http.Request, w http.ResponseWriter,
   211  	session *Session) error {
   212  	// Delete if max-age is <= 0
   213  	if session.Options.MaxAge <= 0 {
   214  		if err := s.erase(session); err != nil {
   215  			return err
   216  		}
   217  		http.SetCookie(w, NewCookie(session.Name(), "", session.Options))
   218  		return nil
   219  	}
   220  
   221  	if session.ID == "" {
   222  		// Because the ID is used in the filename, encode it to
   223  		// use alphanumeric characters only.
   224  		session.ID = strings.TrimRight(
   225  			base32.StdEncoding.EncodeToString(
   226  				securecookie.GenerateRandomKey(32)), "=")
   227  	}
   228  	if err := s.save(session); err != nil {
   229  		return err
   230  	}
   231  	encoded, err := securecookie.EncodeMulti(session.Name(), session.ID,
   232  		s.Codecs...)
   233  	if err != nil {
   234  		return err
   235  	}
   236  	http.SetCookie(w, NewCookie(session.Name(), encoded, session.Options))
   237  	return nil
   238  }
   239  
   240  // MaxAge sets the maximum age for the store and the underlying cookie
   241  // implementation. Individual sessions can be deleted by setting Options.MaxAge
   242  // = -1 for that session.
   243  func (s *FilesystemStore) MaxAge(age int) {
   244  	s.Options.MaxAge = age
   245  
   246  	// Set the maxAge for each securecookie instance.
   247  	for _, codec := range s.Codecs {
   248  		if sc, ok := codec.(*securecookie.SecureCookie); ok {
   249  			sc.MaxAge(age)
   250  		}
   251  	}
   252  }
   253  
   254  // save writes encoded session.Values to a file.
   255  func (s *FilesystemStore) save(session *Session) error {
   256  	encoded, err := securecookie.EncodeMulti(session.Name(), session.Values,
   257  		s.Codecs...)
   258  	if err != nil {
   259  		return err
   260  	}
   261  	filename := filepath.Join(s.path, "session_"+session.ID)
   262  	fileMutex.Lock()
   263  	defer fileMutex.Unlock()
   264  	return ioutil.WriteFile(filename, []byte(encoded), 0600)
   265  }
   266  
   267  // load reads a file and decodes its content into session.Values.
   268  func (s *FilesystemStore) load(session *Session) error {
   269  	filename := filepath.Join(s.path, "session_"+session.ID)
   270  	fileMutex.RLock()
   271  	defer fileMutex.RUnlock()
   272  	fdata, err := ioutil.ReadFile(filename)
   273  	if err != nil {
   274  		return err
   275  	}
   276  	if err = securecookie.DecodeMulti(session.Name(), string(fdata),
   277  		&session.Values, s.Codecs...); err != nil {
   278  		return err
   279  	}
   280  	return nil
   281  }
   282  
   283  // delete session file
   284  func (s *FilesystemStore) erase(session *Session) error {
   285  	filename := filepath.Join(s.path, "session_"+session.ID)
   286  
   287  	fileMutex.RLock()
   288  	defer fileMutex.RUnlock()
   289  
   290  	err := os.Remove(filename)
   291  	return err
   292  }
   293  

View as plain text