packages feed

cgi 3001.1.6.0 → 3001.1.7.0

raw patch · 6 files changed

+333/−89 lines, 6 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Network.CGI.Protocol: instance Eq HeaderName
- Network.CGI.Protocol: instance Ord HeaderName
- Network.CGI.Protocol: instance Show HeaderName
+ Network.CGI: Charset :: String -> Charset
+ Network.CGI: ContentEncoding :: String -> ContentEncoding
+ Network.CGI: Language :: String -> Language
+ Network.CGI: class (Eq a) => Acceptable a
+ Network.CGI: data Accept a
+ Network.CGI: negotiate :: (Acceptable a) => [a] -> Maybe (Accept a) -> [a]
+ Network.CGI: newtype Charset
+ Network.CGI: newtype ContentEncoding
+ Network.CGI: newtype Language
+ Network.CGI: requestAccept :: (MonadCGI m) => m (Maybe (Accept ContentType))
+ Network.CGI: requestAcceptCharset :: (MonadCGI m) => m (Maybe (Accept Charset))
+ Network.CGI: requestAcceptEncoding :: (MonadCGI m) => m (Maybe (Accept ContentEncoding))
+ Network.CGI: requestAcceptLanguage :: (MonadCGI m) => m (Maybe (Accept Language))

Files

