diff options
-rw-r--r-- | securecookie.go | 57 | ||||
-rw-r--r-- | securecookie_test.go | 29 |
2 files changed, 79 insertions, 7 deletions
diff --git a/securecookie.go b/securecookie.go index 1b7acf8..8ed512a 100644 --- a/securecookie.go +++ b/securecookie.go @@ -14,6 +14,7 @@ import ( "crypto/subtle" "encoding/base64" "encoding/gob" + "encoding/json" "errors" "fmt" "hash" @@ -44,6 +45,7 @@ type Codec interface { // GenerateRandomKey(). The key length must correspond to the block size // of the encryption algorithm. For AES, used by default, valid lengths are // 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256. +// The default encoder used for cookie serialization is encoding/gob. func New(hashKey, blockKey []byte) *SecureCookie { s := &SecureCookie{ hashKey: hashKey, @@ -51,6 +53,7 @@ func New(hashKey, blockKey []byte) *SecureCookie { hashFunc: sha256.New, maxAge: 86400 * 30, maxLength: 4096, + enc: GobEncoder{}, } if hashKey == nil { s.err = errHashKeyNotSet @@ -72,11 +75,28 @@ type SecureCookie struct { maxAge int64 minAge int64 err error + enc encoder // For testing purposes, the function that returns the current timestamp. // If not set, it will use time.Now().UTC().Unix(). timeFunc func() int64 } +// Encoder provides an interface for providing custom encoders for cookie +// values. +type encoder interface { + serialize(src interface{}) ([]byte, error) + deserialize(src []byte, dst interface{}) error +} + +// GobEncoder encodes cookie values using encoding/gob. This is the simplest +// encoder and can handle complex types via gob.Register. +type GobEncoder struct{} + +// JSONEncoder encodes cookie values using encoding/json. Users who wish to +// encode complex types need to satisfy the json.Marshaller and +// json.Unmarshaller interfaces. +type JSONEncoder struct{} + // MaxLength restricts the maximum length, in bytes, for the cookie value. // // Default is 4096, which is the maximum value accepted by Internet Explorer. @@ -123,6 +143,15 @@ func (s *SecureCookie) BlockFunc(f func([]byte) (cipher.Block, error)) *SecureCo return s } +// Encoding sets the encoding/serialization method for cookies. +// +// Default is encoding/gob. +func (s *SecureCookie) Encoding(e encoder) *SecureCookie { + s.enc = e + + return s +} + // Encode encodes a cookie value. // // It serializes, optionally encrypts, signs with a message authentication code, and @@ -143,7 +172,7 @@ func (s *SecureCookie) Encode(name string, value interface{}) (string, error) { var err error var b []byte // 1. Serialize. - if b, err = serialize(value); err != nil { + if b, err = s.enc.serialize(value); err != nil { return "", err } // 2. Encrypt (optional). @@ -226,7 +255,7 @@ func (s *SecureCookie) Decode(name, value string, dst interface{}) error { } } // 6. Deserialize. - if err = deserialize(b, dst); err != nil { + if err = s.enc.deserialize(b, dst); err != nil { return err } // Done. @@ -301,7 +330,7 @@ func decrypt(block cipher.Block, value []byte) ([]byte, error) { // Serialization -------------------------------------------------------------- // serialize encodes a value using gob. -func serialize(src interface{}) ([]byte, error) { +func (e GobEncoder) serialize(src interface{}) ([]byte, error) { buf := new(bytes.Buffer) enc := gob.NewEncoder(buf) if err := enc.Encode(src); err != nil { @@ -311,7 +340,7 @@ func serialize(src interface{}) ([]byte, error) { } // deserialize decodes a value using gob. -func deserialize(src []byte, dst interface{}) error { +func (e GobEncoder) deserialize(src []byte, dst interface{}) error { dec := gob.NewDecoder(bytes.NewBuffer(src)) if err := dec.Decode(dst); err != nil { return err @@ -319,6 +348,26 @@ func deserialize(src []byte, dst interface{}) error { return nil } +// serialize encodes a value using encoding/json. +func (e JSONEncoder) serialize(src interface{}) ([]byte, error) { + buf := new(bytes.Buffer) + enc := json.NewEncoder(buf) + if err := enc.Encode(src); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +// deserialize decodes a value using encoding/json. +func (e JSONEncoder) deserialize(src []byte, dst interface{}) error { + dec := json.NewDecoder(bytes.NewBuffer(src)) + if err := dec.Decode(dst); err != nil { + return err + } + return nil +} + // Encoding ------------------------------------------------------------------- // encode encodes a value using base64. diff --git a/securecookie_test.go b/securecookie_test.go index 241ff10..9daa3b7 100644 --- a/securecookie_test.go +++ b/securecookie_test.go @@ -121,18 +121,41 @@ func TestEncription(t *testing.T) { } } -func TestSerialization(t *testing.T) { +func TestGobSerialization(t *testing.T) { var ( + enc GobEncoder serialized []byte deserialized map[string]string err error ) for _, value := range testCookies { - if serialized, err = serialize(value); err != nil { + if serialized, err = enc.serialize(value); err != nil { t.Error(err) } else { deserialized = make(map[string]string) - if err = deserialize(serialized, &deserialized); err != nil { + if err = enc.deserialize(serialized, &deserialized); err != nil { + t.Error(err) + } + if fmt.Sprintf("%v", deserialized) != fmt.Sprintf("%v", value) { + t.Errorf("Expected %v, got %v.", value, deserialized) + } + } + } +} + +func TestJSONSerialization(t *testing.T) { + var ( + enc JSONEncoder + serialized []byte + deserialized map[string]string + err error + ) + for _, value := range testCookies { + if serialized, err = enc.serialize(value); err != nil { + t.Error(err) + } else { + deserialized = make(map[string]string) + if err = enc.deserialize(serialized, &deserialized); err != nil { t.Error(err) } if fmt.Sprintf("%v", deserialized) != fmt.Sprintf("%v", value) { |