{-# LANGUAGE ScopedTypeVariables, OverloadedStrings, ViewPatterns, RecordWildCards, DeriveFunctor #-}

module General.Web(
    Input(..),
    Output(..), readInput, server, general_web_test
    ) where

import Network.Wai.Handler.Warp hiding (Port, Handle)
import Network.Wai.Handler.WarpTLS

import Action.CmdLine
import Network.Wai.Logger
import Network.Wai
import Control.DeepSeq
import Network.HTTP.Types (parseQuery, decodePathSegments)
import Network.HTTP.Types.Status
import qualified Data.Text as Text
import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Lazy.Char8 as LBS
import Data.List.Extra
import Data.Aeson.Encoding
import Data.Char
import Data.String
import Data.Tuple.Extra
import Data.Maybe
import Data.Monoid
import System.FilePath
import Control.Exception.Extra
import System.Time.Extra
import General.Log
import General.Util
import Prelude
import qualified Data.ByteString.UTF8 as UTF8


data Input = Input
    {Input -> [String]
inputURL :: [String]
    ,Input -> [(String, String)]
inputArgs :: [(String, String)]
    } deriving (Input -> Input -> Bool
(Input -> Input -> Bool) -> (Input -> Input -> Bool) -> Eq Input
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Input -> Input -> Bool
$c/= :: Input -> Input -> Bool
== :: Input -> Input -> Bool
$c== :: Input -> Input -> Bool
Eq, Int -> Input -> ShowS
[Input] -> ShowS
Input -> String
(Int -> Input -> ShowS)
-> (Input -> String) -> ([Input] -> ShowS) -> Show Input
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Input] -> ShowS
$cshowList :: [Input] -> ShowS
show :: Input -> String
$cshow :: Input -> String
showsPrec :: Int -> Input -> ShowS
$cshowsPrec :: Int -> Input -> ShowS
Show)

readInput :: String -> Maybe Input
readInput :: String -> Maybe Input
readInput (String -> String -> (String, String)
forall a. Eq a => [a] -> [a] -> ([a], [a])
breakOn String
"?" -> (String
a,String
b)) =
  if ([String] -> Bool
badPath [String]
path Bool -> Bool -> Bool
|| [(String, String)] -> Bool
forall b. [(String, b)] -> Bool
badArgs [(String, String)]
args) then Maybe Input
forall a. Maybe a
Nothing else Input -> Maybe Input
forall a. a -> Maybe a
Just (Input -> Maybe Input) -> Input -> Maybe Input
forall a b. (a -> b) -> a -> b
$ [String] -> [(String, String)] -> Input
Input [String]
path [(String, String)]
args
  where
    path :: [String]
path = String -> [String]
parsePath String
a
    parsePath :: String -> [String]
parsePath = (Text -> String) -> [Text] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map Text -> String
Text.unpack
              ([Text] -> [String]) -> (String -> [Text]) -> String -> [String]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> [Text]
decodePathSegments
              (ByteString -> [Text])
-> (String -> ByteString) -> String -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> ByteString
BS.pack
    -- Note that there is a difference between URL paths
    -- which split on / and only that and file paths where
    -- an escaped %2f is equivalent to /. decodePathSegments
    -- (correctly) only considers the former so here
    -- we add an extra check that the result (which has unescaped %2f to /)
    -- does not contain path separators.
    badPath :: [String] -> Bool
badPath = (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any String -> Bool
forall (t :: * -> *). Foldable t => t Char -> Bool
badSegment ([String] -> Bool) -> ([String] -> [String]) -> [String] -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String -> Bool) -> [String] -> [String]
forall a. (a -> Bool) -> [a] -> [a]
filter (String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= String
"")
    badSegment :: t Char -> Bool
badSegment t Char
seg = (Char -> Bool) -> t Char -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'.') t Char
seg Bool -> Bool -> Bool
|| (Char -> Bool) -> t Char -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Char -> Bool
isPathSeparator t Char
seg
    args :: [(String, String)]
args = String -> [(String, String)]
parseArgs String
b
    parseArgs :: String -> [(String, String)]
