diff --git a/context.go b/context.go index 2268c6b9d..8f0d0d3ba 100644 --- a/context.go +++ b/context.go @@ -18,6 +18,7 @@ type Context interface { Response() http.ResponseWriter Request() *http.Request Session() *Session + Cookies() *Cookies Params() ParamValues Param(string) string Set(string, interface{}) diff --git a/cookies.go b/cookies.go new file mode 100644 index 000000000..131317a64 --- /dev/null +++ b/cookies.go @@ -0,0 +1,59 @@ +package buffalo + +import ( + "net/http" + "time" +) + +// Cookies allows you to easily get cookies from the request, and set cookies on the response. +type Cookies struct { + req *http.Request + res http.ResponseWriter +} + +// Get returns the value of the cookie with the given name. Returns http.ErrNoCookie if there's no cookie with that name in the request. +func (c *Cookies) Get(name string) (string, error) { + ck, err := c.req.Cookie(name) + if err != nil { + return "", err + } + + return ck.Value, nil +} + +// Set a cookie on the response, which will expire after the given duration. +func (c *Cookies) Set(name, value string, maxAge time.Duration) { + ck := http.Cookie{ + Name: name, + Value: value, + MaxAge: int(maxAge.Seconds()), + } + + http.SetCookie(c.res, &ck) +} + +// SetWithExpirationTime sets a cookie that will expire at a specific time. +// Note that the time is determined by the client's browser, so it might not expire at the expected time, +// for example if the client has changed the time on their computer. +func (c *Cookies) SetWithExpirationTime(name, value string, expires time.Time) { + ck := http.Cookie{ + Name: name, + Value: value, + Expires: expires, + } + + http.SetCookie(c.res, &ck) +} + +// Delete sets a header that tells the browser to remove the cookie with the given name. +func (c *Cookies) Delete(name string) { + ck := http.Cookie{ + Name: name, + Value: "v", + // Setting a time in the distant past, like the unix epoch, removes the cookie, + // since it has long expired. + Expires: time.Unix(0, 0), + } + + http.SetCookie(c.res, &ck) +} diff --git a/cookies_test.go b/cookies_test.go new file mode 100644 index 000000000..67a7306fe --- /dev/null +++ b/cookies_test.go @@ -0,0 +1,65 @@ +package buffalo + +import ( + "github.com/stretchr/testify/require" + "net/http" + "net/http/httptest" + "testing" + "time" +) + +func TestCookies_Get(t *testing.T) { + r := require.New(t) + req := httptest.NewRequest("POST", "/", nil) + req.Header.Set("Cookie", "name=Arthur Dent; answer=42") + + c := Cookies{req, nil} + + v, err := c.Get("name") + r.NoError(err) + r.Equal("Arthur Dent", v) + + v, err = c.Get("answer") + r.NoError(err) + r.Equal("42", v) + + _, err = c.Get("unknown") + r.EqualError(err, http.ErrNoCookie.Error()) +} + +func TestCookies_Set(t *testing.T) { + r := require.New(t) + res := httptest.NewRecorder() + + c := Cookies{&http.Request{}, res} + + c.Set("name", "Rob Pike", time.Hour*24) + + h := res.Header().Get("Set-Cookie") + r.Equal("name=Rob Pike; Max-Age=86400", h) +} + +func TestCookies_SetWithExpirationTime(t *testing.T) { + r := require.New(t) + res := httptest.NewRecorder() + + c := Cookies{&http.Request{}, res} + + e := time.Date(2017, 7, 29, 19, 28, 45, 0, time.UTC) + c.SetWithExpirationTime("name", "Rob Pike", e) + + h := res.Header().Get("Set-Cookie") + r.Equal("name=Rob Pike; Expires=Sat, 29 Jul 2017 19:28:45 GMT", h) +} + +func TestCookies_Delete(t *testing.T) { + r := require.New(t) + res := httptest.NewRecorder() + + c := Cookies{&http.Request{}, res} + + c.Delete("remove-me") + + h := res.Header().Get("Set-Cookie") + r.Equal("remove-me=v; Expires=Thu, 01 Jan 1970 00:00:00 GMT", h) +} diff --git a/default_context.go b/default_context.go index cd50c8187..df451b3bb 100644 --- a/default_context.go +++ b/default_context.go @@ -83,6 +83,11 @@ func (d *DefaultContext) Session() *Session { return d.session } +// Cookies for the associated request and response. +func (d *DefaultContext) Cookies() *Cookies { + return &Cookies{d.request, d.response} +} + // Flash messages for the associated Request. func (d *DefaultContext) Flash() *Flash { return d.flash