CVE-2021-38711: Files or Directories Accessible to External Parties (CWE-552)
value: In gitit before 0.15.0.0, the Export feature can be exploited to leak information from files.
CWE description: The product makes files or directories accessible to unauthorized actors, even though they should not be.
CVSS v3 base score: 7.5
Published: 2021-08-16T04:15Z
Repository: https://github.com/jgm/gitit
Language: Other
Files changed: README.markdown, data/default.conf, data/templates/pagetools.st, gitit.cabal, src/Network/Gitit.hs, src/Network/Gitit/Cache.hs, src/Network/Gitit/Config.hs, src/Network/Gitit/ContentTransformer.hs, src/Network/Gitit/Handlers.hs, src/Network/Gitit/Layout.hs, src/Network/Gitit/Types.hs
Commit date: 2021-08-12 16:17:16 -0700
Commit message: Remove the Export feature.

This feature is a security risk; it provided multiple
vectors by which contents of files could be exposed.

API change: remove Network.Gitit.Export.

Diff: diff --git a/README.markdown b/README.markdown
index c5a4ada..d49e0cc 100644
--- a/README.markdown
+++ b/README.markdown
@@ -5,14 +5,13 @@ Gitit is a wiki program written in Haskell. It uses [Happstack] for
 the web server and [pandoc] for markup processing. Pages and uploaded
 files are stored in a [git], [darcs], or [mercurial] repository
 and may be modified either by using the VCS's command-line tools or
 through the wiki's web interface. By default, pandoc's extended version
 of markdown is used as a markup language, but reStructuredText, LaTeX, HTML,
-DocBook, or Emacs Org-mode markup can also be used. Pages can be exported in a
-number of different formats, including LaTeX, RTF, OpenOffice ODT, and
-MediaWiki markup.  Gitit can be configured to display TeX math (using
-[texmath]) and highlighted source code (using [highlighting-kate]).
+DocBook, or Emacs Org-mode markup can also be used.  Gitit can
+be configured to display TeX math (using [texmath]) and
+highlighted source code (using [highlighting-kate]).
 
 Other features include
 
 * plugins: dynamically loaded page transformations written in Haskell
   (see "Network.Gitit.Interface")
@@ -409,11 +408,11 @@ Caching
 -------
 
 By default, gitit does not cache content.  If your wiki receives a lot of
 traffic or contains pages that are slow to render, you may want to activate
 caching.  To do this, set the configuration option `use-cache` to `yes`.
-By default, rendered pages, highlighted source files, and exported PDFs
+By default, rendered pages, and highlighted source files
 will be cached in the `cache` directory. (Another directory can be
 specified by setting the `cache-dir` configuration option.)
 
 Cached pages are updated when pages are modified using the web
 interface. They are not updated when pages are modified directly through
diff --git a/data/default.conf b/data/default.conf
index 567bf8f..cd528f9 100644
--- a/data/default.conf
+++ b/data/default.conf
@@ -264,20 +264,13 @@ feed-days: 14
 # number of days to be included in feeds.
 
 feed-refresh-time: 60
 # number of minutes to cache feeds before refreshing
 
-pdf-export: no
-# if yes, PDF will appear in export options. PDF will be created using
-# pdflatex, which must be installed and in the path. Note that PDF
-# exports create significant additional server load.
-
 pandoc-user-data:
 # if a directory is specified, this will be searched for pandoc
-# customizations. These can include a templates/ directory for custom
-# templates for various export formats, an S5 directory for custom
-# S5 styles, and a reference.odt for ODT exports. If no directory is
+# customizations.  If no directory is
 # specified, $HOME/.pandoc will be searched. See pandoc's README for
 # more information.
 
 xss-sanitize: yes
 # if yes, all HTML (including that produced by pandoc) is filtered
diff --git a/data/templates/pagetools.st b/data/templates/pagetools.st
index 2d01dfa..a5178f4 100644
--- a/data/templates/pagetools.st
+++ b/data/templates/pagetools.st
@@ -7,8 +7,7 @@
       <li><a href="$base$/_delete$pageUrl$">Delete this page</a></li>
       $if(feed)$
       <li><a href="$base$/_feed$pageUrl$" type="application/atom+xml" rel="alternate" title="This page's ATOM Feed">Atom feed</a> <img alt="feed icon" src="$base$/img/icons/feed.png"/></li>
       $endif$
     </ul>
-    $exportbox$
   </fieldset>
 </div>
diff --git a/gitit.cabal b/gitit.cabal
index 361415d..3d0d695 100644
--- a/gitit.cabal
+++ b/gitit.cabal
@@ -113,11 +113,10 @@ Library
                      Network.Gitit.Initialize, Network.Gitit.Config,
                      Network.Gitit.Layout, Network.Gitit.Authentication,
                      Network.Gitit.Authentication.Github,
                      Network.Gitit.Util, Network.Gitit.Server
                      Network.Gitit.Cache, Network.Gitit.State,
-                     Network.Gitit.Export,
                      Network.Gitit.Handlers,
                      Network.Gitit.Plugins, Network.Gitit.Rpxnow,
                      Network.Gitit.Page, Network.Gitit.Feed,
                      Network.Gitit.Compat.Except,
                      Paths_gitit
diff --git a/src/Network/Gitit.hs b/src/Network/Gitit.hs
index 032cc9d..3ad25f8 100644
--- a/src/Network/Gitit.hs
+++ b/src/Network/Gitit.hs
@@ -197,11 +197,10 @@ wikiHandlers =
           authenticate ForModify (unlessNoDelete confirmDelete showPage)
       , method POST >>
           authenticate ForModify (unlessNoDelete deletePage showPage) ]
   , dir "_preview" preview
   , guardIndex >> indexPage
-  , guardCommand "export" >> exportPage
   , method POST >> guardCommand "cancel" >> showPage
   , method POST >> guardCommand "update" >>
       authenticate ForModify (unlessNoEdit updatePage showPage)
   , showPage
   , guardPath isSourceCode >> method GET >> showHighlightedSource
