首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用attoparsec解析IP地址

用attoparsec解析IP地址
EN

Stack Overflow用户
提问于 2015-12-15 13:57:06
回答 2查看 450关注 0票数 2

https://www.fpcomplete.com/school/starting-with-haskell/libraries-and-frameworks/text-manipulation/attoparsec上给出的解析器看起来工作正常,但它有一个问题。

代码(在这里重复)是:

代码语言:javascript
复制
{-# 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似乎是一种选择,因为它是无限的,但同样,对抗性输入可能会使程序耗尽内存。

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

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2015-12-15 15:00:35

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

我们可以定义一个函数来最大限度地解析整数,否则就会失败:

代码语言:javascript
复制
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地址的解析器几乎保持不变:

代码语言:javascript
复制
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"
票数 3
EN

Stack Overflow用户

发布于 2015-12-15 15:18:09

对于简单的非病理性输入,您确实可以从Word8强制使用Integer,这是任意精确的,永远不会溢出:

代码语言:javascript
复制
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."

现在修改后的程序

代码语言:javascript
复制
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

代码语言:javascript
复制
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)
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/34291087

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档