diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index bd23335..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,65 +0,0 @@ -version: 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" diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..2940ec9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +; https://editorconfig.org/ + +root = true + +[*] +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[{Makefile,go.mod,go.sum,*.go,.gitmodules}] +indent_style = tab +indent_size = 4 + +[*.md] +indent_size = 4 +trim_trailing_whitespace = false + +eclint_indent_style = unset diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml deleted file mode 100644 index 12440bf..0000000 --- a/.github/release-drafter.yml +++ /dev/null @@ -1,8 +0,0 @@ -# Config for https://github.com/apps/release-drafter -template: | - - - - ### CHANGELOG - - $CHANGES diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index de8a678..0000000 --- a/.github/stale.yml +++ /dev/null @@ -1,12 +0,0 @@ -daysUntilStale: 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 diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml new file mode 100644 index 0000000..768b05b --- /dev/null +++ b/.github/workflows/issues.yml @@ -0,0 +1,21 @@ +# Add all the issues created to the project. +name: Add issue or pull request to Project + +on: + issues: + types: + - opened + pull_request_target: + types: + - opened + - reopened + +jobs: + add-to-project: + runs-on: ubuntu-latest + steps: + - name: Add issue to project + uses: actions/add-to-project@v0.5.0 + with: + project-url: https://github.com/orgs/gorilla/projects/4 + github-token: ${{ secrets.ADD_TO_PROJECT_TOKEN }} diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 0000000..19a6c1c --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,37 @@ +name: Security +on: + push: + branches: + - main + pull_request: + branches: + - main +permissions: + contents: read +jobs: + scan: + strategy: + matrix: + go: ['1.23'] + fail-fast: true + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Setup Go ${{ matrix.go }} + uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go }} + cache: false + + - name: Run GoSec + uses: securego/gosec@master + with: + args: -exclude-dir examples ./... + + - name: Run GoVulnCheck + uses: golang/govulncheck-action@v1 + with: + go-version-input: ${{ matrix.go }} + go-package: ./... diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..c9987eb --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,35 @@ +name: Test +on: + push: + branches: + - main + pull_request: + branches: + - main +permissions: + contents: read +jobs: + unit: + strategy: + matrix: + go: ['1.23'] + os: [ubuntu-latest, macos-latest, windows-latest] + fail-fast: true + runs-on: ${{ matrix.os }} + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Setup Go ${{ matrix.go }} + uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go }} + cache: false + + - name: Run Tests + run: go test -race -cover -coverprofile=coverage -covermode=atomic -v ./... + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + files: ./coverage diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml new file mode 100644 index 0000000..3471511 --- /dev/null +++ b/.github/workflows/verify.yml @@ -0,0 +1,32 @@ +name: Verify +on: + push: + branches: + - main + pull_request: + branches: + - main +permissions: + contents: read +jobs: + lint: + strategy: + matrix: + go: ['1.23'] + fail-fast: true + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Setup Go ${{ matrix.go }} + uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go }} + cache: false + + - name: Run GolangCI-Lint + uses: golangci/golangci-lint-action@v3 + with: + version: v1.60.1 + args: --timeout=5m diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..84039fe --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +coverage.coverprofile diff --git a/AUTHORS b/AUTHORS deleted file mode 100644 index 1e3e7ac..0000000 --- a/AUTHORS +++ /dev/null @@ -1,43 +0,0 @@ -# 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 diff --git a/LICENSE b/LICENSE index 6903df6..7fa9009 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2012-2018 The Gorilla Authors. All rights reserved. +Copyright (c) 2024 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 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ac37ffd --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +GO_LINT=$(shell which golangci-lint 2> /dev/null || echo '') +GO_LINT_URI=github.com/golangci/golangci-lint/cmd/golangci-lint@latest + +GO_SEC=$(shell which gosec 2> /dev/null || echo '') +GO_SEC_URI=github.com/securego/gosec/v2/cmd/gosec@latest + +GO_VULNCHECK=$(shell which govulncheck 2> /dev/null || echo '') +GO_VULNCHECK_URI=golang.org/x/vuln/cmd/govulncheck@latest + +.PHONY: golangci-lint +golangci-lint: + $(if $(GO_LINT), ,go install $(GO_LINT_URI)) + @echo "##### Running golangci-lint" + golangci-lint run -v + +.PHONY: gosec +gosec: + $(if $(GO_SEC), ,go install $(GO_SEC_URI)) + @echo "##### Running gosec" + gosec ./... + +.PHONY: govulncheck +govulncheck: + $(if $(GO_VULNCHECK), ,go install $(GO_VULNCHECK_URI)) + @echo "##### Running govulncheck" + govulncheck ./... + +.PHONY: verify +verify: golangci-lint gosec govulncheck + +.PHONY: test +test: + @echo "##### Running tests" + go test -race -cover -coverprofile=coverage.coverprofile -covermode=atomic -v ./... diff --git a/README.md b/README.md index 98c993d..d2cbea6 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,14 @@ -# sessions +# Gorilla 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) +> [!IMPORTANT] +> The latest version of this repository requires go 1.23 because of the new partitioned attribute. The last version that is compatible with older versions of go is v1.3.0. + +![testing](https://github.com/gorilla/sessions/actions/workflows/test.yml/badge.svg) +[![codecov](https://codecov.io/github/gorilla/sessions/branch/main/graph/badge.svg)](https://codecov.io/github/gorilla/sessions) +[![godoc](https://godoc.org/github.com/gorilla/sessions?status.svg)](https://godoc.org/github.com/gorilla/sessions) +[![sourcegraph](https://sourcegraph.com/github.com/gorilla/sessions/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/sessions?badge) + +![Gorilla Logo](https://github.com/gorilla/.github/assets/53367916/d92caabf-98e0-473e-bfbf-ab554ba435e5) gorilla/sessions provides cookie and filesystem sessions and infrastructure for custom session backends. @@ -41,7 +48,11 @@ Let's start with an example that shows the sessions API in a nutshell: session.Values["foo"] = "bar" session.Values[42] = 43 // Save it before we write to the response/return from the handler. - session.Save(r, w) + err := session.Save(r, w) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } } ``` @@ -51,8 +62,7 @@ secret key used to authenticate the session. Inside the handler, we call 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). +More examples are available at [package documentation](https://pkg.go.dev/github.com/gorilla/sessions). ## Store Implementations @@ -67,9 +77,11 @@ Other implementations of the `sessions.Store` interface: - [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/danielepintore/gorilla-sessions-mysql](https://github.com/danielepintore/gorilla-sessions-mysql) - 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 @@ -77,6 +89,9 @@ Other implementations of the `sessions.Store` interface: - [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 +- [github.com/ryicoh/tikvstore](github.com/ryicoh/tikvstore) - TiKV ## License diff --git a/cookie.go b/cookie.go index 1928b04..fd6f48c 100644 --- a/cookie.go +++ b/cookie.go @@ -1,4 +1,6 @@ -// +build !go1.11 +// 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 @@ -7,13 +9,15 @@ 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, + Name: name, + Value: value, + Path: options.Path, + Domain: options.Domain, + MaxAge: options.MaxAge, + Secure: options.Secure, + HttpOnly: options.HttpOnly, + Partitioned: options.Partitioned, + SameSite: options.SameSite, } } diff --git a/cookie_go111.go b/cookie_go111.go deleted file mode 100644 index 173d1a3..0000000 --- a/cookie_go111.go +++ /dev/null @@ -1,20 +0,0 @@ -// +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, - } - -} diff --git a/cookie_go111_test.go b/cookie_go111_test.go deleted file mode 100644 index 1aac273..0000000 --- a/cookie_go111_test.go +++ /dev/null @@ -1,29 +0,0 @@ -// +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) - } - } -} diff --git a/cookie_test.go b/cookie_test.go index acb4efb..de530ca 100644 --- a/cookie_test.go +++ b/cookie_test.go @@ -1,3 +1,7 @@ +// 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 ( @@ -7,29 +11,32 @@ import ( // 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 + name string + value string + path string + domain string + maxAge int + secure bool + httpOnly bool + partitioned 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}, + {"", "bar", "/foo/bar", "foo.example.com", 3600, true, true, true}, + {"foo", "", "/foo/bar", "foo.example.com", 3600, true, true, true}, + {"foo", "bar", "", "foo.example.com", 3600, true, true, true}, + {"foo", "bar", "/foo/bar", "", 3600, true, true, true}, + {"foo", "bar", "/foo/bar", "foo.example.com", 0, true, true, true}, + {"foo", "bar", "/foo/bar", "foo.example.com", 3600, false, true, true}, + {"foo", "bar", "/foo/bar", "foo.example.com", 3600, true, false, true}, + {"foo", "bar", "/foo/bar", "foo.example.com", 3600, true, true, false}, } for i, v := range tests { options := &Options{ - Path: v.path, - Domain: v.domain, - MaxAge: v.maxAge, - Secure: v.secure, - HttpOnly: v.httpOnly, + Path: v.path, + Domain: v.domain, + MaxAge: v.maxAge, + Secure: v.secure, + HttpOnly: v.httpOnly, + Partitioned: v.partitioned, } cookie := newCookieFromOptions(v.name, v.value, options) if cookie.Name != v.name { @@ -53,5 +60,8 @@ func TestNewCookieFromOptions(t *testing.T) { if cookie.HttpOnly != v.httpOnly { t.Fatalf("%v: bad cookie httpOnly: got %v, want %v", i+1, cookie.HttpOnly, v.httpOnly) } + if cookie.Partitioned != v.partitioned { + t.Fatalf("%v: bad cookie partitioned: got %v, want %v", i+1, cookie.Partitioned, v.partitioned) + } } } diff --git a/doc.go b/doc.go index 64f858c..946bf5c 100644 --- a/doc.go +++ b/doc.go @@ -30,7 +30,8 @@ Let's start with an example that shows the sessions API in a nutshell: // 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(os.Getenv("SESSION_KEY")) + // 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. @@ -44,7 +45,11 @@ Let's start with an example that shows the sessions API in a nutshell: session.Values["foo"] = "bar" session.Values[42] = 43 // Save it before we write to the response/return from the handler. - session.Save(r, w) + 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 @@ -82,7 +87,11 @@ flashes, call session.Flashes(). Here is an example: // Set a new flash. session.AddFlash("Hello, flash messages world!") } - session.Save(r, w) + 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, @@ -185,7 +194,11 @@ at once: it's sessions.Save(). Here's an example: session2, _ := store.Get(r, "session-two") session2.Values[42] = 43 // Save all sessions. - sessions.Save(r, w) + 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 diff --git a/go.mod b/go.mod index ea28ffe..25a82cc 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,5 @@ module github.com/gorilla/sessions -require ( - github.com/gorilla/context v1.1.1 - github.com/gorilla/securecookie v1.1.1 -) +go 1.23 + +require github.com/gorilla/securecookie v1.1.2 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..285ffee --- /dev/null +++ b/go.sum @@ -0,0 +1,3 @@ +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= diff --git a/options.go b/options.go index 38ba72f..6ed7934 100644 --- a/options.go +++ b/options.go @@ -1,7 +1,11 @@ -// +build !go1.11 +// 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 "net/http" + // Options stores configuration for a session or session store. // // Fields are a subset of http.Cookie fields. @@ -12,7 +16,9 @@ type Options struct { // 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 + MaxAge int + Secure bool + HttpOnly bool + Partitioned bool + SameSite http.SameSite } diff --git a/options_go111.go b/options_go111.go deleted file mode 100644 index 388112a..0000000 --- a/options_go111.go +++ /dev/null @@ -1,22 +0,0 @@ -// +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 -} diff --git a/sessions_test.go b/sessions_test.go index a734f67..9476c22 100644 --- a/sessions_test.go +++ b/sessions_test.go @@ -9,6 +9,7 @@ import ( "encoding/gob" "net/http" "net/http/httptest" + "strings" "testing" ) @@ -20,10 +21,6 @@ func NewRecorder() *httptest.ResponseRecorder { } } -// DefaultRemoteAddr is the default remote address to return in RemoteAddr if -// an explicit DefaultRemoteAddr isn't set on ResponseRecorder. -const DefaultRemoteAddr = "1.2.3.4" - // ---------------------------------------------------------------------------- type FlashMessage struct { @@ -43,6 +40,10 @@ func TestFlashes(t *testing.T) { store := NewCookieStore([]byte("secret-key")) + if store.Options.SameSite != http.SameSiteNoneMode { + t.Fatalf("cookie store error: default same site is not set to None") + } + // Round 1 ---------------------------------------------------------------- req, _ = http.NewRequest("GET", "http://localhost:8080/", nil) @@ -71,6 +72,10 @@ func TestFlashes(t *testing.T) { t.Fatal("No cookies. Header:", hdr) } + if !strings.Contains(cookies[0], "SameSite=None") || !strings.Contains(cookies[0], "Secure") { + t.Fatal("Set-Cookie does not contains SameSite=None with Secure, cookie string:", cookies[0]) + } + 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") } @@ -79,7 +84,6 @@ func TestFlashes(t *testing.T) { 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) @@ -139,7 +143,6 @@ func TestFlashes(t *testing.T) { 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) diff --git a/store.go b/store.go index bb7f964..24db822 100644 --- a/store.go +++ b/store.go @@ -6,16 +6,19 @@ package sessions import ( "encoding/base32" - "io/ioutil" "net/http" "os" "path/filepath" - "strings" "sync" "github.com/gorilla/securecookie" ) +const ( + // File name prefix for session files. + sessionFilePrefix = "session_" +) + // Store is an interface for custom session stores. // // See CookieStore and FilesystemStore for examples. @@ -51,8 +54,10 @@ func NewCookieStore(keyPairs ...[]byte) *CookieStore { cs := &CookieStore{ Codecs: securecookie.CodecsFromPairs(keyPairs...), Options: &Options{ - Path: "/", - MaxAge: 86400 * 30, + Path: "/", + MaxAge: 86400 * 30, + SameSite: http.SameSiteNoneMode, + Secure: true, }, } @@ -201,6 +206,8 @@ func (s *FilesystemStore) New(r *http.Request, name string) (*Session, error) { return session, err } +var base32RawStdEncoding = base32.StdEncoding.WithPadding(base32.NoPadding) + // Save adds a single session to the response. // // If the Options.MaxAge of the session is <= 0 then the session file will be @@ -211,7 +218,7 @@ 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 { + if err := s.erase(session); err != nil && !os.IsNotExist(err) { return err } http.SetCookie(w, NewCookie(session.Name(), "", session.Options)) @@ -221,9 +228,8 @@ func (s *FilesystemStore) Save(r *http.Request, w http.ResponseWriter, 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)), "=") + session.ID = base32RawStdEncoding.EncodeToString( + securecookie.GenerateRandomKey(32)) } if err := s.save(session); err != nil { return err @@ -258,18 +264,18 @@ func (s *FilesystemStore) save(session *Session) error { if err != nil { return err } - filename := filepath.Join(s.path, "session_"+session.ID) + filename := filepath.Join(s.path, sessionFilePrefix+filepath.Base(session.ID)) fileMutex.Lock() defer fileMutex.Unlock() - return ioutil.WriteFile(filename, []byte(encoded), 0600) + return os.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) + filename := filepath.Join(s.path, sessionFilePrefix+filepath.Base(session.ID)) fileMutex.RLock() defer fileMutex.RUnlock() - fdata, err := ioutil.ReadFile(filename) + fdata, err := os.ReadFile(filepath.Clean(filename)) if err != nil { return err } @@ -282,7 +288,7 @@ func (s *FilesystemStore) load(session *Session) error { // delete session file func (s *FilesystemStore) erase(session *Session) error { - filename := filepath.Join(s.path, "session_"+session.ID) + filename := filepath.Join(s.path, sessionFilePrefix+filepath.Base(session.ID)) fileMutex.RLock() defer fileMutex.RUnlock() diff --git a/store_test.go b/store_test.go index 3561e5d..09580bd 100644 --- a/store_test.go +++ b/store_test.go @@ -1,3 +1,7 @@ +// 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 (