diff --git a/src/Network/Gitit/Cache.hs b/src/Network/Gitit/Cache.hs
index 91b6c0a..3334d07 100644
--- a/src/Network/Gitit/Cache.hs
+++ b/src/Network/Gitit/Cache.hs
@@ -39,27 +39,17 @@ import Network.Gitit.Types
 import Control.Monad
 import Control.Monad.Trans (liftIO)
 import Text.Pandoc.UTF8 (encodePath)
 
 -- | Expire a cached file, identified by its filename in the filestore.
--- If there is an associated exported PDF, expire it too.
 -- Returns () after deleting a file from the cache, fails if no cached file.
 expireCachedFile :: String -> GititServerPart ()
 expireCachedFile file = do
   cfg <- getConfig
   let target = encodePath $ cacheDir cfg </> file
   exists <- liftIO $ doesFileExist target
-  when exists $ liftIO $ do
-    liftIO $ removeFile target
-    expireCachedPDF target (defaultExtension cfg)
-
-expireCachedPDF :: String -> String -> IO ()
-expireCachedPDF file ext = 
-  when (takeExtension file == "." ++ ext) $ do
-    let pdfname = file ++ ".export.pdf"
-    exists <- doesFileExist pdfname
-    when exists $ removeFile pdfname
+  when exists $ liftIO $ liftIO $ removeFile target
 
 lookupCache :: String -> GititServerPart (Maybe (UTCTime, B.ByteString))
 lookupCache file = do
   cfg <- getConfig
   let target = encodePath $ cacheDir cfg </> file
@@ -82,6 +72,5 @@ cacheContents file contents = do
   let target = encodePath $ cacheDir cfg </> file
   let targetDir = takeDirectory target
   liftIO $ do
     createDirectoryIfMissing True targetDir
     B.writeFile target contents
-    expireCachedPDF target (defaultExtension cfg)
diff --git a/src/Network/Gitit/Config.hs b/src/Network/Gitit/Config.hs
index 1bfbc47..d39d8cf 100644
--- a/src/Network/Gitit/Config.hs
+++ b/src/Network/Gitit/Config.hs
@@ -128,11 +128,10 @@ extractConfig cp = do
       cfBaseUrl <- get cp "DEFAULT" "base-url"
       cfAbsoluteUrls <- get cp "DEFAULT" "absolute-urls"
       cfWikiTitle <- get cp "DEFAULT" "wiki-title"
       cfFeedDays <- get cp "DEFAULT" "feed-days"
       cfFeedRefreshTime <- get cp "DEFAULT" "feed-refresh-time"
-      cfPDFExport <- get cp "DEFAULT" "pdf-export"
       cfPandocUserData <- get cp "DEFAULT" "pandoc-user-data"
       cfXssSanitize <- get cp "DEFAULT" "xss-sanitize"
       cfRecentActivityDays <- get cp "DEFAULT" "recent-activity-days"
       let (pt, lhs) = parsePageType cfDefaultPageType
       let markupHelpFile = show pt ++ if lhs then "+LHS" else ""
@@ -230,11 +229,10 @@ extractConfig cp = do
         , baseUrl              = stripTrailingSlash cfBaseUrl
         , useAbsoluteUrls      = cfAbsoluteUrls
         , wikiTitle            = cfWikiTitle
         , feedDays             = readNumber "feed-days" cfFeedDays
         , feedRefreshTime      = readNumber "feed-refresh-time" cfFeedRefreshTime
-        , pdfExport            = cfPDFExport
         , pandocUserData       = if null cfPandocUserData
                                     then Nothing
                                     else Just cfPandocUserData
         , xssSanitize          = cfXssSanitize
         , recentActivityDays   = cfRecentActivityDays
diff --git a/src/Network/Gitit/ContentTransformer.hs b/src/Network/Gitit/ContentTransformer.hs
index fa82604..12e450a 100644
--- a/src/Network/Gitit/ContentTransformer.hs
+++ b/src/Network/Gitit/ContentTransformer.hs
@@ -29,11 +29,10 @@ module Network.Gitit.ContentTransformer
   , runFileTransformer
   -- * Gitit responders
   , showRawPage
   , showFileAsText
   , showPage
-  , exportPage
   , showHighlightedSource
   , showFile
   , preview
   , applyPreCommitPlugins
   -- * Cache support for transformers
@@ -43,11 +42,10 @@ module Network.Gitit.ContentTransformer
   , rawContents
   -- * Response-generating combinators
   , textResponse
   , mimeFileResponse
   , mimeResponse
-  , exportPandoc
   , applyWikiTemplate
   -- * Content-type transformation combinators
   , pageToWikiPandoc
   , pageToPandoc
   , pandocToHtml
@@ -77,11 +75,10 @@ import Control.Monad.Except (throwError)
 import Data.Foldable (traverse_)
 import Data.List (stripPrefix)
 import Data.Maybe (isNothing, mapMaybe)
 import Data.Semigroup ((<>))
 import Network.Gitit.Cache (lookupCache, cacheContents)
-import Network.Gitit.Export (exportFormats)
 import Network.Gitit.Framework hiding (uriPath)
 import Network.Gitit.Layout
 import Network.Gitit.Page (stringToPage)
 import Network.Gitit.Server
 import Network.Gitit.State
@@ -184,14 +181,10 @@ showFileAsText = runFileTransformer rawTextResponse
 
 -- | Responds with rendered wiki page.
 showPage :: Handler
 showPage = runPageTransformer htmlViaPandoc
 
--- | Responds with page exported into selected format.
-exportPage :: Handler
-exportPage = runPageTransformer exportViaPandoc
-
 -- | Responds with highlighted source code.
 showHighlightedSource :: Handler
 showHighlightedSource = runFileTransformer highlightRawSource
 
 -- | Responds with non-highlighted source code.
@@ -218,19 +211,10 @@ applyPreCommitPlugins = runPageTransformer . applyPreCommitTransforms
 
 -- | Responds with raw source.
 rawTextResponse :: ContentTransformer Response
 rawTextResponse = rawContents >>= textResponse
 
