]> gitweb @ CieloNegro.org - Lucu.git/commitdiff
Many bugfixes
authorPHO <pho@cielonegro.org>
Wed, 26 Oct 2011 17:21:09 +0000 (02:21 +0900)
committerPHO <pho@cielonegro.org>
Wed, 26 Oct 2011 17:21:09 +0000 (02:21 +0900)
Ditz-issue: 8959dadc07db1bd363283dee401073f6e48dc7fa

17 files changed:
ImplantFile.hs
Lucu.cabal
Network/HTTP/Lucu/Chunk.hs
Network/HTTP/Lucu/Config.hs
Network/HTTP/Lucu/Headers.hs
Network/HTTP/Lucu/Interaction.hs
Network/HTTP/Lucu/MIMEType/Guess.hs
Network/HTTP/Lucu/MultipartForm.hs
Network/HTTP/Lucu/Parser.hs [new file with mode: 0644]
Network/HTTP/Lucu/Parser/Http.hs
Network/HTTP/Lucu/RFC2231.hs
Network/HTTP/Lucu/RequestReader.hs
Network/HTTP/Lucu/Resource.hs
Network/HTTP/Lucu/Resource/Internal.hs
Network/HTTP/Lucu/StaticFile.hs
Network/HTTP/Lucu/Utils.hs
examples/Multipart.hs

index 67633f763e3db855c6b4d4f6c39eeb62bb56aa1b..c3cff030271fd2e881be0d0db07180291858c9b2 100644 (file)
@@ -41,27 +41,27 @@ data CmdOpt
     deriving (Eq, Show)
 
 options ∷ [OptDescr CmdOpt]
     deriving (Eq, Show)
 
 options ∷ [OptDescr CmdOpt]
-options = [ Option ['o'] ["output"]
+options = [ Option "o" ["output"]
                        (ReqArg OptOutput "FILE")
                        "Output to the FILE."
 
                        (ReqArg OptOutput "FILE")
                        "Output to the FILE."
 
-          , Option ['m'] ["module"]
+          , Option "m" ["module"]
                        (ReqArg OptModName "MODULE")
                        "Specify the resulting module name. (required)"
 
                        (ReqArg OptModName "MODULE")
                        "Specify the resulting module name. (required)"
 
-          , Option ['s'] ["symbol"]
+          , Option "s" ["symbol"]
                        (ReqArg OptSymName "SYMBOL")
                        "Specify the resulting symbol name."
 
                        (ReqArg OptSymName "SYMBOL")
                        "Specify the resulting symbol name."
 
-          , Option ['t'] ["mime-type"]
+          , Option "t" ["mime-type"]
                        (ReqArg OptMIMEType "TYPE")
                        "Specify the MIME Type of the file."
 
                        (ReqArg OptMIMEType "TYPE")
                        "Specify the MIME Type of the file."
 
-          , Option ['e'] ["etag"]
+          , Option "e" ["etag"]
                        (ReqArg OptETag "TAG")
                        "Specify the ETag of the file."
 
                        (ReqArg OptETag "TAG")
                        "Specify the ETag of the file."
 
-          , Option ['h'] ["help"]
+          , Option "h" ["help"]
                        (NoArg OptHelp)
                        "Print this message."
           ]
                        (NoArg OptHelp)
                        "Print this message."
           ]
@@ -126,19 +126,15 @@ generateHaskellSource opts srcFile
 
          let hsModule = mkModule modName symName imports decls
              imports  = mkImports useGZip
 
          let hsModule = mkModule modName symName imports decls
              imports  = mkImports useGZip
-             decls    = concat ([ resourceDecl symName useGZip
-                                , entityTagDecl eTag
-                                , lastModifiedDecl lastMod
-                                , contentTypeDecl mimeType
-                                ]
-                                ⧺
-                                if useGZip then
-                                    [ gunzipAndPutChunkDecl
-                                    , dataDecl (name "gzippedData") gzippedB64
-                                    ]
-                                else
-                                    [ dataDecl (name "rawData") rawB64 ]
-                               )
+             decls    = concat [ resourceDecl symName useGZip
+                               , entityTagDecl eTag
+                               , lastModifiedDecl lastMod
+                               , contentTypeDecl mimeType
+                               , if useGZip then
+                                     dataDecl (name "gzippedData") gzippedB64
+                                 else
+                                     dataDecl (name "rawData") rawB64
+                               ]
 
          hPutStrLn output header
          hPutStrLn output (prettyPrint hsModule)
 
          hPutStrLn output header
          hPutStrLn output (prettyPrint hsModule)
@@ -163,16 +159,9 @@ mkImports useGZip
                    False False Nothing Nothing Nothing
       ]
       ⧺
                    False False Nothing Nothing Nothing
       ]
       ⧺
-      if useGZip then
-          [ ImportDecl (⊥) (ModuleName "Blaze.ByteString.Builder.ByteString")
-                       True False Nothing (Just (ModuleName "BB")) Nothing
-          , ImportDecl (⊥) (ModuleName "Codec.Compression.Zlib.Internal")
-                       False False Nothing Nothing Nothing
-          , ImportDecl (⊥) (ModuleName "Data.Text")
-                       True False Nothing (Just (ModuleName "T")) Nothing
-          ]
-      else
-          []
+      [ ImportDecl (⊥) (ModuleName "Codec.Compression.GZip")
+                   False False Nothing Nothing Nothing
+        | useGZip ]
 
 resourceDecl ∷ Name → Bool → [Decl]
 resourceDecl symName useGZip
 
 resourceDecl ∷ Name → Bool → [Decl]
 resourceDecl symName useGZip
@@ -223,16 +212,18 @@ resGetGZipped
           = qualStmt $
             If (var condVarName)
                (doE [ setContentEncodingGZipStmt
           = qualStmt $
             If (var condVarName)
                (doE [ setContentEncodingGZipStmt
-                    , outputStmt (var dataVarName)
+                    , putChunksStmt (var dataVarName)
                     ])
                     ])
-               (function "gunzipAndPutChunk" `app` var dataVarName)
+               (putChunksExp
+                (paren
+                 (function "decompress" `app` var dataVarName)))
 
 resGetRaw ∷ Exp
 resGetRaw
     = function "Just" `app`
       paren (doE [ foundEntityStmt
                  , setContentTypeStmt
 
 resGetRaw ∷ Exp
 resGetRaw
     = function "Just" `app`
       paren (doE [ foundEntityStmt
                  , setContentTypeStmt
-                 , outputStmt (function "rawData")
+                 , putChunksStmt (function "rawData")
                  ])
 
 setContentEncodingGZipStmt ∷ Stmt
                  ])
 
 setContentEncodingGZipStmt ∷ Stmt
@@ -259,9 +250,11 @@ setContentTypeStmt
         function "contentType"
       )
 
         function "contentType"
       )
 
-outputStmt ∷ Exp → Stmt
-outputStmt e
-    = qualStmt $ function "putChunk" `app` e
+putChunksExp ∷ Exp → Exp
+putChunksExp = app (function "putChunks")
+
+putChunksStmt ∷ Exp → Stmt
+putChunksStmt = qualStmt ∘ putChunksExp
 
 entityTagDecl ∷ ETag → [Decl]
 entityTagDecl eTag
 
 entityTagDecl ∷ ETag → [Decl]
 entityTagDecl eTag
@@ -279,6 +272,7 @@ lastModifiedDecl ∷ UTCTime → [Decl]
 lastModifiedDecl lastMod
     = [ TypeSig (⊥) [varName] (TyCon (UnQual (name "UTCTime")))
       , nameBind (⊥) varName valExp
 lastModifiedDecl lastMod
     = [ TypeSig (⊥) [varName] (TyCon (UnQual (name "UTCTime")))
       , nameBind (⊥) varName valExp
+      , InlineSig (⊥) False AlwaysActive (UnQual varName)
       ]
     where
       varName ∷ Name
       ]
     where
       varName ∷ Name
@@ -291,6 +285,7 @@ contentTypeDecl ∷ MIMEType → [Decl]
 contentTypeDecl mime
     = [ TypeSig (⊥) [varName] (TyCon (UnQual (name "MIMEType")))
       , nameBind (⊥) varName valExp
 contentTypeDecl mime
     = [ TypeSig (⊥) [varName] (TyCon (UnQual (name "MIMEType")))
       , nameBind (⊥) varName valExp
+      , InlineSig (⊥) False AlwaysActive (UnQual varName)
       ]
     where
       varName ∷ Name
       ]
     where
       varName ∷ Name
@@ -302,88 +297,11 @@ contentTypeDecl mime
       mimeToString ∷ MIMEType → String
       mimeToString = A.toString ∘ A.fromAsciiBuilder ∘ printMIMEType
 
       mimeToString ∷ MIMEType → String
       mimeToString = A.toString ∘ A.fromAsciiBuilder ∘ printMIMEType
 
