]> gitweb @ CieloNegro.org - Lucu.git/commitdiff
The attoparsec branch. It doesn't even compile for now.
authorPHO <pho@cielonegro.org>
Sat, 30 Jul 2011 11:16:18 +0000 (20:16 +0900)
committerPHO <pho@cielonegro.org>
Sat, 30 Jul 2011 11:16:18 +0000 (20:16 +0900)
16 files changed:
Lucu.cabal
Network/HTTP/Lucu/Authorization.hs
Network/HTTP/Lucu/Chunk.hs
Network/HTTP/Lucu/ContentCoding.hs
Network/HTTP/Lucu/ETag.hs
Network/HTTP/Lucu/Headers.hs
Network/HTTP/Lucu/HttpVersion.hs
Network/HTTP/Lucu/MIMEType.hs
Network/HTTP/Lucu/MIMEType/Guess.hs
Network/HTTP/Lucu/MultipartForm.hs
Network/HTTP/Lucu/Parser.hs [deleted file]
Network/HTTP/Lucu/Parser/Http.hs
Network/HTTP/Lucu/Request.hs
Network/HTTP/Lucu/RequestReader.hs
Network/HTTP/Lucu/Resource.hs
Network/HTTP/Lucu/Utils.hs

index a5ea79344b15d6a2eb8514f12a3b95bb237cf06f..36e1cd2b01e0063e07706526c93d3d57086602bf 100644 (file)
@@ -24,6 +24,7 @@ Extra-Source-Files:
     ImplantFile.hs
     NEWS
     data/CompileMimeTypes.hs
     ImplantFile.hs
     NEWS
     data/CompileMimeTypes.hs
+    data/Makefile
     data/mime.types
     examples/HelloWorld.hs
     examples/Implanted.hs
     data/mime.types
     examples/HelloWorld.hs
     examples/Implanted.hs
@@ -45,6 +46,8 @@ Flag build-lucu-implant-file
 Library
     Build-Depends:
         HsOpenSSL            == 0.10.*,
 Library
     Build-Depends:
         HsOpenSSL            == 0.10.*,
+        ascii                == 0.0.*,
+        attoparsec           == 0.9.*,
         base                 == 4.3.*,
         base-unicode-symbols == 0.2.*,
         base64-bytestring    == 0.1.*,
         base                 == 4.3.*,
         base-unicode-symbols == 0.2.*,
         base64-bytestring    == 0.1.*,
@@ -73,7 +76,6 @@ Library
         Network.HTTP.Lucu.MIMEType
         Network.HTTP.Lucu.MIMEType.DefaultExtensionMap
         Network.HTTP.Lucu.MIMEType.Guess
         Network.HTTP.Lucu.MIMEType
         Network.HTTP.Lucu.MIMEType.DefaultExtensionMap
         Network.HTTP.Lucu.MIMEType.Guess
-        Network.HTTP.Lucu.Parser
         Network.HTTP.Lucu.Parser.Http
         Network.HTTP.Lucu.Request
         Network.HTTP.Lucu.Resource
         Network.HTTP.Lucu.Parser.Http
         Network.HTTP.Lucu.Request
         Network.HTTP.Lucu.Resource
index 6b0e1c268323150607da4f5ea2be37a92ea9ff58..d085234b5e1b4cf8c7c71cae9a10bb059c167a7c 100644 (file)
@@ -14,9 +14,10 @@ module Network.HTTP.Lucu.Authorization
     , authCredentialP -- private
     )
     where
     , authCredentialP -- private
     )
     where
+import Data.Ascii (Ascii)
+import qualified Data.Ascii as A
 import qualified Data.ByteString.Base64 as B64
 import qualified Data.ByteString.Char8 as C8
 import qualified Data.ByteString.Base64 as B64
 import qualified Data.ByteString.Char8 as C8
-import Network.HTTP.Lucu.Parser
 import Network.HTTP.Lucu.Parser.Http
 import Network.HTTP.Lucu.Utils
 import Prelude.Unicode
 import Network.HTTP.Lucu.Parser.Http
 import Network.HTTP.Lucu.Utils
 import Prelude.Unicode
@@ -29,7 +30,7 @@ data AuthChallenge
       deriving (Eq)
 
 -- |'Realm' is just a string which must not contain any non-ASCII letters.
       deriving (Eq)
 
 -- |'Realm' is just a string which must not contain any non-ASCII letters.
-type Realm = String
+type Realm = Ascii
 
 -- |Authorization credential to be sent by client with
 -- \"Authorization\" header. See
 
 -- |Authorization credential to be sent by client with
 -- \"Authorization\" header. See
@@ -40,12 +41,13 @@ data AuthCredential
 
 -- |'UserID' is just a string which must not contain colon and any
 -- non-ASCII letters.
 
 -- |'UserID' is just a string which must not contain colon and any
 -- non-ASCII letters.
-type UserID   = String
+type UserID   = Ascii
 
 -- |'Password' is just a string which must not contain any non-ASCII
 -- letters.
 
 -- |'Password' is just a string which must not contain any non-ASCII
 -- letters.
-type Password = String
+type Password = Ascii
 
 
+-- FIXME: Don't use String for network output.
 instance Show AuthChallenge where
     show (BasicAuthChallenge realm)
         = "Basic realm=" ⧺ quoteStr realm
 instance Show AuthChallenge where
     show (BasicAuthChallenge realm)
         = "Basic realm=" ⧺ quoteStr realm
