M hurl.cabal => hurl.cabal +9 -0
@@ 78,6 78,11 @@ Flag appstream
   Default:      True
   Manual:       True
 
+Flag rewriters
+  Description: Support regexp-based URI rewriting/blocking plugins
+  Default:		True
+  Manual: 		True
+
 source-repository head
     type: git
     location: https://git.adrian.geek.nz/hurl.git
@@ 123,6 128,10 @@ library
     CPP-options:   -DWITH_APPSTREAM
     build-depends: xml-conduit >=1.8 && < 1.9, zlib >= 0.6 && < 0.7, containers
     other-modules: Network.URI.XDG.AppStream, Network.URI.XDG.AppStreamOutput
+  if flag(rewriters)
+    CPP-options:   -DWITH_PLUGIN_REWRITES
+    build-depends: regex >= 1.1 && < 1.2, regex-tdfa >= 1.2 && < 1.4
+    other-modules: Network.URI.PlugIns.Rewriters
 
 executable hurl
   -- .hs file containing the Main module
 
M src/Network/URI/Fetch.hs => src/Network/URI/Fetch.hs +23 -1
@@ 48,6 48,10 @@ import Network.URI.Messages
 import Network.URI.XDG
 #endif
 
+#ifdef WITH_PLUGIN_REWRITES
+import Network.URI.PlugIns.Rewriters
+#endif
+
 -- | Data shared accross multiple URI requests.
 data Session = Session {
 #ifdef WITH_HTTP_URI
@@ 59,6 63,9 @@ data Session = Session {
 #ifdef WITH_XDG
     apps :: XDGConfig,
 #endif
+#ifdef WITH_PLUGIN_REWRITES
+    rewriter :: Rewriter,
+#endif
     -- | The languages (RFC2616-encoded) to which responses should be localized.
     locale :: [String],
     -- | Additional files to serve from about: URIs.
@@ 68,7 75,11 @@ data Session = Session {
 -- | Initializes a default Session object to support HTTPS & Accept-Language
 -- if HTTP is enabled.
 newSession :: IO Session
-newSession = do
+newSession = newSession' ""
+
+-- | Variant of `newSession` which loads plugins for the named app.
+newSession' :: String -> IO Session
+newSession' appname = do
     (ietfLocale, unixLocale) <- rfc2616Locale
 #ifdef WITH_HTTP_URI
     managerHTTP' <- HTTP.newManager TLS.tlsManagerSettings
@@ 79,6 90,9 @@ newSession = do
 #ifdef WITH_XDG
     apps' <- loadXDGConfig unixLocale
 #endif
+#ifdef WITH_PLUGIN_REWRITES
+    rewriters <- parseRewriters appname
+#endif
 
     return Session {
 #ifdef WITH_HTTP_URI
@@ 90,6 104,9 @@ newSession = do
 #ifdef WITH_XDG
         apps = apps',
 #endif
+#ifdef WITH_PLUGIN_REWRITES
+        rewriter = rewriters,
+#endif
         locale = ietfLocale,
         aboutPages = []
     }
@@ 119,6 136,11 @@ htmlERR = "html/x-error\t"
 
 -- | As per `fetchURL`, but also returns the redirected URI.
 fetchURL' :: Session -> [String] -> URI -> IO (URI, String, Either Text ByteString)
+#ifdef WITH_PLUGIN_REWRITES
+fetchURL' session mimes uri
+    | Just uri' <- applyRewriter (rewriter session) uri = fetchURL' session mimes uri'
+#endif
+
 fetchURL' session mimes uri@(URI {uriScheme = "about:", uriPath = ""}) =
     fetchURL' session mimes $ uri {uriPath = "version"}
 fetchURL' Session {aboutPages = pages} _ url@URI {uriScheme = "about:", uriPath = path} =
 
A src/Network/URI/PlugIns/Rewriters.hs => src/Network/URI/PlugIns/Rewriters.hs +49 -0
@@ 0,0 1,49 @@
+{-# LANGUAGE FlexibleContexts #-}
+module Network.URI.PlugIns.Rewriters(parseRewriter, parseRewriters, Rewriter, applyRewriter) where
+
+import Text.RE.Tools.Edit
+import Text.RE.TDFA.String
+import Network.URI (URI, uriToString, parseAbsoluteURI)
+import Data.Maybe (catMaybes, fromMaybe)
+
+import System.Directory as Dir
+import System.FilePath ((</>))
+import Control.Concurrent.Async (forConcurrently)
+
+type Rewriter = Edits Maybe RE String
+parseRewriter :: FilePath -> IO Rewriter
+parseRewriter filepath = do
+    source <- readFile filepath
+    let parseLine line | [pattern, template] <- words line = compileSearchReplace pattern template
+                | [pattern] <- words line = compileSearchReplace pattern "about:blank"
+                | otherwise = Nothing
+    let edits = catMaybes $ map parseLine $ lines source
+    return $ Select $ map Template edits
+
+parseRewriters :: String -> IO Rewriter
+parseRewriters app = do
+    dir <- Dir.getXdgDirectory Dir.XdgConfig "nz.geek.adrian.hurl"
+    exists <- Dir.doesDirectoryExist dir
+    if exists then do
+        rewriters <- loadRewriters dir
+
+        let inner = dir </> app
+        innerExists <- Dir.doesDirectoryExist dir
+        if innerExists then do
+            appRewriters <- loadRewriters inner
+            return $ Select (appRewriters ++ rewriters)
+        else return $ Select rewriters
+    else return $ Select []
+  where
+    loadRewriters dir = do
+        files <- Dir.listDirectory dir
+        raw <- forConcurrently files $ \file -> do
+            rewriter <- parseRewriter file
+            return $ case rewriter of
+                Select x -> x
+                Pipe x -> x
+        return $ concat raw
+
+applyRewriter :: Rewriter -> URI -> Maybe URI
+applyRewriter rewriter uri = parseAbsoluteURI =<<
+        applyEdits firstLine rewriter (uriToString id uri "")