Use more structured URI representation; targets match only hosts.

This resolves #2.
master
vi 2014-08-11 03:32:49 +08:00
parent 2a409bfdc7
commit 49be7aa1a0
6 changed files with 39 additions and 28 deletions

View File

@ -38,6 +38,7 @@ library
functor-infix >= 0.0 && < 0.1, functor-infix >= 0.0 && < 0.1,
http-client >= 0.3 && < 0.4, http-client >= 0.3 && < 0.4,
lens >= 4.3 && < 4.4, lens >= 4.3 && < 4.4,
network >= 2.5 && < 2.6,
pipes >= 4.1 && < 4.2, pipes >= 4.1 && < 4.2,
string-conversions >= 0.3 && < 0.4, string-conversions >= 0.3 && < 0.4,
taggy-lens >= 0.1 && < 0.2, taggy-lens >= 0.1 && < 0.2,
@ -62,6 +63,7 @@ test-suite spec
hspec >= 1.10 && < 1.11, hspec >= 1.10 && < 1.11,
http-client >= 0.3 && < 0.4, http-client >= 0.3 && < 0.4,
lens >= 4.3 && < 4.4, lens >= 4.3 && < 4.4,
network >= 2.5 && < 2.6,
pipes >= 4.1 && < 4.2, pipes >= 4.1 && < 4.2,
string-conversions >= 0.3 && < 0.4, string-conversions >= 0.3 && < 0.4,
taggy-lens >= 0.1 && < 0.2, taggy-lens >= 0.1 && < 0.2,

View File

@ -7,19 +7,19 @@ import Prelude hiding (take, null)
import Control.Applicative ((<$)) import Control.Applicative ((<$))
import Control.Lens ((<&>)) import Control.Lens ((<&>))
import Data.Bool (bool) import Data.Bool (bool)
import Data.Text (Text)
import Network.HTTP.Client (Cookie) import Network.HTTP.Client (Cookie)
import Network.URI (URI)
import Pipes (runEffect, (>->)) import Pipes (runEffect, (>->))
import Pipes.Prelude (take, null) import Pipes.Prelude (take, null)
import Data.HTTPSEverywhere.Rules.Internal (getRulesetsMatching, havingRulesThatTrigger, havingCookieRulesThatTrigger, setSecureFlag) import Data.HTTPSEverywhere.Rules.Internal (getRulesetsMatching, havingRulesThatTrigger, havingCookieRulesThatTrigger, setSecureFlag)
rewriteURL :: Text -> IO (Maybe Text) rewriteURL :: URI -> IO (Maybe URI)
rewriteURL url = runEffect rewriteURL url = runEffect
$ (Nothing <$ getRulesetsMatching url) $ (Nothing <$ getRulesetsMatching url)
>-> (Nothing <$ havingRulesThatTrigger url) >-> (Nothing <$ havingRulesThatTrigger url)
>-> (Nothing <$ take 1) >-> (Nothing <$ take 1)
rewriteCookie :: Text -> Cookie -> IO Cookie rewriteCookie :: URI -> Cookie -> IO Cookie
rewriteCookie url cookie = null producer <&> setSecureFlag cookie `bool` cookie rewriteCookie url cookie = null producer <&> setSecureFlag cookie `bool` cookie
where producer = getRulesetsMatching url >-> (() <$ havingCookieRulesThatTrigger cookie) where producer = getRulesetsMatching url >-> (() <$ havingCookieRulesThatTrigger cookie)

View File