index 27deb740821f9c68bd5b3159c08469513c7c222d..a419464eefbc322989141167b8d4cbf8bcc741db 100644 (file)
@@ -1,38 +1,35 @@
+{-# LANGUAGE
+    UnicodeSyntax
+  #-}
 module Network.HTTP.Lucu.Chunk
     ( chunkHeaderP  -- Num a => Parser a
     , chunkFooterP  -- Parser ()
     , chunkTrailerP -- Parser Headers
     )
     where
 module Network.HTTP.Lucu.Chunk
     ( chunkHeaderP  -- Num a => Parser a
     , chunkFooterP  -- Parser ()
     , chunkTrailerP -- Parser Headers
     )
     where
-
-import           Network.HTTP.Lucu.Headers
-import           Network.HTTP.Lucu.Parser
-import           Network.HTTP.Lucu.Parser.Http
-import           Numeric
-
-
-chunkHeaderP :: Num a => Parser a
-chunkHeaderP = do hexLen <- many1 hexDigit
-                  _      <- extension
-                  _      <- crlf
-
-                  let [(len, _)] = readHex hexLen
+import Control.Applicative
+import Data.Attoparsec.Char8
+import Data.Bits
+import Network.HTTP.Lucu.Headers
+import Network.HTTP.Lucu.Parser.Http
+
+chunkHeaderP ∷ (Integral a, Bits a) ⇒ Parser a
+{-# INLINEABLE chunkHeaderP #-}
+chunkHeaderP = do len ← hexadecimal
+                  extension
+                  crlf
                   return len
     where
                   return len
     where
-      extension :: Parser ()
-      extension = many ( char ';' >>
-                         token    >>
-                         char '=' >>
-                         ( token <|> quotedStr )
-                       )
-                  >>
-                  return ()
-{-# SPECIALIZE chunkHeaderP :: Parser Int #-}
-
-
-chunkFooterP :: Parser ()
-chunkFooterP = crlf >> return ()
-
-
-chunkTrailerP :: Parser Headers
+      extension ∷ Parser ()
+      extension = skipMany $
+                  do _ ← char ';'
+                     _ ← token
+                     _ ← char '='
+                     _ ← token <|> quotedStr
+                     return ()
+
+chunkFooterP ∷ Parser ()
+chunkFooterP = crlf
+
+chunkTrailerP ∷ Parser Headers
 chunkTrailerP = headersP
 chunkTrailerP = headersP
index 27a89415a0d9e1e420ba7f57cd00815c594abb4a..7a0918a8fd364dde1862ffcbb919de12550f488b 100644 (file)
@@ -1,48 +1,63 @@
+{-# LANGUAGE
+    OverloadedStrings
+  , UnicodeSyntax
+  #-}
 module Network.HTTP.Lucu.ContentCoding
 module Network.HTTP.Lucu.ContentCoding
-    ( acceptEncodingListP
+    ( AcceptEncoding(..)
+
+    , acceptEncodingListP
     , normalizeCoding
     , unnormalizeCoding
     , normalizeCoding
     , unnormalizeCoding
-    , orderAcceptEncodings
     )
     where
     )
     where
-
-import           Data.Char
-import           Data.Ord
-import           Data.Maybe
-import           Network.HTTP.Lucu.Parser
-import           Network.HTTP.Lucu.Parser.Http
-
-
-acceptEncodingListP :: Parser [(String, Maybe Double)]
-acceptEncodingListP = allowEOF $! listOf accEncP
-
-      
-accEncP :: Parser (String, Maybe Double)
-accEncP = do coding <- token
-             qVal   <- option Nothing
-                       $ do _ <- string ";q="
-                            q <- qvalue
-                            return $ Just q
+import Control.Applicative
+import Data.Ascii (CIAscii, toCIAscii)
+import Data.Attoparsec.Char8
+import Data.Ord
+import Data.Maybe
+import Network.HTTP.Lucu.Parser.Http
+import Prelude.Unicode
+
+data AcceptEncoding
+    = AcceptEncoding !CIAscii !(Maybe Double)
+      deriving (Eq, Show)
+
+instance Ord AcceptEncoding where
+    (AcceptEncoding c1 q1) `compare` (AcceptEncoding c2 q2)
+        | q1' > q1' = GT
+        | q1' < q2' = LT
+        | otherwise = compare c1 c2
+        where
+          q1' = fromMaybe 0 q1
+          q2' = fromMaybe 0 q2
+
+acceptEncodingListP ∷ Parser [(CIAscii, Maybe Double)]
+acceptEncodingListP = listOf accEncP
+
+accEncP ∷ Parser (CIAscii, Maybe Double)
+accEncP = do coding ← toCIAscii <$> token
+             qVal   ← option Nothing
+                      $ do _ ← string ";q="
+                           q ← qvalue
+                           return $ Just q
              return (normalizeCoding coding, qVal)
 
              return (normalizeCoding coding, qVal)
 
-
-normalizeCoding :: String -> String
+normalizeCoding ∷ CIAscii → CIAscii
 normalizeCoding coding
 normalizeCoding coding
-    = case map toLower coding of
-        "x-gzip"     -> "gzip"
-        "x-compress" -> "compress"
-        other        -> other
-
-
-unnormalizeCoding :: String -> String
+    = if coding ≡ "x-gzip" then
+          "gzip"
+      else
+          if coding ≡ "x-compress" then
+              "compress"
+          else
+              coding
+
+unnormalizeCoding ∷ CIAscii → CIAscii
 unnormalizeCoding coding
 unnormalizeCoding coding
-    = case map toLower coding of
-        "gzip"     -> "x-gzip"
-        "compress" -> "x-compress"
-        other        -> other
-
-
-orderAcceptEncodings :: (String, Maybe Double) -> (String, Maybe Double) -> Ordering
-orderAcceptEncodings (_, q1) (_, q2)
-    = comparing (fromMaybe 0) q1 q2
-
+    = if coding ≡ "gzip" then
+          "x-gzip"
+      else
+          if coding ≡ "compress" then
+              "x-compress"
+          else
+              coding
index d607ad12db4d2fa22ec529a2f0456f9c7e4644f7..41e99f867c694a25eec3ca8a4f3cc498a884d999 100644 (file)
@@ -9,11 +9,9 @@ module Network.HTTP.Lucu.ETag
     , eTagListP
     )
     where
     , eTagListP
     )
     where
-
-import           Control.Monad
-import           Network.HTTP.Lucu.Parser
-import           Network.HTTP.Lucu.Parser.Http hiding (token)
-import           Network.HTTP.Lucu.Utils
+import Control.Monad
+import Network.HTTP.Lucu.Parser.Http hiding (token)
+import Network.HTTP.Lucu.Utils
 
 -- |An entity tag is made of a weakness flag and a opaque string.
 data ETag = ETag {
 
 -- |An entity tag is made of a weakness flag and a opaque string.
 data ETag = ETag {
index 87d858c55ec023a07a263a3f6d2280adaf958eb6..2378ebcc529295f9495f1f1e5daf5daef46ce907 100644 (file)
@@ -1,11 +1,13 @@
+{-# LANGUAGE
+    BangPatterns
+  , GeneralizedNewtypeDeriving
+  , OverloadedStrings
+  , UnicodeSyntax
+  #-}
 module Network.HTTP.Lucu.Headers
     ( Headers
     , HasHeaders(..)
 
 module Network.HTTP.Lucu.Headers
     ( Headers
     , HasHeaders(..)
 
-    , noCaseCmp
-    , noCaseEq
-
-    , emptyHeaders
     , toHeaders
     , fromHeaders
 
     , toHeaders
     , fromHeaders
 
@@ -13,153 +15,74 @@ module Network.HTTP.Lucu.Headers
     , hPutHeaders
     )
     where
     , hPutHeaders
     )
     where
-
-import qualified Data.ByteString as Strict (ByteString)
-import           Data.ByteString.Internal (toForeignPtr, w2c, inlinePerformIO)
-import qualified Data.ByteString.Char8 as C8 hiding (ByteString)
-import           Data.Char
-import           Data.List
-import           Data.Map (Map)
+import Control.Applicative
+import Data.Ascii (Ascii, CIAscii)
+import qualified Data.Ascii as A
+import Data.Attoparsec.Char8 as P
+import qualified Data.ByteString as BS
+import Data.Map (Map)
 import qualified Data.Map as M
 import qualified Data.Map as M
-import           Data.Ord
-import           Data.Word
-import           Foreign.ForeignPtr
-import           Foreign.Ptr
-import           Foreign.Storable
-import           Network.HTTP.Lucu.HandleLike
-import           Network.HTTP.Lucu.Parser
-import           Network.HTTP.Lucu.Parser.Http
-import           Network.HTTP.Lucu.Utils
-
-type Headers = Map NCBS Strict.ByteString
-newtype NCBS = NCBS Strict.ByteString
-
-toNCBS :: Strict.ByteString -> NCBS
-toNCBS = NCBS
-{-# INLINE toNCBS #-}
-
-fromNCBS :: NCBS -> Strict.ByteString
-fromNCBS (NCBS x) = x
-{-# INLINE fromNCBS #-}
-
-instance Eq NCBS where
-    (NCBS a) == (NCBS b) = a == b
-
-instance Ord NCBS where
-    (NCBS a) `compare` (NCBS b) = a `noCaseCmp` b
-
-instance Show NCBS where
-    show (NCBS x) = show x
-
-noCaseCmp :: Strict.ByteString -> Strict.ByteString -> Ordering
-noCaseCmp a b = a `seq` b `seq`
-                toForeignPtr a `cmp` toForeignPtr b
-    where
-      cmp :: (ForeignPtr Word8, Int, Int) -> (ForeignPtr Word8, Int, Int) -> Ordering
-      cmp (x1, s1, l1) (x2, s2, l2)
-          | x1 `seq` s1 `seq` l1 `seq` x2 `seq` s2 `seq` l2 `seq` False = undefined
-          | l1 == 0  && l2 == 0               = EQ
-          | x1 == x2 && s1 == s2 && l1 == l2  = EQ
-          | otherwise
-              = inlinePerformIO $
-                withForeignPtr x1 $ \ p1 ->
-                withForeignPtr x2 $ \ p2 ->
-                noCaseCmp' (p1 `plusPtr` s1) l1 (p2 `plusPtr` s2) l2
-
-
--- もし先頭の文字列が等しければ、短い方が小さい。
-noCaseCmp' :: Ptr Word8 -> Int -> Ptr Word8 -> Int -> IO Ordering
-noCaseCmp' p1 l1 p2 l2
-    | p1 `seq` l1 `seq` p2 `seq` l2 `seq` False = undefined
-    | l1 == 0 && l2 == 0 = return EQ
-    | l1 == 0            = return LT
-    |            l2 == 0 = return GT
-    | otherwise
-        = do c1 <- peek p1
-             c2 <- peek p2
-             case comparing (toLower . w2c) c1 c2 of
-               EQ -> noCaseCmp' (p1 `plusPtr` 1) (l1 - 1) (p2 `plusPtr` 1) (l2 - 1)
-               x  -> return x
-
-
-noCaseEq :: Strict.ByteString -> Strict.ByteString -> Bool
-noCaseEq a b = a `seq` b `seq`
-               toForeignPtr a `cmp` toForeignPtr b
-    where
-      cmp :: (ForeignPtr Word8, Int, Int) -> (ForeignPtr Word8, Int, Int) -> Bool
-      cmp (x1, s1, l1) (x2, s2, l2)
-          | x1 `seq` s1 `seq` l1 `seq` x2 `seq` s2 `seq` l2 `seq` False = undefined
-          | l1 /= l2                          = False
-          | l1 == 0  && l2 == 0               = True
-          | x1 == x2 && s1 == s2 && l1 == l2  = True
-          | otherwise
-              = inlinePerformIO $
-                withForeignPtr x1 $ \ p1 ->
-                withForeignPtr x2 $ \ p2 ->
-                noCaseEq' (p1 `plusPtr` s1) (p2 `plusPtr` s2) l1
-
-
-noCaseEq' :: Ptr Word8 -> Ptr Word8 -> Int -> IO Bool
-noCaseEq' p1 p2 l
-    | p1 `seq` p2 `seq` l `seq` False = undefined
-    | l == 0    = return True
-    | otherwise
-        = do c1 <- peek p1
-             c2 <- peek p2
-             if toLower (w2c c1) == toLower (w2c c2) then
-                 noCaseEq' (p1 `plusPtr` 1) (p2 `plusPtr` 1) (l - 1)
-               else
-                 return False
+import Data.Monoid
+import Data.Monoid.Unicode
+import Network.HTTP.Lucu.HandleLike
+import Network.HTTP.Lucu.Parser.Http
+import Network.HTTP.Lucu.Utils
+import Prelude.Unicode
 
 
+newtype Headers
+    = Headers (Map CIAscii Ascii)
+      deriving (Eq, Show, Monoid)
 
 class HasHeaders a where
 
 class HasHeaders a where
-    getHeaders :: a -> Headers
-    setHeaders :: a -> Headers -> a
-
-    getHeader :: Strict.ByteString -> a -> Maybe Strict.ByteString
-    getHeader key a
-        = key `seq` a `seq`
-          M.lookup (toNCBS key) (getHeaders a)
-
-    deleteHeader :: Strict.ByteString -> a -> a
-    deleteHeader key a
-        = key `seq` a `seq`
-          setHeaders a $ M.delete (toNCBS key) (getHeaders a)
-
-    setHeader :: Strict.ByteString -> Strict.ByteString -> a -> a
-    setHeader key val a
-        = key `seq` val `seq` a `seq`
-          setHeaders a $ M.insert (toNCBS key) val (getHeaders a)
-
-
-emptyHeaders :: Headers
-emptyHeaders = M.empty
-
-
-toHeaders :: [(Strict.ByteString, Strict.ByteString)] -> Headers
-toHeaders xs = mkHeaders xs M.empty
-
-
-mkHeaders :: [(Strict.ByteString, Strict.ByteString)] -> Headers -> Headers
-mkHeaders []              m = m
-mkHeaders ((key, val):xs) m = mkHeaders xs $
-                              case M.lookup (toNCBS key) m of
-                                Nothing  -> M.insert (toNCBS key) val m
-                                Just old -> M.insert (toNCBS key) (merge old val) m
+    getHeaders ∷ a → Headers
+    setHeaders ∷ a → Headers → a
+
+    getHeader ∷ CIAscii → a → Maybe Ascii
+    {-# INLINE getHeader #-}
+    getHeader !key !a
+        = case getHeaders a of
+            Headers m → M.lookup key m
+
+    deleteHeader ∷ CIAscii → a → a
+    {-# INLINE deleteHeader #-}
+    deleteHeader !key !a
+        = case getHeaders a of
+            Headers m
+              → setHeaders a $ Headers $ M.delete key m
+
+    setHeader ∷ CIAscii → Ascii → a → a
+    {-# INLINE setHeader #-}
+    setHeader !key !val !a
+        = case getHeaders a of
+            Headers m
+              → setHeaders a $ Headers $ M.insert key val m
+
+toHeaders ∷ [(CIAscii, Ascii)] → Headers
+{-# INLINE toHeaders #-}
+toHeaders = flip mkHeaders (∅)
+
+mkHeaders ∷ [(CIAscii, Ascii)] → Headers → Headers
+mkHeaders []              (Headers m) = Headers m
+mkHeaders ((key, val):xs) (Headers m)
+    = mkHeaders xs $ Headers $
+      case M.lookup key m of
+        Nothing  → M.insert key val m
+        Just old → M.insert key (merge old val) m
     where
     where
-      merge :: Strict.ByteString -> Strict.ByteString -> Strict.ByteString
-      -- カンマ區切りである事を假定する。RFC ではカンマ區切りに出來ない
-      -- ヘッダは複數個あってはならない事になってゐる。
+      merge ∷ Ascii → Ascii → Ascii
+      {-# INLINE merge #-}
       merge a b
       merge a b
-          | C8.null a && C8.null b = C8.empty
-          | C8.null a              = b
-          |              C8.null b = a
-          | otherwise              = C8.concat [a, C8.pack ", ", b]
-
+          | nullA a ∧ nullA b = (∅)
+          | nullA a           = b
+          |           nullA b = a
+          | otherwise         = a ⊕ ", " ⊕ b
 
 
-fromHeaders :: Headers -> [(Strict.ByteString, Strict.ByteString)]
-fromHeaders hs = [(fromNCBS a, b) | (a, b) <- M.toList hs]
+      nullA ∷ Ascii → Bool
+      {-# INLINE nullA #-}
+      nullA = BS.null ∘ A.toByteString
 
 
+fromHeaders ∷ Headers → [(CIAscii, Ascii)]
+fromHeaders (Headers m) = M.toList m
 
 {-
   message-header = field-name ":" [ field-value ]
 
 {-
   message-header = field-name ":" [ field-value ]
@@ -172,49 +95,39 @@ fromHeaders hs = [(fromNCBS a, b) | (a, b) <- M.toList hs]
   field-value の先頭および末尾にある LWS は全て削除され、それ以外の
   LWS は單一の SP に變換される。
 -}
   field-value の先頭および末尾にある LWS は全て削除され、それ以外の
   LWS は單一の SP に變換される。
 -}
-headersP :: Parser Headers
-headersP = do xs <- many header
-              _  <- crlf
-              return $! toHeaders xs
+headersP ∷ Parser Headers
+{-# INLINEABLE headersP #-}
+headersP = do xs ← P.many header
+              crlf
+              return $ toHeaders xs
     where
     where
-      header :: Parser (Strict.ByteString, Strict.ByteString)
-      header = do name <- token
-                  _    <- char ':'
-                  -- FIXME: これは多少インチキだが、RFC 2616 のこの部分
-                  -- の記述はひどく曖昧であり、この動作が本當に間違って
-                  -- ゐるのかどうかも良く分からない。例へば
-                  -- quoted-string の内部にある空白は纏めていいのか惡い
-                  -- のか?直勸的には駄目さうに思へるが、そんな記述は見
-                  -- 付からない。
-                  contents <- many (lws <|> many1 text)
-                  _        <- crlf
-                  let value = foldr (++) "" contents
-                      norm  = normalize value
-                  return (C8.pack name, C8.pack norm)
-
-      normalize :: String -> String
-      normalize = trimBody . trim isWhiteSpace
-
-      trimBody = concat
-                 . map (\ s -> if head s == ' ' then
-                                   " "
-                               else
-                                   s)
-                 . group
-                 . map (\ c -> if isWhiteSpace c
-                               then ' '
-                               else c)
-
-
-hPutHeaders :: HandleLike h => h -> Headers -> IO ()
-hPutHeaders h hds
-    = h `seq` hds `seq`
-      mapM_ putH (M.toList hds) >> hPutBS h (C8.pack "\r\n")
+      header ∷ Parser (CIAscii, Ascii)
+      header = try $
+               do name ← A.toCIAscii <$> token
+                  _    ← char ':'
+                  skipMany lws
+                  values ← sepBy content lws
+                  skipMany lws
+                  crlf
+                  return (name, joinValues values)
+
+      content ∷ Parser Ascii
+      {-# INLINE content #-}
+      content = A.unsafeFromByteString
+                <$>
+                takeWhile1 (\c → ((¬) (isSPHT c)) ∧ isText c)
+
+      joinValues ∷ [Ascii] → Ascii
+      {-# INLINE joinValues #-}
+      joinValues = A.fromAsciiBuilder ∘ joinWith "\x20"
+
+hPutHeaders ∷ HandleLike h => h → Headers → IO ()
+hPutHeaders !h !(Headers m)
+    = mapM_ putH (M.toList m) >> hPutBS h "\r\n"
     where
     where
-      putH :: (NCBS, Strict.ByteString) -> IO ()
-      putH (name, value)
-          = name `seq` value `seq`
-            do hPutBS h (fromNCBS name)
-               hPutBS h (C8.pack ": ")
-               hPutBS h value
-               hPutBS h (C8.pack "\r\n")
+      putH ∷ (CIAscii, Ascii) → IO ()
+      putH (!name, !value)
+          = do hPutBS h (A.ciToByteString name)
+               hPutBS h ": "
+               hPutBS h (A.toByteString value)
+               hPutBS h "\r\n"
index d48f6ec8c58f3d5009c3038ed500eb4e863e5003..4531c837782ef9b6eda9edd4849e3771f2b0b0a1 100644 (file)
@@ -1,5 +1,6 @@
 {-# LANGUAGE
     BangPatterns
 {-# LANGUAGE
     BangPatterns
+  , OverloadedStrings
   , UnicodeSyntax
   #-}
 {-# OPTIONS_HADDOCK prune #-}
   , UnicodeSyntax
   #-}
 {-# OPTIONS_HADDOCK prune #-}
@@ -11,18 +12,15 @@ module Network.HTTP.Lucu.HttpVersion
     , hPutHttpVersion
     )
     where
     , hPutHttpVersion
     )
     where
-
-import qualified Data.ByteString.Char8 as C8
-import           Network.HTTP.Lucu.HandleLike
-import           Network.HTTP.Lucu.Parser
-import           Prelude hiding (min)
+import Control.Monad.Unicode
+import Data.Attoparsec.Char8
+import Network.HTTP.Lucu.HandleLike
+import Prelude hiding (min)
 
 -- |@'HttpVersion' major minor@ represents \"HTTP\/major.minor\".
 
 -- |@'HttpVersion' major minor@ represents \"HTTP\/major.minor\".
-data HttpVersion = HttpVersion !Int !Int
-                   deriving (Eq)
-
-instance Show HttpVersion where
-    show (HttpVersion maj min) = "HTTP/" ++ show maj ++ "." ++ show min
+data HttpVersion
+    = HttpVersion !Int !Int
+      deriving (Eq, Show)
 
 instance Ord HttpVersion where
     (HttpVersion majA minA) `compare` (HttpVersion majB minB)
 
 instance Ord HttpVersion where
     (HttpVersion majA minA) `compare` (HttpVersion majB minB)
@@ -32,30 +30,26 @@ instance Ord HttpVersion where
         | minA < minB = LT
         | otherwise   = EQ
 
         | minA < minB = LT
         | otherwise   = EQ
 
-
-httpVersionP :: Parser HttpVersion
+httpVersionP ∷ Parser HttpVersion
 httpVersionP = string "HTTP/"
 httpVersionP = string "HTTP/"
-               >>
-               -- 頻出するので高速化
-               choice [ string "1.0" >> return (HttpVersion 1 0)
-                      , string "1.1" >> return (HttpVersion 1 1)
-                        -- 一般の場合
-                      , do major <- many1 digit
-                           _     <- char '.'
-                           minor <- many1 digit
-                           return $ HttpVersion (read major) (read minor)
+               ≫
+               choice [ string "1.1" ≫ return (HttpVersion 1 1)
+                      , string "1.0" ≫ return (HttpVersion 1 0)
+                      , do major ← decimal
+                           _     ← char '.'
+                           minor ← decimal
+                           return $ HttpVersion major minor
                       ]
 
                       ]
 
-
-hPutHttpVersion :: HandleLike h => h -> HttpVersion -> IO ()
+hPutHttpVersion ∷ HandleLike h ⇒ h → HttpVersion → IO ()
 hPutHttpVersion !h !v
     = case v of
         -- 頻出するので高速化
 hPutHttpVersion !h !v
     = case v of
         -- 頻出するので高速化
-        HttpVersion 1 0 -> hPutBS h (C8.pack "HTTP/1.0")
-        HttpVersion 1 1 -> hPutBS h (C8.pack "HTTP/1.1")
+        HttpVersion 1 0 → hPutBS h "HTTP/1.0"
+        HttpVersion 1 1 → hPutBS h "HTTP/1.1"
         -- 一般の場合
         HttpVersion !maj !min
         -- 一般の場合
         HttpVersion !maj !min
-            -> do hPutBS   h (C8.pack "HTTP/")
-                  hPutStr  h (show maj)
-                  hPutChar h '.'
-                  hPutStr  h (show min)
+            → do hPutBS   h "HTTP/"
+                 hPutStr  h (show maj)
+                 hPutChar h '.'
+                 hPutStr  h (show min)
index a3f3fc5453ff77ee9436b347da9ee94c46879296..88a2eeffde0ad8e7e673af93c425f506c6d5706e 100644 (file)
@@ -8,44 +8,49 @@
 module Network.HTTP.Lucu.MIMEType
     ( MIMEType(..)
     , parseMIMEType
 module Network.HTTP.Lucu.MIMEType
     ( MIMEType(..)
     , parseMIMEType
+    , printMIMEType
+
     , mimeTypeP
     , mimeTypeListP
     )
     where
     , mimeTypeP
     , mimeTypeListP
     )
     where
-
+import Data.Ascii (Ascii, CIAscii)
+import qualified Data.Ascii as A
 import qualified Data.ByteString.Lazy as B
 import qualified Data.ByteString.Lazy as B
-import           Network.HTTP.Lucu.Parser
-import           Network.HTTP.Lucu.Parser.Http
-import           Network.HTTP.Lucu.Utils
-import           Prelude hiding (min)
+import Network.HTTP.Lucu.Parser.Http
+import Network.HTTP.Lucu.Utils
+import Prelude hiding (min)
 
 -- |@'MIMEType' \"major\" \"minor\" [(\"name\", \"value\")]@
 -- represents \"major\/minor; name=value\".
 data MIMEType = MIMEType {
 
 -- |@'MIMEType' \"major\" \"minor\" [(\"name\", \"value\")]@
 -- represents \"major\/minor; name=value\".
 data MIMEType = MIMEType {
-      mtMajor  :: !String
-    , mtMinor  :: !String
-    , mtParams :: ![ (String, String) ]
-    } deriving (Eq)
-
+      mtMajor  :: !CIAscii
+    , mtMinor  :: !CIAscii
+    , mtParams :: ![ (CIAscii, Ascii) ]
+    } deriving (Eq, Show)
 
 
-instance Show MIMEType where
-    show (MIMEType maj min params)
-        = maj ++ "/" ++ min ++
-          if null params then
-              ""
-          else
-              "; " ++ joinWith "; " (map showPair params)
-        where
-          showPair :: (String, String) -> String
-          showPair (name, value)
-              = name ++ "=" ++ if any (not . isToken) value then
-                                   quoteStr value
-                               else
-                                   value
-
-
-instance Read MIMEType where
-    readsPrec _ s = [(parseMIMEType s, "")]
+-- |Convert a 'MIMEType' to 'Ascii'.
+printMIMEType ∷ MIMEType → Ascii
+printMIMEType (MIMEType maj min params)
+    = A.fromAsciiBuilder $
+      ( A.toAsciiBuilder maj ⊕
+        A.toAsciiBuilder "/" ⊕
+        A.toAsciiBuilder min ⊕
+        if null params then
+            (∅)
+        else
+            A.toAsciiBuilder "; " ⊕
+            joinWith "; " (map printPair params)
+      )
+    where
+      printPair ∷ (CIAscii, Ascii) → A.AsciiBuilder
+      printPair (name, value)
+          = A.toAsciiBuilder (A.fromCIAscii name) ⊕
+            A.toAsciiBuilder "=" ⊕
+            if any ((¬) ∘ isToken) value then
+                quoteStr value
+            else
+                A.toAsciiBuilder value
 
 -- |Parse 'MIMEType' from a 'Prelude.String'. This function throws an
 -- exception for parse error.
 
 -- |Parse 'MIMEType' from a 'Prelude.String'. This function throws an
 -- exception for parse error.
index 39de37e07d68464b8029021f745e956fa236c036..5a10bb60bd7e16ee6ae008534c8c5cc914568cb5 100644 (file)
@@ -14,17 +14,15 @@ module Network.HTTP.Lucu.MIMEType.Guess
     , serializeExtMap
     )
     where
     , serializeExtMap
     )
     where
-
 import qualified Data.ByteString.Lazy.Char8 as B
 import qualified Data.Map as M
 import qualified Data.ByteString.Lazy.Char8 as B
 import qualified Data.Map as M
-import           Data.Map (Map)
-import           Data.Maybe
-import           Language.Haskell.Pretty
-import           Language.Haskell.Syntax
-import           Network.HTTP.Lucu.MIMEType
-import           Network.HTTP.Lucu.Parser
-import           Network.HTTP.Lucu.Parser.Http
-import           Network.HTTP.Lucu.Utils
+import Data.Map (Map)
+import Data.Maybe
+import Language.Haskell.Pretty
+import Language.Haskell.Syntax
+import Network.HTTP.Lucu.MIMEType
+import Network.HTTP.Lucu.Parser.Http
+import Network.HTTP.Lucu.Utils
 
 -- |'Data.Map.Map' from extension to MIME Type.
 type ExtMap = Map String MIMEType
 
 -- |'Data.Map.Map' from extension to MIME Type.
 type ExtMap = Map String MIMEType
index c4631300e9efae3b3d14ac57917597ef685032fd..741427f271636e48eb3d1cf060b4fbf794c6c662 100644 (file)
@@ -7,19 +7,16 @@ module Network.HTTP.Lucu.MultipartForm
     , multipartFormP
     )
     where
     , multipartFormP
     )
     where
-
 import qualified Data.ByteString.Char8 as C8
 import qualified Data.ByteString.Lazy.Char8 as L8
 import           Data.Char
 import           Data.List
 import           Network.HTTP.Lucu.Abortion
 import           Network.HTTP.Lucu.Headers
 import qualified Data.ByteString.Char8 as C8
 import qualified Data.ByteString.Lazy.Char8 as L8
 import           Data.Char
 import           Data.List
 import           Network.HTTP.Lucu.Abortion
 import           Network.HTTP.Lucu.Headers
-import           Network.HTTP.Lucu.Parser
 import           Network.HTTP.Lucu.Parser.Http
 import           Network.HTTP.Lucu.Response
 import           Network.HTTP.Lucu.Utils
 
 import           Network.HTTP.Lucu.Parser.Http
 import           Network.HTTP.Lucu.Response
 import           Network.HTTP.Lucu.Utils
 
-
 data Part = Part Headers L8.ByteString
 
 -- |This data type represents a form value and possibly an uploaded
 data Part = Part Headers L8.ByteString
 
 -- |This data type represents a form value and possibly an uploaded
diff --git a/Network/HTTP/Lucu/Parser.hs b/Network/HTTP/Lucu/Parser.hs
deleted file mode 100644 (file)
index 7809f53..0000000
+++ /dev/null
@@ -1,339 +0,0 @@
-{-# LANGUAGE
-    BangPatterns
-  , ScopedTypeVariables
-  , UnboxedTuples
-  , UnicodeSyntax
-  #-}
--- |Yet another parser combinator. This is mostly a subset of
--- "Text.ParserCombinators.Parsec" but there are some differences:
---
--- * This parser works on 'Data.ByteString.Base.LazyByteString'
---   instead of 'Prelude.String'.
---
--- * Backtracking is the only possible behavior so there is no \"try\"
---   action.
---
--- * On success, the remaining string is returned as well as the
---   parser result.
---
--- * You can choose whether to treat reaching EOF (trying to eat one
---   more letter at the end of string) a fatal error or to treat it a
---   normal failure. If a fatal error occurs, the entire parsing
---   process immediately fails without trying any backtracks. The
---   default behavior is to treat EOF fatal.
---
--- In general, you don't have to use this module directly.
-module Network.HTTP.Lucu.Parser
-    ( Parser
-    , ParserResult(..)
-
-    , failP
-
-    , parse
-    , parseStr
-
-    , anyChar
-    , eof
-    , allowEOF
-    , satisfy
-    , char
-    , string
-    , (<|>)
-    , choice
-    , oneOf
-    , digit
-    , hexDigit
-    , notFollowedBy
-    , many
-    , manyChar
-    , many1
-    , count
-    , option
-    , sepBy
-    , sepBy1
-
-    , sp
-    , ht
-    , crlf
-    )
-    where
-
-import           Control.Monad.State.Strict hiding (state)
-import qualified Data.ByteString.Lazy as Lazy (ByteString)
-import qualified Data.ByteString.Lazy.Char8 as B hiding (ByteString)
-import qualified Data.Foldable as Fold
-import           Data.Int
-import qualified Data.Sequence as Seq
-import           Data.Sequence (Seq, (|>))
-
--- |@'Parser' a@ is obviously a parser which parses and returns @a@.
-newtype Parser a = Parser {
-      runParser :: State ParserState (ParserResult a)
-    }
-
-
-data ParserState
-    = PST {
-        pstInput      :: Lazy.ByteString
-      , pstIsEOFFatal :: !Bool
-      }
-    deriving (Eq, Show)
-
-
-data ParserResult a = Success !a
-                    | IllegalInput -- 受理出來ない入力があった
-                    | ReachedEOF   -- 限界を越えて讀まうとした
-                      deriving (Eq, Show)
-
-
---  (>>=) :: Parser a -> (a -> Parser b) -> Parser b
-instance Monad Parser where
-    p >>= f = Parser $! do saved <- get -- 失敗した時の爲に状態を保存
-                           result <- runParser p
-                           case result of
-                             Success a    -> runParser (f a)
-                             IllegalInput -> do put saved -- 状態を復歸
-                                                return IllegalInput
-                             ReachedEOF   -> do put saved -- 状態を復歸
-                                                return ReachedEOF
-    return !x = Parser $! return $! Success x
-    fail _    = Parser $! return $! IllegalInput
-
-instance Functor Parser where
-    fmap f p = p >>= return . f
-
--- |@'failP'@ is just a synonym for @'Prelude.fail'
--- 'Prelude.undefined'@.
-failP :: Parser a
-failP = fail undefined
-
--- |@'parse' p bstr@ parses @bstr@ with @p@ and returns @(# result,
--- remaining #)@.
-parse :: Parser a -> Lazy.ByteString -> (# ParserResult a, Lazy.ByteString #)
-parse !p input -- input は lazy である必要有り。
-    = let (!result, state') = runState (runParser p) (PST input True)
-      in
-        (# result, pstInput state' #) -- pstInput state' も lazy である必要有り。
-
--- |@'parseStr' p str@ packs @str@ and parses it.
-parseStr :: Parser a -> String -> (# ParserResult a, Lazy.ByteString #)
-parseStr !p input -- input は lazy である必要有り。
-    = parse p (B.pack input)
-
-
-anyChar :: Parser Char
-anyChar = Parser $!
-          do state@(PST input _) <- get
-             if B.null input then
-                 return ReachedEOF
-               else
-                 do put $! state { pstInput = B.tail input }
-                    return (Success $! B.head input)
-
-
-eof :: Parser ()
-eof = Parser $!
-      do PST input _ <- get
-         if B.null input then
-             return $! Success ()
-           else
-             return IllegalInput
-
--- |@'allowEOF' p@ makes @p@ treat reaching EOF a normal failure.
-allowEOF :: Parser a -> Parser a
-allowEOF !f
-    = Parser $! do saved@(PST _ isEOFFatal) <- get
-                   put $! saved { pstIsEOFFatal = False }
-
-                   result <- runParser f
-                         
-                   state <- get
-                   put $! state { pstIsEOFFatal = isEOFFatal }
-
-                   return result
-
-
-satisfy :: (Char -> Bool) -> Parser Char
-satisfy !f
-    = do c <- anyChar
-         if f c then
-             return c
-           else
-             failP
-
-
-char :: Char -> Parser Char
-char !c = satisfy (== c)
-
-
-string :: String -> Parser String
-string !str
-    = let bs  = B.pack str
-          len = B.length bs
-      in
-        Parser $!
-        do st <- get
-           let (bs', rest) = B.splitAt len $ pstInput st
-               st'         = st { pstInput = rest }
-           if B.length bs' < len then
-               return ReachedEOF
-             else
-               if bs == bs' then
-                   do put st'
-                      return $ Success str
-               else
-                   return IllegalInput
-
-
-infixr 0 <|>
-
--- |This is the backtracking alternation. There is no non-backtracking
--- equivalent.
-(<|>) :: Parser a -> Parser a -> Parser a
-(!f) <|> (!g)
-    = Parser $! do saved  <- get -- 状態を保存
-                   result <- runParser f
-                   case result of
-                     Success a    -> return $! Success a
-                     IllegalInput -> do put saved -- 状態を復歸
-                                        runParser g
-                     ReachedEOF   -> if pstIsEOFFatal saved then
-                                         do put saved
-                                            return ReachedEOF
-                                     else
-                                         do put saved
-                                            runParser g
-
-
-choice :: [Parser a] -> Parser a
-choice = foldl (<|>) failP
-
-
-oneOf :: [Char] -> Parser Char
-oneOf = foldl (<|>) failP . map char
-
-
-notFollowedBy :: Parser a -> Parser ()
-notFollowedBy !p
-    = Parser $! do saved  <- get -- 状態を保存
-                   result <- runParser p
-                   case result of
-                     Success _    -> do put saved -- 状態を復歸
-                                        return IllegalInput
-                     IllegalInput -> do put saved -- 状態を復歸
-                                        return $! Success ()
-                     ReachedEOF   -> do put saved -- 状態を復歸
-                                        return $! Success ()
-
-
-digit :: Parser Char
-digit = do c <- anyChar
-           if c >= '0' && c <= '9' then
-               return c
-             else
-               failP
-
-
-hexDigit :: Parser Char
-hexDigit = do c <- anyChar
-              if (c >= '0' && c <= '9') ||
-                 (c >= 'a' && c <= 'f') ||
-                 (c >= 'A' && c <= 'F') then
-                  return c
-                else
-                  failP
-
-
-many :: forall a. Parser a -> Parser [a]
-many !p = Parser $!
-          do state <- get
-             let (# result, state' #) = many' state Seq.empty
-             put state'
-             return result
-    where
-      many' :: ParserState -> Seq a -> (# ParserResult [a], ParserState #)
-      many' !st !soFar
-          = case runState (runParser p) st of
-              (Success a,  st') -> many' st' (soFar |> a)
-              (IllegalInput, _) -> (# Success (Fold.toList soFar), st #)
-              (ReachedEOF  , _) -> if pstIsEOFFatal st then
-                                       (# ReachedEOF, st #)
-                                   else
-                                       (# Success (Fold.toList soFar), st #)
-
-manyChar :: Parser Char -> Parser Lazy.ByteString
-manyChar !p = Parser $!
-              do state <- get
-                 case scan' state 0 of
-                   Success len
-                       -> do let (bs, rest) = B.splitAt len (pstInput state)
-                                 state'     = state { pstInput = rest }
-                             put state'
-                             return $ Success bs
-                   ReachedEOF
-                       -> if pstIsEOFFatal state then
-                              return ReachedEOF
-                          else
-                              error "internal error"
-                   _   -> error "internal error"
-    where
-      scan' :: ParserState -> Int64 -> ParserResult Int64
-      scan' !st !soFar
-          = case runState (runParser p) st of
-              (Success _   , st') -> scan' st' (soFar + 1)
-              (IllegalInput, _  ) -> Success soFar
-              (ReachedEOF  , _  ) -> if pstIsEOFFatal st then
-                                         ReachedEOF
-                                     else
-                                         Success soFar
-
-
-many1 :: Parser a -> Parser [a]
-many1 !p = do x  <- p
-              xs <- many p
-              return (x:xs)
-
-
-count :: Int -> Parser a -> Parser [a]
-count !n !p = Parser $! count' n p Seq.empty
-
--- This implementation is rather ugly but we need to make it
--- tail-recursive to avoid stack overflow.
-count' :: Int -> Parser a -> Seq a -> State ParserState (ParserResult [a])
-count' 0  _  !soFar = return $! Success $! Fold.toList soFar
-count' !n !p !soFar = do saved  <- get
-                         result <- runParser p
-                         case result of
-                           Success a    -> count' (n-1) p (soFar |> a)
-                           IllegalInput -> do put saved
-                                              return IllegalInput
-                           ReachedEOF   -> do put saved
-                                              return ReachedEOF
-
-
--- def may be a _|_
-option :: a -> Parser a -> Parser a
-option def !p = p <|> return def
-
-
-sepBy :: Parser a -> Parser sep -> Parser [a]
-sepBy !p !sep = sepBy1 p sep <|> return []
-
-
-sepBy1 :: Parser a -> Parser sep -> Parser [a]
-sepBy1 !p !sep
-    = do x  <- p
-         xs <- many $! sep >> p
-         return (x:xs)
-
-
-sp :: Parser Char
-sp = char ' '
-
-
-ht :: Parser Char
-ht = char '\t'
-
-
-crlf :: Parser String
-crlf = string "\x0d\x0a"
index fe54bde4c5d9f08b10ce443dd029f6d5bd838aa2..65ba8b27ccb1ff66f52d6bd83a6b2af86f3980be 100644 (file)
@@ -1,5 +1,6 @@
 {-# LANGUAGE
     BangPatterns
 {-# LANGUAGE
     BangPatterns
+  , OverloadedStrings
   , UnicodeSyntax
   #-}
 -- |This is an auxiliary parser utilities for parsing things related
   , UnicodeSyntax
   #-}
 -- |This is an auxiliary parser utilities for parsing things related
 -- In general you don't have to use this module directly.
 module Network.HTTP.Lucu.Parser.Http
     ( isCtl
 -- In general you don't have to use this module directly.
 module Network.HTTP.Lucu.Parser.Http
     ( isCtl
+    , isText
     , isSeparator
     , isChar
     , isToken
     , isSeparator
     , isChar
     , isToken
+    , isSPHT
+
     , listOf
     , listOf
-    , token
+
+    , crlf
+    , sp
     , lws
     , lws
-    , text
-    , separator
+
+    , token
+    , separators
     , quotedStr
     , qvalue
     , quotedStr
     , qvalue
+
+    , atMost
     )
     where
     )
     where
+import Control.Applicative
+import Control.Applicative.Unicode
+import Control.Monad.Unicode
+import Data.Ascii (Ascii)
+import qualified Data.Ascii as A
+import Data.Attoparsec.Char8 as P
+import qualified Data.Attoparsec.FastSet as FS
+import qualified Data.ByteString.Char8 as BS
+import Prelude.Unicode
 
 
-import           Network.HTTP.Lucu.Parser
-
--- |@'isCtl' c@ is 'Prelude.False' iff @0x20 <= @c@ < 0x7F@.
-isCtl :: Char -> Bool
+-- |@'isCtl' c@ is 'Prelude.False' iff @0x20 <= c < 0x7F@.
+isCtl ∷ Char → Bool
+{-# INLINE isCtl #-}
 isCtl c
 isCtl c
-    | c <  '\x1f' = True
-    | c >= '\x7f' = True
-    | otherwise   = False
+    | c ≤ '\x1f' = True
+    | c > '\x7f' = True
+    | otherwise  = False
+
+-- |@'isText'@ is equivalent to @'not' '.' 'isCtl'@
+isText ∷ Char → Bool
+{-# INLINE isText #-}
+isText = (¬) ∘ isCtl
 
 -- |@'isSeparator' c@ is 'Prelude.True' iff c is one of HTTP
 -- separators.
 
 -- |@'isSeparator' c@ is 'Prelude.True' iff c is one of HTTP
 -- separators.
-isSeparator :: Char -> Bool
-isSeparator '('  = True
-isSeparator ')'  = True
-isSeparator '<'  = True
-isSeparator '>'  = True
-isSeparator '@'  = True
-isSeparator ','  = True
-isSeparator ';'  = True
-isSeparator ':'  = True
-isSeparator '\\' = True
-isSeparator '"'  = True
-isSeparator '/'  = True
-isSeparator '['  = True
-isSeparator ']'  = True
-isSeparator '?'  = True
-isSeparator '='  = True
-isSeparator '{'  = True
-isSeparator '}'  = True
-isSeparator ' '  = True
-isSeparator '\t' = True
-isSeparator _    = False
+isSeparator ∷ Char → Bool
+{-# INLINE isSeparator #-}
+isSeparator = flip FS.memberChar set
+    where
+      {-# NOINLINE set #-}
+      set = FS.charClass "()<>@,;:\\\"/[]?={}\x20\x09"
 
 -- |@'isChar' c@ is 'Prelude.True' iff @c <= 0x7f@.
 
 -- |@'isChar' c@ is 'Prelude.True' iff @c <= 0x7f@.
-isChar :: Char -> Bool
-isChar c
-    | c <= '\x7f' = True
-    | otherwise   = False
+isChar ∷ Char → Bool
+{-# INLINE isChar #-}
+isChar = (≤ '\x7F')
 
 -- |@'isToken' c@ is equivalent to @not ('isCtl' c || 'isSeparator'
 -- c)@
 
 -- |@'isToken' c@ is equivalent to @not ('isCtl' c || 'isSeparator'
 -- c)@
-isToken :: Char -> Bool
-isToken c = c `seq`
-            not (isCtl c || isSeparator c)
-
--- |@'listOf' p@ is similar to @'Network.HTTP.Lucu.Parser.sepBy' p
--- ('Network.HTTP.Lucu.Parser.char' \',\')@ but it allows any
--- occurrences of LWS before and after each tokens.
-listOf :: Parser a -> Parser [a]
-listOf !p = do _ <- many lws
-               sepBy p $! do _ <- many lws
-                             _ <- char ','
-                             many lws
-
--- |'token' is equivalent to @'Network.HTTP.Lucu.Parser.many1' $
--- 'Network.HTTP.Lucu.Parser.satisfy' 'isToken'@
-token :: Parser String
-token = many1 $! satisfy isToken
-
--- |'lws' is an HTTP LWS: @'Network.HTTP.Lucu.Parser.crlf'?
--- ('Network.HTTP.Lucu.Parser.sp' | 'Network.HTTP.Lucu.Parser.ht')+@
-lws :: Parser String
-lws = do s  <- option "" crlf
-         xs <- many1 (sp <|> ht)
-         return (s ++ xs)
-
--- |'text' accepts one character which doesn't satisfy 'isCtl'.
-text :: Parser Char
-text = satisfy (not . isCtl)
-
--- |'separator' accepts one character which satisfies 'isSeparator'.
-separator :: Parser Char
-separator = satisfy isSeparator
+isToken ∷ Char → Bool
+{-# INLINE isToken #-}
+isToken !c
+    = (¬) (isCtl c ∨ isSeparator c)
+
+-- |@'listOf' p@ is similar to @'sepBy' p ('char' \',\')@ but it
+-- allows any occurrences of 'lws' before and after each tokens.
+listOf ∷ Parser a → Parser [a]
+{-# INLINEABLE listOf #-}
+listOf p
+    = try $
+      do skipMany lws
+         sepBy p $ do skipMany lws
+                      _ <- char ','
+                      skipMany lws
+
+-- |'token' is similar to @'takeWhile1' 'isToken'@
+token ∷ Parser Ascii
+{-# INLINE token #-}
+token = A.unsafeFromByteString <$> takeWhile1 isToken
+
+-- |The CRLF: 0x0D 0x0A.
+crlf ∷ Parser ()
+{-# INLINE crlf #-}
+crlf = string "\x0D\x0A" ≫ return ()
+
+-- |The SP: 0x20.
+sp ∷ Parser ()
+{-# INLINE sp #-}
+sp = char '\x20' ≫ return ()
+
+-- |HTTP LWS: crlf? (sp | ht)+
+lws ∷ Parser ()
+{-# INLINEABLE lws #-}
+lws = try $
+      do option () crlf
+         _ ← satisfy isSPHT
+         skipWhile isSPHT
+
+-- |Returns 'True' for SP and HT.
+isSPHT ∷ Char → Bool
+{-# INLINE isSPHT #-}
+isSPHT '\x20' = True
+isSPHT '\x09' = True
+isSPHT _      = False
+
+-- |@'separators'@ is similar to @'takeWhile1' 'isSeparator'@.
+separators ∷ Parser Ascii
+{-# INLINE separators #-}
+separators = A.unsafeFromByteString <$> takeWhile1 isSeparator
 
 -- |'quotedStr' accepts a string surrounded by double quotation
 -- marks. Quotes can be escaped by backslashes.
 
 -- |'quotedStr' accepts a string surrounded by double quotation
 -- marks. Quotes can be escaped by backslashes.
-quotedStr :: Parser String
-quotedStr = do _  <- char '"'
-               xs <- many (qdtext <|> quotedPair)
-               _  <- char '"'
-               return $ foldr (++) "" xs
+quotedStr ∷ Parser Ascii
+{-# INLINEABLE quotedStr #-}
+quotedStr = try $
+            do _  ← char '"'
+               xs ← P.many (qdtext <|> quotedPair)
+               _  ← char '"'
+               return $ A.unsafeFromByteString $ BS.pack xs
     where
     where
-      qdtext = do c <- satisfy (/= '"')
-                  return [c]
+      qdtext ∷ Parser Char
+      {-# INLINE qdtext #-}
+      qdtext = satisfy (\c → c ≢ '"' ∧ (¬) (isCtl c))
 
 
-      quotedPair = do _ <- char '\\'
-                      c <- satisfy isChar
-                      return [c]
+      quotedPair ∷ Parser Char
+      {-# INLINE quotedPair #-}
+      quotedPair = char '\\' ≫ satisfy isChar
 
 -- |'qvalue' accepts a so-called qvalue.
 
 -- |'qvalue' accepts a so-called qvalue.
-qvalue :: Parser Double
-qvalue = do x  <- char '0'
-            xs <- option ""
-                  $ do y  <- char '.'
-                       ys <- many digit -- 本當は三文字までに制限
-                       return (y:ys)
+qvalue ∷ Parser Double
+{-# INLINEABLE qvalue #-}
+qvalue = do x  ← char '0'
+            xs ← option "" $
+                 do y  ← char '.'
+                    ys ← atMost 3 digit
+                    return (y:ys)
             return $ read (x:xs)
          <|>
             return $ read (x:xs)
          <|>
-         do x  <- char '1'
-            xs <- option ""
-                  $ do y  <- char '.'
-                       ys <- many (char '0') -- 本當は三文字までに制限
-                       return (y:ys)
+         do x   char '1'
+            xs ← option "" $
+                 do y  ← char '.'
+                    ys ← atMost 3 (char '0')
+                    return (y:ys)
             return $ read (x:xs)
             return $ read (x:xs)
+
+-- |@'atMost' n v@ is like @'P.many' v@ but applies the given action
+-- at most @n@ times.
+atMost ∷ Alternative f ⇒ Int → f a → f [a]
+{-# INLINE atMost #-}
+atMost 0 _ = pure []
+atMost n v = ( (:) <$> v ⊛ atMost (n-1) v )
+             <|>
+             pure []
index 712a6107f2932f93d603e9e272013e65c2553578..8b516cca432b6cc5b67aa6298059c2e220806f91 100644 (file)
@@ -1,3 +1,7 @@
+{-# LANGUAGE
+    OverloadedStrings
+  , UnicodeSyntax
+  #-}
 {-# OPTIONS_HADDOCK prune #-}
 
 -- |Definition of things related on HTTP request.
 {-# OPTIONS_HADDOCK prune #-}
 
 -- |Definition of things related on HTTP request.
@@ -9,12 +13,16 @@ module Network.HTTP.Lucu.Request
     , requestP
     )
     where
     , requestP
     )
     where
-
-import           Network.HTTP.Lucu.Headers
-import           Network.HTTP.Lucu.HttpVersion
-import           Network.HTTP.Lucu.Parser
-import           Network.HTTP.Lucu.Parser.Http
-import           Network.URI
+import Control.Applicative
+import Control.Monad.Unicode
+import Data.Ascii (Ascii)
+import Data.Attoparsec.Char8
+import qualified Data.ByteString.Char8 as C8
+import Network.HTTP.Lucu.Headers
+import Network.HTTP.Lucu.HttpVersion
+import Network.HTTP.Lucu.Parser.Http
+import Network.URI
+import Prelude.Unicode
 
 -- |This is the definition of HTTP request methods, which shouldn't
 -- require any description.
 
 -- |This is the definition of HTTP request methods, which shouldn't
 -- require any description.
@@ -26,28 +34,27 @@ data Method = OPTIONS
             | DELETE
             | TRACE
             | CONNECT
             | DELETE
             | TRACE
             | CONNECT
-            | ExtensionMethod !String
+            | ExtensionMethod !Ascii
               deriving (Eq, Show)
 
 -- |This is the definition of HTTP reqest.
 data Request
     = Request {
               deriving (Eq, Show)
 
 -- |This is the definition of HTTP reqest.
 data Request
     = Request {
-        reqMethod  :: !Method
-      , reqURI     :: !URI
-      , reqVersion :: !HttpVersion
-      , reqHeaders :: !Headers
+        reqMethod   !Method
+      , reqURI      !URI
+      , reqVersion  !HttpVersion
+      , reqHeaders  !Headers
       }
       }
-    deriving (Show, Eq)
+    deriving (Eq, Show)
 
 instance HasHeaders Request where
     getHeaders = reqHeaders
     setHeaders req hdr = req { reqHeaders = hdr }
 
 
 instance HasHeaders Request where
     getHeaders = reqHeaders
     setHeaders req hdr = req { reqHeaders = hdr }
 
-
-requestP :: Parser Request
-requestP = do _                      <- many crlf
-              (method, uri, version) <- requestLineP
-              headers                <- headersP
+requestP ∷ Parser Request
+requestP = do skipMany crlf
+              (method, uri, version) ← requestLineP
+              headers                ← headersP
               return Request {
                            reqMethod  = method
                          , reqURI     = uri
               return Request {
                            reqMethod  = method
                          , reqURI     = uri
@@ -55,35 +62,32 @@ requestP = do _                      <- many crlf
                          , reqHeaders = headers
                          }
 
                          , reqHeaders = headers
                          }
 
-
-requestLineP :: Parser (Method, URI, HttpVersion)
-requestLineP = do method <- methodP
-                  _      <- sp
-                  uri    <- uriP
-                  _      <- sp
-                  ver    <- httpVersionP
-                  _      <- crlf
+requestLineP ∷ Parser (Method, URI, HttpVersion)
+requestLineP = do method ← methodP
+                  sp
+                  uri    ← uriP
+                  sp
+                  ver    ← httpVersionP
+                  crlf
                   return (method, uri, ver)
 
                   return (method, uri, ver)
 
+methodP ∷ Parser Method
+methodP = choice
+          [ string "OPTIONS" ≫ return OPTIONS
+          , string "GET"     ≫ return GET
+          , string "HEAD"    ≫ return HEAD
+          , string "POST"    ≫ return POST
+          , string "PUT"     ≫ return PUT
+          , string "DELETE"  ≫ return DELETE
+          , string "TRACE"   ≫ return TRACE
+          , string "CONNECT" ≫ return CONNECT
+          , ExtensionMethod <$> token
+          ]
 
 
-methodP :: Parser Method
-methodP = ( let methods = [ ("OPTIONS", OPTIONS)
-                          , ("GET"    , GET    )
-                          , ("HEAD"   , HEAD   )
-                          , ("POST"   , POST   )
-                          , ("PUT"    , PUT    )
-                          , ("DELETE" , DELETE )
-                          , ("TRACE"  , TRACE  )
-                          , ("CONNECT", CONNECT)
-                          ]
-            in choice $ map (\ (str, mth)
-                                 -> string str >> return mth) methods )
-          <|>
-          fmap ExtensionMethod token
-
-
-uriP :: Parser URI
-uriP = do str <- many1 $ satisfy (\ c -> not (isCtl c || c == ' '))
+uriP ∷ Parser URI
+uriP = try $
+       do bs ← takeWhile1 (\c → (¬) (isCtl c ∨ c ≡ '\x20'))
+          let str = C8.unpack bs
           case parseURIReference str of
           case parseURIReference str of
-            Nothing  -> failP
-            Just uri -> return uri
\ No newline at end of file
+            Nothing  -> fail ("Unparsable URI: " ⧺ str)
+            Just uri -> return uri
index d3b8daad721a88b8b28a700c28565a278101d20a..ab8e5c7528f594242b9f0aeea51d4da5d3f770a0 100644 (file)
@@ -7,7 +7,6 @@ module Network.HTTP.Lucu.RequestReader
     ( requestReader
     )
     where
     ( requestReader
     )
     where
-
 import           Control.Concurrent.STM
 import           Control.Exception
 import           Control.Monad
 import           Control.Concurrent.STM
 import           Control.Exception
 import           Control.Monad
@@ -23,7 +22,6 @@ import           Network.HTTP.Lucu.Chunk
 import           Network.HTTP.Lucu.DefaultPage
 import           Network.HTTP.Lucu.HandleLike
 import           Network.HTTP.Lucu.Interaction
 import           Network.HTTP.Lucu.DefaultPage
 import           Network.HTTP.Lucu.HandleLike
 import           Network.HTTP.Lucu.Interaction
-import           Network.HTTP.Lucu.Parser
 import           Network.HTTP.Lucu.Postprocess
 import           Network.HTTP.Lucu.Preprocess
 import           Network.HTTP.Lucu.Request
 import           Network.HTTP.Lucu.Postprocess
 import           Network.HTTP.Lucu.Preprocess
 import           Network.HTTP.Lucu.Request
index fa08fa5c3450c28b2131c7aa3320da814afa4e21..3bc75246cca62f8e9ba6b8be59d8a01d53aefd70 100644 (file)
@@ -139,7 +139,6 @@ module Network.HTTP.Lucu.Resource
     , driftTo
     )
     where
     , driftTo
     )
     where
-
 import           Control.Concurrent.STM
 import           Control.Monad.Reader
 import qualified Data.ByteString as Strict (ByteString)
 import           Control.Concurrent.STM
 import           Control.Monad.Reader
 import qualified Data.ByteString as Strict (ByteString)
@@ -161,7 +160,6 @@ import qualified Network.HTTP.Lucu.Headers as H
 import           Network.HTTP.Lucu.HttpVersion
 import           Network.HTTP.Lucu.Interaction
 import           Network.HTTP.Lucu.MultipartForm
 import           Network.HTTP.Lucu.HttpVersion
 import           Network.HTTP.Lucu.Interaction
 import           Network.HTTP.Lucu.MultipartForm
-import           Network.HTTP.Lucu.Parser
 import           Network.HTTP.Lucu.Postprocess
 import           Network.HTTP.Lucu.Request
 import           Network.HTTP.Lucu.Response
 import           Network.HTTP.Lucu.Postprocess
 import           Network.HTTP.Lucu.Request
 import           Network.HTTP.Lucu.Response
index c85c9a72b6a023241857ba1c613eb1e4d5a2485e..387cca24a58f16bf3b08f4a63a99e2a3f41aeedc 100644 (file)
@@ -1,5 +1,6 @@
 {-# LANGUAGE
     BangPatterns
 {-# LANGUAGE
     BangPatterns
+  , OverloadedStrings
   , UnicodeSyntax
   #-}
 -- |Utility functions used internally in the Lucu httpd. These
   , UnicodeSyntax
   #-}
 -- |Utility functions used internally in the Lucu httpd. These
@@ -7,71 +8,72 @@
 module Network.HTTP.Lucu.Utils
     ( splitBy
     , joinWith
 module Network.HTTP.Lucu.Utils
     ( splitBy
     , joinWith
-    , trim
-    , isWhiteSpace
     , quoteStr
     , parseWWWFormURLEncoded
     )
     where
     , quoteStr
     , parseWWWFormURLEncoded
     )
     where
-
 import Control.Monad
 import Control.Monad
-import Data.List     hiding (last)
+import Data.Ascii (Ascii, AsciiBuilder)
+import qualified Data.Ascii as A
+import qualified Data.ByteString.Char8 as BS
+import Data.List hiding (last)
+import Data.Monoid.Unicode
 import Network.URI
 import Network.URI
-import Prelude       hiding (last)
+import Prelude hiding (last)
+import Prelude.Unicode
 
 -- |> splitBy (== ':') "ab:c:def"
 --  > ==> ["ab", "c", "def"]
 
 -- |> splitBy (== ':') "ab:c:def"
 --  > ==> ["ab", "c", "def"]
-splitBy :: (a -> Bool) -> [a] -> [[a]]
+splitBy ∷ (a → Bool) → [a] → [[a]]
 splitBy isSep src
     = case break isSep src
 splitBy isSep src
     = case break isSep src
-      of (last , []       ) -> [last]
-         (first, _sep:rest) -> first : splitBy isSep rest
+      of (last , []       )  [last]
+         (first, _sep:rest)  first : splitBy isSep rest
 
 -- |> joinWith ":" ["ab", "c", "def"]
 --  > ==> "ab:c:def"
 
 -- |> joinWith ":" ["ab", "c", "def"]
 --  > ==> "ab:c:def"
-joinWith :: [a] -> [[a]] -> [a]
-joinWith = (join .) . intersperse
-
--- |> trim (== '_') "__ab_c__def___"
---  > ==> "ab_c__def"
-trim :: (a -> Bool) -> [a] -> [a]
-trim !p = trimTail . trimHead
+joinWith ∷ Ascii → [Ascii] → AsciiBuilder
+{-# INLINEABLE joinWith #-}
+joinWith sep = flip go (∅)
     where
     where
-      trimHead = dropWhile p
-      trimTail = reverse . trimHead . reverse
-
--- |@'isWhiteSpace' c@ is 'Prelude.True' iff c is one of SP, HT, CR
--- and LF.
-isWhiteSpace :: Char -> Bool
-isWhiteSpace ' '  = True
-isWhiteSpace '\t' = True
-isWhiteSpace '\r' = True
-isWhiteSpace '\n' = True
-isWhiteSpace _    = False
-{-# INLINE isWhiteSpace #-}
+      go ∷ [Ascii] → A.AsciiBuilder → A.AsciiBuilder
+      {-# INLINE go #-}
+      go []     ab = ab
+      go (x:[]) ab = ab ⊕ A.toAsciiBuilder x
+      go (x:xs) ab = go xs ( ab ⊕
+                             A.toAsciiBuilder sep ⊕
+                             A.toAsciiBuilder x )
 
 -- |> quoteStr "abc"
 --  > ==> "\"abc\""
 --
 --  > quoteStr "ab\"c"
 --  > ==> "\"ab\\\"c\""
 
 -- |> quoteStr "abc"
 --  > ==> "\"abc\""
 --
 --  > quoteStr "ab\"c"
 --  > ==> "\"ab\\\"c\""
-quoteStr :: String -> String
-quoteStr !str = concat (["\""] ++ map quote str ++ ["\""])
+quoteStr ∷ Ascii → AsciiBuilder
+quoteStr str = A.toAsciiBuilder "\"" ⊕
+               go (A.toByteString str) (∅) ⊕
+               A.toAsciiBuilder "\""
     where
     where
-      quote :: Char -> String
-      quote '"' = "\\\""
-      quote c   = [c]
+      go ∷ BS.ByteString → AsciiBuilder → AsciiBuilder
+      go bs ab
+          = case BS.break (≡ '"') bs of
+              (x, y)
+                  | BS.null y → ab ⊕ b2ab x
+                  | otherwise → go (BS.tail y) (ab ⊕ b2ab x
+                                                   ⊕ A.toAsciiBuilder "\\\"")
 
 
+      b2ab ∷ BS.ByteString → AsciiBuilder
+      b2ab = A.toAsciiBuilder ∘ A.unsafeFromByteString
 
 -- |> parseWWWFormURLEncoded "aaa=bbb&ccc=ddd"
 --  > ==> [("aaa", "bbb"), ("ccc", "ddd")]
 
 -- |> parseWWWFormURLEncoded "aaa=bbb&ccc=ddd"
 --  > ==> [("aaa", "bbb"), ("ccc", "ddd")]
-parseWWWFormURLEncoded :: String -> [(String, String)]
+parseWWWFormURLEncoded ∷ String → [(String, String)]
 parseWWWFormURLEncoded src
     | src == "" = []
 parseWWWFormURLEncoded src
     | src == "" = []
-    | otherwise = do pairStr <- splitBy (\ c -> c == ';' || c == '&') src
+    | otherwise = do pairStr <- splitBy (\ c  c == ';' || c == '&') src
                      let (key, value) = break (== '=') pairStr
                      return ( unEscapeString key
                             , unEscapeString $ case value of
                      let (key, value) = break (== '=') pairStr
                      return ( unEscapeString key
                             , unEscapeString $ case value of
-                                                 ('=':val) -> val
-                                                 val       -> val
+                                                 ('=':val)  val
+                                                 val        val
                             )
                             )