-gunzipAndPutChunkDecl ∷ [Decl]
-gunzipAndPutChunkDecl
-    = [ TypeSig (⊥) [funName]
-                    (TyFun (TyCon (Qual (ModuleName "Lazy") (name "ByteString")))
-                           tyResourceUnit)
-      , sfun (⊥) funName [] (UnGuardedRhs funExp) (binds goDecl)
-      ]
-    where
-      funName ∷ Name
-      funName = name "gunzipAndPutChunk"
-
-      goName ∷ Name
-      goName = name "go"
-
-      tyResourceUnit ∷ Type
-      tyResourceUnit
-          = TyApp (TyCon (UnQual (name "Resource")))
-                  (TyTuple Boxed [])
-
-      funExp ∷ Exp
-      funExp = var goName
-               `app`
-               function "."
-               `app`
-               metaFunction "decompressWithErrors"
-                                [ function "gzipFormat"
-                                , function "defaultDecompressParams"
-                                ]
-
-      goDecl ∷ [Decl]
-      goDecl = [ TypeSig (⊥) [goName]
-                             (TyFun (TyCon (UnQual (name "DecompressStream")))
-                                    tyResourceUnit)
-               , FunBind [ Match (⊥) goName [pvar (name "StreamEnd")]
-                                 Nothing (UnGuardedRhs endExp) (binds [])
-                         , Match (⊥) goName [pApp (name "StreamChunk")
-                                                  [ pvar (name "x")
-                                                  , pvar (name "xs") ]]
-                                 Nothing (UnGuardedRhs chunkExp) (binds [])
-                         , Match (⊥) goName [pApp (name "StreamError")
-                                                   [ wildcard
-                                                   , pvar (name "msg") ]]
-                                 Nothing (UnGuardedRhs errorExp) (binds [])
-                         ]
-               ]
-
-      endExp ∷ Exp
-      endExp = function "return" `app` tuple []
-
-      chunkExp ∷ Exp
-      chunkExp = function "putBuilder"
-                 `app`
-                 paren ( qvar (ModuleName "BB") (name "fromByteString")
-                         `app`
-                         var (name "x")
-                       )
-                 `app`
-                 function ">>"
-                 `app`
-                 function "go" `app` var (name "xs")
-
-      errorExp ∷ Exp
-      errorExp = metaFunction "abort"
-                 [ var (name "InternalServerError")
-                 , listE []
-                 , function "Just"
-                   `app`
-                   paren ( qvar (ModuleName "T") (name "pack")
-                           `app`
-                           paren ( strE "gunzip: "
-                                   `app`
-                                   function "++"
-                                   `app`
-                                   var (name "msg")
-                                 )
-                         )
-                 ]
-
 dataDecl ∷ Name → [Strict.ByteString] → [Decl]
 dataDecl varName chunks
     = [ TypeSig (⊥) [varName] (TyCon (Qual (ModuleName "Lazy") (name "ByteString")))
       , nameBind (⊥) varName valExp
 dataDecl ∷ Name → [Strict.ByteString] → [Decl]
 dataDecl varName chunks
     = [ TypeSig (⊥) [varName] (TyCon (Qual (ModuleName "Lazy") (name "ByteString")))
       , nameBind (⊥) varName valExp
+      , InlineSig (⊥) False AlwaysActive (UnQual varName)
       ]
     where
       valExp ∷ Exp
       ]
     where
       valExp ∷ Exp
@@ -570,12 +488,15 @@ openOutput opts
   entityTag = strongETag "d41d8cd98f00b204e9800998ecf8427e"
 
   lastModified ∷ UTCTime
   entityTag = strongETag "d41d8cd98f00b204e9800998ecf8427e"
 
   lastModified ∷ UTCTime