parseArgs = ((ByteString, Maybe ByteString) -> (String, String))
-> [(ByteString, Maybe ByteString)] -> [(String, String)]
forall a b. (a -> b) -> [a] -> [b]
map (ByteString -> String
UTF8.toString (ByteString -> String)
-> (Maybe ByteString -> String)
-> (ByteString, Maybe ByteString)
-> (String, String)
forall a a' b b'. (a -> a') -> (b -> b') -> (a, b) -> (a', b')
*** String -> (ByteString -> String) -> Maybe ByteString -> String
forall b a. b -> (a -> b) -> Maybe a -> b
maybe String
"" ByteString -> String
UTF8.toString)
              ([(ByteString, Maybe ByteString)] -> [(String, String)])
-> (String -> [(ByteString, Maybe ByteString)])
-> String
-> [(String, String)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> [(ByteString, Maybe ByteString)]
parseQuery
              (ByteString -> [(ByteString, Maybe ByteString)])
-> (String -> ByteString)
-> String
-> [(ByteString, Maybe ByteString)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> ByteString
UTF8.fromString
    badArgs :: [(String, b)] -> Bool
badArgs = Bool -> Bool
not (Bool -> Bool) -> ([(String, b)] -> Bool) -> [(String, b)] -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((String, b) -> Bool) -> [(String, b)] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all ((Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isLower (String -> Bool) -> ((String, b) -> String) -> (String, b) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String, b) -> String
forall a b. (a, b) -> a
fst)

data Output
    = OutputText LBS.ByteString
    | OutputHTML LBS.ByteString
    | OutputJavascript LBS.ByteString
    | OutputJSON Encoding
    | OutputFail LBS.ByteString
    | OutputFile FilePath
      deriving Int -> Output -> ShowS
[Output] -> ShowS
Output -> String
(Int -> Output -> ShowS)
-> (Output -> String) -> ([Output] -> ShowS) -> Show Output
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Output] -> ShowS
$cshowList :: [Output] -> ShowS
show :: Output -> String
$cshow :: Output -> String
showsPrec :: Int -> Output -> ShowS
$cshowsPrec :: Int -> Output -> ShowS
Show

-- | Force all the output (no delayed exceptions) and produce bytestrings
forceBS :: Output -> LBS.ByteString
forceBS :: Output -> ByteString
forceBS (OutputText ByteString
x) = ByteString -> ByteString
forall a. NFData a => a -> a
force ByteString
x
forceBS (OutputJSON Encoding
x) = ByteString -> ByteString
forall a. NFData a => a -> a
force (ByteString -> ByteString) -> ByteString -> ByteString
forall a b. (a -> b) -> a -> b
$ Encoding -> ByteString
forall a. Encoding' a -> ByteString
encodingToLazyByteString Encoding
x
forceBS (OutputHTML ByteString
x) = ByteString -> ByteString
forall a. NFData a => a -> a
force ByteString
x
forceBS (OutputJavascript ByteString
x) = ByteString -> ByteString
forall a. NFData a => a -> a
force ByteString
x
forceBS (OutputFail ByteString
x) = ByteString -> ByteString
forall a. NFData a => a -> a
force ByteString
x
forceBS (OutputFile String
x) = String -> ()
forall a. NFData a => a -> ()
rnf String
x () -> ByteString -> ByteString
`seq` ByteString
LBS.empty

instance NFData Output where
    rnf :: Output -> ()
rnf Output
x = Output -> ByteString
forceBS Output
x ByteString -> () -> ()
`seq` ()

server :: Log -> CmdLine -> (Input -> IO Output) -> IO ()
server :: Log -> CmdLine -> (Input -> IO Output) -> IO ()
server Log
log Server{Bool
Int
String
Maybe String
Language
no_security_headers :: CmdLine -> Bool
datadir :: CmdLine -> Maybe String
key :: CmdLine -> String
cert :: CmdLine -> String
https :: CmdLine -> Bool
host :: CmdLine -> String
home :: CmdLine -> String
scope :: CmdLine -> String
links :: CmdLine -> Bool
local :: CmdLine -> Bool
logs :: CmdLine -> String
cdn :: CmdLine -> String
port :: CmdLine -> Int
haddock :: CmdLine -> Maybe String
language :: CmdLine -> Language
database :: CmdLine -> String
no_security_headers :: Bool
datadir :: Maybe String
key :: String
cert :: String
https :: Bool
host :: String
home :: String
scope :: String
language :: Language
links :: Bool
haddock :: Maybe String
local :: Bool
logs :: String
cdn :: String
database :: String
port :: Int
..} Input -> IO Output
act = do
    let
        host' :: HostPreference
host' = String -> HostPreference
forall a. IsString a => String -> a
fromString (String -> HostPreference) -> String -> HostPreference
forall a b. (a -> b) -> a -> b
$
                  if String
host String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"" then
                    if Bool
local then
                      String
"127.0.0.1"
                    else
                      String
"*"
                  else
                    String
host
        set :: Settings
set = (SomeException -> Response) -> Settings -> Settings
setOnExceptionResponse SomeException -> Response
exceptionResponseForDebug
            (Settings -> Settings)
-> (Settings -> Settings) -> Settings -> Settings
forall b c a. (b -> c) -> (a -> b) -> a -> c
. HostPreference -> Settings -> Settings
setHost HostPreference
host'
            (Settings -> Settings)
-> (Settings -> Settings) -> Settings -> Settings
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Settings -> Settings
setPort Int
port (Settings -> Settings) -> Settings -> Settings
forall a b. (a -> b) -> a -> b
$
            Settings
defaultSettings
        runServer :: Application -> IO ()
        runServer :: Application -> IO ()
runServer = if Bool
https then TLSSettings -> Settings -> Application -> IO ()
runTLS (String -> String -> TLSSettings
tlsSettings String
cert String
key) Settings
set
                             else Settings -> Application -> IO ()
runSettings Settings
set
        secH :: [(HeaderName, ByteString)]
secH = if Bool
no_security_headers then []
                                      else [
             -- The CSP is giving additional instructions to the browser.
             (HeaderName
"Content-Security-Policy",
              -- For any content type not specifically enumerated in this CSP
              -- (e.g. fonts), the only valid origin is the same as the current
              -- page.
              ByteString
"default-src 'self';"
              -- As an exception to the default rule, allow scripts from jquery
              -- and the CDN.
              ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
" script-src 'self' https://code.jquery.com/ https://rawcdn.githack.com;"
              -- As an exception to the default rule, allow stylesheets from
              -- the CDN. TODO: for now, we are also enabling inline styles,
              -- because it the chosen plugin uses them.
              ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
" style-src 'self' 'unsafe-inline' https://rawcdn.githack.com;"
              -- As an exception to the default rule, allow images from the
              -- CDN.
              ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
" img-src 'self' https://rawcdn.githack.com;"
              -- Only allow this request in an iframe if the containing page
              -- has the same origin.
              ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
" frame-ancestors 'self';"
              -- Forms are only allowed to target addresses under the same
              -- origin as the page.
              ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
" form-action 'self';"
              -- Any request originating from this page and specifying http as
              -- its protocol will be automatically upgraded to https.
              ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
" upgrade-insecure-requests;"
              -- Do not display http content if the page was loaded under
              -- https.
              ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
" block-all-mixed-content"),

             -- Tells the browser this web page should not be rendered inside a
             -- frame, except if the framing page comes from the same origin
             -- (i.e. DNS name + port). This is to thwart invisible, keylogging
             -- framing pages.
             (HeaderName
"X-Frame-Options", ByteString
"sameorigin"),

             -- Tells browsers to trust the Content-Type header and not try to
             -- otherwise guess at response types. In particular, prevents
             -- dangerous browser behaviour that would execute a file loaded
             -- from a <script> or <style> tag despite not having a
             -- text/javascript or text/css Content-Type.
             (HeaderName
"X-Content-Type-Options", ByteString
"nosniff"),

             -- Browser should try to detect "reflected" XSS attacks, where
             -- some suspicious payload of the request appears in the response.
             -- How browsers do that is unspecified. On detection, browser
             -- should block the page from rendering at all.
             (HeaderName
"X-XSS-Protection", ByteString
"1; mode=block"),

             -- Do not include referrer information if user-agent generates a
             -- request from an HTTPS page to an HTTP one. Note: this is
             -- technically redundant as this should be the browser default
             -- behaviour.
             (HeaderName
"Referrer-Policy", ByteString
"no-referrer-when-downgrade"),

             -- Strict Transport Security (aka HSTS) tells the browser that,
             -- from now on and until max-age seconds have passed, it should
             -- never try to connect to this domain name through unprotected
             -- HTTP. The browser will automatically upgrade any HTTP request
             -- to this domain name to HTTPS, client side, before any network
             -- call happens.
             (HeaderName
"Strict-Transport-Security", ByteString
"max-age=31536000; includeSubDomains")]

    Log -> String -> IO ()
logAddMessage Log
log (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ String
"Server starting on port " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show Int
port String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" and host/IP " String -> ShowS
forall a. [a] -> [a] -> [a]
++ HostPreference -> String
forall a. Show a => a -> String
show HostPreference
host'

    Application -> IO ()
runServer (Application -> IO ()) -> Application -> IO ()
forall a b. (a -> b) -> a -> b
$ \Request
req Response -> IO ResponseReceived
reply -> do
        let pq :: String
pq = ByteString -> String
BS.unpack (ByteString -> String) -> ByteString -> String
forall a b. (a -> b) -> a -> b
$ Request -> ByteString
rawPathInfo Request
req ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> Request -> ByteString
rawQueryString Request
req
        String -> IO ()
putStrLn String
pq
        (Seconds
time, Either String (Output, ByteString)
res) <- IO (Either String (Output, ByteString))
-> IO (Seconds, Either String (Output, ByteString))
forall (m :: * -> *) a. MonadIO m => m a -> m (Seconds, a)
duration (IO (Either String (Output, ByteString))
 -> IO (Seconds, Either String (Output, ByteString)))
-> IO (Either String (Output, ByteString))
-> IO (Seconds, Either String (Output, ByteString))
forall a b. (a -> b) -> a -> b
$ case String -> Maybe Input
readInput String
pq of
            Maybe Input
Nothing -> Either String (Output, ByteString)
-> IO (Either String (Output, ByteString))
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either String (Output, ByteString)
 -> IO (Either String (Output, ByteString)))
-> Either String (Output, ByteString)
-> IO (Either String (Output, ByteString))
forall a b. (a -> b) -> a -> b
$ (Output, ByteString) -> Either String (Output, ByteString)
forall a b. b -> Either a b
Right (ByteString -> Output
OutputFail ByteString
"", String -> ByteString
LBS.pack (String -> ByteString) -> String -> ByteString
forall a b. (a -> b) -> a -> b
$ String
"Bad URL: " String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
pq)
            Just Input
pay ->
                (SomeException -> IO (Either String (Output, ByteString)))
-> IO (Either String (Output, ByteString))
-> IO (Either String (Output, ByteString))
forall a. (SomeException -> IO a) -> IO a -> IO a
handle_ ((String -> Either String (Output, ByteString))
-> IO String -> IO (Either String (Output, ByteString))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap String -> Either String (Output, ByteString)
forall a b. a -> Either a b
Left (IO String -> IO (Either String (Output, ByteString)))
-> (SomeException -> IO String)
-> SomeException
-> IO (Either String (Output, ByteString))
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SomeException -> IO String
forall e. Show e => e -> IO String
showException) (IO (Either String (Output, ByteString))
 -> IO (Either String (Output, ByteString)))
-> IO (Either String (Output, ByteString))
-> IO (Either String (Output, ByteString))
forall a b. (a -> b) -> a -> b
$ do
                    Output
s <- Input -> IO Output
act Input
pay; ByteString
bs <- ByteString -> IO ByteString
forall a. a -> IO a
evaluate (ByteString -> IO ByteString) -> ByteString -> IO ByteString
forall a b. (a -> b) -> a -> b
$ Output -> ByteString
forceBS Output
s; Either String (Output, ByteString)
-> IO (Either String (Output, ByteString))
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either String (Output, ByteString)
 -> IO (Either String (Output, ByteString)))
