2015-12-15 67 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