@ -14,8 +14,8 @@ import Data.Functor.Infix ((<$$>))
import Data.List (find) import Data.List (find)
import Data.Maybe (isJust) import Data.Maybe (isJust)
import qualified Data.HTTPSEverywhere.Rules.Raw as Raw (getRule, getRules) import qualified Data.HTTPSEverywhere.Rules.Raw as Raw (getRule, getRules)
import Data.Text (Text)
import Network.HTTP.Client (Cookie(..)) import Network.HTTP.Client (Cookie(..))
import Network.URI (URI)
import Pipes (Producer, Consumer, for, each, await, yield, lift, (>->)) import Pipes (Producer, Consumer, for, each, await, yield, lift, (>->))
import Pipes.Prelude (filter) import Pipes.Prelude (filter)
@ -26,12 +26,12 @@ getRulesets :: Producer RuleSet IO ()
getRulesets = lift Raw.getRules getRulesets = lift Raw.getRules
>>= flip (for . each) (flip (for . each) yield <=< lift . (parseRuleSets <$$> Raw.getRule)) >>= flip (for . each) (flip (for . each) yield <=< lift . (parseRuleSets <$$> Raw.getRule))
getRulesetsMatching :: Text -> Producer RuleSet IO () getRulesetsMatching :: URI -> Producer RuleSet IO ()
getRulesetsMatching url = getRulesets getRulesetsMatching url = getRulesets
>-> filter (flip hasTargetMatching url) >-> filter (flip hasTargetMatching url)
>-> filter (not . flip hasExclusionMatching url) >-> filter (not . flip hasExclusionMatching url)
havingRulesThatTrigger :: Text -> Consumer RuleSet IO (Maybe Text) havingRulesThatTrigger :: URI -> Consumer RuleSet IO (Maybe URI)
havingRulesThatTrigger url = flip hasTriggeringRuleOn url <$> await havingRulesThatTrigger url = flip hasTriggeringRuleOn url <$> await
>>= maybe (havingRulesThatTrigger url) (return . Just) >>= maybe (havingRulesThatTrigger url) (return . Just)
@ -39,15 +39,15 @@ havingCookieRulesThatTrigger :: Cookie -> Consumer RuleSet IO Bool
havingCookieRulesThatTrigger cookie = flip hasTriggeringCookieRuleOn cookie <$> await havingCookieRulesThatTrigger cookie = flip hasTriggeringCookieRuleOn cookie <$> await
>>= bool (havingCookieRulesThatTrigger cookie) (return True) >>= bool (havingCookieRulesThatTrigger cookie) (return True)
hasTargetMatching :: RuleSet -> Text -> Bool hasTargetMatching :: RuleSet -> URI -> Bool
hasTargetMatching ruleset url = getTargets ruleset <*> [url] & or hasTargetMatching ruleset url = getTargets ruleset <*> [url] & or
where getTargets = getTarget <$$> ruleSetTargets where getTargets = getTarget <$$> ruleSetTargets
hasExclusionMatching :: RuleSet -> Text -> Bool hasExclusionMatching :: RuleSet -> URI -> Bool
hasExclusionMatching ruleset url = getExclusions ruleset <*> [url] & or & not hasExclusionMatching ruleset url = getExclusions ruleset <*> [url] & or & not
where getExclusions = getExclusion <$$> ruleSetExclusions where getExclusions = getExclusion <$$> ruleSetExclusions
hasTriggeringRuleOn :: RuleSet -> Text -> Maybe Text -- Nothing ~ False hasTriggeringRuleOn :: RuleSet -> URI -> Maybe URI -- Nothing ~ False
hasTriggeringRuleOn ruleset url = getRules ruleset <*> [url] & find isJust & join hasTriggeringRuleOn ruleset url = getRules ruleset <*> [url] & find isJust & join
where getRules = getRule <$$> ruleSetRules where getRules = getRule <$$> ruleSetRules

View File

