diff --git a/src/Config.hs b/src/Config.hs new file mode 100644 index 0000000..5904c76 --- /dev/null +++ b/src/Config.hs @@ -0,0 +1,10 @@ +module Config where + +import System.Directory + +data ViddlConfig = ViddlConfig + { webPort :: Int + , dlDir :: FilePath } + +defConfig :: IO ViddlConfig +defConfig = getTemporaryDirectory >>= pure . ViddlConfig 3000 . (<> "/viddl") diff --git a/src/Helpers.hs b/src/Helpers.hs index 7d666f6..19b4589 100644 --- a/src/Helpers.hs +++ b/src/Helpers.hs @@ -3,8 +3,26 @@ module Helpers where import YTDL +import Config + +import Text.Read (readMaybe) import qualified Data.Text.Lazy as TL -import Network.URI (parseURI) +import Network.URI +import System.Exit + +parseArgs :: ViddlConfig -> [String] -> IO ViddlConfig +parseArgs _ ("-h":_) = exitHelp +parseArgs _ ("--help":_) = exitHelp +parseArgs cfg@(ViddlConfig _ d) ("-p":p':xs) = case (readMaybe p' :: Maybe Int) of + (Just p) -> parseArgs (ViddlConfig p d) xs + Nothing -> parseArgs cfg (p':xs) +parseArgs (ViddlConfig p _) ("-d":d:xs) = parseArgs (ViddlConfig p d) xs +parseArgs cfg (_:xs) = parseArgs cfg xs +parseArgs cfg [] = pure cfg + + +exitHelp :: IO a +exitHelp = putStrLn "viddl [-p port] [-d dir]" >> exitSuccess getRes :: TL.Text -> Maybe Resolution getRes ("144p") = Just P144 @@ -22,7 +40,13 @@ isRes res = case getRes res of (Just _) -> True _ -> False +-- ssrf paranoia +isOkPath :: String -> Bool +isOkPath p = not $ isIPv4address p || isIPv6address p || p == "localhost" + isURL :: TL.Text -> Bool isURL uri = case parseURI (TL.unpack uri) of - (Just _) -> True - _ -> False + (Just u) -> case uriAuthority u of + (Just (URIAuth _ p _)) -> isOkPath p + _ -> False + _ -> False diff --git a/src/Main.hs b/src/Main.hs index 3d5a504..474eead 100644 --- a/src/Main.hs +++ b/src/Main.hs @@ -6,37 +6,46 @@ import Templates.Index import Templates.Error import YTDL import Helpers +import Config -import Control.Monad.IO.Class +import Control.Monad.Trans.Reader +import Control.Monad.Trans.Class import qualified Data.Text.Lazy as TL -import Web.Scotty +import System.Environment +import Web.Scotty.Trans -safeDownloadAction :: ActionM () +type Scotty = ScottyT TL.Text (ReaderT ViddlConfig IO) +type Action = ActionT TL.Text (ReaderT ViddlConfig IO) + +safeDownloadAction :: Action () safeDownloadAction = downloadAction `rescue` (html . errorPage) -downloadAction :: ActionM () +downloadAction :: Action () downloadAction = do url <- param "url" res <- param "resolution" if (isURL url) && (isRes res) then do let (Just res') = getRes res -- safe cause we checked with isRes - ytdlRes <- liftIO $ ytdl (TL.unpack url) res' + ytdlRes <- lift $ ytdl (TL.unpack url) res' case ytdlRes of (Right filePath) -> do setHeader "content-type" "video/mp4" file filePath (Left err) -> html $ errorPage (TL.pack err) - -- liftIO $ ytdlClean ident else html $ errorPage "Invalid input!" -- todo ReaderT -app :: ScottyM () +app :: Scotty () app = do get "/" $ html indexPage get "/video.mp4" safeDownloadAction get "/audio.mp3" safeDownloadAction main :: IO () -main = scotty 3000 app +main = do + args <- getArgs + defCfg <- defConfig + cfg <- parseArgs defCfg args + scottyT (webPort cfg) (\(ReaderT ma) -> ma cfg) app diff --git a/src/Templates/Error.hs b/src/Templates/Error.hs index 47ae1fd..7356fa4 100644 --- a/src/Templates/Error.hs +++ b/src/Templates/Error.hs @@ -5,6 +5,7 @@ module Templates.Error (errorPage) where import qualified Data.Text.Lazy as TL import Text.RawString.QQ +import Text.HTML.SanitizeXSS errorPage :: TL.Text -> TL.Text errorPage msg = [r| @@ -17,7 +18,7 @@ errorPage msg = [r|
|] <> msg <> [r|
+|] <> (TL.fromStrict (sanitize (TL.toStrict msg))) <> [r|
viddl is free open source software and is powered by youtube-dl.