--- | Responds with a wiki page in the format specified
--- by the @format@ parameter.
-exportViaPandoc :: ContentTransformer Response
-exportViaPandoc = rawContents >>=
-                  maybe mzero return >>=
-                  contentsToPage >>=
-                  pageToWikiPandoc >>=
-                  exportPandoc
-
 -- | Responds with a wiki page. Uses the cache when
 -- possible and caches the rendered page when appropriate.
 htmlViaPandoc :: ContentTransformer Response
 htmlViaPandoc = cachedHtml `mplus`
                   (rawContents >>=
@@ -320,21 +304,10 @@ mimeResponse :: Monad m
              -> String        -- ^ Mime type
              -> m Response
 mimeResponse c mimeType =
   return . setContentType mimeType . toResponse $ c
 
--- | Converts Pandoc to response using format specified in parameters.
-exportPandoc :: Pandoc -> ContentTransformer Response
-exportPandoc doc = do
-  params <- getParams
-  page <- getPageName
-  cfg <- lift getConfig
-  let format = pFormat params
-  case lookup format (exportFormats cfg) of
-       Nothing     -> error $ "Unknown export format: " ++ format
-       Just writer -> lift (writer page doc)
-
 -- | Adds the sidebar, page tabs, and other elements of the wiki page
 -- layout to the raw content.
 applyWikiTemplate :: Html -> ContentTransformer Response
 applyWikiTemplate c = do
   Context { ctxLayout = layout } <- get
diff --git a/src/Network/Gitit/Export.hs b/src/Network/Gitit/Export.hs
deleted file mode 100644
index 0842a8c..0000000
--- a/src/Network/Gitit/Export.hs
+++ /dev/null
@@ -1,307 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-
-Copyright (C) 2009 John MacFarlane <jgm@berkeley.edu>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
--}
-
-{- Functions for exporting wiki pages in various formats.
--}
-
-module Network.Gitit.Export ( exportFormats ) where
-import Control.Exception (throwIO)
-import Text.Pandoc hiding (HTMLMathMethod(..), getDataFileName)
-import qualified Text.Pandoc as Pandoc
-import Text.Pandoc.PDF (makePDF)
-import Text.Pandoc.SelfContained as SelfContained
-import qualified Text.Pandoc.UTF8 as UTF8
-import qualified Data.Map as M
-import Network.Gitit.Server
-import Network.Gitit.Framework (pathForPage)
-import Network.Gitit.State (getConfig)
-import Network.Gitit.Types
-import Network.Gitit.Cache (cacheContents, lookupCache)
-import Text.DocTemplates as DT
-import Control.Monad.Trans (liftIO)
-import Control.Monad (unless)
-import Text.XHtml (noHtml)
-import qualified Data.ByteString as B
-import qualified Data.ByteString.Lazy as L
-import System.FilePath ((</>), takeDirectory)
-import System.Environment (setEnv)
-import System.Directory (doesFileExist)
-import Text.HTML.SanitizeXSS
-import Data.ByteString.Lazy (fromStrict)
-import Data.Text (Text)
-import qualified Data.Text as T
-import Data.Text.Encoding (encodeUtf8)
-import Data.List (isPrefixOf)
-import Skylighting (styleToCss, pygments)
-import System.IO.Temp (withSystemTempDirectory)
-import Paths_gitit (getDataFileName)
-
-defaultRespOptions :: WriterOptions
-defaultRespOptions = def { writerHighlightStyle = Just pygments }
-
-respondX :: String -> String -> String
-          -> (WriterOptions -> Pandoc -> PandocIO L.ByteString)
-          -> WriterOptions -> String -> Pandoc -> Handler
-respondX templ mimetype ext fn opts page doc = do
-  cfg <- getConfig
-  doc' <- if ext `elem` ["odt","pdf","beamer","epub","docx","rtf"]
-             then fixURLs page doc
-             else return doc
-  doc'' <- liftIO $ runIO $ do
-        setUserDataDir $ pandocUserData cfg
-        compiledTemplate <- compileDefaultTemplate (T.pack templ)
-        fn opts{ writerTemplate = Just compiledTemplate } doc'
-  either (liftIO . throwIO)
-         (ok . setContentType mimetype .
-           (if null ext then id else setFilename (page ++ "." ++ ext)) .
-            toResponseBS B.empty)
-         doc''
-
-respondS :: String -> String -> String -> (WriterOptions -> Pandoc -> PandocIO Text)
-          -> WriterOptions -> String -> Pandoc -> Handler
-respondS templ mimetype ext fn =
-  respondX templ mimetype ext (\o d -> fromStrict . encodeUtf8 <$> fn o d)
-
-respondSlides :: String -> (WriterOptions -> Pandoc -> PandocIO Text) -> String -> Pandoc -> Handler
-respondSlides templ fn page doc = do
-    cfg <- getConfig
-    let math = case mathMethod cfg of
-                   MathML       -> Pandoc.MathML
-                   WebTeX u     -> Pandoc.WebTeX $ T.pack u
-                   _            -> Pandoc.PlainMath
-    let opts' = defaultRespOptions { writerIncremental = True
-                                   , writerHTMLMathMethod = math}
-    -- We sanitize the body only, to protect against XSS attacks.
-    -- (Sanitizing the whole HTML page would strip out javascript
-    -- needed for the slides.)  We then pass the body into the
-    -- slide template using the 'body' variable.
-    Pandoc meta blocks <- fixURLs page doc
-    docOrError <- liftIO $ runIO $ do
-          setUserDataDir $ pandocUserData cfg
-          body' <- writeHtml5String opts' (Pandoc meta blocks) -- just body
-          let body'' = T.unpack
-                       $ (if xssSanitize cfg then sanitizeBalance else id)
-                       $ body'
-          let setVariable key val (DT.Context ctx) =
-                DT.Context $ M.insert (T.pack key) (toVal (T.pack val)) ctx
-          variables' <- if mathMethod cfg == MathML
-                          then do
-                              s <- readDataFile "MathMLinHTML.js"
-                              return $ setVariable "mathml-script"
-                                         (UTF8.toString s) mempty
-                          else return mempty
-          compiledTemplate <- compileDefaultTemplate (T.pack templ)
-          dzcore <- if templ == "dzslides"
-                      then do
-                        dztempl <- readDataFile $ "dzslides" </> "template.html"
-                        return $ unlines
-                            $ dropWhile (not . isPrefixOf "<!-- {{{{ dzslides core")
-                            $ lines $ UTF8.toString dztempl
-                      else return ""
-          let opts'' = opts'{
-                             writerVariables =
-                               setVariable "body" body'' $
-                               setVariable "dzslides-core" dzcore $
-                               setVariable "highlighting-css" pygmentsCss
-                               $ variables'
-                            ,writerTemplate = Just compiledTemplate }
-          h <- fn opts'' (Pandoc meta [])
-          makeSelfContained h
-    either (liftIO . throwIO)
-           (ok . setContentType "text/html;charset=UTF-8" .
-             (setFilename (page ++ ".html")) .
-             toResponseBS B.empty . L.fromStrict . UTF8.fromText)
-           docOrError
-
-respondLaTeX :: String -> Pandoc -> Handler
-respondLaTeX = respondS "latex" "application/x-latex" "tex"
-  writeLaTeX defaultRespOptions
-
-respondConTeXt :: String -> Pandoc -> Handler
-respondConTeXt = respondS "context" "application/x-context" "tex"
-  writeConTeXt defaultRespOptions
-
-
-respondRTF :: String -> Pandoc -> Handler
-respondRTF = respondX "rtf" "application/rtf" "rtf"
-  (\o d -> L.fromStrict . UTF8.fromText <$> writeRTF o d) defaultRespOptions
-
-respondRST :: String -> Pandoc -> Handler
-respondRST = respondS "rst" "text/plain; charset=utf-8" ""
-  writeRST defaultRespOptions{writerReferenceLinks = True}
-
-respondMarkdown :: String -> Pandoc -> Handler
-respondMarkdown = respondS "markdown" "text/plain; charset=utf-8" ""
-  writeMarkdown defaultRespOptions{writerReferenceLinks = True}
-
-respondCommonMark :: String -> Pandoc -> Handler
-respondCommonMark = respondS "commonmark" "text/plain; charset=utf-8" ""
-  writeCommonMark defaultRespOptions{writerReferenceLinks = True}
-
-respondPlain :: String -> Pandoc -> Handler
-respondPlain = respondS "plain" "text/plain; charset=utf-8" ""
-  writePlain defaultRespOptions
-
-respondMan :: String -> Pandoc -> Handler
-respondMan = respondS "man" "text/plain; charset=utf-8" ""
-  writeMan defaultRespOptions
-
-respondTexinfo :: String -> Pandoc -> Handler
-respondTexinfo = respondS "texinfo" "application/x-texinfo" "texi"
-  writeTexinfo defaultRespOptions
-
-respondDocbook :: String -> Pandoc -> Handler
-respondDocbook = respondS "docbook" "application/docbook+xml" "xml"
-  writeDocbook5 defaultRespOptions
-
-respondOrg :: String -> Pandoc -> Handler
-respondOrg = respondS "org" "text/plain; charset=utf-8" ""
-  writeOrg defaultRespOptions
-
-respondICML :: String -> Pandoc -> Handler
-respondICML = respondX "icml" "application/xml; charset=utf-8" ""
-              (\o d -> L.fromStrict . UTF8.fromText <$> writeICML o d)
-                         defaultRespOptions
-
-respondTextile :: String -> Pandoc -> Handler
-respondTextile = respondS "textile" "text/plain; charset=utf-8" ""
-  writeTextile defaultRespOptions
-
-respondAsciiDoc :: String -> Pandoc -> Handler
-respondAsciiDoc = respondS "asciidoc" "text/plain; charset=utf-8" ""
-  writeAsciiDoc defaultRespOptions
-
-respondMediaWiki :: String -> Pandoc -> Handler
-respondMediaWiki = respondS "mediawiki" "text/plain; charset=utf-8" ""
-  writeMediaWiki defaultRespOptions
-
-respondODT :: String -> Pandoc -> Handler
-respondODT = respondX "opendocument" "application/vnd.oasis.opendocument.text"
-              "odt" writeODT defaultRespOptions
-
-respondEPUB :: String -> Pandoc -> Handler
-respondEPUB = respondX "html" "application/epub+zip" "epub" writeEPUB3
-               defaultRespOptions
-
-respondDocx :: String -> Pandoc -> Handler
-respondDocx = respondX "native"
-  "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
-  "docx" writeDocx defaultRespOptions
-
-respondPDF :: Bool -> String -> Pandoc -> Handler
-respondPDF useBeamer page old_pndc = fixURLs page old_pndc >>= \pndc -> do
-  cfg <- getConfig
-  unless (pdfExport cfg) $ error "PDF export disabled"
-  let cacheName = pathForPage page (defaultExtension cfg) ++ ".export.pdf"
-  cached <- if useCache cfg
-               then lookupCache cacheName
-               else return Nothing
-  pdf' <- case cached of
-            Just (_modtime, bs) -> return $ Right $ L.fromChunks [bs]
-            Nothing -> liftIO $
-                withSystemTempDirectory "gitit" $ \tmpdir -> do
-              let toc = tableOfContents cfg
-              -- ensure that LaTeX \include commands can't include
-              -- files outside the working directory, e.g. /etc/passwd:
-              writeFile (tmpdir </> "texmf.cnf")
-                "openout_any = p\nopenin_any = p\n"
-              setEnv "TEXMFCNF" (tmpdir ++ ":")
-              res <- runIO $ do
-                setUserDataDir $ pandocUserData cfg
-                setInputFiles [baseUrl cfg]
-                let templ = if useBeamer then "beamer" else "latex"
-                compiledTemplate <- compileDefaultTemplate templ
-                makePDF "pdflatex" [] (if useBeamer then writeBeamer else writeLaTeX)
-                  defaultRespOptions{ writerTemplate = Just compiledTemplate
-                                    , writerTableOfContents = toc } pndc
-              either (liftIO . throwIO) return res
-
-  case pdf' of
-       Left logOutput' -> simpleErrorHandler ("PDF creation failed:\n"
-                           ++ UTF8.toStringLazy logOutput')
-       Right pdfBS -> do
-              case cached of
-                Nothing ->
-                     cacheContents cacheName $ B.concat . L.toChunks $ pdfBS
-                _ -> return ()
-              ok $ setContentType "application/pdf" $ setFilename (page ++ ".pdf") $
-                        (toResponse noHtml) {rsBody = pdfBS}
-
--- | When we create a PDF or ODT from a Gitit page, we need to fix the URLs of any
--- images on the page. Those URLs will often be relative to the staticDir, but the
--- PDF or ODT processor only understands paths relative to the working directory.
---
--- Because the working directory will not in general be the root of the gitit instance
--- at the time the Pandoc is fed to e.g. pdflatex, this function replaces the URLs of
--- images in the staticDir with their correct absolute file path.
-fixURLs :: String -> Pandoc -> GititServerPart Pandoc
-fixURLs page pndc = do
-    cfg <- getConfig
-    defaultStatic <- liftIO $ getDataFileName $ "data" </> "static"
-
-    let static = staticDir cfg
-    let repoPath = repositoryPath cfg
-
-    let go (Image attr ils (url, title)) = do
-           fixedURL <- fixURL $ T.unpack url
-           return $ Image attr ils (T.pack fixedURL, title)
-        go x                        = return x
-
-        fixURL ('/':url) = resolve url
-        fixURL url       = resolve $ takeDirectory page </> url
-
-        resolve p = do
-           sp <- doesFileExist $ static </> p
-           dsp <- doesFileExist $ defaultStatic </> p
-           return (if sp then static </> p
-                   else (if dsp then defaultStatic </> p
-                         else repoPath </> p))
-    liftIO $ bottomUpM go pndc
-
-exportFormats :: Config -> [(String, String -> Pandoc -> Handler)]
-exportFormats cfg = if pdfExport cfg
-                       then ("PDF", respondPDF False) :
-                            ("Beamer", respondPDF True) :
-                            rest
-                       else rest
-   where rest = [ ("LaTeX",     respondLaTeX)     -- (description, writer)
-                , ("ConTeXt",   respondConTeXt)
-                , ("Texinfo",   respondTexinfo)
-                , ("reST",      respondRST)
-                , ("Markdown",  respondMarkdown)
-                , ("CommonMark",respondCommonMark)
-                , ("Plain text",respondPlain)
-                , ("MediaWiki", respondMediaWiki)
-                , ("Org-mode",  respondOrg)
-                , ("ICML",      respondICML)
-                , ("Textile",   respondTextile)
-                , ("AsciiDoc",  respondAsciiDoc)
-                , ("Man page",  respondMan)
-                , ("DocBook",   respondDocbook)
-                , ("DZSlides",  respondSlides "dzslides" writeDZSlides)
-                , ("Slidy",     respondSlides "slidy" writeSlidy)
-                , ("S5",        respondSlides "s5" writeS5)
-                , ("EPUB",      respondEPUB)
-                , ("ODT",       respondODT)
-                , ("DOCX",      respondDocx)
-                , ("RTF",       respondRTF) ]
-
-pygmentsCss :: String
-pygmentsCss = styleToCss pygments
diff --git a/src/Network/Gitit/Handlers.hs b/src/Network/Gitit/Handlers.hs
index d9f894f..44b19fe 100644
--- a/src/Network/Gitit/Handlers.hs
+++ b/src/Network/Gitit/Handlers.hs
@@ -40,11 +40,10 @@ module Network.Gitit.Handlers (
                       , showPageHistory
                       , showFileHistory
                       , showPage
                       , showPageDiff
                       , showFileDiff
-                      , exportPage
                       , updatePage
                       , editPage
                       , deletePage
                       , confirmDelete
                       , showHighlightedSource
@@ -59,11 +58,11 @@ import Network.Gitit.Layout
 import Network.Gitit.Types
 import Network.Gitit.Feed (filestoreToXmlFeed, FeedConfig(..))
 import Network.Gitit.Util (orIfNull)
 import Network.Gitit.Cache (expireCachedFile, lookupCache, cacheContents)
 import Network.Gitit.ContentTransformer (showRawPage, showFileAsText, showPage,
-        exportPage, showHighlightedSource, preview, applyPreCommitPlugins)
+        showHighlightedSource, preview, applyPreCommitPlugins)
 import Network.Gitit.Page (readCategories)
 import qualified Control.Exception as E
 import System.FilePath
 import Network.Gitit.State
 import Text.XHtml hiding ( (</>), dir, method, password, rev )