-> Either String (Output, ByteString)
-> IO (Either String (Output, ByteString))
forall a b. (a -> b) -> a -> b
$ (Output, ByteString) -> Either String (Output, ByteString)
forall a b. b -> Either a b
Right (Output
s, ByteString
bs)
        Log -> String -> String -> Seconds -> Maybe String -> IO ()
logAddEntry Log
log (SockAddr -> String
showSockAddr (SockAddr -> String) -> SockAddr -> String
forall a b. (a -> b) -> a -> b
$ Request -> SockAddr
remoteHost Request
req) String
pq Seconds
time ((String -> Maybe String)
-> ((Output, ByteString) -> Maybe String)
-> Either String (Output, ByteString)
-> Maybe String
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either String -> Maybe String
forall a. a -> Maybe a
Just (Maybe String -> (Output, ByteString) -> Maybe String
forall a b. a -> b -> a
const Maybe String
forall a. Maybe a
Nothing) Either String (Output, ByteString)
res)
        case Either String (Output, ByteString)
res of
            Left String
s -> Response -> IO ResponseReceived
reply (Response -> IO ResponseReceived)
-> Response -> IO ResponseReceived
forall a b. (a -> b) -> a -> b
$ Status -> [(HeaderName, ByteString)] -> ByteString -> Response
responseLBS Status
status500 [] (ByteString -> Response) -> ByteString -> Response
forall a b. (a -> b) -> a -> b
$ String -> ByteString
LBS.pack String
s
            Right (Output
v, ByteString
bs) -> Response -> IO ResponseReceived
reply (Response -> IO ResponseReceived)
-> Response -> IO ResponseReceived
forall a b. (a -> b) -> a -> b
$ case Output
v of
                OutputFile String
