2015-12-15 68 views
2

https://www.fpcomplete.com/school/starting-with-haskell/libraries-and-frameworks/text-manipulation/attoparsec处给出的解析器似乎可行,但存在问题。使用attoparsec解析IP地址

的代码(这里不再重复)是:

{-# LANGUAGE OverloadedStrings #-} 

-- This attoparsec module is intended for parsing text that is 
-- represented using an 8-bit character set, e.g. ASCII or ISO-8859-15. 
import Data.Attoparsec.Char8 
import Data.Word 

-- | Type for IP's. 
data IP = IP Word8 Word8 Word8 Word8 deriving Show 

parseIP :: Parser IP 
parseIP = do 
    d1 <- decimal 
    char '.' 
    d2 <- decimal 
    char '.' 
    d3 <- decimal 
    char '.' 
    d4 <- decimal 
    return $ IP d1 d2 d3 d4 

main :: IO() 
main = print $ parseOnly parseIP "131.45.68.123" 

如果分析器是输入一个无效的IP地址,如“1000.1000.1000.1000”,它不会失败,并返回一个垃圾结果,由于裹挟数字转换。

有没有简单的方法来解决这个问题?一种方法是使用更大的Word类型,如Word32,并检查数字是否小于256.但是,即使输入是病态的(例如溢出Word32),也可能返回垃圾。转换为Integer似乎是一种选择,因为它是无限的,但同样,对抗性输入可能会导致程序内存不足。

那么避免这些问题的(希望优雅的)解析器会是什么样子?

+0

你试过像'除非(0 <= D1 && D 1 <= 255)$失败“D1不是[0,255] “'Parser'是一个monad,所以失败了,'unless除了'会起作用 – epsilonhalbe

+1

这是行不通的,因为库已经将字符串转换成了一个'Word8',并且会通过这个条件。 – donatello

回答

3

我对您的问题的理解是,您不仅希望在输入数字过大时失败,而且也不希望解析器消耗比所需更多的输入。

我们可以定义一个函数来分析整数最多,否则失败:

import Data.Attoparsec.ByteString.Char8 
import Data.Word 
import Data.ByteString (ByteString) 
import qualified Data.ByteString as B 
import Control.Applicative 
import Data.List (foldl') 
import Control.Monad 

decimalMax :: Integral a => Integer -> Parser a 
decimalMax dMax = do 
    let numDigs = ceiling $ log (fromIntegral(dMax+1))/log 10 
     getVal = foldl' (\s d -> s*10+fromIntegral (d-48)) 0 . B.unpack 
    val <- getVal <$> scan 0 (\n c -> 
      if n > numDigs || not (isDigit c) then Nothing else Just (n+1)) 
    if val <= dMax 
    then return $ fromIntegral val 
    else fail $ "decimalMax: parsed decimal exceeded" ++ show dMax 

此函数计算的最大号码位数,那么只需消耗最多,很多数字。您的IP地址解析器几乎保持不变:

parseIP :: Parser IP 
parseIP = IP <$> dd <*> dd <*> dd <*> dig where 
    dig = decimalMax 255 
    dd = dig <* char '.' 

main :: IO() 
main = do 
    print $ parseOnly parseIP "131.45.68.123" 
    print $ parseOnly parseIP "1000.1000.1000.1000" 
+0

谢谢,这也解决了解析有界整数的一般问题。你有一个小错字 - “C8.scan”应该是“扫描”。 – donatello

1

对于简单的非病理性的投入,你确实可以只从Integer,这是任意精度的强制来Word8,绝不会溢出:

byte :: Parser Word8 
byte = do 
    n <- (decimal :: Parser Integer) 
    if n < 256 then return n 
       else fail $ "Byte Overflow: " ++ show n ++ " is greater than 255." 

现在修改后的程序,

parseIP = do 
    d1 <- byte 
    char '.' 
    d2 <- byte 
    char '.' 
    d3 <- byte 
    char '.' 
    d4 <- byte 
    return $ IP d1 d2 d3 d4 

应该产生必要的输出。

如果您想要处理试图通过将“1291293919818283309400919 ...”作为一个非常长的数字来拒绝您的人,那么我预计需要更多的工作来验证某件事真的是那么长,以便您扫描最多三位数字在第一个char '.'立即失败之前。

下看起来编译并与import qualified Data.ByteString as BS往上顶工作:

scan0to3digits :: Int -> Char -> Maybe Int 
scan0to3digits = scan 0 helper where 
    helper n c 
    | n < 3 && isDigit c = Just (n + 1) 
    | otherwise   = Nothing 

byte :: Parser Word8 
byte = do 
    raw <- scan 0 scan0to3digits 
    let p = BS.foldl' (\sum w8 -> 10 * sum + fromIntegral w8 - 48) 0 raw 
    if BS.length raw == 0 
     then fail "Expected one or more digits..." 
     else if p > 255 
     then fail $ "Byte Overflow: " ++ show n ++ " is greater than 255." 
     else return (fromInteger p) 
+0

谢谢你的回复。你的方法与之前的答案类似。 – donatello