Network/CGI.hs view
@@ -66,6 +66,7 @@   , getInputFilename, getInputContentType   -- * Environment   , getVar, getVarWithDefault, getVars+  -- * Request information   , serverName, serverPort   , requestMethod, pathInfo   , pathTranslated, scriptName@@ -76,6 +77,11 @@   , requestHeader   -- * Program and request URI   , progURI, queryURI, requestURI+  -- * Content negotiation+  , Acceptable, Accept+  , Charset(..), ContentEncoding(..), Language(..)+  , requestAccept, requestAcceptCharset, requestAcceptEncoding, requestAcceptLanguage+  , negotiate   -- * Content type   , ContentType(..), showContentType, parseContentType   -- * Cookies@@ -105,8 +111,8 @@  import Network.CGI.Cookie (Cookie(..), showCookie, newCookie, findCookie) import qualified Network.CGI.Cookie as Cookie (deleteCookie)-import Network.CGI.Header (ContentType(..), -                                  parseContentType, showContentType)+import Network.CGI.Accept+import Network.CGI.Header import Network.CGI.Monad import Network.CGI.Protocol import Network.CGI.Compat@@ -197,9 +203,17 @@ outputError c m es =        do logCGI $ show (c,m,es)          setStatus c m-         setHeader "Content-type" "text/html; charset=ISO-8859-1"-         page <- errorPage c m es -         output $ renderHtml page+         let textType = ContentType "text" "plain" [("charset","ISO-8859-1")]+             htmlType = ContentType "text" "html"  [("charset","ISO-8859-1")]+         cts <- liftM (negotiate [htmlType,textType]) requestAccept+         case cts of+           ct:_ | ct == textType -> +                do setHeader "Content-type" (showContentType textType)+                   text <- errorText c m es+                   output text+           _ -> do setHeader "Content-type" (showContentType htmlType)+                   page <- errorPage c m es +                   output $ renderHtml page  -- | Create an HTML error page. errorPage :: MonadCGI m => @@ -219,6 +233,13 @@                   +++ body << (h1 << tit +++ map (paragraph <<) es                                 +++ hr +++ address << sig) +errorText :: MonadCGI m => +             Int      -- ^ Status code+          -> String   -- ^ Status message+          -> [String] -- ^ Error information+          -> m String+errorText c m es = return $ unlines $ (show c ++ " " ++ m) : es+ -- -- * Specific HTTP errors --@@ -269,6 +290,9 @@            m [(String,String)] getVars = liftM Map.toList $ cgiGet cgiVars +--+-- * Request information+--  -- | The server\'s hostname, DNS alias, or IP address as it would  --   appear in self-referencing URLs.@@ -366,6 +390,24 @@ requestHeader name = getVar var   where var = "HTTP_" ++ map toUpper (replace '-' '_' name) +--+-- * Content negotiation+--++requestHeaderValue :: (MonadCGI m, HeaderValue a) => String -> m (Maybe a)+requestHeaderValue h = liftM (>>= parseM parseHeaderValue h) $ requestHeader h++requestAccept :: MonadCGI m => m (Maybe (Accept ContentType))+requestAccept = requestHeaderValue "Accept"++requestAcceptCharset :: MonadCGI m => m (Maybe (Accept Charset))+requestAcceptCharset = requestHeaderValue "Accept-Charset"++requestAcceptEncoding :: MonadCGI m => m (Maybe (Accept ContentEncoding))+requestAcceptEncoding = requestHeaderValue "Accept-Encoding"++requestAcceptLanguage :: MonadCGI m => m (Maybe (Accept Language))+requestAcceptLanguage = requestHeaderValue "Accept-Language"  -- -- * Program and request URI
+ Network/CGI/Accept.hs view
@@ -0,0 +1,176 @@+module Network.CGI.Accept (+  -- * Accept-X headers+    Acceptable+  , Accept+  , Charset(..), ContentEncoding(..), Language(..)+  -- * Content negotiation+  , negotiate+                          ) where++import Control.Monad+import Data.Char+import Data.Function+import Data.List+import Data.Maybe+import Numeric++import Text.ParserCombinators.Parsec++import Network.CGI.Header+++-- +-- * Accept-X headers+--++newtype Accept a = Accept [(a, Quality)]+    deriving (Show)++type Quality = Double++-- A bounded join-semilattice+class Eq a => Acceptable a where+    includes :: a -> a -> Bool+    top :: a++instance HeaderValue a => HeaderValue (Accept a) where+    parseHeaderValue = fmap Accept $ sepBy p (lexeme (char ','))+        where p = do a <- parseHeaderValue+                     q <- option 1 $ do lexeme $ char ';'+                                        lexeme $ char 'q'+                                        lexeme $ char '='+                                        lexeme pQuality+                     return (a,q)+              pQuality = (char '0' >> option "0" (char '.' >> many digit) >>= \ds -> return (read ("0." ++ ds ++ "0")))+                         <|> (char '1' >> optional (char '.' >> many (char '0')) >> return 1)+    prettyHeaderValue (Accept xs) = concat $ intersperse ", " [prettyHeaderValue a ++ "; q=" ++ showQuality q | (a,q) <- xs]+        where showQuality q = showFFloat (Just 3) q ""++starOrEqualTo :: String -> String -> Bool+starOrEqualTo x y = x == "*" || x == y+++negotiate :: Acceptable a => [a] -> Maybe (Accept a) -> [a]+negotiate ys Nothing = ys+negotiate ys (Just xs) = reverse [ z | (q,z) <- sortBy (compare `on` fst) [ (quality xs y,y) | y <- ys], q > 0]++--testNegotiate :: (HeaderValue a, Acceptable a) => [String] -> String -> [a]+--testNegotiate ts a = negotiate [t | Just t <- map (parseM parseHeaderValue "<source>") ts] (parseM parseHeaderValue "<source>" a)++quality :: Acceptable a => Accept a -> a -> Quality+quality (Accept xs) y = fromMaybe 0 $ listToMaybe $ sort $ map snd $ sortBy (compareSpecificity `on` fst) $ filter ((`includes` y) . fst) xs++compareSpecificity :: Acceptable a => a -> a -> Ordering+compareSpecificity x y +    | x `includes` y && y `includes` x = EQ+    | x `includes` y = GT+    | y `includes` x = LT+    | otherwise = error "Non-comparable Acceptables"++--+-- ** Accept+--++instance Acceptable ContentType where+    includes x y = ctType x `starOrEqualTo` ctType y+                   && ctSubtype x `starOrEqualTo` ctSubtype y+                   && all (hasParameter y) (ctParameters x)+    top = ContentType "*" "*" []++hasParameter :: ContentType -> (String, String) -> Bool+hasParameter t (k,v) = maybe False (==v) $ lookup k (ctParameters t)++--+-- ** Accept-Charset+--++{-+RFC 2616 14.2:++The special value "*", if present in the Accept-Charset field, matches+every character set (including ISO-8859-1) which is not mentioned+elsewhere in the Accept-Charset field. If no "*" is present in an+Accept-Charset field, then all character sets not explicitly mentioned+get a quality value of 0, except for ISO-8859-1, which gets a quality+value of 1 if not explicitly mentioned.++If no Accept-Charset header is present, the default is that any+character set is acceptable. If an Accept-Charset header is present,+and if the server cannot send a response which is acceptable according+to the Accept-Charset header, then the server SHOULD send an error+response with the 406 (not acceptable) status code, though the sending+of an unacceptable response is also allowed.+-}++newtype Charset = Charset String+    deriving (Show)++instance Eq Charset where+    Charset x == Charset y = caseInsensitiveEq x y++instance Ord Charset where+    Charset x `compare` Charset y = caseInsensitiveCompare x y++instance HeaderValue Charset where+    parseHeaderValue = fmap Charset $ many ws1 >> lexeme p_token+    prettyHeaderValue (Charset s) = s++instance Acceptable Charset where+    Charset x `includes` Charset y = starOrEqualTo x y+    top = Charset "*"++--+-- ** Accept-Encoding+--++{-+RFC 2616, section 14.3+-}++newtype ContentEncoding = ContentEncoding String+    deriving (Show)++instance Eq ContentEncoding where+    ContentEncoding x == ContentEncoding y = caseInsensitiveEq x y++instance Ord ContentEncoding where+    ContentEncoding x `compare` ContentEncoding y = caseInsensitiveCompare x y++instance HeaderValue ContentEncoding where+    parseHeaderValue = fmap ContentEncoding $ many ws1 >> lexeme p_token+    prettyHeaderValue (ContentEncoding s) = s++instance Acceptable ContentEncoding where+    ContentEncoding x `includes` ContentEncoding y = starOrEqualTo x y+    top = ContentEncoding "*"++--+-- ** Accept-Language+--++newtype Language = Language String+    deriving (Show)++instance Eq Language where+    Language x == Language y = caseInsensitiveEq x y++instance Ord Language where+    Language x `compare` Language y = caseInsensitiveCompare x y++instance HeaderValue Language where+    parseHeaderValue = fmap Language $ many ws1 >> lexeme p_token+    prettyHeaderValue (Language s) = s++{-+RFC 2616 14.4++A language-range matches a language-tag if it exactly equals the tag,+or if it exactly equals a prefix of the tag such that the first tag+character following the prefix is "-". The special range "*", if+present in the Accept-Language field, matches every tag not matched by+any other range present in the Accept-Language field.+-}+instance Acceptable Language where+    Language x `includes` Language y =+        x == "*" || x == y || (x `isPrefixOf` y && "-" `isPrefixOf` drop (length x) y)+    top = Language "*"
Network/CGI/Header.hs view
@@ -17,45 +17,66 @@ ----------------------------------------------------------------------------- module Network.CGI.Header (                               -- * Headers-                              Header, -                              pHeader,+                              Headers,+                              HeaderName(..),+                              HeaderValue(..),                               pHeaders,-                              parseHeaders,                                -- * Content-type                               ContentType(..),                                getContentType,                               parseContentType,-                              pContentType,                               showContentType,                                -- * Content-transfer-encoding                               ContentTransferEncoding(..),                               getContentTransferEncoding,-                              parseContentTransferEncoding,                                -- * Content-disposition                               ContentDisposition(..),                               getContentDisposition,                           -                              parseContentDisposition,-                              showContentDisposition,                                                              -- * Utilities-                              parseM) where+                              parseM,+                              caseInsensitiveEq,+                              caseInsensitiveCompare,+                              lexeme, ws1, p_token+                          ) where +import Control.Monad import Data.Char+import Data.Function import Data.List+import Data.Maybe+import Data.Monoid+ import Text.ParserCombinators.Parsec -type Header = (String, String)+--+-- * Headers+-- -pHeaders :: Parser [Header]-pHeaders = many pHeader+-- | HTTP headers.+type Headers = [(HeaderName, String)] -parseHeaders :: Monad m => SourceName -> String -> m [Header]-parseHeaders s inp = parseM pHeaders s inp+-- | A string with case insensitive equality and comparisons.+newtype HeaderName = HeaderName String deriving (Show) -pHeader :: Parser Header+instance Eq HeaderName where+    HeaderName x == HeaderName y = map toLower x == map toLower y++instance Ord HeaderName where+    HeaderName x `compare` HeaderName y = map toLower x `compare` map toLower y+++class HeaderValue a where+    parseHeaderValue :: Parser a+    prettyHeaderValue :: a -> String++pHeaders :: Parser Headers+pHeaders = many pHeader++pHeader :: Parser (HeaderName, String) pHeader =      do name <- many1 headerNameChar        char ':'@@ -63,7 +84,7 @@        line <- lineString        crLf        extraLines <- many extraFieldLine-       return (map toLower name, concat (line:extraLines))+       return (HeaderName name, concat (line:extraLines))  extraFieldLine :: Parser String extraFieldLine = @@ -72,6 +93,9 @@        crLf        return (sp:line) +getHeaderValue :: (Monad m, HeaderValue a) => String -> Headers -> m a+getHeaderValue h hs = lookupM (HeaderName h) hs >>= parseM parseHeaderValue h+ -- -- * Parameters (for Content-type etc.) --@@ -85,9 +109,20 @@                 | otherwise = [c]  p_parameter :: Parser (String,String)-p_parameter =+p_parameter = try $   do lexeme $ char ';'      p_name <- lexeme $ p_token+     -- Don't allow parameters named q. This is needed for parsing Accept-X +     -- headers. From RFC 2616 14.1:+     --    Note: Use of the "q" parameter name to separate media type+     --    parameters from Accept extension parameters is due to historical+     --    practice. Although this prevents any media type parameter named+     --    "q" from being used with a media range, such an event is believed+     --    to be unlikely given the lack of any "q" parameters in the IANA+     --    media type registry and the rare usage of any media type+     --    parameters in Accept. Future media types are discouraged from+     --    registering any parameter named "q".+     when (p_name == "q") pzero      lexeme $ char '='      -- Workaround for seemingly standardized web browser bug      -- where nothing is escaped in the filename parameter@@ -98,7 +133,6 @@      p_value <- litStr <|> p_token      return (map toLower p_name, p_value) - --  -- * Content type --@@ -125,31 +159,41 @@                      --   top-level type, e.g. @(\"charset\",\"ISO-8859-1\")@.                      ctParameters :: [(String, String)]                     }-    deriving (Show, Read, Eq, Ord)+    deriving (Show, Read) --- | Produce the standard string representation of a content-type,---   e.g. \"text\/html; charset=ISO-8859-1\".-showContentType :: ContentType -> String-showContentType (ContentType x y ps) = x ++ "/" ++ y ++ showParameters ps+instance Eq ContentType where+    x == y = ctType x `caseInsensitiveEq` ctType y +             && ctSubtype x `caseInsensitiveEq` ctSubtype y +             && ctParameters x == ctParameters y -pContentType :: Parser ContentType-pContentType = -  do many ws1-     c_type <- p_token-     lexeme $ char '/'-     c_subtype <- lexeme $ p_token-     c_parameters <- many p_parameter-     return $ ContentType (map toLower c_type) (map toLower c_subtype) c_parameters+instance Ord ContentType where+    x `compare` y = mconcat [ctType x `caseInsensitiveCompare` ctType y,+                             ctSubtype x `caseInsensitiveCompare` ctSubtype y,+                             ctParameters x `compare` ctParameters y] +instance HeaderValue ContentType where+    parseHeaderValue = +        do many ws1+           c_type <- p_token+           char '/'+           c_subtype <- lexeme $ p_token+           c_parameters <- many p_parameter+           return $ ContentType (map toLower c_type) (map toLower c_subtype) c_parameters+    prettyHeaderValue (ContentType x y ps) = x ++ "/" ++ y ++ showParameters ps++ -- | Parse the standard representation of a content-type. --   If the input cannot be parsed, this function calls --   'fail' with a (hopefully) informative error message. parseContentType :: Monad m => String -> m ContentType-parseContentType = parseM pContentType "Content-type"+parseContentType = parseM parseHeaderValue "Content-type" -getContentType :: Monad m => [Header] -> m ContentType-getContentType hs = lookupM "content-type" hs >>= parseContentType+showContentType :: ContentType -> String+showContentType = prettyHeaderValue +getContentType :: Monad m => Headers -> m ContentType+getContentType = getHeaderValue "content-type"+ -- -- * Content transfer encoding --@@ -158,19 +202,15 @@ 	ContentTransferEncoding String     deriving (Show, Read, Eq, Ord) -pContentTransferEncoding :: Parser ContentTransferEncoding-pContentTransferEncoding =-  do many ws1-     c_cte <- p_token-     return $ ContentTransferEncoding (map toLower c_cte)--parseContentTransferEncoding :: Monad m => String -> m ContentTransferEncoding-parseContentTransferEncoding = -    parseM pContentTransferEncoding "Content-transfer-encoding"+instance HeaderValue ContentTransferEncoding where+    parseHeaderValue = +        do many ws1+           c_cte <- p_token+           return $ ContentTransferEncoding (map toLower c_cte)+    prettyHeaderValue (ContentTransferEncoding s) = s -getContentTransferEncoding :: Monad m => [Header] -> m ContentTransferEncoding-getContentTransferEncoding hs = -    lookupM "content-transfer-encoding" hs >>= parseContentTransferEncoding+getContentTransferEncoding :: Monad m => Headers -> m ContentTransferEncoding+getContentTransferEncoding = getHeaderValue "content-transfer-encoding"  -- -- * Content disposition@@ -180,24 +220,18 @@ 	ContentDisposition String [(String, String)]     deriving (Show, Read, Eq, Ord) -pContentDisposition :: Parser ContentDisposition-pContentDisposition =-  do many ws1-     c_cd <- p_token-     c_parameters <- many p_parameter-     return $ ContentDisposition (map toLower c_cd) c_parameters--parseContentDisposition :: Monad m => String -> m ContentDisposition-parseContentDisposition = parseM pContentDisposition "Content-disposition"--getContentDisposition :: Monad m => [Header] -> m ContentDisposition-getContentDisposition hs = -    lookupM "content-disposition" hs  >>= parseContentDisposition+instance HeaderValue ContentDisposition where+    parseHeaderValue = +        do many ws1+           c_cd <- p_token+           c_parameters <- many p_parameter+           return $ ContentDisposition (map toLower c_cd) c_parameters+    prettyHeaderValue (ContentDisposition t hs) = +        t ++ concat ["; " ++ n ++ "=" ++ quote v | (n,v) <- hs]+            where quote x = "\"" ++ x ++ "\"" -- NOTE: silly, but de-facto standard -showContentDisposition :: ContentDisposition -> String-showContentDisposition (ContentDisposition t hs) = -    t ++ concat ["; " ++ n ++ "=" ++ quote v | (n,v) <- hs]-  where quote x = "\"" ++ x ++ "\"" -- NOTE: silly, but de-facto standard+getContentDisposition :: Monad m => Headers -> m ContentDisposition+getContentDisposition = getHeaderValue "content-disposition"  -- -- * Utilities@@ -211,6 +245,12 @@  lookupM :: (Monad m, Eq a, Show a) => a -> [(a,b)] -> m b lookupM n = maybe (fail ("No such field: " ++ show n)) return . lookup n++caseInsensitiveEq :: String -> String -> Bool+caseInsensitiveEq x y = map toLower x == map toLower y++caseInsensitiveCompare :: String -> String -> Ordering+caseInsensitiveCompare x y = map toLower x `compare` map toLower y  --  -- * Parsing utilities
Network/CGI/Multipart.hs view
@@ -18,15 +18,13 @@ module Network.CGI.Multipart      (      -- * Multi-part messages-     MultiPart(..), BodyPart(..), Header+     MultiPart(..), BodyPart(..)     , parseMultipartBody, hGetMultipartBody     , showMultipartBody      -- * Headers     , ContentType(..), ContentTransferEncoding(..)     , ContentDisposition(..)     , parseContentType-    , parseContentTransferEncoding-    , parseContentDisposition     , getContentType     , getContentTransferEncoding     , getContentDisposition@@ -48,10 +46,10 @@ --  data MultiPart = MultiPart [BodyPart]-               deriving (Show, Read, Eq, Ord)+               deriving (Show, Eq, Ord) -data BodyPart = BodyPart [Header] ByteString-                deriving (Show, Read, Eq, Ord)+data BodyPart = BodyPart Headers ByteString+                deriving (Show, Eq, Ord)  -- | Read a multi-part message from a 'ByteString'. parseMultipartBody :: String -- ^ Boundary@@ -83,7 +81,7 @@  showBodyPart :: BodyPart -> ByteString showBodyPart (BodyPart hs c) = -    unlinesCRLF $ [BS.pack (n++": "++v) | (n,v) <- hs] ++ [BS.empty,c]+    unlinesCRLF $ [BS.pack (n++": "++v) | (HeaderName n,v) <- hs] ++ [BS.empty,c]   --
Network/CGI/Protocol.hs view
@@ -33,7 +33,6 @@  ) where  import Control.Monad.Trans (MonadIO(..))-import Data.Char (toLower) import Data.List (intersperse) import qualified Data.Map as Map import Data.Map (Map)@@ -47,6 +46,7 @@  import Data.Typeable (Typeable(..), mkTyConApp, mkTyCon) +import Network.CGI.Header import Network.CGI.Multipart  @@ -91,19 +91,6 @@ data CGIResult = CGIOutput ByteString                | CGINothing                  deriving (Show, Read, Eq, Ord)--type Headers = [(HeaderName, String)]---- | A string with case insensitive equality and comparisons.-newtype HeaderName = HeaderName String deriving (Show)--instance Eq HeaderName where-    HeaderName x == HeaderName y = map toLower x == map toLower y--instance Ord HeaderName where-    HeaderName x `compare` HeaderName y = map toLower x `compare` map toLower y--  -- -- * Running CGI actions
cgi.cabal view
@@ -1,5 +1,5 @@ Name: cgi-Version: 3001.1.6.0+Version: 3001.1.7.0 Copyright: Bjorn Bringert, Andy Gill, Ian Lynagh, Erik Meijer,             Sven Panne, Jeremy Shaw Maintainer: bjorn@bringert.net@@ -23,6 +23,7 @@     Network.CGI.Cookie,     Network.CGI.Compat   Other-modules:+    Network.CGI.Accept,     Network.CGI.Multipart,     Network.CGI.Header   Extensions: MultiParamTypeClasses