file -> Status
-> [(HeaderName, ByteString)]
-> String
-> Maybe FilePart
-> Response
responseFile Status
status200
                    ([(HeaderName
"content-type",ByteString
c) | Just ByteString
c <- [String -> [(String, ByteString)] -> Maybe ByteString
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup (ShowS
takeExtension String
file) [(String, ByteString)]
contentType]] [(HeaderName, ByteString)]
-> [(HeaderName, ByteString)] -> [(HeaderName, ByteString)]
forall a. [a] -> [a] -> [a]
++ [(HeaderName, ByteString)]
secH) String
file Maybe FilePart
forall a. Maybe a
Nothing
                OutputText{} -> Status -> [(HeaderName, ByteString)] -> ByteString -> Response
responseLBS Status
status200 ((HeaderName
"content-type",ByteString
"text/plain") (HeaderName, ByteString)
-> [(HeaderName, ByteString)] -> [(HeaderName, ByteString)]
forall a. a -> [a] -> [a]
: [(HeaderName, ByteString)]
secH) ByteString
bs
                OutputJSON{} -> Status -> [(HeaderName, ByteString)] -> ByteString -> Response
responseLBS Status
status200 ((HeaderName
"content-type",ByteString
"application/json") (HeaderName, ByteString)
-> [(HeaderName, ByteString)] -> [(HeaderName, ByteString)]
forall a. a -> [a] -> [a]
: (HeaderName
"access-control-allow-origin",ByteString
"*") (HeaderName, ByteString)
-> [(HeaderName, ByteString)] -> [(HeaderName, ByteString)]
forall a. a -> [a] -> [a]
: [(HeaderName, ByteString)]
secH) ByteString
bs
                OutputFail{} -> Status -> [(HeaderName, ByteString)] -> ByteString -> Response
