簡體   English   中英

使用attoparsec解析IP地址

[英]Parse IP address with attoparsec

https://www.fpcomplete.com/school/starting-with-haskell/libraries-and-frameworks/text-manipulation/attoparsec給出的解析器似乎可以正常工作,但是有問題。

代碼(此處重復)為:

{-# 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似乎是一個選擇,因為它是不受限制的,但是同樣,對抗性輸入可能會使程序用盡內存。

那么,一個避免這些問題的(非常優雅的)解析器會是什么樣呢?

我對您的問題的理解是,您不僅希望在輸入數字太大時失敗,而且還不想讓解析器消耗比所需更多的輸入。

我們可以定義一個函數來解析最大整數,否則將失敗:

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"

對於簡單的非病理性輸入,您確實可以強制使用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 ...”作為一個很長的數字來處理嘗試DoS的人,那么我預言需要做更多的工作來驗證某個東西真的是這個長度,以便您在掃描之前最多掃描三位數立即失敗第一個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)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM