packages feed

snap-cors (empty) → 1.0.0

raw patch · 5 files changed

+212/−0 lines, 5 filesdep +basedep +hashabledep +networksetup-changed

Dependencies added: base, hashable, network, snap, text, transformers, unordered-containers

Files

+ Changelog.md view
@@ -0,0 +1,3 @@+# 1.0.0++* Initial release. Support setting the `Access-Control-Allow-Origin` and `Access-Control-Allow-Credentials` headers.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2013, Oliver Charles++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 Oliver Charles nor the names of other+      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.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ snap-cors.cabal view
@@ -0,0 +1,38 @@+name:                snap-cors+version:             1.0.0+synopsis:            Add CORS headers to Snap applications+description:+  Add CORS (cross-origin resource sharing) headers to Snap applications. This+  enables web applications running on other domains to make requests against+  another application.+  .+  Currently this package provides support for setting the+  @Access-Control-Allow-Origin@ and @Access-Control-Allow-Credentials@ headers,+  it does not yet do pre-flighting. If you need this, please open an issue on+  Github and I'll fix it ASAP (otherwise I will add this feature as time permits).++homepage:            http://github.com/ocharles/snap-cors+license:             BSD3+license-file:        LICENSE+author:              Oliver Charles+maintainer:          ollie@ocharles.org.uk+copyright:           Oliver Charles (c) 2013+category:            Web+build-type:          Simple+cabal-version:       >=1.8+extra-source-files:+  Changelog.md++library+  hs-source-dirs: src+  exposed-modules:+    Snap.CORS+  build-depends:+    base >= 4.5 && < 5,+    hashable >= 1.1 && <1.2,+    network >= 2.4 && < 2.5,+    snap >= 0.13 && < 0.14,+    text >= 0.11 && < 0.12,+    transformers >= 0.3 && < 0.4,+    unordered-containers >= 0.2 && <0.3+  ghc-options: -Wall
+ src/Snap/CORS.hs view
@@ -0,0 +1,139 @@+{-# LANGUAGE OverloadedStrings #-}+-- | Add <http://www.w3.org/TR/cors/ CORS> (cross-origin resource sharing)+-- headers to a Snap application. CORS headers can be added either conditionally+-- or unconditionally to the entire site, or you can apply CORS headers to a+-- single route.+module Snap.CORS+  ( -- * Wrappers+    wrapCORS+  , wrapCORSWithOptions++  -- * Applying CORS to a specific response+  , applyCORS++    -- * Option Specification+  , CORSOptions(..)+  , defaultOptions++    -- ** Origin lists+  , OriginList(..)+  , OriginSet, mkOriginSet, origins+                            +    -- * Internals+  , HashableURI(..)+  ) where++import Control.Applicative+import Control.Monad (guard, mzero, void, when)+import Control.Monad.Trans.Class (lift)+import Control.Monad.Trans.Maybe (MaybeT(..))+import Data.Hashable (Hashable(..))+import Data.Text.Encoding (decodeUtf8, encodeUtf8)+import Network.URI (URI (..), URIAuth (..),  parseURI)++import qualified Data.HashSet as HashSet+import qualified Data.Text as Text+import qualified Snap++-- | A set of origins. RFC 6454 specifies that origins are a scheme, host and+-- port, so the 'OriginSet' wrapper around a 'HashSet.HashSet' ensures that each+-- 'URI' constists of nothing more than this.+newtype OriginSet = OriginSet { origins :: HashSet.HashSet HashableURI }++-- | Used to specify the contents of the @Access-Control-Allow-Origin@ header.+data OriginList+  = Everywhere+  -- ^ Allow any origin to access this resource. Corresponds to+  -- @Access-Control-Allow-Origin: *@ +  | Nowhere+  -- ^ Do not allow cross-origin requests    +  | Origins OriginSet+  -- ^ Allow cross-origin requests from these origins.++-- | Specify the options to use when building CORS headers for a response. Most+-- of these options are 'Snap.Handler' actions to allow you to conditionally+-- determine the setting of each header.+data CORSOptions m = CORSOptions+  { corsAllowOrigin :: m OriginList+  -- ^ Which origins are allowed to make cross-origin requests.++  , corsAllowCredentials :: m Bool+  -- ^ Whether or not to allow exposing the response when the omit credentials+  -- flag is unset.  +  }++-- | Liberal default options. Specifies that all origins may make cross-origin+-- requests, allow-credentials is true. Headers are determined unconditionally.+defaultOptions :: Monad m => CORSOptions m+defaultOptions = CORSOptions+  { corsAllowOrigin = return Everywhere+  , corsAllowCredentials = return True+  }++-- | Apply CORS for every request, unconditionally.+--+-- 'wrapCors' ≡ 'wrapCORSWithOptions' 'defaultOptions'+wrapCORS :: Snap.Initializer b v ()+wrapCORS = wrapCORSWithOptions defaultOptions++-- | Initialize CORS for all requests with specific options.+wrapCORSWithOptions :: CORSOptions (Snap.Handler b v) -> Snap.Initializer b v ()+wrapCORSWithOptions options = Snap.wrapSite (applyCORS options >>)++-- | Apply CORS headers to a specific request. This is useful if you only have+-- a single action that needs CORS headers, and you don't want to pay for+-- conditional checks on every request.+applyCORS :: Snap.MonadSnap m => CORSOptions m -> m ()+applyCORS options = void $ runMaybeT $ do+  origin <- MaybeT $ Snap.getsRequest (Snap.getHeader "Origin")+  originUri <- MaybeT $ pure $+    fmap simplifyURI $ parseURI $ Text.unpack $ decodeUtf8 origin++  originList <- lift $ corsAllowOrigin options++  case originList of+    Everywhere -> return ()+    Nowhere -> mzero+    (Origins (OriginSet xs)) ->+      guard (HashableURI originUri `HashSet.member` xs)++  lift $ do+    addHeader "Access-Control-Allow-Origin"+              (encodeUtf8 $ Text.pack $ show originUri)+    allowCredentials <- corsAllowCredentials options+    when (allowCredentials) $+      addHeader "Access-Control-Allow-Credentials" "true"++ where+  addHeader k v = Snap.modifyResponse (Snap.addHeader k v)++mkOriginSet :: [URI] -> OriginSet+mkOriginSet = OriginSet . HashSet.fromList . map (HashableURI . simplifyURI)++simplifyURI :: URI -> URI+simplifyURI uri = uri { uriAuthority = fmap simplifyURIAuth (uriAuthority uri)+                       , uriPath = ""+                       , uriQuery = ""+                       , uriFragment = ""+                       }+ where simplifyURIAuth auth = auth { uriUserInfo = "" }++-- | A @newtype@ over 'URI' with a 'Hashable' instance.+newtype HashableURI = HashableURI URI+  deriving (Eq, Show)++instance Hashable HashableURI where+  hashWithSalt s (HashableURI (URI scheme authority path query fragment)) =+    s `hashWithSalt`+    scheme `hashWithSalt`+    fmap hashAuthority authority `hashWithSalt`+    path `hashWithSalt`+    query `hashWithSalt`+    fragment++   where+    hashAuthority (URIAuth userInfo regName port) =+          s `hashWithSalt`+          userInfo `hashWithSalt`+          regName `hashWithSalt`+          port