responseLBS Status
status400 ((HeaderName
"content-type",ByteString
"text/plain") (HeaderName, ByteString)
-> [(HeaderName, ByteString)] -> [(HeaderName, ByteString)]
forall a. a -> [a] -> [a]
: [(HeaderName, ByteString)]
secH) ByteString
bs
                OutputHTML{} -> Status -> [(HeaderName, ByteString)] -> ByteString -> Response
responseLBS Status
status200 ((HeaderName
"content-type",ByteString
"text/html") (HeaderName, ByteString)
-> [(HeaderName, ByteString)] -> [(HeaderName, ByteString)]
forall a. a -> [a] -> [a]
: [(HeaderName, ByteString)]
secH) ByteString
bs
                OutputJavascript{} -> Status -> [(HeaderName, ByteString)] -> ByteString -> Response
responseLBS Status
status200 ((HeaderName
"content-type",ByteString
"text/javascript") (HeaderName, ByteString)
-> [(HeaderName, ByteString)] -> [(HeaderName, ByteString)]
forall a. a -> [a] -> [a]
: [(HeaderName, ByteString)]
secH) ByteString
bs

contentType :: [(String, ByteString)]
contentType = [(String
".html",ByteString
"text/html"),(String
".css",ByteString
"text/css"),(String
".js",ByteString
"text/javascript")]

