pax_global_header00006660000000000000000000000064137172421500014514gustar00rootroot0000000000000052 comment=61fa50d034f99479a7de0d1c02c5e9dea5ad30cb sessions-1.2.1/000077500000000000000000000000001371724215000133635ustar00rootroot00000000000000sessions-1.2.1/.circleci/000077500000000000000000000000001371724215000152165ustar00rootroot00000000000000sessions-1.2.1/.circleci/config.yml000066400000000000000000000022271371724215000172110ustar00rootroot00000000000000version: 2.0 jobs: # Base test configuration for Go library tests Each distinct version should # inherit this base, and override (at least) the container image used. "test": &test docker: - image: circleci/golang:latest working_directory: /go/src/github.com/gorilla/sessions steps: &steps - checkout - run: go version - run: go get -t -v ./... - run: diff -u <(echo -n) <(gofmt -d .) - run: if [[ "$LATEST" = true ]]; then go vet -v .; fi - run: go test -v -race ./... "latest": <<: *test environment: LATEST: true "1.12": <<: *test docker: - image: circleci/golang:1.12 "1.11": <<: *test docker: - image: circleci/golang:1.11 "1.10": <<: *test docker: - image: circleci/golang:1.10 "1.9": <<: *test docker: - image: circleci/golang:1.9 "1.8": <<: *test docker: - image: circleci/golang:1.8 "1.7": <<: *test docker: - image: circleci/golang:1.7 workflows: version: 2 build: jobs: - "latest" - "1.12" - "1.11" - "1.10" - "1.9" - "1.8" - "1.7" sessions-1.2.1/.github/000077500000000000000000000000001371724215000147235ustar00rootroot00000000000000sessions-1.2.1/.github/release-drafter.yml000066400000000000000000000001771371724215000205200ustar00rootroot00000000000000# Config for https://github.com/apps/release-drafter template: | ### CHANGELOG $CHANGES sessions-1.2.1/.github/stale.yml000066400000000000000000000005271371724215000165620ustar00rootroot00000000000000daysUntilStale: 60 daysUntilClose: 7 # Issues with these labels will never be considered stale exemptLabels: - v2 - needs-review - work-required staleLabel: stale markComment: > This issue has been automatically marked as stale because it hasn't seen a recent update. It'll be automatically closed in a few days. closeComment: false sessions-1.2.1/AUTHORS000066400000000000000000000031021371724215000144270ustar00rootroot00000000000000# This is the official list of gorilla/sessions authors for copyright purposes. # # Please keep the list sorted. Ahmadreza Zibaei Anton Lindström Brian Jones Collin Stedman Deniz Eren Dmitry Chestnykh Dustin Oprea Egon Elbre enumappstore Geofrey Ernest Google LLC (https://opensource.google.com/) Jerry Saravia Jonathan Gillham Justin Clift Justin Hellings Kamil Kisiel Keiji Yoshida kliron Kshitij Saraogi Lauris BH Lukas Rist Mark Dain Matt Ho Matt Silverlock Mattias Wadman Michael Schuett Michael Stapelberg Mirco Zeiss moraes nvcnvn pappz Pontus Leitzler QuaSoft rcadena rodrigo moraes Shawn Smith Taylor Hurt Tortuoise Vitor De Mario sessions-1.2.1/LICENSE000066400000000000000000000027161371724215000143760ustar00rootroot00000000000000Copyright (c) 2012-2018 The Gorilla Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. sessions-1.2.1/README.md000066400000000000000000000116221371724215000146440ustar00rootroot00000000000000# sessions [![GoDoc](https://godoc.org/github.com/gorilla/sessions?status.svg)](https://godoc.org/github.com/gorilla/sessions) [![Build Status](https://travis-ci.org/gorilla/sessions.svg?branch=master)](https://travis-ci.org/gorilla/sessions) [![Sourcegraph](https://sourcegraph.com/github.com/gorilla/sessions/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/sessions?badge) gorilla/sessions provides cookie and filesystem sessions and infrastructure for custom session backends. The key features are: - Simple API: use it as an easy way to set signed (and optionally encrypted) cookies. - Built-in backends to store sessions in cookies or the filesystem. - Flash messages: session values that last until read. - Convenient way to switch session persistency (aka "remember me") and set other attributes. - Mechanism to rotate authentication and encryption keys. - Multiple sessions per request, even using different backends. - Interfaces and infrastructure for custom session backends: sessions from different stores can be retrieved and batch-saved using a common API. Let's start with an example that shows the sessions API in a nutshell: ```go import ( "net/http" "github.com/gorilla/sessions" ) // Note: Don't store your key in your source code. Pass it via an // environmental variable, or flag (or both), and don't accidentally commit it // alongside your code. Ensure your key is sufficiently random - i.e. use Go's // crypto/rand or securecookie.GenerateRandomKey(32) and persist the result. var store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY"))) func MyHandler(w http.ResponseWriter, r *http.Request) { // Get a session. We're ignoring the error resulted from decoding an // existing session: Get() always returns a session, even if empty. session, _ := store.Get(r, "session-name") // Set some session values. session.Values["foo"] = "bar" session.Values[42] = 43 // Save it before we write to the response/return from the handler. err := session.Save(r, w) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } ``` First we initialize a session store calling `NewCookieStore()` and passing a secret key used to authenticate the session. Inside the handler, we call `store.Get()` to retrieve an existing session or create a new one. Then we set some session values in session.Values, which is a `map[interface{}]interface{}`. And finally we call `session.Save()` to save the session in the response. More examples are available [on the Gorilla website](https://www.gorillatoolkit.org/pkg/sessions). ## Store Implementations Other implementations of the `sessions.Store` interface: - [github.com/starJammer/gorilla-sessions-arangodb](https://github.com/starJammer/gorilla-sessions-arangodb) - ArangoDB - [github.com/yosssi/boltstore](https://github.com/yosssi/boltstore) - Bolt - [github.com/srinathgs/couchbasestore](https://github.com/srinathgs/couchbasestore) - Couchbase - [github.com/denizeren/dynamostore](https://github.com/denizeren/dynamostore) - Dynamodb on AWS - [github.com/savaki/dynastore](https://github.com/savaki/dynastore) - DynamoDB on AWS (Official AWS library) - [github.com/bradleypeabody/gorilla-sessions-memcache](https://github.com/bradleypeabody/gorilla-sessions-memcache) - Memcache - [github.com/dsoprea/go-appengine-sessioncascade](https://github.com/dsoprea/go-appengine-sessioncascade) - Memcache/Datastore/Context in AppEngine - [github.com/kidstuff/mongostore](https://github.com/kidstuff/mongostore) - MongoDB - [github.com/srinathgs/mysqlstore](https://github.com/srinathgs/mysqlstore) - MySQL - [github.com/EnumApps/clustersqlstore](https://github.com/EnumApps/clustersqlstore) - MySQL Cluster - [github.com/antonlindstrom/pgstore](https://github.com/antonlindstrom/pgstore) - PostgreSQL - [github.com/boj/redistore](https://github.com/boj/redistore) - Redis - [github.com/rbcervilla/redisstore](https://github.com/rbcervilla/redisstore) - Redis (Single, Sentinel, Cluster) - [github.com/boj/rethinkstore](https://github.com/boj/rethinkstore) - RethinkDB - [github.com/boj/riakstore](https://github.com/boj/riakstore) - Riak - [github.com/michaeljs1990/sqlitestore](https://github.com/michaeljs1990/sqlitestore) - SQLite - [github.com/wader/gormstore](https://github.com/wader/gormstore) - GORM (MySQL, PostgreSQL, SQLite) - [github.com/gernest/qlstore](https://github.com/gernest/qlstore) - ql - [github.com/quasoft/memstore](https://github.com/quasoft/memstore) - In-memory implementation for use in unit tests - [github.com/lafriks/xormstore](https://github.com/lafriks/xormstore) - XORM (MySQL, PostgreSQL, SQLite, Microsoft SQL Server, TiDB) - [github.com/GoogleCloudPlatform/firestore-gorilla-sessions](https://github.com/GoogleCloudPlatform/firestore-gorilla-sessions) - Cloud Firestore - [github.com/stephenafamo/crdbstore](https://github.com/stephenafamo/crdbstore) - CockroachDB ## License BSD licensed. See the LICENSE file for details. sessions-1.2.1/cookie.go000066400000000000000000000006311371724215000151630ustar00rootroot00000000000000// +build !go1.11 package sessions import "net/http" // newCookieFromOptions returns an http.Cookie with the options set. func newCookieFromOptions(name, value string, options *Options) *http.Cookie { return &http.Cookie{ Name: name, Value: value, Path: options.Path, Domain: options.Domain, MaxAge: options.MaxAge, Secure: options.Secure, HttpOnly: options.HttpOnly, } } sessions-1.2.1/cookie_go111.go000066400000000000000000000006661371724215000161030ustar00rootroot00000000000000// +build go1.11 package sessions import "net/http" // newCookieFromOptions returns an http.Cookie with the options set. func newCookieFromOptions(name, value string, options *Options) *http.Cookie { return &http.Cookie{ Name: name, Value: value, Path: options.Path, Domain: options.Domain, MaxAge: options.MaxAge, Secure: options.Secure, HttpOnly: options.HttpOnly, SameSite: options.SameSite, } } sessions-1.2.1/cookie_go111_test.go000066400000000000000000000011461371724215000171340ustar00rootroot00000000000000// +build go1.11 package sessions import ( "net/http" "testing" ) // Test for setting SameSite field in new http.Cookie from name, value // and options func TestNewCookieFromOptionsSameSite(t *testing.T) { tests := []struct { sameSite http.SameSite }{ {http.SameSiteDefaultMode}, {http.SameSiteLaxMode}, {http.SameSiteStrictMode}, } for i, v := range tests { options := &Options{ SameSite: v.sameSite, } cookie := newCookieFromOptions("", "", options) if cookie.SameSite != v.sameSite { t.Fatalf("%v: bad cookie sameSite: got %v, want %v", i+1, cookie.SameSite, v.sameSite) } } } sessions-1.2.1/cookie_test.go000066400000000000000000000034141371724215000162240ustar00rootroot00000000000000package sessions import ( "testing" ) // Test for creating new http.Cookie from name, value and options func TestNewCookieFromOptions(t *testing.T) { tests := []struct { name string value string path string domain string maxAge int secure bool httpOnly bool }{ {"", "bar", "/foo/bar", "foo.example.com", 3600, true, true}, {"foo", "", "/foo/bar", "foo.example.com", 3600, true, true}, {"foo", "bar", "", "foo.example.com", 3600, true, true}, {"foo", "bar", "/foo/bar", "", 3600, true, true}, {"foo", "bar", "/foo/bar", "foo.example.com", 0, true, true}, {"foo", "bar", "/foo/bar", "foo.example.com", 3600, false, true}, {"foo", "bar", "/foo/bar", "foo.example.com", 3600, true, false}, } for i, v := range tests { options := &Options{ Path: v.path, Domain: v.domain, MaxAge: v.maxAge, Secure: v.secure, HttpOnly: v.httpOnly, } cookie := newCookieFromOptions(v.name, v.value, options) if cookie.Name != v.name { t.Fatalf("%v: bad cookie name: got %q, want %q", i+1, cookie.Name, v.name) } if cookie.Value != v.value { t.Fatalf("%v: bad cookie value: got %q, want %q", i+1, cookie.Value, v.value) } if cookie.Path != v.path { t.Fatalf("%v: bad cookie path: got %q, want %q", i+1, cookie.Path, v.path) } if cookie.Domain != v.domain { t.Fatalf("%v: bad cookie domain: got %q, want %q", i+1, cookie.Domain, v.domain) } if cookie.MaxAge != v.maxAge { t.Fatalf("%v: bad cookie maxAge: got %q, want %q", i+1, cookie.MaxAge, v.maxAge) } if cookie.Secure != v.secure { t.Fatalf("%v: bad cookie secure: got %v, want %v", i+1, cookie.Secure, v.secure) } if cookie.HttpOnly != v.httpOnly { t.Fatalf("%v: bad cookie httpOnly: got %v, want %v", i+1, cookie.HttpOnly, v.httpOnly) } } } sessions-1.2.1/doc.go000066400000000000000000000165711371724215000144710ustar00rootroot00000000000000// Copyright 2012 The Gorilla Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. /* Package sessions provides cookie and filesystem sessions and infrastructure for custom session backends. The key features are: * Simple API: use it as an easy way to set signed (and optionally encrypted) cookies. * Built-in backends to store sessions in cookies or the filesystem. * Flash messages: session values that last until read. * Convenient way to switch session persistency (aka "remember me") and set other attributes. * Mechanism to rotate authentication and encryption keys. * Multiple sessions per request, even using different backends. * Interfaces and infrastructure for custom session backends: sessions from different stores can be retrieved and batch-saved using a common API. Let's start with an example that shows the sessions API in a nutshell: import ( "net/http" "github.com/gorilla/sessions" ) // Note: Don't store your key in your source code. Pass it via an // environmental variable, or flag (or both), and don't accidentally commit it // alongside your code. Ensure your key is sufficiently random - i.e. use Go's // crypto/rand or securecookie.GenerateRandomKey(32) and persist the result. // Ensure SESSION_KEY exists in the environment, or sessions will fail. var store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY"))) func MyHandler(w http.ResponseWriter, r *http.Request) { // Get a session. Get() always returns a session, even if empty. session, err := store.Get(r, "session-name") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // Set some session values. session.Values["foo"] = "bar" session.Values[42] = 43 // Save it before we write to the response/return from the handler. err = session.Save(r, w) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } First we initialize a session store calling NewCookieStore() and passing a secret key used to authenticate the session. Inside the handler, we call store.Get() to retrieve an existing session or a new one. Then we set some session values in session.Values, which is a map[interface{}]interface{}. And finally we call session.Save() to save the session in the response. Note that in production code, we should check for errors when calling session.Save(r, w), and either display an error message or otherwise handle it. Save must be called before writing to the response, otherwise the session cookie will not be sent to the client. That's all you need to know for the basic usage. Let's take a look at other options, starting with flash messages. Flash messages are session values that last until read. The term appeared with Ruby On Rails a few years back. When we request a flash message, it is removed from the session. To add a flash, call session.AddFlash(), and to get all flashes, call session.Flashes(). Here is an example: func MyHandler(w http.ResponseWriter, r *http.Request) { // Get a session. session, err := store.Get(r, "session-name") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // Get the previous flashes, if any. if flashes := session.Flashes(); len(flashes) > 0 { // Use the flash values. } else { // Set a new flash. session.AddFlash("Hello, flash messages world!") } err = session.Save(r, w) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } Flash messages are useful to set information to be read after a redirection, like after form submissions. There may also be cases where you want to store a complex datatype within a session, such as a struct. Sessions are serialised using the encoding/gob package, so it is easy to register new datatypes for storage in sessions: import( "encoding/gob" "github.com/gorilla/sessions" ) type Person struct { FirstName string LastName string Email string Age int } type M map[string]interface{} func init() { gob.Register(&Person{}) gob.Register(&M{}) } As it's not possible to pass a raw type as a parameter to a function, gob.Register() relies on us passing it a value of the desired type. In the example above we've passed it a pointer to a struct and a pointer to a custom type representing a map[string]interface. (We could have passed non-pointer values if we wished.) This will then allow us to serialise/deserialise values of those types to and from our sessions. Note that because session values are stored in a map[string]interface{}, there's a need to type-assert data when retrieving it. We'll use the Person struct we registered above: func MyHandler(w http.ResponseWriter, r *http.Request) { session, err := store.Get(r, "session-name") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // Retrieve our struct and type-assert it val := session.Values["person"] var person = &Person{} if person, ok := val.(*Person); !ok { // Handle the case that it's not an expected type } // Now we can use our person object } By default, session cookies last for a month. This is probably too long for some cases, but it is easy to change this and other attributes during runtime. Sessions can be configured individually or the store can be configured and then all sessions saved using it will use that configuration. We access session.Options or store.Options to set a new configuration. The fields are basically a subset of http.Cookie fields. Let's change the maximum age of a session to one week: session.Options = &sessions.Options{ Path: "/", MaxAge: 86400 * 7, HttpOnly: true, } Sometimes we may want to change authentication and/or encryption keys without breaking existing sessions. The CookieStore supports key rotation, and to use it you just need to set multiple authentication and encryption keys, in pairs, to be tested in order: var store = sessions.NewCookieStore( []byte("new-authentication-key"), []byte("new-encryption-key"), []byte("old-authentication-key"), []byte("old-encryption-key"), ) New sessions will be saved using the first pair. Old sessions can still be read because the first pair will fail, and the second will be tested. This makes it easy to "rotate" secret keys and still be able to validate existing sessions. Note: for all pairs the encryption key is optional; set it to nil or omit it and and encryption won't be used. Multiple sessions can be used in the same request, even with different session backends. When this happens, calling Save() on each session individually would be cumbersome, so we have a way to save all sessions at once: it's sessions.Save(). Here's an example: var store = sessions.NewCookieStore([]byte("something-very-secret")) func MyHandler(w http.ResponseWriter, r *http.Request) { // Get a session and set a value. session1, _ := store.Get(r, "session-one") session1.Values["foo"] = "bar" // Get another session and set another value. session2, _ := store.Get(r, "session-two") session2.Values[42] = 43 // Save all sessions. err = sessions.Save(r, w) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } This is possible because when we call Get() from a session store, it adds the session to a common registry. Save() uses it to save all registered sessions. */ package sessions sessions-1.2.1/go.mod000066400000000000000000000001231371724215000144650ustar00rootroot00000000000000module github.com/gorilla/sessions require github.com/gorilla/securecookie v1.1.1 sessions-1.2.1/go.sum000066400000000000000000000002651371724215000145210ustar00rootroot00000000000000github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= sessions-1.2.1/lex.go000066400000000000000000000027411371724215000145060ustar00rootroot00000000000000// This file contains code adapted from the Go standard library // https://github.com/golang/go/blob/39ad0fd0789872f9469167be7fe9578625ff246e/src/net/http/lex.go package sessions import "strings" var isTokenTable = [127]bool{ '!': true, '#': true, '$': true, '%': true, '&': true, '\'': true, '*': true, '+': true, '-': true, '.': true, '0': true, '1': true, '2': true, '3': true, '4': true, '5': true, '6': true, '7': true, '8': true, '9': true, 'A': true, 'B': true, 'C': true, 'D': true, 'E': true, 'F': true, 'G': true, 'H': true, 'I': true, 'J': true, 'K': true, 'L': true, 'M': true, 'N': true, 'O': true, 'P': true, 'Q': true, 'R': true, 'S': true, 'T': true, 'U': true, 'W': true, 'V': true, 'X': true, 'Y': true, 'Z': true, '^': true, '_': true, '`': true, 'a': true, 'b': true, 'c': true, 'd': true, 'e': true, 'f': true, 'g': true, 'h': true, 'i': true, 'j': true, 'k': true, 'l': true, 'm': true, 'n': true, 'o': true, 'p': true, 'q': true, 'r': true, 's': true, 't': true, 'u': true, 'v': true, 'w': true, 'x': true, 'y': true, 'z': true, '|': true, '~': true, } func isToken(r rune) bool { i := int(r) return i < len(isTokenTable) && isTokenTable[i] } func isNotToken(r rune) bool { return !isToken(r) } func isCookieNameValid(raw string) bool { if raw == "" { return false } return strings.IndexFunc(raw, isNotToken) < 0 } sessions-1.2.1/options.go000066400000000000000000000007361371724215000154130ustar00rootroot00000000000000// +build !go1.11 package sessions // Options stores configuration for a session or session store. // // Fields are a subset of http.Cookie fields. type Options struct { Path string Domain string // MaxAge=0 means no Max-Age attribute specified and the cookie will be // deleted after the browser session ends. // MaxAge<0 means delete cookie immediately. // MaxAge>0 means Max-Age attribute present and given in seconds. MaxAge int Secure bool HttpOnly bool } sessions-1.2.1/options_go111.go000066400000000000000000000010611371724215000163130ustar00rootroot00000000000000// +build go1.11 package sessions import "net/http" // Options stores configuration for a session or session store. // // Fields are a subset of http.Cookie fields. type Options struct { Path string Domain string // MaxAge=0 means no Max-Age attribute specified and the cookie will be // deleted after the browser session ends. // MaxAge<0 means delete cookie immediately. // MaxAge>0 means Max-Age attribute present and given in seconds. MaxAge int Secure bool HttpOnly bool // Defaults to http.SameSiteDefaultMode SameSite http.SameSite } sessions-1.2.1/sessions.go000066400000000000000000000133371371724215000155670ustar00rootroot00000000000000// Copyright 2012 The Gorilla Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package sessions import ( "context" "encoding/gob" "fmt" "net/http" "time" ) // Default flashes key. const flashesKey = "_flash" // Session -------------------------------------------------------------------- // NewSession is called by session stores to create a new session instance. func NewSession(store Store, name string) *Session { return &Session{ Values: make(map[interface{}]interface{}), store: store, name: name, Options: new(Options), } } // Session stores the values and optional configuration for a session. type Session struct { // The ID of the session, generated by stores. It should not be used for // user data. ID string // Values contains the user-data for the session. Values map[interface{}]interface{} Options *Options IsNew bool store Store name string } // Flashes returns a slice of flash messages from the session. // // A single variadic argument is accepted, and it is optional: it defines // the flash key. If not defined "_flash" is used by default. func (s *Session) Flashes(vars ...string) []interface{} { var flashes []interface{} key := flashesKey if len(vars) > 0 { key = vars[0] } if v, ok := s.Values[key]; ok { // Drop the flashes and return it. delete(s.Values, key) flashes = v.([]interface{}) } return flashes } // AddFlash adds a flash message to the session. // // A single variadic argument is accepted, and it is optional: it defines // the flash key. If not defined "_flash" is used by default. func (s *Session) AddFlash(value interface{}, vars ...string) { key := flashesKey if len(vars) > 0 { key = vars[0] } var flashes []interface{} if v, ok := s.Values[key]; ok { flashes = v.([]interface{}) } s.Values[key] = append(flashes, value) } // Save is a convenience method to save this session. It is the same as calling // store.Save(request, response, session). You should call Save before writing to // the response or returning from the handler. func (s *Session) Save(r *http.Request, w http.ResponseWriter) error { return s.store.Save(r, w, s) } // Name returns the name used to register the session. func (s *Session) Name() string { return s.name } // Store returns the session store used to register the session. func (s *Session) Store() Store { return s.store } // Registry ------------------------------------------------------------------- // sessionInfo stores a session tracked by the registry. type sessionInfo struct { s *Session e error } // contextKey is the type used to store the registry in the context. type contextKey int // registryKey is the key used to store the registry in the context. const registryKey contextKey = 0 // GetRegistry returns a registry instance for the current request. func GetRegistry(r *http.Request) *Registry { var ctx = r.Context() registry := ctx.Value(registryKey) if registry != nil { return registry.(*Registry) } newRegistry := &Registry{ request: r, sessions: make(map[string]sessionInfo), } *r = *r.WithContext(context.WithValue(ctx, registryKey, newRegistry)) return newRegistry } // Registry stores sessions used during a request. type Registry struct { request *http.Request sessions map[string]sessionInfo } // Get registers and returns a session for the given name and session store. // // It returns a new session if there are no sessions registered for the name. func (s *Registry) Get(store Store, name string) (session *Session, err error) { if !isCookieNameValid(name) { return nil, fmt.Errorf("sessions: invalid character in cookie name: %s", name) } if info, ok := s.sessions[name]; ok { session, err = info.s, info.e } else { session, err = store.New(s.request, name) session.name = name s.sessions[name] = sessionInfo{s: session, e: err} } session.store = store return } // Save saves all sessions registered for the current request. func (s *Registry) Save(w http.ResponseWriter) error { var errMulti MultiError for name, info := range s.sessions { session := info.s if session.store == nil { errMulti = append(errMulti, fmt.Errorf( "sessions: missing store for session %q", name)) } else if err := session.store.Save(s.request, w, session); err != nil { errMulti = append(errMulti, fmt.Errorf( "sessions: error saving session %q -- %v", name, err)) } } if errMulti != nil { return errMulti } return nil } // Helpers -------------------------------------------------------------------- func init() { gob.Register([]interface{}{}) } // Save saves all sessions used during the current request. func Save(r *http.Request, w http.ResponseWriter) error { return GetRegistry(r).Save(w) } // NewCookie returns an http.Cookie with the options set. It also sets // the Expires field calculated based on the MaxAge value, for Internet // Explorer compatibility. func NewCookie(name, value string, options *Options) *http.Cookie { cookie := newCookieFromOptions(name, value, options) if options.MaxAge > 0 { d := time.Duration(options.MaxAge) * time.Second cookie.Expires = time.Now().Add(d) } else if options.MaxAge < 0 { // Set it to the past to expire now. cookie.Expires = time.Unix(1, 0) } return cookie } // Error ---------------------------------------------------------------------- // MultiError stores multiple errors. // // Borrowed from the App Engine SDK. type MultiError []error func (m MultiError) Error() string { s, n := "", 0 for _, e := range m { if e != nil { if n == 0 { s = e.Error() } n++ } } switch n { case 0: return "(0 errors)" case 1: return s case 2: return s + " (and 1 other error)" } return fmt.Sprintf("%s (and %d other errors)", s, n-1) } sessions-1.2.1/sessions_test.go000066400000000000000000000132051371724215000166200ustar00rootroot00000000000000// Copyright 2012 The Gorilla Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package sessions import ( "bytes" "encoding/gob" "net/http" "net/http/httptest" "testing" ) // NewRecorder returns an initialized ResponseRecorder. func NewRecorder() *httptest.ResponseRecorder { return &httptest.ResponseRecorder{ HeaderMap: make(http.Header), Body: new(bytes.Buffer), } } // ---------------------------------------------------------------------------- type FlashMessage struct { Type int Message string } func TestFlashes(t *testing.T) { var req *http.Request var rsp *httptest.ResponseRecorder var hdr http.Header var err error var ok bool var cookies []string var session *Session var flashes []interface{} store := NewCookieStore([]byte("secret-key")) // Round 1 ---------------------------------------------------------------- req, _ = http.NewRequest("GET", "http://localhost:8080/", nil) rsp = NewRecorder() // Get a session. if session, err = store.Get(req, "session-key"); err != nil { t.Fatalf("Error getting session: %v", err) } // Get a flash. flashes = session.Flashes() if len(flashes) != 0 { t.Errorf("Expected empty flashes; Got %v", flashes) } // Add some flashes. session.AddFlash("foo") session.AddFlash("bar") // Custom key. session.AddFlash("baz", "custom_key") // Save. if err = Save(req, rsp); err != nil { t.Fatalf("Error saving session: %v", err) } hdr = rsp.Header() cookies, ok = hdr["Set-Cookie"] if !ok || len(cookies) != 1 { t.Fatal("No cookies. Header:", hdr) } if _, err = store.Get(req, "session:key"); err.Error() != "sessions: invalid character in cookie name: session:key" { t.Fatalf("Expected error due to invalid cookie name") } // Round 2 ---------------------------------------------------------------- req, _ = http.NewRequest("GET", "http://localhost:8080/", nil) req.Header.Add("Cookie", cookies[0]) rsp = NewRecorder() // Get a session. if session, err = store.Get(req, "session-key"); err != nil { t.Fatalf("Error getting session: %v", err) } // Check all saved values. flashes = session.Flashes() if len(flashes) != 2 { t.Fatalf("Expected flashes; Got %v", flashes) } if flashes[0] != "foo" || flashes[1] != "bar" { t.Errorf("Expected foo,bar; Got %v", flashes) } flashes = session.Flashes() if len(flashes) != 0 { t.Errorf("Expected dumped flashes; Got %v", flashes) } // Custom key. flashes = session.Flashes("custom_key") if len(flashes) != 1 { t.Errorf("Expected flashes; Got %v", flashes) } else if flashes[0] != "baz" { t.Errorf("Expected baz; Got %v", flashes) } flashes = session.Flashes("custom_key") if len(flashes) != 0 { t.Errorf("Expected dumped flashes; Got %v", flashes) } // Round 3 ---------------------------------------------------------------- // Custom type req, _ = http.NewRequest("GET", "http://localhost:8080/", nil) rsp = NewRecorder() // Get a session. if session, err = store.Get(req, "session-key"); err != nil { t.Fatalf("Error getting session: %v", err) } // Get a flash. flashes = session.Flashes() if len(flashes) != 0 { t.Errorf("Expected empty flashes; Got %v", flashes) } // Add some flashes. session.AddFlash(&FlashMessage{42, "foo"}) // Save. if err = Save(req, rsp); err != nil { t.Fatalf("Error saving session: %v", err) } hdr = rsp.Header() cookies, ok = hdr["Set-Cookie"] if !ok || len(cookies) != 1 { t.Fatal("No cookies. Header:", hdr) } // Round 4 ---------------------------------------------------------------- // Custom type req, _ = http.NewRequest("GET", "http://localhost:8080/", nil) req.Header.Add("Cookie", cookies[0]) rsp = NewRecorder() // Get a session. if session, err = store.Get(req, "session-key"); err != nil { t.Fatalf("Error getting session: %v", err) } // Check all saved values. flashes = session.Flashes() if len(flashes) != 1 { t.Fatalf("Expected flashes; Got %v", flashes) } custom := flashes[0].(FlashMessage) if custom.Type != 42 || custom.Message != "foo" { t.Errorf("Expected %#v, got %#v", FlashMessage{42, "foo"}, custom) } // Round 5 ---------------------------------------------------------------- // Check if a request shallow copy resets the request context data store. req, _ = http.NewRequest("GET", "http://localhost:8080/", nil) // Get a session. if session, err = store.Get(req, "session-key"); err != nil { t.Fatalf("Error getting session: %v", err) } // Put a test value into the session data store. session.Values["test"] = "test-value" // Create a shallow copy of the request. req = req.WithContext(req.Context()) // Get the session again. if session, err = store.Get(req, "session-key"); err != nil { t.Fatalf("Error getting session: %v", err) } // Check if the previous inserted value still exists. if session.Values["test"] == nil { t.Fatalf("Session test value is lost in the request context!") } // Check if the previous inserted value has the same value. if session.Values["test"] != "test-value" { t.Fatalf("Session test value is changed in the request context!") } } func TestCookieStoreMapPanic(t *testing.T) { defer func() { err := recover() if err != nil { t.Fatal(err) } }() store := NewCookieStore([]byte("aaa0defe5d2839cbc46fc4f080cd7adc")) req, err := http.NewRequest("GET", "http://www.example.com", nil) if err != nil { t.Fatal("failed to create request", err) } w := httptest.NewRecorder() session := NewSession(store, "hello") session.Values["data"] = "hello-world" err = session.Save(req, w) if err != nil { t.Fatal("failed to save session", err) } } func init() { gob.Register(FlashMessage{}) } sessions-1.2.1/store.go000066400000000000000000000205711371724215000150530ustar00rootroot00000000000000// Copyright 2012 The Gorilla Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package sessions import ( "encoding/base32" "io/ioutil" "net/http" "os" "path/filepath" "strings" "sync" "github.com/gorilla/securecookie" ) // Store is an interface for custom session stores. // // See CookieStore and FilesystemStore for examples. type Store interface { // Get should return a cached session. Get(r *http.Request, name string) (*Session, error) // New should create and return a new session. // // Note that New should never return a nil session, even in the case of // an error if using the Registry infrastructure to cache the session. New(r *http.Request, name string) (*Session, error) // Save should persist session to the underlying store implementation. Save(r *http.Request, w http.ResponseWriter, s *Session) error } // CookieStore ---------------------------------------------------------------- // NewCookieStore returns a new CookieStore. // // Keys are defined in pairs to allow key rotation, but the common case is // to set a single authentication key and optionally an encryption key. // // The first key in a pair is used for authentication and the second for // encryption. The encryption key can be set to nil or omitted in the last // pair, but the authentication key is required in all pairs. // // It is recommended to use an authentication key with 32 or 64 bytes. // The encryption key, if set, must be either 16, 24, or 32 bytes to select // AES-128, AES-192, or AES-256 modes. func NewCookieStore(keyPairs ...[]byte) *CookieStore { cs := &CookieStore{ Codecs: securecookie.CodecsFromPairs(keyPairs...), Options: &Options{ Path: "/", MaxAge: 86400 * 30, }, } cs.MaxAge(cs.Options.MaxAge) return cs } // CookieStore stores sessions using secure cookies. type CookieStore struct { Codecs []securecookie.Codec Options *Options // default configuration } // Get returns a session for the given name after adding it to the registry. // // It returns a new session if the sessions doesn't exist. Access IsNew on // the session to check if it is an existing session or a new one. // // It returns a new session and an error if the session exists but could // not be decoded. func (s *CookieStore) Get(r *http.Request, name string) (*Session, error) { return GetRegistry(r).Get(s, name) } // New returns a session for the given name without adding it to the registry. // // The difference between New() and Get() is that calling New() twice will // decode the session data twice, while Get() registers and reuses the same // decoded session after the first call. func (s *CookieStore) New(r *http.Request, name string) (*Session, error) { session := NewSession(s, name) opts := *s.Options session.Options = &opts session.IsNew = true var err error if c, errCookie := r.Cookie(name); errCookie == nil { err = securecookie.DecodeMulti(name, c.Value, &session.Values, s.Codecs...) if err == nil { session.IsNew = false } } return session, err } // Save adds a single session to the response. func (s *CookieStore) Save(r *http.Request, w http.ResponseWriter, session *Session) error { encoded, err := securecookie.EncodeMulti(session.Name(), session.Values, s.Codecs...) if err != nil { return err } http.SetCookie(w, NewCookie(session.Name(), encoded, session.Options)) return nil } // MaxAge sets the maximum age for the store and the underlying cookie // implementation. Individual sessions can be deleted by setting Options.MaxAge // = -1 for that session. func (s *CookieStore) MaxAge(age int) { s.Options.MaxAge = age // Set the maxAge for each securecookie instance. for _, codec := range s.Codecs { if sc, ok := codec.(*securecookie.SecureCookie); ok { sc.MaxAge(age) } } } // FilesystemStore ------------------------------------------------------------ var fileMutex sync.RWMutex // NewFilesystemStore returns a new FilesystemStore. // // The path argument is the directory where sessions will be saved. If empty // it will use os.TempDir(). // // See NewCookieStore() for a description of the other parameters. func NewFilesystemStore(path string, keyPairs ...[]byte) *FilesystemStore { if path == "" { path = os.TempDir() } fs := &FilesystemStore{ Codecs: securecookie.CodecsFromPairs(keyPairs...), Options: &Options{ Path: "/", MaxAge: 86400 * 30, }, path: path, } fs.MaxAge(fs.Options.MaxAge) return fs } // FilesystemStore stores sessions in the filesystem. // // It also serves as a reference for custom stores. // // This store is still experimental and not well tested. Feedback is welcome. type FilesystemStore struct { Codecs []securecookie.Codec Options *Options // default configuration path string } // MaxLength restricts the maximum length of new sessions to l. // If l is 0 there is no limit to the size of a session, use with caution. // The default for a new FilesystemStore is 4096. func (s *FilesystemStore) MaxLength(l int) { for _, c := range s.Codecs { if codec, ok := c.(*securecookie.SecureCookie); ok { codec.MaxLength(l) } } } // Get returns a session for the given name after adding it to the registry. // // See CookieStore.Get(). func (s *FilesystemStore) Get(r *http.Request, name string) (*Session, error) { return GetRegistry(r).Get(s, name) } // New returns a session for the given name without adding it to the registry. // // See CookieStore.New(). func (s *FilesystemStore) New(r *http.Request, name string) (*Session, error) { session := NewSession(s, name) opts := *s.Options session.Options = &opts session.IsNew = true var err error if c, errCookie := r.Cookie(name); errCookie == nil { err = securecookie.DecodeMulti(name, c.Value, &session.ID, s.Codecs...) if err == nil { err = s.load(session) if err == nil { session.IsNew = false } } } return session, err } // Save adds a single session to the response. // // If the Options.MaxAge of the session is <= 0 then the session file will be // deleted from the store path. With this process it enforces the properly // session cookie handling so no need to trust in the cookie management in the // web browser. func (s *FilesystemStore) Save(r *http.Request, w http.ResponseWriter, session *Session) error { // Delete if max-age is <= 0 if session.Options.MaxAge <= 0 { if err := s.erase(session); err != nil { return err } http.SetCookie(w, NewCookie(session.Name(), "", session.Options)) return nil } if session.ID == "" { // Because the ID is used in the filename, encode it to // use alphanumeric characters only. session.ID = strings.TrimRight( base32.StdEncoding.EncodeToString( securecookie.GenerateRandomKey(32)), "=") } if err := s.save(session); err != nil { return err } encoded, err := securecookie.EncodeMulti(session.Name(), session.ID, s.Codecs...) if err != nil { return err } http.SetCookie(w, NewCookie(session.Name(), encoded, session.Options)) return nil } // MaxAge sets the maximum age for the store and the underlying cookie // implementation. Individual sessions can be deleted by setting Options.MaxAge // = -1 for that session. func (s *FilesystemStore) MaxAge(age int) { s.Options.MaxAge = age // Set the maxAge for each securecookie instance. for _, codec := range s.Codecs { if sc, ok := codec.(*securecookie.SecureCookie); ok { sc.MaxAge(age) } } } // save writes encoded session.Values to a file. func (s *FilesystemStore) save(session *Session) error { encoded, err := securecookie.EncodeMulti(session.Name(), session.Values, s.Codecs...) if err != nil { return err } filename := filepath.Join(s.path, "session_"+session.ID) fileMutex.Lock() defer fileMutex.Unlock() return ioutil.WriteFile(filename, []byte(encoded), 0600) } // load reads a file and decodes its content into session.Values. func (s *FilesystemStore) load(session *Session) error { filename := filepath.Join(s.path, "session_"+session.ID) fileMutex.RLock() defer fileMutex.RUnlock() fdata, err := ioutil.ReadFile(filename) if err != nil { return err } if err = securecookie.DecodeMulti(session.Name(), string(fdata), &session.Values, s.Codecs...); err != nil { return err } return nil } // delete session file func (s *FilesystemStore) erase(session *Session) error { filename := filepath.Join(s.path, "session_"+session.ID) fileMutex.RLock() defer fileMutex.RUnlock() err := os.Remove(filename) return err } sessions-1.2.1/store_test.go000066400000000000000000000061761371724215000161170ustar00rootroot00000000000000package sessions import ( "encoding/base64" "net/http" "net/http/httptest" "testing" ) // Test for GH-8 for CookieStore func TestGH8CookieStore(t *testing.T) { originalPath := "/" store := NewCookieStore() store.Options.Path = originalPath req, err := http.NewRequest("GET", "http://www.example.com", nil) if err != nil { t.Fatal("failed to create request", err) } session, err := store.New(req, "hello") if err != nil { t.Fatal("failed to create session", err) } store.Options.Path = "/foo" if session.Options.Path != originalPath { t.Fatalf("bad session path: got %q, want %q", session.Options.Path, originalPath) } } // Test for GH-8 for FilesystemStore func TestGH8FilesystemStore(t *testing.T) { originalPath := "/" store := NewFilesystemStore("") store.Options.Path = originalPath req, err := http.NewRequest("GET", "http://www.example.com", nil) if err != nil { t.Fatal("failed to create request", err) } session, err := store.New(req, "hello") if err != nil { t.Fatal("failed to create session", err) } store.Options.Path = "/foo" if session.Options.Path != originalPath { t.Fatalf("bad session path: got %q, want %q", session.Options.Path, originalPath) } } // Test for GH-2. func TestGH2MaxLength(t *testing.T) { store := NewFilesystemStore("", []byte("some key")) req, err := http.NewRequest("GET", "http://www.example.com", nil) if err != nil { t.Fatal("failed to create request", err) } w := httptest.NewRecorder() session, err := store.New(req, "my session") if err != nil { t.Fatal("failed to create session", err) } session.Values["big"] = make([]byte, base64.StdEncoding.DecodedLen(4096*2)) err = session.Save(req, w) if err == nil { t.Fatal("expected an error, got nil") } store.MaxLength(4096 * 3) // A bit more than the value size to account for encoding overhead. err = session.Save(req, w) if err != nil { t.Fatal("failed to Save:", err) } } // Test delete filesystem store with max-age: -1 func TestGH8FilesystemStoreDelete(t *testing.T) { store := NewFilesystemStore("", []byte("some key")) req, err := http.NewRequest("GET", "http://www.example.com", nil) if err != nil { t.Fatal("failed to create request", err) } w := httptest.NewRecorder() session, err := store.New(req, "hello") if err != nil { t.Fatal("failed to create session", err) } err = session.Save(req, w) if err != nil { t.Fatal("failed to save session", err) } session.Options.MaxAge = -1 err = session.Save(req, w) if err != nil { t.Fatal("failed to delete session", err) } } // Test delete filesystem store with max-age: 0 func TestGH8FilesystemStoreDelete2(t *testing.T) { store := NewFilesystemStore("", []byte("some key")) req, err := http.NewRequest("GET", "http://www.example.com", nil) if err != nil { t.Fatal("failed to create request", err) } w := httptest.NewRecorder() session, err := store.New(req, "hello") if err != nil { t.Fatal("failed to create session", err) } err = session.Save(req, w) if err != nil { t.Fatal("failed to save session", err) } session.Options.MaxAge = 0 err = session.Save(req, w) if err != nil { t.Fatal("failed to delete session", err) } }