adblock2privoxy-1.0.0: src/OptionsConverter.hs
module OptionsConverter (
HeaderFilters,
Filter (..),
HeaderType (..),
HeaderFilter (..),
headerFilters
) where
import InputParser
import Control.Applicative
import Control.Monad
import Data.List
import Data.Monoid hiding (Any)
import Data.Maybe
import Data.String.Utils (replace)
import {-# SOURCE #-} UrlBlocker
type FilterFabrique = Policy -> RequestOptions -> HeaderPolicy
data HeaderType = HeaderType {_name :: String, _taggerType :: TaggerType, _level :: Int,
_typeCode :: Char, _fabrique :: FilterFabrique}
data Filter = Filter { _code :: String, _regex :: String, _orEmpty :: Bool } deriving Eq
data HeaderPolicy = Specific Filter | Any | None deriving Eq
data HeaderFilter = HeaderFilter HeaderType Filter
type HeaderFilters = [[HeaderFilter]]
allTypes :: [HeaderType]
allTypes = [accept, contentType, requestedWith, referer]
accept, contentType, requestedWith, referer :: HeaderType
accept = HeaderType "accept" Client 1 'A' acceptFilter
contentType = HeaderType "content-type" Server 1 'C' contentTypeFilter
requestedWith = HeaderType "x-requested-with" Client 1 'X' requestedWithFilter
referer = HeaderType "referer" Client 2 'R' refererFilter
headerFilters :: Policy -> Int -> RequestOptions -> Maybe HeaderFilters
headerFilters _ 0 _ = Just []
headerFilters policy level requestOptions@RequestOptions{_requestType = requestType}
= let requestOptions' = requestOptions{_requestType = convertPopup $ convertOther requestType}
in do
nextLevel <- headerFilters policy (level - 1) requestOptions'
let
passthrough = checkPassthrough requestOptions'
filters = do
headerType <- allTypes
guard (_level headerType == level)
case (_fabrique headerType) policy requestOptions' of
Specific filter' -> return $ Just $ HeaderFilter headerType filter'
None -> return Nothing
Any -> mzero
when (not passthrough && all isNothing filters && not (null filters)) $ fail "filters blocked"
return $ case catMaybes filters of
[] -> nextLevel
filters' -> filters' : nextLevel
convertPopup :: Restrictions RequestType -> Restrictions RequestType
convertPopup (Restrictions positive negative)= Restrictions positive' negative
where
positiveContentTypes = fromMaybe [] positive >>= contentTypes True
positive' | Popup `elem` negative && null positiveContentTypes = Nothing
| otherwise = positive
convertOther :: Restrictions RequestType -> Restrictions RequestType
convertOther (Restrictions positive negative)= Restrictions positive' negative'
where
allContentOptions = [Script, Image, Stylesheet, Object, ObjectSubrequest, Document]
positiveList = fromMaybe [] positive
negative' | Other `elem` positiveList = allContentOptions \\ positiveList
| otherwise = negative
positive' | Other `elem` negative = Just $ allContentOptions \\ negative'
| positive == Just [Other] = Nothing
| otherwise = positive
checkPassthrough :: RequestOptions -> Bool
checkPassthrough RequestOptions {_requestType = (Restrictions positive _) }
= fromMaybe False $ (not . null . intersect [Subdocument, Popup]) <$> positive
acceptFilter, contentTypeFilter, requestedWithFilter, refererFilter :: FilterFabrique
contentTypeFilter policy (RequestOptions (Restrictions positive negative) thirdParty _ _ _ _ _ _)
| fromMaybe True emptyPositive && (not $ isNothing positive) = None
| result == mempty = Any
| otherwise = Specific $ Filter code regex orEmpty
where
negative' | isNothing positive && fromMaybe False thirdParty = Document : negative
| otherwise = negative
negativePart = mappend ("n", "") <$> convert False negative'
positivePart = positive >>= convert True
result@(code, regex) = mconcat $ catMaybes [positivePart, negativePart]
orEmpty = (policy == Unblock) && isNothing positive
emptyPositive = null . filter (`notElem` (fromMaybe "" $ fst <$> negativePart)) . fst <$> positivePart
convert _ [] = Nothing
convert include requestTypes | null code' = Nothing
| otherwise = Just (code', regex')
where contentTypes' = nub $ requestTypes >>= contentTypes include
code' = sort $ (head . dropWhile (`elem` "/(?:x-)")) <$> contentTypes'
regex' = lookahead contentTypes' "[\\s\\w]*" include
acceptFilter excludePattern options = case contentTypeFilter excludePattern options of
Specific res -> Specific res {_orEmpty = False}
other -> other
requestedWithFilter _ RequestOptions{ _requestType = Restrictions positive negative } =
case result of
Nothing -> Any
Just result' -> Specific $ Filter (code result') (lookahead ["xmlhttprequest"] "\\s*" result') (not result')
where
code True = "x"
code False = "nx"
result | Xmlhttprequest `elem` negative = Just False
| Xmlhttprequest `elem` fromMaybe [] positive = Just True
| (hasContentTypes False negative)
&& (fromMaybe True $ not . hasContentTypes True <$> positive) = Just True
| otherwise = Nothing
hasContentTypes include = not . all null . fmap (contentTypes include)
refererFilter policy RequestOptions{ _thirdParty = thirdParty, _domain = Restrictions positive negative }
| fromMaybe False emptyPositive = None
| result == mempty = Any
| otherwise = Specific $ Filter code regex orEmpty
where
negativePart = mappend ("n", "") <$> convert False negative
positivePart = positive >>= convert True
thirdPartyPart tp = (if tp then "t" else "nt",
concat ["(?", lookAheadPolicy $ not tp,
":\\s*(?:https?:\\/\\/)?(?:[\\w.-]*\\.)?([\\w-]+\\.[\\w-]+)[^\\w.-].*\\1$)",
"\ns@^referer:.*@$&\\t$host@Di"])
result@(code, regex) = mconcat $ catMaybes [positivePart, negativePart, thirdPartyPart <$> thirdParty]
emptyPositive = null . filter (`notElem` negative) <$> positive
orEmpty = (policy == Unblock) && (isNothing positive || (not $ fromMaybe True thirdParty))
convert _ [] = Nothing
convert include domains = let
code' = intercalate "][" $ sort domains
regex' = lookahead domains "[^\\n]*[./]" include
in Just ("[" ++ code' ++ "]", regex')
lookAheadPolicy :: Bool -> [Char]
lookAheadPolicy True = "="
lookAheadPolicy False = "!"
lookahead :: [String] -> String -> Bool -> String
lookahead list prefix include = join ["(?", lookAheadPolicy include,
":", prefix ,"(?:", intercalate "|" $ excapeRx <$> list, "))"]
where
excapeRx = replace "/" "\\/" . replace "." "\\."
contentTypes :: Bool -> RequestType -> [String]
contentTypes _ Script = ["/(?:x-)?javascript"]
contentTypes _ Image = ["image/"]
contentTypes _ Stylesheet = ["/css"]
contentTypes _ Object = ["video/","audio/","/(?:x-)?shockwave-flash"]
contentTypes _ ObjectSubrequest = ["video/","audio/","/octet-stream"]
contentTypes _ Document = ["/html", "/xml"]
contentTypes False Subdocument = ["/html", "/xml"]
contentTypes _ _ = []