Documentation ¶
Overview ¶
Copyright (c) 2016, Gerasimos Maropoulos 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 the copyright holder 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 JULIEN SCHMIDT 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.
This source code file is based on the Gorilla's sessions package.
Copyright (c) 2016, Gerasimos Maropoulos 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 the copyright holder 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 JULIEN SCHMIDT 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.
This source code file is based on the Gorilla's sessions package.
Copyright (c) 2016, Gerasimos Maropoulos 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 the copyright holder 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 JULIEN SCHMIDT 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.
This source code file is based on the Gorilla's sessions package.
Copyright (c) 2016, Gerasimos Maropoulos 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 the copyright holder 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 JULIEN SCHMIDT 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.
This source code file is based on the Gorilla's sessions package.
Index ¶
- Variables
- func Clear(r *http.Request)
- func DecodeMulti(name string, value string, dst interface{}, codecs ...Codec) error
- func Delete(r *http.Request, key interface{})
- func EncodeMulti(name string, value interface{}, codecs ...Codec) (string, error)
- func GenerateRandomKey(length int) []byte
- func Get(r *http.Request, key interface{}) interface{}
- func GetAll(r *http.Request) map[interface{}]interface{}
- func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool)
- func GetOk(r *http.Request, key interface{}) (interface{}, bool)
- func NewCookie(name, value string, options *Options) *http.Cookie
- func Purge(maxAge int) int
- func Save(r *http.Request, w http.ResponseWriter) error
- func Set(r *http.Request, key, val interface{})
- type Codec
- type CookieStore
- type Error
- type FilesystemStore
- func (s *FilesystemStore) Get(r *http.Request, name string) (*Session, error)
- func (s *FilesystemStore) MaxAge(age int)
- func (s *FilesystemStore) MaxLength(l int)
- func (s *FilesystemStore) NewStore(r *http.Request, name string) (*Session, error)
- func (s *FilesystemStore) Save(r *http.Request, w http.ResponseWriter, session *Session) error
- type GobEncoder
- type JSONEncoder
- type MultiError
- type Options
- type Registry
- type SecureCookie
- func (s *SecureCookie) BlockFunc(f func([]byte) (cipher.Block, error)) *SecureCookie
- func (s *SecureCookie) Decode(name, value string, dst interface{}) error
- func (s *SecureCookie) Encode(name string, value interface{}) (string, error)
- func (s *SecureCookie) HashFunc(f func() hash.Hash) *SecureCookie
- func (s *SecureCookie) MaxAge(value int) *SecureCookie
- func (s *SecureCookie) MaxLength(value int) *SecureCookie
- func (s *SecureCookie) MinAge(value int) *SecureCookie
- func (s *SecureCookie) SetSerializer(sz Serializer) *SecureCookie
- type Serializer
- type Session
- func (s *Session) AddFlash(value interface{}, vars ...string)
- func (s *Session) Clear()
- func (s *Session) Delete(key interface{})
- func (s *Session) Flashes(vars ...string) []interface{}
- func (s *Session) Get(key interface{}) interface{}
- func (s *Session) GetInt(key interface{}) int
- func (s *Session) GetString(key interface{}) string
- func (s *Session) Name() string
- func (s *Session) Save(ctx *iris.Context) error
- func (s *Session) SaveClassic(req *http.Request, res http.ResponseWriter) error
- func (s *Session) Set(key interface{}, val interface{})
- func (s *Session) Store() Store
- type SessionWrapper
- type Store
Constants ¶
This section is empty.
Variables ¶
var ( // ErrMacInvalid indicates that cookie decoding failed because the HMAC // could not be extracted and verified. Direct use of this error // variable is deprecated; it is public only for legacy compatibility, // and may be privatized in the future, as it is rarely useful to // distinguish between this error and other Error implementations. ErrMacInvalid = cookieError{/* contains filtered or unexported fields */} )
Functions ¶
func Clear ¶
Clear removes all values stored for a given request.
This is usually called by a handler wrapper to clean up request variables at the end of a request lifetime. See ClearHandler().
func DecodeMulti ¶
DecodeMulti decodes a cookie value using a group of codecs.
The codecs are tried in order. Multiple codecs are accepted to allow key rotation.
On error, may return a MultiError.
func EncodeMulti ¶
EncodeMulti encodes a cookie value using a group of codecs.
The codecs are tried in order. Multiple codecs are accepted to allow key rotation.
On error, may return a MultiError.
func GenerateRandomKey ¶
GenerateRandomKey creates a random key with the given length in bytes. On failure, returns nil.
Callers should explicitly check for the possibility of a nil return, treat it as a failure of the system random number generator, and not continue.
func GetAll ¶
GetAll returns all stored values for the request as a map. Nil is returned for invalid requests.
func GetAllOk ¶
GetAllOk returns all stored values for the request as a map and a boolean value that indicates if the request was registered.
func NewCookie ¶
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 Purge ¶
Purge removes request data stored for longer than maxAge, in seconds. It returns the amount of requests removed.
If maxAge <= 0, all request data is removed.
This is only used for sanity check: in case context cleaning was not properly set some request data can be kept forever, consuming an increasing amount of memory. In case this is detected, Purge() must be called periodically until the problem is fixed.
Types ¶
type Codec ¶
type Codec interface { Encode(name string, value interface{}) (string, error) Decode(name, value string, dst interface{}) error }
Codec defines an interface to encode and decode cookie values.
func CodecsFromPairs ¶
CodecsFromPairs returns a slice of SecureCookie instances.
It is a convenience function to create a list of codecs for key rotation. Note that the generated Codecs will have the default options applied: callers should iterate over each Codec and type-assert the underlying *SecureCookie to change these.
Example:
codecs := securecookie.CodecsFromPairs( []byte("new-hash-key"), []byte("new-block-key"), []byte("old-hash-key"), []byte("old-block-key"), ) // Modify each instance. for _, s := range codecs { if cookie, ok := s.(*securecookie.SecureCookie); ok { cookie.MaxAge(86400 * 7) cookie.SetSerializer(securecookie.JSONEncoder{}) cookie.HashFunc(sha512.New512_256) } }
type CookieStore ¶
CookieStore stores sessions using secure cookies.
func NewCookieStore ¶
func NewCookieStore(keyPairs ...[]byte) *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.
Use the convenience function GenerateRandomKey() to create strong keys.
func (*CookieStore) Get ¶
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 (*CookieStore) MaxAge ¶
func (s *CookieStore) MaxAge(age int)
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 (*CookieStore) NewStore ¶
NewStore returns a session store for the given name without adding it to the registry.
The difference between NewStore() and Get() is that calling NewStore() twice will decode the session data twice, while Get() registers and reuses the same decoded session after the first call.
func (*CookieStore) Save ¶
func (s *CookieStore) Save(r *http.Request, w http.ResponseWriter, session *Session) error
Save adds a single session to the response.
type Error ¶
type Error interface { error // IsUsage returns true for errors indicating the client code probably // uses this library incorrectly. For example, the client may have // failed to provide a valid hash key, or may have failed to configure // the Serializer adequately for encoding value. IsUsage() bool // IsDecode returns true for errors indicating that a cookie could not // be decoded and validated. Since cookies are usually untrusted // user-provided input, errors of this type should be expected. // Usually, the proper action is simply to reject the request. IsDecode() bool // IsInternal returns true for unexpected errors occurring in the // securecookie implementation. IsInternal() bool // Cause, if it returns a non-nil value, indicates that this error was // propagated from some underlying library. If this method returns nil, // this error was raised directly by this library. // // Cause is provided principally for debugging/logging purposes; it is // rare that application logic should perform meaningfully different // logic based on Cause. See, for example, the caveats described on // (MultiError).Cause(). Cause() error }
Error is the interface of all errors returned by functions in this library.
type FilesystemStore ¶
type FilesystemStore struct { Codecs []Codec Options *Options // default configuration // contains filtered or unexported fields }
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.
func NewFilesystemStore ¶
func NewFilesystemStore(path string, keyPairs ...[]byte) *FilesystemStore
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 (*FilesystemStore) Get ¶
Get returns a session for the given name after adding it to the registry.
See CookieStore.Get().
func (*FilesystemStore) MaxAge ¶
func (s *FilesystemStore) MaxAge(age int)
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 (*FilesystemStore) MaxLength ¶
func (s *FilesystemStore) MaxLength(l int)
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 (*FilesystemStore) NewStore ¶
NewStore returns a session store for the given name without adding it to the registry.
See CookieStore.NewStore().
func (*FilesystemStore) Save ¶
func (s *FilesystemStore) Save(r *http.Request, w http.ResponseWriter, session *Session) error
Save adds a single session to the response.
type GobEncoder ¶
type GobEncoder struct{}
GobEncoder encodes cookie values using encoding/gob. This is the simplest encoder and can handle complex types via gob.Register.
func (GobEncoder) Deserialize ¶
func (e GobEncoder) Deserialize(src []byte, dst interface{}) error
Deserialize decodes a value using gob.
func (GobEncoder) Serialize ¶
func (e GobEncoder) Serialize(src interface{}) ([]byte, error)
Serialize encodes a value using gob.
type JSONEncoder ¶
type JSONEncoder 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.
func (JSONEncoder) Deserialize ¶
func (e JSONEncoder) Deserialize(src []byte, dst interface{}) error
Deserialize decodes a value using encoding/json.
func (JSONEncoder) Serialize ¶
func (e JSONEncoder) Serialize(src interface{}) ([]byte, error)
Serialize encodes a value using encoding/json.
type MultiError ¶
type MultiError []error
MultiError groups multiple errors.
func (MultiError) Cause ¶
func (m MultiError) Cause() error
Cause returns nil for MultiError; there is no unique underlying cause in the general case.
Note: we could conceivably return a non-nil Cause only when there is exactly one child error with a Cause. However, it would be brittle for client code to rely on the arity of causes inside a MultiError, so we have opted not to provide this functionality. Clients which really wish to access the Causes of the underlying errors are free to iterate through the errors themselves.
func (MultiError) Error ¶
func (m MultiError) Error() string
func (MultiError) IsDecode ¶
func (m MultiError) IsDecode() bool
IsDecode returns if any error is is decoded
func (MultiError) IsInternal ¶
func (m MultiError) IsInternal() bool
IsInternal returns if any error is internal
func (MultiError) IsUsage ¶
func (m MultiError) IsUsage() bool
IsUsage returns if any error is usage
type Options ¶
type Options struct { Path string Domain string // MaxAge=0 means no 'Max-Age' attribute specified. // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'. // MaxAge>0 means Max-Age attribute present and given in seconds. MaxAge int Secure bool HTTPOnly bool }
Options stores configuration for a session or session store.
Fields are a subset of http.Cookie fields.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry stores sessions used during a request.
func GetRegistry ¶
GetRegistry returns a registry instance for the current request.
type SecureCookie ¶
type SecureCookie struct {
// contains filtered or unexported fields
}
SecureCookie encodes and decodes authenticated and optionally encrypted cookie values.
func NewSecureCookie ¶
func NewSecureCookie(hashKey, blockKey []byte) *SecureCookie
NewSecureCookie returns a new SecureCookie.
hashKey is required, used to authenticate values using HMAC. Create it using GenerateRandomKey(). It is recommended to use a key with 32 or 64 bytes.
blockKey is optional, used to encrypt values. Create it using 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.
Note that keys created using GenerateRandomKey() are not automatically persisted. New keys will be created when the application is restarted, and previously issued cookies will not be able to be decoded.
func (*SecureCookie) BlockFunc ¶
func (s *SecureCookie) BlockFunc(f func([]byte) (cipher.Block, error)) *SecureCookie
BlockFunc sets the encryption function used to create a cipher.Block.
Default is crypto/aes.New.
func (*SecureCookie) Decode ¶
func (s *SecureCookie) Decode(name, value string, dst interface{}) error
Decode decodes a cookie value.
It decodes, verifies a message authentication code, optionally decrypts and finally deserializes the value.
The name argument is the cookie name. It must be the same name used when it was stored. The value argument is the encoded cookie value. The dst argument is where the cookie will be decoded. It must be a pointer.
func (*SecureCookie) Encode ¶
func (s *SecureCookie) Encode(name string, value interface{}) (string, error)
Encode encodes a cookie value.
It serializes, optionally encrypts, signs with a message authentication code, and finally encodes the value.
The name argument is the cookie name. It is stored with the encoded value. The value argument is the value to be encoded. It can be any value that can be encoded using the currently selected serializer; see SetSerializer().
It is the client's responsibility to ensure that value, when encoded using the current serialization/encryption settings on s and then base64-encoded, is shorter than the maximum permissible length.
func (*SecureCookie) HashFunc ¶
func (s *SecureCookie) HashFunc(f func() hash.Hash) *SecureCookie
HashFunc sets the hash function used to create HMAC.
Default is crypto/sha256.New.
func (*SecureCookie) MaxAge ¶
func (s *SecureCookie) MaxAge(value int) *SecureCookie
MaxAge restricts the maximum age, in seconds, for the cookie value.
Default is 86400 * 30. Set it to 0 for no restriction.
func (*SecureCookie) MaxLength ¶
func (s *SecureCookie) MaxLength(value int) *SecureCookie
MaxLength restricts the maximum length, in bytes, for the cookie value.
Default is 4096, which is the maximum value accepted by Internet Explorer.
func (*SecureCookie) MinAge ¶
func (s *SecureCookie) MinAge(value int) *SecureCookie
MinAge restricts the minimum age, in seconds, for the cookie value.
Default is 0 (no restriction).
func (*SecureCookie) SetSerializer ¶
func (s *SecureCookie) SetSerializer(sz Serializer) *SecureCookie
SetSerializer sets the encoding/serialization method for cookies.
Default is encoding/gob. To encode special structures using encoding/gob, they must be registered first using gob.Register().
type Serializer ¶
type Serializer interface { Serialize(src interface{}) ([]byte, error) Deserialize(src []byte, dst interface{}) error }
Serializer provides an interface for providing custom serializers for cookie values.
type Session ¶
type Session struct { ID string Values map[interface{}]interface{} Options *Options IsNew bool // contains filtered or unexported fields }
Session stores the values and optional configuration for a session.
func NewSession ¶
NewSession is called by session stores to create a new session instance.
func (*Session) AddFlash ¶
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 (*Session) Delete ¶
func (s *Session) Delete(key interface{})
Delete removes without other checking a pair by its key
func (*Session) Flashes ¶
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 (*Session) Get ¶
func (s *Session) Get(key interface{}) interface{}
Get returns a value from a key
func (*Session) GetString ¶
GetString same as Get but returns string if nothing found returns empty string ""
func (*Session) Save ¶
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 (*Session) SaveClassic ¶
SaveClassic 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.
type SessionWrapper ¶
type SessionWrapper struct {
// contains filtered or unexported fields
}
SessionWrapper is the Iris' session wrapper for the session it contains the name of the session and the Store
func New ¶
func New(name string, store Store) SessionWrapper
New creates the session by it's name and returns a new ready-to-use iris.Handler
func (SessionWrapper) Clear ¶
func (s SessionWrapper) Clear(ctx *iris.Context)
Clear remove all items from this handler's session
func (SessionWrapper) Get ¶
func (s SessionWrapper) Get(ctx *iris.Context) (*Session, error)
Get returns a session by it's context same as GetSession
func (SessionWrapper) GetSession ¶
func (s SessionWrapper) GetSession(ctx *iris.Context) (*Session, error)
GetSession returns a session by it's context same as Get
type Store ¶
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. NewStore(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 }
Store is an interface for custom session stores.
See CookieStore and FilesystemStore for examples.