mirror of
https://github.com/jgm/pandoc.git
synced 2026-09-27 01:45:56 +00:00
Treat data: and file: URI schemes case-insensitively.
URI schemes are case-insensitive (RFC 3986, section 3.1), but several places matched only lowercase "data:" or "file:": - Text.Pandoc.URI.pBase64DataURI (also used by isURI) - downloadOrRead: the fast-path prefix check and the parseURI fallback for data: and file: URIs - Text.Pandoc.App.Input.readSource: file:, http:, and https: URIs given as input sources - Text.Pandoc.MediaBag: canonicalize's fast path and insertMedia's data-URI branch Previously, e.g. DATA:image/gif;base64,... would be handed to openURL and the image dropped, and a large uppercase data URI would also force a full parseURI in MediaBag.canonicalize. Now such URIs are handled the same as their lowercase equivalents. Added a test case. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
23bc02c63c
commit
ac4abe44e3
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 '/'
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user