+  {-# NOINLINE lastModified #-}
   lastModified = read "2007-11-05 04:47:56.008366 UTC"
 
   contentType ∷ MIMEType
   lastModified = read "2007-11-05 04:47:56.008366 UTC"
 
   contentType ∷ MIMEType
+  {-# NOINLINE contentType #-}
   contentType = parseMIMEType "image/png"
 
   rawData ∷ Lazy.ByteString
   contentType = parseMIMEType "image/png"
 
   rawData ∷ Lazy.ByteString
+  {-# NOINLINE rawData #-}
   rawData = Lazy.fromChunks
             [ B64.decodeLenient "IyEvdXNyL2Jpbi9lbnYgcnVuZ2hjCgppbXBvcnQgRG..."
             , B64.decodeLenient "Otb/+DniOlRgAAAAYAAAAGAAAAB/6QOmToAEIGAAAA..."
   rawData = Lazy.fromChunks
             [ B64.decodeLenient "IyEvdXNyL2Jpbi9lbnYgcnVuZ2hjCgppbXBvcnQgRG..."
             , B64.decodeLenient "Otb/+DniOlRgAAAAYAAAAGAAAAB/6QOmToAEIGAAAA..."
@@ -585,9 +506,7 @@ openOutput opts
   壓縮される場合は次のやうに變はる:
   ------------------------------------------------------------------------------
   -- import に追加
   壓縮される場合は次のやうに變はる:
   ------------------------------------------------------------------------------
   -- import に追加
-  import qualified Blaze.ByteString.Builder.ByteString as BB
-  import Codec.Compression.Zlib.Internal
-  import qualified Data.Text as T
+  import Codec.Compression.Zlib
 
   -- ResourceDef は次のやうに變化
   baz ∷ ResourceDef
 
   -- ResourceDef は次のやうに變化
   baz ∷ ResourceDef
@@ -601,9 +520,9 @@ openOutput opts
                         gzipAllowed ← isEncodingAcceptable "gzip"
                         if gzipAllowed then
                             do setContentEncoding ["gzip"]
                         gzipAllowed ← isEncodingAcceptable "gzip"
                         if gzipAllowed then
                             do setContentEncoding ["gzip"]
-                               putChunk gzippedData
+                               putChunks gzippedData
                         else
                         else
-                            gunzipAndPutChunk gzippedData
+                            putChunks (decompress gzippedData)
         , resHead
             = Just $ do foundEntity entityTag lastModified
                         setContentType contentType
         , resHead
             = Just $ do foundEntity entityTag lastModified
                         setContentType contentType
@@ -612,17 +531,9 @@ openOutput opts
         , resDelete = Nothing
         }
 
         , resDelete = Nothing
         }
 
-  -- 追加
-  gunzipAndPutChunk :: Lazy.ByteString -> Resource ()
-  gunzipAndPutChunk = go . decompressWithErrors gzipFormat defaultDecompressParams
-      where
-        go :: DecompressStream -> Resource ()
-        go StreamEnd = return ()
-        go (StreamChunk x xs) = putBuilder (BB.fromByteString x) >> go xs
-        go (StreamError _ msg) = abort InternalServerError [] (Just (T.pack ("gunzip: " ++ msg)))
-  
   -- rawData の代はりに gzippedData
   gzippedData ∷ Lazy.ByteString
   -- rawData の代はりに gzippedData
   gzippedData ∷ Lazy.ByteString
+  {-# NOINLINE gzippedData #-}
   gzippedData = Lazy.fromChunks
                 [ B64.decodeLenient "IyEvdXNyL2Jpbi9lbnYgcnVuZ2hjCgppbXBvcnQ..."
                 , B64.decodeLenient "Otb/+DniOlRgAAAAYAAAAGAAAAB/6QOmToAEIGA..."
   gzippedData = Lazy.fromChunks
                 [ B64.decodeLenient "IyEvdXNyL2Jpbi9lbnYgcnVuZ2hjCgppbXBvcnQ..."
                 , B64.decodeLenient "Otb/+DniOlRgAAAAYAAAAGAAAAB/6QOmToAEIGA..."
index cd690665cee75aee4c1e676ddacdc01ba7bde259..a104edf8acce619775e3ab490620d0f62c3817e3 100644 (file)
@@ -84,6 +84,7 @@ Library
         Network.HTTP.Lucu.MIMEType.DefaultExtensionMap
         Network.HTTP.Lucu.MIMEType.Guess
         Network.HTTP.Lucu.Parser.Http
         Network.HTTP.Lucu.MIMEType.DefaultExtensionMap
         Network.HTTP.Lucu.MIMEType.Guess
         Network.HTTP.Lucu.Parser.Http
+        Network.HTTP.Lucu.Parser
         Network.HTTP.Lucu.RFC2231
         Network.HTTP.Lucu.Request
         Network.HTTP.Lucu.Resource
         Network.HTTP.Lucu.RFC2231
         Network.HTTP.Lucu.Request
         Network.HTTP.Lucu.Resource
index 25d69078b79387a8afc6aad93fcb02dfb65f4b70..b48727cc0f364fdbe3037cd759cbbf01476216a4 100644 (file)
@@ -21,12 +21,11 @@ chunkHeaderP = do len ← hexadecimal
                   return len
     where
       extension ∷ Parser ()
                   return len
     where
       extension ∷ Parser ()
-      extension = skipMany $
-                  do _ ← char ';'
-                     _ ← token
-                     _ ← char '='
-                     _ ← token <|> quotedStr
-                     return ()
+      extension
+          = skipMany ( char ';' *>
+                       token    *>
+                       char '=' *>
+                       (token <|> quotedStr) )
 
 chunkFooterP ∷ Parser ()
 chunkFooterP = crlf
 
 chunkFooterP ∷ Parser ()
 chunkFooterP = crlf
index 68bc365ade7c2d950d2949b70c3a13d0b74ca144..2ea2055de853c35faa89f4d2e21cf3d3c2d74d83 100644 (file)
@@ -59,9 +59,8 @@ data Config = Config {
     -- |The maximum length of request entity to accept in octets. Note
     -- that this is nothing but a default value used by
     -- 'Network.HTTP.Lucu.Resource.getForm' and such when they are
     -- |The maximum length of request entity to accept in octets. Note
     -- that this is nothing but a default value used by
     -- 'Network.HTTP.Lucu.Resource.getForm' and such when they are
-    -- applied to 'Network.HTTP.Lucu.Resource.defaultLimit', so there
-    -- is no guarantee that this value always constrains all the
-    -- requests.
+    -- applied to 'Nothing', so there is no guarantee that this value
+    -- always constrains all the requests.
     , cnfMaxEntityLength ∷ !Int
 
     -- |Whether to dump too late abortions to the stderr or not. See
     , cnfMaxEntityLength ∷ !Int
 
     -- |Whether to dump too late abortions to the stderr or not. See
index 06dc8f95f0f2ee2c9aca0c1927b9b049797abc49..5e48ee4bd52ad8a1edc1c5a3c9ede9b2b687f100 100644 (file)
@@ -17,17 +17,18 @@ module Network.HTTP.Lucu.Headers
     )
     where
 import Control.Applicative
     )
     where
 import Control.Applicative
+import Control.Monad
 import Data.Ascii (Ascii, AsciiBuilder, CIAscii)
 import qualified Data.Ascii as A
 import Data.Attoparsec.Char8 as P
 import qualified Data.ByteString as BS
 import Data.Ascii (Ascii, AsciiBuilder, CIAscii)
 import qualified Data.Ascii as A
 import Data.Attoparsec.Char8 as P
 import qualified Data.ByteString as BS
+import Data.List
 import Data.Map (Map)
 import qualified Data.Map as M
 import qualified Data.Map.Unicode as M
 import Data.Monoid
 import Data.Monoid.Unicode
 import Network.HTTP.Lucu.Parser.Http
 import Data.Map (Map)
 import qualified Data.Map as M
 import qualified Data.Map.Unicode as M
 import Data.Monoid
 import Data.Monoid.Unicode
 import Network.HTTP.Lucu.Parser.Http
-import Network.HTTP.Lucu.Utils
 import Prelude.Unicode
 
 newtype Headers
 import Prelude.Unicode
 
 newtype Headers
@@ -123,9 +124,9 @@ headersP = do xs ← P.many header
     where
       header ∷ Parser (CIAscii, Ascii)
       header = do name ← A.toCIAscii <$> token
     where
       header ∷ Parser (CIAscii, Ascii)
       header = do name ← A.toCIAscii <$> token
-                  _    ← char ':'
+                  void $ char ':'
                   skipMany lws
                   skipMany lws
-                  values ← sepBy content (try lws)
+                  values ← content `sepBy` try lws
                   skipMany (try lws)
                   crlf
                   return (name, joinValues values)
                   skipMany (try lws)
                   crlf
                   return (name, joinValues values)
@@ -134,11 +135,14 @@ headersP = do xs ← P.many header
       {-# INLINE content #-}
       content = A.unsafeFromByteString
                 <$>
       {-# INLINE content #-}
       content = A.unsafeFromByteString
                 <$>
-                takeWhile1 (\c → (¬) (isSPHT c) ∧ isText c)
+                takeWhile1 (\c → isText c ∧ c ≢ '\x20')
 
       joinValues ∷ [Ascii] → Ascii
       {-# INLINE joinValues #-}
 
       joinValues ∷ [Ascii] → Ascii
       {-# INLINE joinValues #-}
-      joinValues = A.fromAsciiBuilder ∘ joinWith "\x20" ∘ map A.toAsciiBuilder
+      joinValues = A.fromAsciiBuilder
+                   ∘ mconcat
+                   ∘ intersperse (A.toAsciiBuilder "\x20")
+                   ∘ map A.toAsciiBuilder
 
 printHeaders ∷ Headers → AsciiBuilder
 printHeaders (Headers m)
 
 printHeaders ∷ Headers → AsciiBuilder
 printHeaders (Headers m)
index e486e1a32d2895faaa1165727fc01fd9c15f255d..e871159ada06c278078b8d29f8fb61aaec2ca8a2 100644 (file)
@@ -110,6 +110,7 @@ mkSemanticallyInvalidInteraction ∷ Config
                                  → IO SemanticallyInvalidInteraction
 mkSemanticallyInvalidInteraction config@(Config {..}) (AugmentedRequest {..})
     = do date ← getCurrentDate
                                  → IO SemanticallyInvalidInteraction
 mkSemanticallyInvalidInteraction config@(Config {..}) (AugmentedRequest {..})
     = do date ← getCurrentDate
+         -- FIXME: DRY
          let res  = setHeader "Server"       cnfServerSoftware      $
                     setHeader "Date"         date                   $
                     setHeader "Content-Type" defaultPageContentType $
          let res  = setHeader "Server"       cnfServerSoftware      $
                     setHeader "Date"         date                   $
                     setHeader "Content-Type" defaultPageContentType $
index f0f93b1e8e224c5926c9c586b468a945acdd66c6..86d7df6ef48277a798bd6e46145f77222097583d 100644 (file)
@@ -135,6 +135,7 @@ serializeExtMap extMap moduleName variableName
           decls     = [ TypeSig (⊥) [name variableName]
                                     (TyCon (UnQual (name "ExtMap")))
                       , nameBind (⊥) (name variableName) extMapExp
           decls     = [ TypeSig (⊥) [name variableName]
                                     (TyCon (UnQual (name "ExtMap")))
                       , nameBind (⊥) (name variableName) extMapExp
+                      , InlineSig (⊥) False AlwaysActive (UnQual (name variableName))
                       ]
           comment   = concat [ "{- !!! WARNING !!!\n"
                              , "   This file is automatically generated.\n"
                       ]
           comment   = concat [ "{- !!! WARNING !!!\n"
                              , "   This file is automatically generated.\n"
index 7d0866cd4225fe0ec54f8c77f84a01fc24e5dbe6..72eef21c1ec9e99be27857e48169cd0b068d6c3e 100644 (file)
@@ -11,9 +11,10 @@ module Network.HTTP.Lucu.MultipartForm
     )
     where
 import Control.Applicative hiding (many)
     )
     where
 import Control.Applicative hiding (many)
+import Control.Monad
 import Data.Ascii (Ascii, CIAscii)
 import qualified Data.Ascii as A
 import Data.Ascii (Ascii, CIAscii)
 import qualified Data.Ascii as A
-import Data.Attoparsec.Char8
+import Data.Attoparsec
 import qualified Data.ByteString.Char8 as BS
 import qualified Data.ByteString.Lazy.Char8 as LS
 import Data.Map (Map)
 import qualified Data.ByteString.Char8 as BS
 import qualified Data.ByteString.Lazy.Char8 as LS
 import Data.Map (Map)
@@ -22,6 +23,7 @@ import Data.Maybe
 import Data.Monoid.Unicode
 import Data.Text (Text)
 import Network.HTTP.Lucu.Headers
 import Data.Monoid.Unicode
 import Data.Text (Text)
 import Network.HTTP.Lucu.Headers
+import Network.HTTP.Lucu.Parser
 import Network.HTTP.Lucu.Parser.Http
 import Network.HTTP.Lucu.RFC2231
 import Prelude.Unicode
 import Network.HTTP.Lucu.Parser.Http
 import Network.HTTP.Lucu.RFC2231
 import Prelude.Unicode
@@ -60,33 +62,34 @@ printContDispo d
 
 multipartFormP ∷ Ascii → Parser [(Text, FormData)]
 multipartFormP boundary
 
 multipartFormP ∷ Ascii → Parser [(Text, FormData)]
 multipartFormP boundary
-    = do parts ← many $ try $ partP boundary
-         _     ← string "--"
-         _     ← string $ A.toByteString boundary
-         _     ← string "--"
+    = do void boundaryP
+         parts ← many $ partP boundaryP
+         void (string "--" <?> "suffix")
          crlf
          catMaybes <$> mapM partToFormPair parts
          crlf
          catMaybes <$> mapM partToFormPair parts
+      <?>
+      "multipartFormP"
+    where
+      boundaryP ∷ Parser BS.ByteString
+      boundaryP = string ("--" ⊕ A.toByteString boundary)
+                  <?>
+                  "boundaryP"
 
 
-partP ∷ Ascii → Parser Part
-partP boundary
-    = do _    ← string "--"
-         _    ← string $ A.toByteString boundary
-         crlf
+partP ∷ Parser α → Parser Part
+partP boundaryP
+    = do crlf
          hs   ← headersP
          d    ← getContDispo hs
          hs   ← headersP
          d    ← getContDispo hs
-         body ← bodyP boundary
+         body ← bodyP boundaryP
          return $ Part hs d body
          return $ Part hs d body
+      <?>
+      "partP"
 
 
-bodyP ∷ Ascii → Parser LS.ByteString
-bodyP boundary
-    = do body ← manyCharsTill anyChar $
-                    try $
-                    do crlf
-                       _ ← string "--"
-                       _ ← string $ A.toByteString boundary
-                       return ()
-         crlf
-         return body
+bodyP ∷ Parser α → Parser LS.ByteString
+bodyP boundaryP
+    = manyOctetsTill anyWord8 (try $ crlf *> boundaryP)
+      <?>
+      "bodyP"
 
 partToFormPair ∷ Monad m ⇒ Part → m (Maybe (Text, FormData))
 {-# INLINEABLE partToFormPair #-}
 
 partToFormPair ∷ Monad m ⇒ Part → m (Maybe (Text, FormData))
 {-# INLINEABLE partToFormPair #-}
@@ -138,6 +141,10 @@ getContDispo hdr
                                           ])
 
 contDispoP ∷ Parser ContDispo
                                           ])
 
 contDispoP ∷ Parser ContDispo
-contDispoP = do dispoType ← A.toCIAscii <$> token
-                params    ← paramsP
-                return $ ContDispo dispoType params
+{-# INLINEABLE contDispoP #-}
+contDispoP
+    = do dispoType ← A.toCIAscii <$> token
+         params    ← paramsP
+         return $ ContDispo dispoType params
+      <?>
+      "contDispoP"
diff --git a/Network/HTTP/Lucu/Parser.hs b/Network/HTTP/Lucu/Parser.hs
new file mode 100644 (file)
index 0000000..6b935c8
--- /dev/null
@@ -0,0 +1,97 @@
+{-# LANGUAGE
+    BangPatterns
+  , ScopedTypeVariables
+  , UnicodeSyntax
+  #-}
+-- |This is an auxiliary parser utilities. You usually don't have to
+-- use this module directly.
+module Network.HTTP.Lucu.Parser
+    ( atMost
+    , manyOctetsTill
+    )
+    where
+import Blaze.ByteString.Builder (Builder, Write)
+import qualified Blaze.ByteString.Builder as BB
+import qualified Blaze.ByteString.Builder.Internal as BI
+import Control.Applicative
+import Control.Applicative.Unicode hiding ((∅))
+import Control.Monad.Unicode
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Lazy as LS
+import Data.Monoid
+import Data.Monoid.Unicode
+import Data.Word
+import Prelude.Unicode
+
+-- |@'atMost' n v@ is like @'P.many' v@ but accumulates @v@ 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 []
+
+data OctetAccumState
+    = OctetAccumState {
+        casChunks    ∷ !Builder
+      , casLastChunk ∷ !Write
+      }
+
+instance Monoid OctetAccumState where
+    {-# INLINE mempty #-}
+    mempty
+        = OctetAccumState {
+            casChunks    = (∅)
+          , casLastChunk = (∅)
+          }
+    {-# INLINEABLE mappend #-}
+    mappend !a !b
+        = b {
+            casChunks = casChunks a ⊕ lastChunk a ⊕ casChunks b
+          }
+
+lastChunk ∷ OctetAccumState → Builder
+{-# INLINEABLE lastChunk #-}
+lastChunk !s = case toChunk s of
+                 c → BB.insertByteString c
+    where
+      toChunk ∷ OctetAccumState → BS.ByteString
+      {-# INLINE toChunk #-}
+      toChunk = BB.toByteString ∘ BB.fromWrite ∘ casLastChunk
+
+snoc ∷ OctetAccumState → Word8 → OctetAccumState
+{-# INLINEABLE snoc #-}
+snoc !s !o
+    | BI.getBound (casLastChunk s) ≥ BI.defaultBufferSize
+        = s {
+            casChunks    = casChunks s ⊕ lastChunk s
+          , casLastChunk = BB.writeWord8 o
+          }
+    | otherwise
+        = s {
+            casLastChunk = casLastChunk s ⊕ BB.writeWord8 o
+          }
+
+finish ∷ OctetAccumState → LS.ByteString
+{-# INLINEABLE finish #-}
+finish = BB.toLazyByteString ∘ toChunks
+    where
+      toChunks ∷ OctetAccumState → Builder
+      {-# INLINE toChunks #-}
+      toChunks !s = casChunks s ⊕ lastChunk s
+
+-- |@'manyOctetsTill' p end@ takes as many octets untill @p@ succeeds.
+manyOctetsTill ∷ ∀m b. (Monad m, Alternative m)
+              ⇒ m Word8
+              → m b
+              → m LS.ByteString
+{-# INLINEABLE manyOctetsTill #-}
+manyOctetsTill p end = scan (∅)
+    where
+      scan ∷ OctetAccumState → m LS.ByteString
+      {-# INLINE scan #-}
+      scan !s
+          = (end *> pure (finish s))
+            <|>
+            (scan =≪ (snoc s <$> p))
index e3fbf3501b1cc50800bf1af90f88b123beee0030..72d8ca1721ae7908a94cd1b2445eff1aaa16e1c8 100644 (file)
@@ -1,6 +1,5 @@
 {-# LANGUAGE
     OverloadedStrings
 {-# LANGUAGE
     OverloadedStrings
-  , ScopedTypeVariables
   , UnicodeSyntax
   #-}
 -- |This is an auxiliary parser utilities for parsing things related
   , UnicodeSyntax
   #-}
 -- |This is an auxiliary parser utilities for parsing things related
@@ -25,26 +24,16 @@ module Network.HTTP.Lucu.Parser.Http
     , separators
     , quotedStr
     , qvalue
     , separators
     , quotedStr
     , qvalue
-
-    , atMost
-    , manyCharsTill
     )
     where
 import Control.Applicative
     )
     where
 import Control.Applicative
-import Control.Applicative.Unicode hiding ((∅))
-import Control.Monad.Unicode
+import Control.Monad
 import Data.Ascii (Ascii)
 import qualified Data.Ascii as A
 import Data.Attoparsec.Char8 as P hiding (scan)
 import qualified Data.Attoparsec.FastSet as FS
 import qualified Data.ByteString.Char8 as BS
 import Data.Ascii (Ascii)
 import qualified Data.Ascii as A
 import Data.Attoparsec.Char8 as P hiding (scan)
 import qualified Data.Attoparsec.FastSet as FS
 import qualified Data.ByteString.Char8 as BS
-import qualified Data.ByteString.Lazy.Char8 as LS
-import qualified Data.ByteString.Lazy.Internal as LS
-import Data.Foldable
-import Data.Monoid
-import Data.Monoid.Unicode
-import qualified Data.Sequence as S
-import Data.Sequence.Unicode hiding ((∅))
+import Network.HTTP.Lucu.Parser
 import Prelude.Unicode
 
 -- |@'isCtl' c@ returns 'False' iff @0x20 <= c < 0x7F@.
 import Prelude.Unicode
 
 -- |@'isCtl' c@ returns 'False' iff @0x20 <= c < 0x7F@.
@@ -84,20 +73,27 @@ isToken c = (¬) (isCtl c ∨ isSeparator c)
 -- allows any occurrences of 'lws' before and after each tokens.
 listOf ∷ Parser a → Parser [a]
 {-# INLINEABLE listOf #-}
 -- allows any occurrences of 'lws' before and after each tokens.
 listOf ∷ Parser a → Parser [a]
 {-# INLINEABLE listOf #-}
-listOf p = do skipMany lws
-              sepBy p $ do skipMany lws
-                           _ ← char ','
-                           skipMany lws
+listOf p
+    = do skipMany lws
+         p `sepBy` do skipMany lws
+                      void $ char ','
+                      skipMany lws
+      <?>
+      "listOf"
 
 -- |'token' is almost the same as @'takeWhile1' 'isToken'@
 token ∷ Parser Ascii
 {-# INLINE token #-}
 
 -- |'token' is almost the same as @'takeWhile1' 'isToken'@
 token ∷ Parser Ascii
 {-# INLINE token #-}
-token = A.unsafeFromByteString <$> takeWhile1 isToken
+token = (A.unsafeFromByteString <$> takeWhile1 isToken)
+        <?>
+        "token"
 
 -- |The CRLF: 0x0D 0x0A.
 crlf ∷ Parser ()
 {-# INLINE crlf #-}
 
 -- |The CRLF: 0x0D 0x0A.
 crlf ∷ Parser ()
 {-# INLINE crlf #-}
-crlf = string "\x0D\x0A" *> return ()
+crlf = (string "\x0D\x0A" *> return ())
+       <?>
+       "crlf"
 
 -- |The SP: 0x20.
 sp ∷ Parser ()
 
 -- |The SP: 0x20.
 sp ∷ Parser ()
@@ -107,9 +103,9 @@ sp = char '\x20' *> return ()
 -- |HTTP LWS: crlf? (sp | ht)+
 lws ∷ Parser ()
 {-# INLINEABLE lws #-}
 -- |HTTP LWS: crlf? (sp | ht)+
 lws ∷ Parser ()
 {-# INLINEABLE lws #-}
-lws = do option () crlf
-         _ ← takeWhile1 isSPHT
-         return ()
+lws = (option () crlf *> void (takeWhile1 isSPHT))
+      <?>
+      "lws"
 
 -- |Returns 'True' for SP and HT.
 isSPHT ∷ Char → Bool
 
 -- |Returns 'True' for SP and HT.
 isSPHT ∷ Char → Bool
@@ -121,106 +117,49 @@ isSPHT _      = False
 -- |@'separators'@ is almost the same as @'takeWhile1' 'isSeparator'@.
 separators ∷ Parser Ascii
 {-# INLINE separators #-}
 -- |@'separators'@ is almost the same as @'takeWhile1' 'isSeparator'@.
 separators ∷ Parser Ascii
 {-# INLINE separators #-}
-separators = A.unsafeFromByteString <$> takeWhile1 isSeparator
+separators = (A.unsafeFromByteString <$> takeWhile1 isSeparator)
+             <?>
+             "separators"
 
 -- |'quotedStr' accepts a string surrounded by double quotation
 -- marks. Quotes can be escaped by backslashes.
 quotedStr ∷ Parser Ascii
 {-# INLINEABLE quotedStr #-}
 
 -- |'quotedStr' accepts a string surrounded by double quotation
 -- marks. Quotes can be escaped by backslashes.
 quotedStr ∷ Parser Ascii
 {-# INLINEABLE quotedStr #-}
-quotedStr = try $
-            do _  ← char '"'
+quotedStr = do void $ char '"'
                xs ← P.many (qdtext <|> quotedPair)
                xs ← P.many (qdtext <|> quotedPair)
-               _  ← char '"'
+               void $ char '"'
                return $ A.unsafeFromByteString $ BS.pack xs
                return $ A.unsafeFromByteString $ BS.pack xs
+            <?>
+            "quotedStr"
     where
       qdtext ∷ Parser Char
       {-# INLINE qdtext #-}
       qdtext = satisfy (\c → c ≢ '"' ∧ (¬) (isCtl c))
     where
       qdtext ∷ Parser Char
       {-# INLINE qdtext #-}
       qdtext = satisfy (\c → c ≢ '"' ∧ (¬) (isCtl c))
+               <?>
+               "qdtext"
 
       quotedPair ∷ Parser Char
       {-# INLINE quotedPair #-}
 
       quotedPair ∷ Parser Char
       {-# INLINE quotedPair #-}
-      quotedPair = char '\\' *> satisfy isChar
+      quotedPair = (char '\\' *> satisfy isChar)
+                   <?>
+                   "quotedPair"
 
 -- |'qvalue' accepts a so-called qvalue.
 qvalue ∷ Parser Double
 {-# INLINEABLE qvalue #-}
 
 -- |'qvalue' accepts a so-called qvalue.
 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)
-         <|>
-         do x  ← char '1'
-            xs ← option "" $
-                 do y  ← char '.'
-                    ys ← atMost 3 (char '0')
-                    return (y:ys)
-            return $ read (x:xs)
-
--- |@'atMost' n v@ is like @'P.many' v@ but accumulates @v@ 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 []
-
-data CharAccumState
-    = CharAccumState {
-        casChunks    ∷ !(S.Seq BS.ByteString)
-      , casLastChunk ∷ !(S.Seq Char)
-      }
-
-instance Monoid CharAccumState where
-    mempty
-        = CharAccumState {
-            casChunks    = (∅)
-          , casLastChunk = (∅)
-          }
-    mappend a b
-        = b {
-            casChunks = (casChunks a ⊳ lastChunk a) ⋈ casChunks b
-          }
-
-lastChunk ∷ CharAccumState → BS.ByteString
-{-# INLINE lastChunk #-}
-lastChunk = BS.pack ∘ toList ∘ casLastChunk
-
-snoc ∷ CharAccumState → Char → CharAccumState
-{-# INLINEABLE snoc #-}
-snoc cas c
-    | S.length (casLastChunk cas) ≥ LS.defaultChunkSize
-        = cas {
-            casChunks    = casChunks cas ⊳ lastChunk cas
-          , casLastChunk = S.singleton c
-          }
-    | otherwise
-        = cas {
-            casLastChunk = casLastChunk cas ⊳ c
-          }
-
-finish ∷ CharAccumState → LS.ByteString
-{-# INLINEABLE finish #-}
-finish cas
-    = let chunks = toList $ casChunks cas ⊳ lastChunk cas
-          str    = LS.fromChunks chunks
-      in
-        str
-
--- |@'manyCharsTill' p end@ takes as many characters untill @p@
--- succeeds.
-manyCharsTill ∷ ∀m b. (Monad m, Alternative m)
-              ⇒ m Char
-              → m b
-              → m LS.ByteString
-{-# INLINEABLE manyCharsTill #-}
-manyCharsTill p end = scan (∅)
-    where
-      scan ∷ CharAccumState → m LS.ByteString
-      {-# INLINE scan #-}
-      scan s
-          = (end *> pure (finish s))
-            <|>
-            (scan =≪ (snoc s <$> p))
+qvalue = ( do x  ← char '0'
+              xs ← option "" $
+                   do y  ← char '.'
+                      ys ← atMost 3 digit
+                      return (y:ys)
+              return $ read (x:xs)
+           <|>
+           do x  ← char '1'
+              xs ← option "" $
+                   do y  ← char '.'
+                      ys ← atMost 3 (char '0')
+                      return (y:ys)
+              return $ read (x:xs)
+         )
+         <?>
+         "qvalue"
index ee929ad8d0660eb023782be5a4a6b806dbf82434..791c891f46d8be9009da9632537b40400c4bf378 100644 (file)
@@ -17,6 +17,7 @@ module Network.HTTP.Lucu.RFC2231
     where
 import Control.Applicative
 import qualified Control.Exception as E
     where
 import Control.Applicative
 import qualified Control.Exception as E
+import Control.Monad hiding (mapM)
 import Control.Monad.Unicode
 import Data.Ascii (Ascii, CIAscii, AsciiBuilder)
 import qualified Data.Ascii as A
 import Control.Monad.Unicode
 import Data.Ascii (Ascii, CIAscii, AsciiBuilder)
 import qualified Data.Ascii as A
@@ -46,25 +47,31 @@ import System.IO.Unsafe
 
 -- |Convert parameter values to an 'AsciiBuilder'.
 printParams ∷ Map CIAscii Text → AsciiBuilder
 
 -- |Convert parameter values to an 'AsciiBuilder'.
 printParams ∷ Map CIAscii Text → AsciiBuilder
-printParams params
-    | M.null params = (∅)
-    | otherwise     = A.toAsciiBuilder "; " ⊕
-                      joinWith "; " (map printPair $ M.toList params)
+{-# INLINEABLE printParams #-}
+printParams m = M.foldlWithKey f (∅) m
+    -- THINKME: Use foldlWithKey' for newer Data.Map
+    where
+      f ∷ AsciiBuilder → CIAscii → Text → AsciiBuilder
+      {-# INLINE f #-}
+      f ab k v = ab ⊕ A.toAsciiBuilder "; " ⊕ printPair k v
 
 
-printPair ∷ (CIAscii, Text) → AsciiBuilder
-printPair (name, value)
+printPair ∷ CIAscii → Text → AsciiBuilder
+{-# INLINEABLE printPair #-}
+printPair name value
     | T.any (> '\xFF') value
         = printPairInUTF8 name value
     | otherwise
         = printPairInAscii name (A.unsafeFromByteString $ encodeUtf8 value)
 
 printPairInUTF8 ∷ CIAscii → Text → AsciiBuilder
     | T.any (> '\xFF') value
         = printPairInUTF8 name value
     | otherwise
         = printPairInAscii name (A.unsafeFromByteString $ encodeUtf8 value)
 
 printPairInUTF8 ∷ CIAscii → Text → AsciiBuilder
+{-# INLINEABLE printPairInUTF8 #-}
 printPairInUTF8 name value
     = A.toAsciiBuilder (A.fromCIAscii name) ⊕
       A.toAsciiBuilder "*=utf-8''" ⊕
       escapeUnsafeChars (encodeUtf8 value) (∅)
 
 printPairInAscii ∷ CIAscii → Ascii → AsciiBuilder
 printPairInUTF8 name value
     = A.toAsciiBuilder (A.fromCIAscii name) ⊕
       A.toAsciiBuilder "*=utf-8''" ⊕
       escapeUnsafeChars (encodeUtf8 value) (∅)
 
 printPairInAscii ∷ CIAscii → Ascii → AsciiBuilder
+{-# INLINEABLE printPairInAscii #-}
 printPairInAscii name value
     = A.toAsciiBuilder (A.fromCIAscii name) ⊕
       A.toAsciiBuilder "=" ⊕
 printPairInAscii name value
     = A.toAsciiBuilder (A.fromCIAscii name) ⊕
       A.toAsciiBuilder "=" ⊕
@@ -74,6 +81,7 @@ printPairInAscii name value
           A.toAsciiBuilder value
 
 escapeUnsafeChars ∷ BS.ByteString → AsciiBuilder → AsciiBuilder
           A.toAsciiBuilder value
 
 escapeUnsafeChars ∷ BS.ByteString → AsciiBuilder → AsciiBuilder
+{-# INLINEABLE escapeUnsafeChars #-}
 escapeUnsafeChars bs b
     = case BS.uncons bs of
         Nothing         → b
 escapeUnsafeChars bs b
     = case BS.uncons bs of
         Nothing         → b
@@ -84,15 +92,18 @@ escapeUnsafeChars bs b
                           b ⊕ toHex (fromIntegral $ fromEnum c)
 
 toHex ∷ Word8 → AsciiBuilder
                           b ⊕ toHex (fromIntegral $ fromEnum c)
 
 toHex ∷ Word8 → AsciiBuilder
+{-# INLINEABLE toHex #-}
 toHex o = A.toAsciiBuilder "%" ⊕
           A.toAsciiBuilder (A.unsafeFromString [ toHex' (o `shiftR` 8)
                                                , toHex' (o .&.   0x0F) ])
 toHex o = A.toAsciiBuilder "%" ⊕
           A.toAsciiBuilder (A.unsafeFromString [ toHex' (o `shiftR` 8)
                                                , toHex' (o .&.   0x0F) ])
-
-toHex' ∷ Word8 → Char
-toHex' o
-    | o ≤ 0x09  = toEnum $ fromIntegral $ fromEnum '0' + fromIntegral o
-    | otherwise = toEnum $ fromIntegral $ fromEnum 'A' + fromIntegral (o - 0x0A)
-
+    where
+      toHex' ∷ Word8 → Char
+      {-# INLINEABLE toHex' #-}
+      toHex' h
+          | h ≤ 0x09  = toEnum $ fromIntegral
+                               $ fromEnum '0' + fromIntegral h
+          | otherwise = toEnum $ fromIntegral
+                               $ fromEnum 'A' + fromIntegral (h - 0x0A)
 
 data ExtendedParam
     = InitialEncodedParam {
 
 data ExtendedParam
     = InitialEncodedParam {
@@ -112,19 +123,21 @@ data ExtendedParam
       }
 
 section ∷ ExtendedParam → Integer
       }
 
 section ∷ ExtendedParam → Integer
+{-# INLINE section #-}
 section (InitialEncodedParam {..}) = 0
 section ep                         = epSection ep
 
 -- |'Parser' for parameter values.
 paramsP ∷ Parser (Map CIAscii Text)
 section (InitialEncodedParam {..}) = 0
 section ep                         = epSection ep
 
 -- |'Parser' for parameter values.
 paramsP ∷ Parser (Map CIAscii Text)
+{-# INLINEABLE paramsP #-}
 paramsP = decodeParams =≪ P.many (try paramP)
 
 paramP ∷ Parser ExtendedParam
 paramP = do skipMany lws
 paramsP = decodeParams =≪ P.many (try paramP)
 
 paramP ∷ Parser ExtendedParam
 paramP = do skipMany lws
-            _   ← char ';'
+            void $ char ';'
             skipMany lws
             epm ← nameP
             skipMany lws
             epm ← nameP
-            _   ← char '='
+            void $ char '='
             case epm of
               (name, 0, True)
                   → do (charset, payload) ← initialEncodedValue
             case epm of
               (name, 0, True)
                   → do (charset, payload) ← initialEncodedValue
@@ -139,22 +152,16 @@ paramP = do skipMany lws
 nameP ∷ Parser (CIAscii, Integer, Bool)
 nameP = do name      ← (A.toCIAscii ∘ A.unsafeFromByteString) <$>
                        takeWhile1 (\c → isToken c ∧ c ≢ '*')
 nameP ∷ Parser (CIAscii, Integer, Bool)
 nameP = do name      ← (A.toCIAscii ∘ A.unsafeFromByteString) <$>
                        takeWhile1 (\c → isToken c ∧ c ≢ '*')
-           sect      ← option 0 $
-                       try $
-                       do _ ← char '*'
-                          n ← decimal
-                          return n
-           isEncoded ← option False $
-                       do _ ← char '*'
-                          return True
+           sect      ← option 0     $ try (char '*' *> decimal  )
+           isEncoded ← option False $ try (char '*' *> pure True)
            return (name, sect, isEncoded)
 
 initialEncodedValue ∷ Parser (CIAscii, BS.ByteString)
 initialEncodedValue
     = do charset ← metadata
            return (name, sect, isEncoded)
 
 initialEncodedValue ∷ Parser (CIAscii, BS.ByteString)
 initialEncodedValue
     = do charset ← metadata
-         _       ← char '\''
-         _       ← metadata -- Ignore the language tag
-         _       ← char '\''
+         void $ char '\''
+         void $ metadata -- Ignore the language tag
+         void $ char '\''
          payload ← encodedPayload
          if charset ≡ "" then
              -- NOTE: I'm not sure this is the right thing, but RFC
          payload ← encodedPayload
          if charset ≡ "" then
              -- NOTE: I'm not sure this is the right thing, but RFC
@@ -166,13 +173,15 @@ initialEncodedValue
     where
       metadata ∷ Parser CIAscii
       metadata = (A.toCIAscii ∘ A.unsafeFromByteString) <$>
     where
       metadata ∷ Parser CIAscii
       metadata = (A.toCIAscii ∘ A.unsafeFromByteString) <$>
-                 takeWhile (\c → isToken c ∧ c ≢ '\'')
+                 takeWhile (\c → c ≢ '\'' ∧ isToken c)
 
 encodedPayload ∷ Parser BS.ByteString
 
 encodedPayload ∷ Parser BS.ByteString
+{-# INLINE encodedPayload #-}
 encodedPayload = BS.concat <$> P.many (hexChar <|> rawChars)
 
 hexChar ∷ Parser BS.ByteString
 encodedPayload = BS.concat <$> P.many (hexChar <|> rawChars)
 
 hexChar ∷ Parser BS.ByteString
-hexChar = do _ ← char '%'
+{-# INLINEABLE hexChar #-}
+hexChar = do void $ char '%'
              h ← satisfy isHexChar
              l ← satisfy isHexChar
              return $ BS.singleton $ hexToChar h l
              h ← satisfy isHexChar
              l ← satisfy isHexChar
              return $ BS.singleton $ hexToChar h l
@@ -181,19 +190,23 @@ isHexChar ∷ Char → Bool
 isHexChar = inClass "0-9a-fA-F"
 
 hexToChar ∷ Char → Char → Char
 isHexChar = inClass "0-9a-fA-F"
 
 hexToChar ∷ Char → Char → Char
+{-# INLINE hexToChar #-}
 hexToChar h l
     = chr $ (hexToInt h `shiftL` 8) .&. hexToInt l
 
 hexToInt ∷ Char → Int
 hexToChar h l
     = chr $ (hexToInt h `shiftL` 8) .&. hexToInt l
 
 hexToInt ∷ Char → Int
+{-# INLINEABLE hexToInt #-}
 hexToInt c
     | c ≤ '9'   = ord c - ord '0'
     | c ≤ 'F'   = ord c - ord 'A' + 10
     | otherwise = ord c - ord 'a' + 10
 
 rawChars ∷ Parser BS.ByteString
 hexToInt c
     | c ≤ '9'   = ord c - ord '0'
     | c ≤ 'F'   = ord c - ord 'A' + 10
     | otherwise = ord c - ord 'a' + 10
 
 rawChars ∷ Parser BS.ByteString
+{-# INLINE rawChars #-}
 rawChars = takeWhile1 (\c → isToken c ∧ c ≢ '%')
 
 decodeParams ∷ Monad m ⇒ [ExtendedParam] → m (Map CIAscii Text)
 rawChars = takeWhile1 (\c → isToken c ∧ c ≢ '%')
 
 decodeParams ∷ Monad m ⇒ [ExtendedParam] → m (Map CIAscii Text)
+{-# INLINE decodeParams #-}
 decodeParams = (mapM decodeSections =≪) ∘ sortBySection
 
 sortBySection ∷ ∀m. Monad m
 decodeParams = (mapM decodeSections =≪) ∘ sortBySection
 
 sortBySection ∷ ∀m. Monad m
@@ -213,12 +226,13 @@ sortBySection = flip go (∅)
                     in
                       go xs m'
               Just s
                     in
                       go xs m'
               Just s
-                  → case M.insertLookupWithKey (\_ s' _ → s') (section x) x s of
-                       (Nothing, s')
-                           → let m' = M.insert (epName x) s' m
+                  → case M.lookup (section x) s of
+                       Nothing
+                           → let s' = M.insert (section x) x  s
+                                 m' = M.insert (epName  x) s' m
                              in
                                go xs m'
                              in
                                go xs m'
-                       (Just _, _)
+                       Just _
                            → fail (concat [ "Duplicate section "
                                           , show $ section x
                                           , " for parameter '"
                            → fail (concat [ "Duplicate section "
                                           , show $ section x
                                           , " for parameter '"
index b0af8d1f38d773571c8374ce4c5cff2101992b75..5a4559e1948e29c6015ff831231b9751b91521a5 100644 (file)
@@ -16,6 +16,7 @@ import Control.Monad
 import qualified Data.Attoparsec.Lazy as LP
 import qualified Data.ByteString as Strict
 import qualified Data.ByteString.Lazy as Lazy
 import qualified Data.Attoparsec.Lazy as LP
 import qualified Data.ByteString as Strict
 import qualified Data.ByteString.Lazy as Lazy
+import Data.List
 import qualified Data.Strict.Maybe as S
 import Data.Monoid.Unicode
 import qualified Data.Sequence as S
 import qualified Data.Strict.Maybe as S
 import Data.Monoid.Unicode
 import qualified Data.Sequence as S
@@ -143,6 +144,7 @@ acceptRequestForResource ctx@(Context {..}) ar@(AugmentedRequest {..}) input rsr
     = do cert ← hGetPeerCert cHandle
          ni   ← mkNormalInteraction cConfig cAddr cert ar rsrcPath
          tid  ← spawnResource rsrcDef ni
     = do cert ← hGetPeerCert cHandle
          ni   ← mkNormalInteraction cConfig cAddr cert ar rsrcPath
          tid  ← spawnResource rsrcDef ni
+         enqueue ctx ni
          if reqMustHaveBody arRequest then
              waitForReceiveBodyReq ctx ni tid input
          else
          if reqMustHaveBody arRequest then
              waitForReceiveBodyReq ctx ni tid input
          else
@@ -209,9 +211,9 @@ wasteAllChunks ctx rsrcTid = go
               LP.Done input' chunkLen
                   | chunkLen ≡ 0 → gotFinalChunk input'
                   | otherwise    → gotChunk input' chunkLen
               LP.Done input' chunkLen
                   | chunkLen ≡ 0 → gotFinalChunk input'
                   | otherwise    → gotChunk input' chunkLen
-              LP.Fail _ _ msg
-                  → chunkWasMalformed rsrcTid
-                        $ "wasteAllChunks: chunkHeaderP: " ⧺ msg
+              LP.Fail _ eCtx e
+                  → chunkWasMalformed rsrcTid eCtx e
+                        "wasteAllChunks: chunkHeaderP"
       go input (InChunk chunkLen)
           = gotChunk input chunkLen
 
       go input (InChunk chunkLen)
           = gotChunk input chunkLen
 
@@ -222,18 +224,18 @@ wasteAllChunks ctx rsrcTid = go
               case LP.parse chunkFooterP input' of
                 LP.Done input'' _
                     → go input'' Initial
               case LP.parse chunkFooterP input' of
                 LP.Done input'' _
                     → go input'' Initial
-                LP.Fail _ _ msg
-                    → chunkWasMalformed rsrcTid
-                          $ "wasteAllChunks: chunkFooterP: " ⧺ msg
+                LP.Fail _ eCtx e
+                    → chunkWasMalformed rsrcTid eCtx e
+                          "wasteAllChunks: chunkFooterP"
 
       gotFinalChunk ∷ Lazy.ByteString → IO ()
       gotFinalChunk input
           = case LP.parse chunkTrailerP input of
               LP.Done input' _
                   → acceptRequest ctx input'
 
       gotFinalChunk ∷ Lazy.ByteString → IO ()
       gotFinalChunk input
           = case LP.parse chunkTrailerP input of
               LP.Done input' _
                   → acceptRequest ctx input'
-              LP.Fail _ _ msg
-                  → chunkWasMalformed rsrcTid
-                        $ "wasteAllChunks: chunkTrailerP: " ⧺ msg
+              LP.Fail _ eCtx e
+                  → chunkWasMalformed rsrcTid eCtx e
+                        "wasteAllChunks: chunkTrailerP"
 
 readCurrentChunk ∷ HandleLike h
                  ⇒ Context h
 
 readCurrentChunk ∷ HandleLike h
                  ⇒ Context h
@@ -253,9 +255,9 @@ readCurrentChunk ctx ni@(NI {..}) rsrcTid wanted = go
                       → gotFinalChunk input'
                   | otherwise
                       → gotChunk input' chunkLen
                       → gotFinalChunk input'
                   | otherwise
                       → gotChunk input' chunkLen
-              LP.Fail _ _ msg
-                  → chunkWasMalformed rsrcTid
-                        $ "readCurrentChunk: chunkHeaderP: " ⧺ msg
+              LP.Fail _ eCtx e
+                  → chunkWasMalformed rsrcTid eCtx e
+                        "readCurrentChunk: chunkHeaderP"
       go input (InChunk chunkLen)
           = gotChunk input chunkLen
 
       go input (InChunk chunkLen)
           = gotChunk input chunkLen
 
@@ -271,9 +273,9 @@ readCurrentChunk ctx ni@(NI {..}) rsrcTid wanted = go
                    case LP.parse chunkFooterP input' of
                      LP.Done input'' _
                          → waitForReceiveChunkedBodyReq ctx ni rsrcTid input'' Initial
                    case LP.parse chunkFooterP input' of
                      LP.Done input'' _
                          → waitForReceiveChunkedBodyReq ctx ni rsrcTid input'' Initial
-                     LP.Fail _ _ msg
-                         → chunkWasMalformed rsrcTid
-                               $ "readCurrentChunk: chunkFooterP: " ⧺ msg
+                     LP.Fail _ eCtx e
+                         → chunkWasMalformed rsrcTid eCtx e
+                               "readCurrentChunk: chunkFooterP: "
                else
                    waitForReceiveChunkedBodyReq ctx ni rsrcTid input' $ InChunk chunkLen'
 
                else
                    waitForReceiveChunkedBodyReq ctx ni rsrcTid input' $ InChunk chunkLen'
 
@@ -283,15 +285,20 @@ readCurrentChunk ctx ni@(NI {..}) rsrcTid wanted = go
                case LP.parse chunkTrailerP input of
                  LP.Done input' _
                      → acceptRequest ctx input'
                case LP.parse chunkTrailerP input of
                  LP.Done input' _
                      → acceptRequest ctx input'
-                 LP.Fail _ _ msg
-                     → chunkWasMalformed rsrcTid
-                           $ "readCurrentChunk: chunkTrailerP: " ⧺ msg
+                 LP.Fail _ eCtx e
+                     → chunkWasMalformed rsrcTid eCtx e
+                           "readCurrentChunk: chunkTrailerP"
 
 
-chunkWasMalformed ∷ ThreadId → String → IO ()
-chunkWasMalformed tid msg
+chunkWasMalformed ∷ ThreadId → [String] → String → String → IO ()
+chunkWasMalformed tid eCtx e msg
     = let abo = mkAbortion BadRequest [("Connection", "close")]
                 $ Just
     = let abo = mkAbortion BadRequest [("Connection", "close")]
                 $ Just
-                $ "chunkWasMalformed: " ⊕ T.pack msg
+                $ "chunkWasMalformed: "
+                ⊕ T.pack msg
+                ⊕ ": "
+                ⊕ T.pack (intercalate ", " eCtx)
+                ⊕ ": "
+                ⊕ T.pack e
       in
         throwTo tid abo
 
       in
         throwTo tid abo
 
index d61f2f45ec3950505020c19c770559ecdd0a2d3b..71ff4838c3945380d44f2dee36fddc2b3952d3d1 100644 (file)
@@ -1,5 +1,6 @@
 {-# LANGUAGE
 {-# LANGUAGE
-    GeneralizedNewtypeDeriving
+    BangPatterns
+  , GeneralizedNewtypeDeriving
   , DoAndIfThenElse
   , OverloadedStrings
   , RecordWildCards
   , DoAndIfThenElse
   , OverloadedStrings
   , RecordWildCards
@@ -136,7 +137,9 @@ module Network.HTTP.Lucu.Resource
     , putBuilder
     )
     where
     , putBuilder
     )
     where
-import qualified Blaze.ByteString.Builder.ByteString as BB
+import Blaze.ByteString.Builder (Builder)
+import qualified Blaze.ByteString.Builder as BB
+import qualified Blaze.ByteString.Builder.Internal as BB
 import Control.Applicative
 import Control.Monad
 import Control.Monad.IO.Class
 import Control.Applicative
 import Control.Monad
 import Control.Monad.IO.Class
@@ -148,14 +151,11 @@ import qualified Data.Attoparsec.Lazy  as LP
 import Data.ByteString (ByteString)
 import qualified Data.ByteString as Strict
 import qualified Data.ByteString.Lazy as Lazy
 import Data.ByteString (ByteString)
 import qualified Data.ByteString as Strict
 import qualified Data.ByteString.Lazy as Lazy
-import qualified Data.ByteString.Lazy.Internal as Lazy
-import Data.Foldable (toList)
 import Data.List
 import qualified Data.Map as M
 import Data.Maybe
 import Data.List
 import qualified Data.Map as M
 import Data.Maybe
+import Data.Monoid
 import Data.Monoid.Unicode
 import Data.Monoid.Unicode
-import Data.Sequence (Seq)
-import Data.Sequence.Unicode hiding ((∅))
 import Data.Text (Text)
 import qualified Data.Text as T
 import qualified Data.Text.Encoding as T
 import Data.Text (Text)
 import qualified Data.Text as T
 import qualified Data.Text.Encoding as T
@@ -552,24 +552,23 @@ getChunks Nothing
 getChunks' ∷ Int → Resource Lazy.ByteString
 getChunks' limit = go limit (∅)
     where
 getChunks' ∷ Int → Resource Lazy.ByteString
 getChunks' limit = go limit (∅)
     where
-      go ∷ Int → Seq Strict.ByteString → Resource Lazy.ByteString
-      go 0 _  = do chunk ← getChunk 1
-                   if Strict.null chunk then
-                       return (∅)
-                   else
-                       abort $ mkAbortion' RequestEntityTooLarge
-                             $ "Request body must be smaller than "
-                             ⊕ T.pack (show limit)
-                             ⊕ " bytes."
-      go n xs = do let n' = min n Lazy.defaultChunkSize
-                   chunk ← getChunk n'
-                   if Strict.null chunk then
-                       -- Got EOF
-                       return $ Lazy.fromChunks $ toList xs
-                   else
-                       do let n'' = n' - Strict.length chunk
-                              xs' = xs ⊳ chunk
-                          go n'' xs'
+      go ∷ Int → Builder → Resource Lazy.ByteString
+      go  0  _ = do chunk ← getChunk 1
+                    if Strict.null chunk then
+                        return (∅)
+                    else
+                        abort $ mkAbortion' RequestEntityTooLarge
+                              $ "Request body must be smaller than "
+                              ⊕ T.pack (show limit)
+                              ⊕ " bytes."
+      go !n !b = do c ← getChunk $ min n BB.defaultBufferSize
+                    if Strict.null c then
+                        -- Got EOF
+                        return $ BB.toLazyByteString b
+                    else
+                        do let n'  = n - Strict.length c
+                               xs' = b ⊕ BB.fromByteString c
+                           go n' xs'
 
 -- |@'getForm' limit@ attempts to read the request body with
 -- 'getChunks' and parse it as @application\/x-www-form-urlencoded@ or
 
 -- |@'getForm' limit@ attempts to read the request body with
 -- 'getChunks' and parse it as @application\/x-www-form-urlencoded@ or
@@ -624,7 +623,12 @@ getForm limit
                        case LP.parse (p b) src of
                          LP.Done _ formList
                              → return formList
                        case LP.parse (p b) src of
                          LP.Done _ formList
                              → return formList
-                         _   → abort $ mkAbortion' BadRequest "Unparsable multipart/form-data"
+                         LP.Fail _ eCtx e
+                             → abort $ mkAbortion' BadRequest
+                                     $ "Unparsable multipart/form-data: "
+                                     ⊕ T.pack (intercalate ", " eCtx)
+                                     ⊕ ": "
+                                     ⊕ T.pack e
           where
             p b = do xs ← multipartFormP b
                      P.endOfInput
           where
             p b = do xs ← multipartFormP b
                      P.endOfInput
@@ -674,7 +678,10 @@ setContentEncoding codings
                   _               → abort $ mkAbortion' InternalServerError
                                             "setContentEncoding: Unknown HTTP version"
          setHeader "Content-Encoding"
                   _               → abort $ mkAbortion' InternalServerError
                                             "setContentEncoding: Unknown HTTP version"
          setHeader "Content-Encoding"
-                   (A.fromAsciiBuilder $ joinWith ", " $ map tr codings)
+             $ A.fromAsciiBuilder
+             $ mconcat
+             $ intersperse (A.toAsciiBuilder ", ")
+             $ map tr codings
     where
       toAB = A.toAsciiBuilder ∘ A.fromCIAscii
 
     where
       toAB = A.toAsciiBuilder ∘ A.fromCIAscii
 
index e8aa3ef6b555da151896040e3d9640e25e9cec6f..9df36a601cf7dc460dd8b98f92ed084054719504 100644 (file)
@@ -41,6 +41,7 @@ import qualified Data.Ascii as A
 import qualified Data.ByteString as Strict
 import Data.List
 import Data.Maybe
 import qualified Data.ByteString as Strict
 import Data.List
 import Data.Maybe
+import Data.Monoid
 import Data.Monoid.Unicode
 import qualified Data.Text as T
 import Network.HTTP.Lucu.Abortion
 import Data.Monoid.Unicode
 import qualified Data.Text as T
 import Network.HTTP.Lucu.Abortion
@@ -52,7 +53,6 @@ import Network.HTTP.Lucu.Interaction
 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
-import Network.HTTP.Lucu.Utils
 import Network.Socket
 import OpenSSL.X509
 import Prelude hiding (catch)
 import Network.Socket
 import OpenSSL.X509
 import Prelude hiding (catch)
@@ -166,12 +166,12 @@ spawnResource (ResourceDef {..}) ni@(NI {..})
               _      → error $ "Unknown request method: " ⧺ show (reqMethod req)
 
       notAllowed ∷ Resource ()
               _      → error $ "Unknown request method: " ⧺ show (reqMethod req)
 
       notAllowed ∷ Resource ()
-      notAllowed
-          = setStatus MethodNotAllowed
-            *>
-            (setHeader "Allow" $ A.fromAsciiBuilder
-                               $ joinWith ", "
-                               $ map A.toAsciiBuilder allowedMethods)
+      notAllowed = do setStatus MethodNotAllowed
+                      setHeader "Allow"
+                          $ A.fromAsciiBuilder
+                          $ mconcat
+                          $ intersperse (A.toAsciiBuilder ", ")
+                          $ map A.toAsciiBuilder allowedMethods
 
       allowedMethods ∷ [Ascii]
       allowedMethods = nub $ concat [ methods resGet    ["GET"]
 
       allowedMethods ∷ [Ascii]
       allowedMethods = nub $ concat [ methods resGet    ["GET"]
index f0e9bd80508d8589eba5121c2d61dec1b09ad1bc..4f669314aee7e599703e999433a63713fe1b4a6f 100644 (file)
@@ -46,7 +46,8 @@ staticFile path
       }
 
 octetStream ∷ MIMEType
       }
 
 octetStream ∷ MIMEType
-octetStream = mkMIMEType "application" "octet-stream"
+{-# NOINLINE octetStream #-}
+octetStream = parseMIMEType "application/octet-stream"
 
 handleStaticFile ∷ Bool → FilePath → Resource ()
 handleStaticFile sendContent path
 
 handleStaticFile ∷ Bool → FilePath → Resource ()
 handleStaticFile sendContent path
index 4db7c0555e1e05513052ef0573e4dfab2874fc24..7dbb1162cbda616ae5eb57ce989bc481f813a9ac 100644 (file)
@@ -6,7 +6,6 @@
 -- functions may be useful too for something else.
 module Network.HTTP.Lucu.Utils
     ( splitBy
 -- functions may be useful too for something else.
 module Network.HTTP.Lucu.Utils
     ( splitBy
-    , joinWith
     , quoteStr
     , parseWWWFormURLEncoded
     , splitPathInfo
     , quoteStr
     , parseWWWFormURLEncoded
     , splitPathInfo
@@ -31,21 +30,9 @@ import Prelude.Unicode
 splitBy ∷ (a → Bool) → [a] → [[a]]
 {-# INLINEABLE splitBy #-}
 splitBy isSep src
 splitBy ∷ (a → Bool) → [a] → [[a]]
 {-# INLINEABLE splitBy #-}
 splitBy isSep src
-    = case break isSep src
-      of (last , []       ) → [last]
-         (first, _sep:rest) → first : splitBy isSep rest
-
--- |> joinWith ":" ["ab", "c", "def"]
---  > ==> "ab:c:def"
-joinWith ∷ Ascii → [AsciiBuilder] → AsciiBuilder
-{-# INLINEABLE joinWith #-}
-joinWith sep = flip go (∅)
-    where
-      go ∷ [AsciiBuilder] → AsciiBuilder → AsciiBuilder
-      {-# INLINE go #-}
-      go []     ab = ab
-      go (x:[]) ab = ab ⊕ x
-      go (x:xs) ab = go xs (ab ⊕ A.toAsciiBuilder sep ⊕ x)
+    = case break isSep src of
+        (last , []       ) → [last]
+        (first, _sep:rest) → first : splitBy isSep rest
 
 -- |> quoteStr "abc"
 --  > ==> "\"abc\""
 
 -- |> quoteStr "abc"
 --  > ==> "\"abc\""
index 9c42e7269a4984452d8914aea1644ba02891f1eb..8ddc6189be39a8ad942d372671819fd6f066e53f 100644 (file)
@@ -21,21 +21,21 @@ resMain ∷ ResourceDef
 resMain 
     = emptyResource {
         resGet
 resMain 
     = emptyResource {
         resGet
-          = Just $ do setContentType $ mkMIMEType "text" "html"
-                      output ("<title>Multipart Form Test</title>\n" ⊕
-                              "<form action=\"/\" method=\"post\" enctype=\"multipart/form-data\">\n" ⊕
-                              "  Upload some file:\n" ⊕
-                              "  <input type=\"text\" name=\"text\">\n" ⊕
-                              "  <input type=\"file\" name=\"file\">\n" ⊕
-                              "  <input type=\"submit\" value=\"Submit\">\n" ⊕
-                              "</form>\n")
+          = Just $ do setContentType $ parseMIMEType "text/html"
+                      putChunks $ "<title>Multipart Form Test</title>\n"
+                                ⊕ "<form action=\"/\" method=\"post\" enctype=\"multipart/form-data\">\n"
+                                ⊕ "  Upload some file:\n"
+                                ⊕ "  <input type=\"text\" name=\"text\">\n"
+                                ⊕ "  <input type=\"file\" name=\"file\">\n"
+                                ⊕ "  <input type=\"submit\" value=\"Submit\">\n"
+                                ⊕ "</form>\n"
       , resPost
       , resPost
-          = Just $ do form ← inputForm defaultLimit
+          = Just $ do form ← getForm Nothing
                       let text     = fromMaybe (∅) $ fdContent <$> lookup "text" form
                           file     = fromMaybe (∅) $ fdContent <$> lookup "file" form
                           fileName = fdFileName =≪ lookup "file" form
                       let text     = fromMaybe (∅) $ fdContent <$> lookup "text" form
                           file     = fromMaybe (∅) $ fdContent <$> lookup "file" form
                           fileName = fdFileName =≪ lookup "file" form
-                      setContentType $ mkMIMEType "text" "plain"
-                      outputChunk ("You entered \"" ⊕ text ⊕ "\".\n")
-                      outputChunk ("You uploaded a " ⊕ Lazy.pack (show $ Lazy.length file) ⊕ " bytes long file.\n")
-                      output ("The file name is " ⊕ Lazy.pack (show fileName) ⊕ ".\n")
+                      setContentType $ parseMIMEType "text/plain"
+                      putChunks $ "You entered \"" ⊕ text ⊕ "\".\n"
+                      putChunks $ "You uploaded a " ⊕ Lazy.pack (show $ Lazy.length file) ⊕ " bytes long file.\n"
+                      putChunks $ "The file name is " ⊕ Lazy.pack (show fileName) ⊕ ".\n"
       }
       }