@ -1,5 +1,4 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE RecordWildCards #-}
@ -12,13 +11,15 @@ module Data.HTTPSEverywhere.Rules.Internal.Parser (
import Prelude hiding (head, last, tail, init) import Prelude hiding (head, last, tail, init)
import Control.Lens (toListOf, only, to, (^..), (^.), (&), (<&>), _Just) import Control.Lens (toListOf, only, to, (^..), (^.), (&), (<&>), _Just)
import Control.Monad (join)
import Data.Functor.Infix ((<$>),(<$$>)) import Data.Functor.Infix ((<$>),(<$$>))
import Data.Maybe (catMaybes, fromJust) import Data.Maybe (catMaybes, fromJust, fromMaybe)
import Data.String.Conversions (cs) import Data.String.Conversions (cs)
import qualified Data.Text as Strict (Text) import qualified Data.Text as Strict (Text)
import Data.Text (append, head, last, tail, init, replace) import Data.Text (append, head, last, tail, init, replace)
import qualified Data.Text.Lazy as Lazy (Text) import qualified Data.Text.Lazy as Lazy (Text)
import Network.HTTP.Client (Cookie(..)) import Network.HTTP.Client (Cookie(..))
import Network.URI (URI(uriAuthority), URIAuth(uriRegName), parseURI)
import Text.Taggy.Lens (html, allNamed, attr, Element) import Text.Taggy.Lens (html, allNamed, attr, Element)
import Data.HTTPSEverywhere.Rules.Internal.Types (RuleSet(..), Target(..), Rule(..), Exclusion(..), CookieRule(..)) import Data.HTTPSEverywhere.Rules.Internal.Types (RuleSet(..), Target(..), Rule(..), Exclusion(..), CookieRule(..))
@ -36,22 +37,23 @@ parseRuleSet xml = xml ^. attr "name" <&> \ruleSetName -> do
RuleSet ruleSetName ruleSetTargets ruleSetRules ruleSetExclusions ruleSetCookieRules RuleSet ruleSetName ruleSetTargets ruleSetRules ruleSetExclusions ruleSetCookieRules
parseTarget :: Strict.Text -> Target parseTarget :: Strict.Text -> Target
parseTarget = Target . fromJust . match . fromWildcard . escapeInput parseTarget = Target . checkRegName . fromJust . match . fromWildcard . replace "." "\\."
where fromWildcard str = if where fromWildcard str
| head str == '*' -> prepend ".*" $ tail str | head str == '*' = flip append ".*" $ tail str
| last str == '*' -> append ".*" $ init str | last str == '*' = append ".*" $ init str
| otherwise -> str | otherwise = str
escapeInput = replace "." "\\." checkRegName predicate = fromMaybe False . (predicate <$$> getRegName)
prepend = flip append getRegName = cs . uriRegName <$$> uriAuthority
parseRule :: Element -> Maybe Rule parseRule :: Element -> Maybe Rule
parseRule element = do parseRule element = do
pattern <- element ^. attr "from" pattern <- element ^. attr "from"
replacement <- element ^. attr "to" replacement <- element ^. attr "to"
findAndReplace pattern replacement <&> Rule substitute <- findAndReplace pattern replacement
return . Rule $ join . fmap (parseURI . cs) . substitute . cs . show
parseExclusion :: Strict.Text -> Maybe Exclusion parseExclusion :: Strict.Text -> Maybe Exclusion
parseExclusion = Exclusion <$$> match parseExclusion = Exclusion . (. cs . show) <$$> match
parseCookieRule :: Element -> Maybe CookieRule parseCookieRule :: Element -> Maybe CookieRule
parseCookieRule element = CookieRule <$> do parseCookieRule element = CookieRule <$> do

View File

@ -8,10 +8,11 @@ module Data.HTTPSEverywhere.Rules.Internal.Types (
import Data.Text (Text) import Data.Text (Text)
import Network.HTTP.Client (Cookie) import Network.HTTP.Client (Cookie)
import Network.URI (URI)
newtype Rule = Rule { getRule :: Text -> Maybe Text } newtype Rule = Rule { getRule :: URI -> Maybe URI }
newtype Target = Target { getTarget :: Text -> Bool } newtype Target = Target { getTarget :: URI -> Bool }
newtype Exclusion = Exclusion { getExclusion :: Text -> Bool } newtype Exclusion = Exclusion { getExclusion :: URI -> Bool }
newtype CookieRule = CookieRule { getCookieRule :: Cookie -> Bool } newtype CookieRule = CookieRule { getCookieRule :: Cookie -> Bool }
data RuleSet = RuleSet data RuleSet = RuleSet

View File

@ -4,17 +4,23 @@ module Data.HTTPSEverywhere.Rules.Internal.ParserSpec (
spec spec
) where ) where
import Data.HTTPSEverywhere.Rules.Internal.Types (Target(..))
import Data.HTTPSEverywhere.Rules.Internal.Parser (parseTarget) import Data.HTTPSEverywhere.Rules.Internal.Parser (parseTarget)
import Data.HTTPSEverywhere.Rules.Internal.Types (Target(..))
import Data.Maybe (fromJust)
import Network.URI (parseURI)
import Test.Hspec (Spec, describe, it, shouldBe) import Test.Hspec (Spec, describe, it, shouldBe)
spec :: Spec spec :: Spec
spec = do spec = do
describe "parseTarget" $ do describe "parseTarget" $ do
let parseTarget' = \target domain -> ($ domain) . getTarget $ parseTarget target let checkTarget = \target domain -> ($ domain) . getTarget $ parseTarget target
unsafeParseURL = fromJust . parseURI
it "*.facebook.com should match s-static.ak.facebook.com" $ do it "*.facebook.com should match s-static.ak.facebook.com" $ do
parseTarget' "*.facebook.com" "s-static.ak.facebook.com" `shouldBe` True let url = unsafeParseURL "http://s-static.ak.facebook.com"
checkTarget "*.facebook.com" url `shouldBe` True
it "google.* should match google.com.id" $ do it "google.* should match google.com.id" $ do
parseTarget' "google.*" "google.com.id" `shouldBe` True let url = unsafeParseURL "http://google.com.id"
checkTarget "google.*" url `shouldBe` True
it "*.google.* should not match google.com.id" $ do it "*.google.* should not match google.com.id" $ do
parseTarget' "*.google.*" "google.com.id" `shouldBe` False let url = unsafeParseURL "http://google.com.id"
checkTarget "*.google.*" url `shouldBe` False