diff --git a/src/Network/Gitit/Layout.hs b/src/Network/Gitit/Layout.hs
index fd307c7..f835d42 100644
--- a/src/Network/Gitit/Layout.hs
+++ b/src/Network/Gitit/Layout.hs
@@ -29,16 +29,15 @@ module Network.Gitit.Layout ( defaultPageLayout
 where
 import Network.Gitit.Server
 import Network.Gitit.Framework
 import Network.Gitit.State
 import Network.Gitit.Types
-import Network.Gitit.Export (exportFormats)
 import Network.HTTP (urlEncodeVars)
 import qualified Text.StringTemplate as T
 import Text.XHtml hiding ( (</>), dir, method, password, rev )
 import Text.XHtml.Strict ( stringToHtmlString )
-import Data.Maybe (isNothing, isJust, fromJust)
+import Data.Maybe (isNothing)
 
 defaultPageLayout :: PageLayout
 defaultPageLayout = PageLayout
   { pgPageName       = ""
   , pgRevision       = Nothing
@@ -109,32 +108,19 @@ filledPageTemplate base' cfg layout htmlContents templ =
                    setBoolAttr "pagetools" (pgShowPageTools layout) .
                    setBoolAttr "sitenav" (pgShowSiteNav layout) .
                    maybe id (T.setAttribute "markuphelp") (pgMarkupHelp layout) .
                    setBoolAttr "printable" (pgPrintable layout) .
                    maybe id (T.setAttribute "revision") rev .
-                   T.setAttribute "exportbox"
-                       (renderHtmlFragment $  exportBox base' cfg page rev) .
                    (if null (pgTabs layout) then id else T.setAttribute "tabs"
                        (renderHtmlFragment tabs)) .
                    (\f x xs -> if null xs then x else f xs) (T.setAttribute "messages") id (pgMessages layout) .
                    T.setAttribute "usecache" (useCache cfg) .
                    T.setAttribute "content" (renderHtmlFragment htmlContents) .
                    setBoolAttr "wikiupload" ( uploadsAllowed cfg) $
                    templ
 
 
-exportBox :: String -> Config -> String -> Maybe String -> Html
-exportBox base' cfg page rev | not (isSourceCode page) =
-  gui (base' ++ urlForPage page) ! [identifier "exportbox"] <<
-    ([ textfield "revision" ! [thestyle "display: none;",
-         value (fromJust rev)] | isJust rev ] ++
-     [ select ! [name "format"] <<
-         map ((\f -> option ! [value f] << f) . fst) (exportFormats cfg)
-     , primHtmlChar "nbsp"
-     , submit "export" "Export" ])
-exportBox _ _ _ _ = noHtml
-
 -- auxiliary functions:
 
 linkForTab :: (Tab -> Html -> Html) -> String -> String -> Maybe String -> Tab -> Html
 linkForTab tabli base' page _ HistoryTab =
   tabli HistoryTab << anchor ! [href $ base' ++ "/_history" ++ urlForPage page] << "history"
diff --git a/src/Network/Gitit/Types.hs b/src/Network/Gitit/Types.hs
index 8f037b9..aa23ec1 100644
--- a/src/Network/Gitit/Types.hs
+++ b/src/Network/Gitit/Types.hs
@@ -202,12 +202,10 @@ data Config = Config {
   wikiTitle            :: String,
   -- | Number of days history to be included in feed
   feedDays             :: Integer,
   -- | Number of minutes to cache feeds before refreshing
   feedRefreshTime      :: Integer,
-  -- | Allow PDF export?
-  pdfExport            :: Bool,
   -- | Directory to search for pandoc customizations
   pandocUserData       :: Maybe FilePath,
   -- | Filter HTML through xss-sanitize
   xssSanitize          :: Bool,
   -- | The default number of days in the past to look for \"recent\" activity
@@ -465,11 +463,11 @@ instance FromData Command where
      fromData = do
        pairs <- lookPairs
        return $ case map fst pairs `intersect` commandList of
                  []          -> Command Nothing
                  (c:_)       -> Command $ Just c
-               where commandList = ["update", "cancel", "export"]
+               where commandList = ["update", "cancel"]
 
 -- | State for a single wiki.
 data WikiState = WikiState {
                      wikiConfig    :: Config
                    , wikiFileStore :: FileStore

Vulnerable code: DocBook, or Emacs Org-mode markup can also be used. Pages can be exported in a
number of different formats, including LaTeX, RTF, OpenOffice ODT, and
MediaWiki markup.  Gitit can be configured to display TeX math (using
[texmath]) and highlighted source code (using [highlighting-kate]).
By default, rendered pages, highlighted source files, and exported PDFs
pdf-export: no
# if yes, PDF will appear in export options. PDF will be created using
# pdflatex, which must be installed and in the path. Note that PDF
# exports create significant additional server load.

# customizations. These can include a templates/ directory for custom
# templates for various export formats, an S5 directory for custom
# S5 styles, and a reference.odt for ODT exports. If no directory is
    $exportbox$
                     Network.Gitit.Export,
  , guardCommand "export" >> exportPage
  when exists $ liftIO $ do
    liftIO $ removeFile target
    expireCachedPDF target (defaultExtension cfg)

expireCachedPDF :: String -> String -> IO ()
expireCachedPDF file ext = 
  when (takeExtension file == "." ++ ext) $ do
    let pdfname = file ++ ".export.pdf"
    exists <- doesFileExist pdfname
    when exists $ removeFile pdfname
    expireCachedPDF target (defaultExtension cfg)
      cfPDFExport <- get cp "DEFAULT" "pdf-export"
        , pdfExport            = cfPDFExport
  , exportPage
  , exportPandoc
import Network.Gitit.Export (exportFormats)
exportPage :: Handler
exportPage = runPageTransformer exportViaPandoc

exportViaPandoc :: ContentTransformer Response
exportViaPandoc = rawContents >>=
                  maybe mzero return >>=
                  contentsToPage >>=
                  pageToWikiPandoc >>=
                  exportPandoc

exportPandoc :: Pandoc -> ContentTransformer Response
exportPandoc doc = do
  params <- getParams
  page <- getPageName
  cfg <- lift getConfig
  let format = pFormat params
  case lookup format (exportFormats cfg) of
       Nothing     -> error $ "Unknown export format: " ++ format
       Just writer -> lift (writer page doc)

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleContexts #-}
{-
Copyright (C) 2009 John MacFarlane <jgm@berkeley.edu>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-}

{- Functions for exporting wiki pages in various formats.
-}

module Network.Gitit.Export ( exportFormats ) where
import Control.Exception (throwIO)
import Text.Pandoc hiding (HTMLMathMethod(..), getDataFileName)
import qualified Text.Pandoc as Pandoc
import Text.Pandoc.PDF (makePDF)
import Text.Pandoc.SelfContained as SelfContained
import qualified Text.Pandoc.UTF8 as UTF8
import qualified Data.Map as M
import Network.Gitit.Server
import Network.Gitit.Framework (pathForPage)
import Network.Gitit.State (getConfig)
import Network.Gitit.Types
import Network.Gitit.Cache (cacheContents, lookupCache)
import Text.DocTemplates as DT
import Control.Monad.Trans (liftIO)
import Control.Monad (unless)
import Text.XHtml (noHtml)
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L
import System.FilePath ((</>), takeDirectory)
import System.Environment (setEnv)
import System.Directory (doesFileExist)
import Text.HTML.SanitizeXSS
import Data.ByteString.Lazy (fromStrict)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.Encoding (encodeUtf8)
import Data.List (isPrefixOf)
import Skylighting (styleToCss, pygments)
import System.IO.Temp (withSystemTempDirectory)
import Paths_gitit (getDataFileName)

defaultRespOptions :: WriterOptions
defaultRespOptions = def { writerHighlightStyle = Just pygments }

respondX :: String -> String -> String
          -> (WriterOptions -> Pandoc -> PandocIO L.ByteString)
          -> WriterOptions -> String -> Pandoc -> Handler
respondX templ mimetype ext fn opts page doc = do
  cfg <- getConfig
  doc' <- if ext `elem` ["odt","pdf","beamer","epub","docx","rtf"]
             then fixURLs page doc
             else return doc
  doc'' <- liftIO $ runIO $ do
        setUserDataDir $ pandocUserData cfg
        compiledTemplate <- compileDefaultTemplate (T.pack templ)
        fn opts{ writerTemplate = Just compiledTemplate } doc'
  either (liftIO . throwIO)
         (ok . setContentType mimetype .
           (if null ext then id else setFilename (page ++ "." ++ ext)) .
            toResponseBS B.empty)
         doc''

respondS :: String -> String -> String -> (WriterOptions -> Pandoc -> PandocIO Text)
          -> WriterOptions -> String -> Pandoc -> Handler
respondS templ mimetype ext fn =
  respondX templ mimetype ext (\o d -> fromStrict . encodeUtf8 <$> fn o d)

respondSlides :: String -> (WriterOptions -> Pandoc -> PandocIO Text) -> String -> Pandoc -> Handler
respondSlides templ fn page doc = do
    cfg <- getConfig
    let math = case mathMethod cfg of
                   MathML       -> Pandoc.MathML
                   WebTeX u     -> Pandoc.WebTeX $ T.pack u
                   _            -> Pandoc.PlainMath
    let opts' = defaultRespOptions { writerIncremental = True
                                   , writerHTMLMathMethod = math}
    -- We sanitize the body only, to protect against XSS attacks.
    -- (Sanitizing the whole HTML page would strip out javascript
    -- needed for the slides.)  We then pass the body into the
    -- slide template using the 'body' variable.
    Pandoc meta blocks <- fixURLs page doc
    docOrError <- liftIO $ runIO $ do
          setUserDataDir $ pandocUserData cfg
          body' <- writeHtml5String opts' (Pandoc meta blocks) -- just body
          let body'' = T.unpack
                       $ (if xssSanitize cfg then sanitizeBalance else id)
                       $ body'
          let setVariable key val (DT.Context ctx) =
                DT.Context $ M.insert (T.pack key) (toVal (T.pack val)) ctx
          variables' <- if mathMethod cfg == MathML
                          then do
                              s <- readDataFile "MathMLinHTML.js"
                              return $ setVariable "mathml-script"
                                         (UTF8.toString s) mempty
                          else return mempty
          compiledTemplate <- compileDefaultTemplate (T.pack templ)
          dzcore <- if templ == "dzslides"
                      then do
                        dztempl <- readDataFile $ "dzslides" </> "template.html"
                        return $ unlines
                            $ dropWhile (not . isPrefixOf "<!-- {{{{ dzslides core")
                            $ lines $ UTF8.toString dztempl
                      else return ""
          let opts'' = opts'{
                             writerVariables =
                               setVariable "body" body'' $
                               setVariable "dzslides-core" dzcore $
                               setVariable "highlighting-css" pygmentsCss
                               $ variables'
                            ,writerTemplate = Just compiledTemplate }
          h <- fn opts'' (Pandoc meta [])
          makeSelfContained h
    either (liftIO . throwIO)
           (ok . setContentType "text/html;charset=UTF-8" .
             (setFilename (page ++ ".html")) .
             toResponseBS B.empty . L.fromStrict . UTF8.fromText)
           docOrError

respondLaTeX :: String -> Pandoc -> Handler
respondLaTeX = respondS "latex" "application/x-latex" "tex"
  writeLaTeX defaultRespOptions

respondConTeXt :: String -> Pandoc -> Handler
respondConTeXt = respondS "context" "application/x-context" "tex"
  writeConTeXt defaultRespOptions


respondRTF :: String -> Pandoc -> Handler
respondRTF = respondX "rtf" "application/rtf" "rtf"
  (\o d -> L.fromStrict . UTF8.fromText <$> writeRTF o d) defaultRespOptions

respondRST :: String -> Pandoc -> Handler
respondRST = respondS "rst" "text/plain; charset=utf-8" ""
  writeRST defaultRespOptions{writerReferenceLinks = True}

respondMarkdown :: String -> Pandoc -> Handler
respondMarkdown = respondS "markdown" "text/plain; charset=utf-8" ""
  writeMarkdown defaultRespOptions{writerReferenceLinks = True}

respondCommonMark :: String -> Pandoc -> Handler
respondCommonMark = respondS "commonmark" "text/plain; charset=utf-8" ""
  writeCommonMark defaultRespOptions{writerReferenceLinks = True}

respondPlain :: String -> Pandoc -> Handler
respondPlain = respondS "plain" "text/plain; charset=utf-8" ""
  writePlain defaultRespOptions

respondMan :: String -> Pandoc -> Handler
respondMan = respondS "man" "text/plain; charset=utf-8" ""
  writeMan defaultRespOptions

respondTexinfo :: String -> Pandoc -> Handler
respondTexinfo = respondS "texinfo" "application/x-texinfo" "texi"
  writeTexinfo defaultRespOptions

respondDocbook :: String -> Pandoc -> Handler
respondDocbook = respondS "docbook" "application/docbook+xml" "xml"
  writeDocbook5 defaultRespOptions

respondOrg :: String -> Pandoc -> Handler
respondOrg = respondS "org" "text/plain; charset=utf-8" ""
  writeOrg defaultRespOptions

respondICML :: String -> Pandoc -> Handler
respondICML = respondX "icml" "application/xml; charset=utf-8" ""
              (\o d -> L.fromStrict . UTF8.fromText <$> writeICML o d)
                         defaultRespOptions

respondTextile :: String -> Pandoc -> Handler
respondTextile = respondS "textile" "text/plain; charset=utf-8" ""
  writeTextile defaultRespOptions

respondAsciiDoc :: String -> Pandoc -> Handler
respondAsciiDoc = respondS "asciidoc" "text/plain; charset=utf-8" ""
  writeAsciiDoc defaultRespOptions

respondMediaWiki :: String -> Pandoc -> Handler
respondMediaWiki = respondS "mediawiki" "text/plain; charset=utf-8" ""
  writeMediaWiki defaultRespOptions

respondODT :: String -> Pandoc -> Handler
respondODT = respondX "opendocument" "application/vnd.oasis.opendocument.text"
              "odt" writeODT defaultRespOptions

respondEPUB :: String -> Pandoc -> Handler
respondEPUB = respondX "html" "application/epub+zip" "epub" writeEPUB3
               defaultRespOptions

respondDocx :: String -> Pandoc -> Handler
respondDocx = respondX "native"
  "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
  "docx" writeDocx defaultRespOptions

respondPDF :: Bool -> String -> Pandoc -> Handler
respondPDF useBeamer page old_pndc = fixURLs page old_pndc >>= \pndc -> do
  cfg <- getConfig
  unless (pdfExport cfg) $ error "PDF export disabled"
  let cacheName = pathForPage page (defaultExtension cfg) ++ ".export.pdf"
  cached <- if useCache cfg
               then lookupCache cacheName
               else return Nothing
  pdf' <- case cached of
            Just (_modtime, bs) -> return $ Right $ L.fromChunks [bs]
            Nothing -> liftIO $
                withSystemTempDirectory "gitit" $ \tmpdir -> do
              let toc = tableOfContents cfg
              -- ensure that LaTeX \include commands can't include
              -- files outside the working directory, e.g. /etc/passwd:
              writeFile (tmpdir </> "texmf.cnf")
                "openout_any = p\nopenin_any = p\n"
              setEnv "TEXMFCNF" (tmpdir ++ ":")
              res <- runIO $ do
                setUserDataDir $ pandocUserData cfg
                setInputFiles [baseUrl cfg]
                let templ = if useBeamer then "beamer" else "latex"
                compiledTemplate <- compileDefaultTemplate templ
                makePDF "pdflatex" [] (if useBeamer then writeBeamer else writeLaTeX)
                  defaultRespOptions{ writerTemplate = Just compiledTemplate
                                    , writerTableOfContents = toc } pndc
              either (liftIO . throwIO) return res

  case pdf' of
       Left logOutput' -> simpleErrorHandler ("PDF creation failed:\n"
                           ++ UTF8.toStringLazy logOutput')
       Right pdfBS -> do
              case cached of
                Nothing ->
                     cacheContents cacheName $ B.concat . L.toChunks $ pdfBS
                _ -> return ()
              ok $ setContentType "application/pdf" $ setFilename (page ++ ".pdf") $
                        (toResponse noHtml) {rsBody = pdfBS}

fixURLs :: String -> Pandoc -> GititServerPart Pandoc
fixURLs page pndc = do
    cfg <- getConfig
    defaultStatic <- liftIO $ getDataFileName $ "data" </> "static"

    let static = staticDir cfg
    let repoPath = repositoryPath cfg

    let go (Image attr ils (url, title)) = do
           fixedURL <- fixURL $ T.unpack url
           return $ Image attr ils (T.pack fixedURL, title)
        go x                        = return x

        fixURL ('/':url) = resolve url
        fixURL url       = resolve $ takeDirectory page </> url

        resolve p = do
           sp <- doesFileExist $ static </> p
           dsp <- doesFileExist $ defaultStatic </> p
           return (if sp then static </> p
                   else (if dsp then defaultStatic </> p
                         else repoPath </> p))
    liftIO $ bottomUpM go pndc

exportFormats :: Config -> [(String, String -> Pandoc -> Handler)]
exportFormats cfg = if pdfExport cfg
                       then ("PDF", respondPDF False) :
                            ("Beamer", respondPDF True) :
                            rest
                       else rest
   where rest = [ ("LaTeX",     respondLaTeX)     -- (description, writer)
                , ("ConTeXt",   respondConTeXt)
                , ("Texinfo",   respondTexinfo)
                , ("reST",      respondRST)
                , ("Markdown",  respondMarkdown)
                , ("CommonMark",respondCommonMark)
                , ("Plain text",respondPlain)
                , ("MediaWiki", respondMediaWiki)
                , ("Org-mode",  respondOrg)
                , ("ICML",      respondICML)
                , ("Textile",   respondTextile)
                , ("AsciiDoc",  respondAsciiDoc)
                , ("Man page",  respondMan)
                , ("DocBook",   respondDocbook)
                , ("DZSlides",  respondSlides "dzslides" writeDZSlides)
                , ("Slidy",     respondSlides "slidy" writeSlidy)
                , ("S5",        respondSlides "s5" writeS5)
                , ("EPUB",      respondEPUB)
                , ("ODT",       respondODT)
                , ("DOCX",      respondDocx)
                , ("RTF",       respondRTF) ]

pygmentsCss :: String
pygmentsCss = styleToCss pygments
                      , exportPage
        exportPage, showHighlightedSource, preview, applyPreCommitPlugins)
import Network.Gitit.Export (exportFormats)
import Data.Maybe (isNothing, isJust, fromJust)
                   T.setAttribute "exportbox"
                       (renderHtmlFragment $  exportBox base' cfg page rev) .
exportBox :: String -> Config -> String -> Maybe String -> Html
exportBox base' cfg page rev | not (isSourceCode page) =
  gui (base' ++ urlForPage page) ! [identifier "exportbox"] <<
    ([ textfield "revision" ! [thestyle "display: none;",
         value (fromJust rev)] | isJust rev ] ++
     [ select ! [name "format"] <<
         map ((\f -> option ! [value f] << f) . fst) (exportFormats cfg)
     , primHtmlChar "nbsp"
     , submit "export" "Export" ])
exportBox _ _ _ _ = noHtml

  -- | Allow PDF export?
  pdfExport            :: Bool,
               where commandList = ["update", "cancel", "export"]
Fixed code: DocBook, or Emacs Org-mode markup can also be used.  Gitit can
be configured to display TeX math (using [texmath]) and
highlighted source code (using [highlighting-kate]).
By default, rendered pages, and highlighted source files
# customizations.  If no directory is
  when exists $ liftIO $ liftIO $ removeFile target
        showHighlightedSource, preview, applyPreCommitPlugins)
import Data.Maybe (isNothing)
               where commandList = ["update", "cancel"]