diff --git a/src/Text/Pandoc/App/Input.hs b/src/Text/Pandoc/App/Input.hs index 87e71da6b..8d142ada7 100644 --- a/src/Text/Pandoc/App/Input.hs +++ b/src/Text/Pandoc/App/Input.hs @@ -16,6 +16,7 @@ module Text.Pandoc.App.Input import Control.Monad ((>=>), when) import Control.Monad.Except (throwError, catchError) +import Data.Char (toLower) import Data.Text (Text) import Network.URI (URI (..), parseURI) import Text.Pandoc.Transforms (adjustLinksAndIds) @@ -89,8 +90,9 @@ readSource :: PandocMonad m readSource "-" = (,Nothing) <$> readStdinStrict readSource src = case parseURI src of - Just u | uriScheme u `elem` ["http:","https:"] -> openURL (T.pack src) - | uriScheme u == "file:" -> + Just u | map toLower (uriScheme u) `elem` ["http:","https:"] -> + openURL (T.pack src) + | map toLower (uriScheme u) == "file:" -> (,Nothing) <$> readFileStrict (uriPathToPath $ T.pack $ uriPath u) _ -> (,Nothing) <$> readFileStrict src diff --git a/src/Text/Pandoc/Class/PandocMonad.hs b/src/Text/Pandoc/Class/PandocMonad.hs index 48cbfbf10..ba4e682f0 100644 --- a/src/Text/Pandoc/Class/PandocMonad.hs +++ b/src/Text/Pandoc/Class/PandocMonad.hs @@ -65,7 +65,7 @@ module Text.Pandoc.Class.PandocMonad import Control.Monad.Except (MonadError (catchError, throwError)) import Control.Monad.Trans (MonadTrans, lift) import Control.Monad (when) -import Data.Char (chr, digitToInt, isHexDigit) +import Data.Char (chr, digitToInt, isHexDigit, toLower) import Data.List (intercalate) import Data.Word (Word8) import Data.Time (UTCTime) @@ -386,7 +386,7 @@ downloadOrRead :: PandocMonad m => T.Text -> m (B.ByteString, Maybe MimeType) downloadOrRead s - | "data:" `T.isPrefixOf` s, + | T.toLower (T.take 5 s) == "data:", Right (bs, mt) <- A.parseOnly (pBase64DataURI <* A.endOfInput) s = pure (bs, Just mt) | otherwise = do @@ -403,9 +403,10 @@ downloadOrRead s Nothing -> openURL s' -- will throw error (Nothing, s') -> case parseURI (T.unpack s') of -- requires absolute URI - Just URI{ uriScheme = "file:", uriPath = upath} + Just URI{ uriScheme = sch, uriPath = upath} + | map toLower sch == "file:" -> readLocalFile $ uriPathToPath (T.pack upath) - Just URI{ uriScheme = "data:", uriPath = upath} + | map toLower sch == "data:" -> pure $ extractURIData upath -- We don't want to treat C:/ as a scheme: Just u' | length (uriScheme u') > 2 -> openURL (T.pack $ show u') diff --git a/src/Text/Pandoc/MediaBag.hs b/src/Text/Pandoc/MediaBag.hs index 56333ff79..f3b28bb30 100644 --- a/src/Text/Pandoc/MediaBag.hs +++ b/src/Text/Pandoc/MediaBag.hs @@ -25,6 +25,7 @@ module Text.Pandoc.MediaBag ( ) where import Crypto.Hash (hashlazy, Digest, SHA1) import qualified Data.ByteString.Lazy as BL +import Data.Char (toLower) import Data.Data (Data) import qualified Data.Map as M import Data.Maybe (fromMaybe, isNothing) @@ -55,12 +56,16 @@ newtype MediaBag = MediaBag (M.Map Text MediaItem) instance Show MediaBag where show bag = "MediaBag " ++ show (mediaDirectory bag) +-- | Check for the (case-insensitive) @data:@ URI scheme. +isDataURI :: FilePath -> Bool +isDataURI = (== "data:") . map toLower . take 5 + -- | We represent paths with /, in normalized form. Percent-encoding -- is not resolved. canonicalize :: FilePath -> Text --- avoid an expensive call to isURI for data URIs: -canonicalize fp@('d':'a':'t':'a':':':_) = T.pack fp canonicalize fp + -- avoid an expensive call to isURI for data URIs: + | isDataURI fp = T.pack fp | isURI fp = T.pack fp | otherwise = T.replace "\\" "/" . T.pack . normalise $ fp @@ -80,7 +85,7 @@ insertMedia :: FilePath -- ^ relative path and canonical name of resource -> MediaBag -> MediaBag insertMedia fp mbMime contents (MediaBag mediamap) - | 'd':'a':'t':'a':':':_ <- fp + | isDataURI fp , Just mt' <- mbMime = MediaBag (M.insert fp' MediaItem{ mediaPath = hashpath diff --git a/src/Text/Pandoc/URI.hs b/src/Text/Pandoc/URI.hs index 6086cbe8b..c66d219ba 100644 --- a/src/Text/Pandoc/URI.hs +++ b/src/Text/Pandoc/URI.hs @@ -126,7 +126,7 @@ pBase64DataURI :: A.Parser (B.ByteString, MimeType) pBase64DataURI = base64uri where base64uri = do - A.string "data:" + A.asciiCI "data:" -- the scheme is case-insensitive (RFC 3986) mime <- do n1 <- restrictedName A.char '/' diff --git a/test/Tests/MediaBag.hs b/test/Tests/MediaBag.hs index 8c7be1fe3..e47c0c49c 100644 --- a/test/Tests/MediaBag.hs +++ b/test/Tests/MediaBag.hs @@ -27,7 +27,9 @@ tests = [ -- absolute path -> extracted with hashed name B.para (B.image (T.pack absLalune) "" mempty) <> B.para (B.image "data:image/png;base64,cHJpbnQgImhlbGxvIgo=;.lua+%2f%2e%2e%2f%2e%2e%2fa%2elua" "" mempty) <> - B.para (B.image "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" "" mempty) + B.para (B.image "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" "" mempty) <> + -- the data: scheme is case-insensitive + B.para (B.image "DATA:image/gif;base64,dXBwZXJjYXNlIGRhdGEgdXJpIHRlc3QK" "" mempty) let fooDir = absTmpdir "foo" runIOorExplode $ do fillMediaBag d @@ -42,6 +44,9 @@ tests = [ (exists3 && not exists4) exists5 <- doesFileExist (fooDir "d5fceb6532643d0d84ffe09c40c481ecdf59e15a.gif") assertBool "data uri with gif is not properly decoded" exists5 + exists5a <- doesFileExist + (fooDir "81c7546d23179ce1b344a763aa9038c3a8ff85d0.gif") + assertBool "data uri with uppercase scheme is not extracted" exists5a -- double-encoded version: let e = B.doc $ B.para (B.image "data:image/png;base64,cHJpbnQgInB3bmVkIgo=;.lua+%252f%252e%252e%252f%252e%252e%252fb%252elua" "" mempty)