general_web_test :: IO ()
general_web_test :: IO ()
general_web_test = do
    String -> IO () -> IO ()
testing String
"General.Web.readInput" (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ do
        let b
a === :: b -> b -> IO ()
=== b
b = if b
a b -> b -> Bool
forall a. Eq a => a -> a -> Bool
== b
b then Char -> IO ()
putChar Char
'.' else String -> IO ()
forall a. Partial => String -> IO a
errorIO (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ (b, b) -> String
forall a. Show a => a -> String
show (b
a,b
b)
        String -> Maybe Input
readInput String
"abc" Maybe Input -> Maybe Input -> IO ()
forall b. (Eq b, Show b) => b -> b -> IO ()
=== Input -> Maybe Input
forall a. a -> Maybe a
Just ([String] -> [(String, String)] -> Input
Input [String
"abc"] [])
        String -> Maybe Input
readInput String
"/abc" Maybe Input -> Maybe Input -> IO ()
forall b. (Eq b, Show b) => b -> b -> IO ()
=== Input -> Maybe Input
forall a. a -> Maybe a
Just ([String] -> [(String, String)] -> Input
Input [String
"abc"] [])
        String -> Maybe Input
readInput String
"/abc/" Maybe Input -> Maybe Input -> IO ()
forall b. (Eq b, Show b) => b -> b -> IO ()
=== Input -> Maybe Input
forall a. a -> Maybe a
Just ([String] -> [(String, String)] -> Input
Input [String
"abc", String
""] [])
        String -> Maybe Input
readInput String
"abc?ab=cd&ef=gh" Maybe Input -> Maybe Input -> IO ()
forall b. (Eq b, Show b) => b -> b -> IO ()
=== Input -> Maybe Input
forall a. a -> Maybe a
Just ([String] -> [(String, String)] -> Input
Input [String
"abc"] [(String
"ab", String
"cd"), (String
"ef", String
"gh")])
        String -> Maybe Input
readInput String
"%2fabc" Maybe Input -> Maybe Input -> IO ()
forall b. (Eq b, Show b) => b -> b -> IO ()
=== Maybe Input
forall a. Maybe a
Nothing
        String -> Maybe Input
readInput String
"%2F" Maybe Input -> Maybe Input -> IO ()
forall b. (Eq b, Show b) => b -> b -> IO ()
=== Maybe Input
forall a. Maybe a
Nothing
        String -> Maybe Input
readInput String
"def%2fabc" Maybe Input -> Maybe Input -> IO ()
forall b. (Eq b, Show b) => b -> b -> IO ()
=== Maybe Input
forall a. Maybe a
Nothing
        String -> Maybe Input
readInput String
"." Maybe Input -> Maybe Input -> IO ()
forall b. (Eq b, Show b) => b -> b -> IO ()
=== Maybe Input
forall a. Maybe a
Nothing
        String -> Maybe Input
readInput String
".." Maybe Input -> Maybe Input -> IO ()
forall b. (Eq b, Show b) => b -> b -> IO ()
=== Maybe Input
forall a. Maybe a
Nothing
        String -> Maybe Input
readInput String
"..a" Maybe Input -> Maybe Input -> IO ()
forall b. (Eq b, Show b) => b -> b -> IO ()
=== Input -> Maybe Input
forall a. a -> Maybe a
Just ([String] -> [(String, String)] -> Input
Input [String
"..a"] [])
        String -> Maybe Input
readInput String
"../a" Maybe Input -> Maybe Input -> IO ()
forall b. (Eq b, Show b) => b -> b -> IO ()
=== Maybe Input
forall a. Maybe a
Nothing
        String -> Maybe Input
readInput String
"a/../a" Maybe Input -> Maybe Input -> IO ()
forall b. (Eq b, Show b) => b -> b -> IO ()
=== Maybe Input
forall a. Maybe a
Nothing
        String -> Maybe Input
readInput String
"%2e" Maybe Input -> Maybe Input -> IO ()
forall b. (Eq b, Show b) => b -> b -> IO ()
=== Maybe Input
forall a. Maybe a
Nothing
        String -> Maybe Input
readInput String
"%2E" Maybe Input -> Maybe Input -> IO ()
forall b. (Eq b, Show b) => b -> b -> IO ()
=== Maybe Input
forall a. Maybe a
Nothing