From f953f2f72cf545ad7ade50d42df696829908a783 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Fri, 3 Jul 2015 22:15:49 -0400 Subject: [PATCH] Only parse valid octets (0 - 255). Add some more tests and documentation for existing methods. Switch to 'parseTest' from 'parse' in doctests. --- src/IPv4Pattern.hs | 133 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 113 insertions(+), 20 deletions(-) diff --git a/src/IPv4Pattern.hs b/src/IPv4Pattern.hs index 9f8c87f..19fec7b 100644 --- a/src/IPv4Pattern.hs +++ b/src/IPv4Pattern.hs @@ -28,6 +28,7 @@ import Test.Tasty ( TestTree, testGroup ) import Test.Tasty.HUnit ( (@?=), testCase ) import Text.Parsec import Text.Parsec.String ( Parser ) +import Text.Read ( readMaybe ) newtype IPv4Octet = IPv4Octet Int @@ -57,11 +58,11 @@ data IPv4Pattern = -- -- ==== _Examples_ -- --- >>> parse v4seq_member "" "127" --- Right (IPv4SequenceMemberOctet (IPv4Octet 127)) +-- >>> parseTest v4seq_member "127" +-- IPv4SequenceMemberOctet (IPv4Octet 127) -- --- >>> parse v4seq_member "" "1..5" --- Right (IPv4SequenceMemberOctetRange (IPv4Octet 1) (IPv4Octet 5)) +-- >>> parseTest v4seq_member "1..5" +-- IPv4SequenceMemberOctetRange (IPv4Octet 1) (IPv4Octet 5) -- v4seq_member :: Parser IPv4SequenceMember v4seq_member = try both <|> just_one @@ -85,14 +86,14 @@ v4seq_member = try both <|> just_one -- -- ==== _Examples_ -- --- >>> parse v4sequence "" "1" --- Right (IPv4SequenceSingleMember (IPv4SequenceMemberOctet (IPv4Octet 1))) +-- >>> parseTest v4sequence "1" +-- IPv4SequenceSingleMember (IPv4SequenceMemberOctet (IPv4Octet 1)) -- --- >>> parse v4sequence "" "1..2" --- Right (IPv4SequenceSingleMember (IPv4SequenceMemberOctetRange (IPv4Octet 1) (IPv4Octet 2))) +-- >>> parseTest v4sequence "1..2" +-- IPv4SequenceSingleMember (IPv4SequenceMemberOctetRange (IPv4Octet 1) (IPv4Octet 2)) -- --- >>> parse v4sequence "" "1..2;8" --- Right (IPv4SequenceOptions (IPv4SequenceMemberOctetRange (IPv4Octet 1) (IPv4Octet 2)) (IPv4SequenceSingleMember (IPv4SequenceMemberOctet (IPv4Octet 8)))) +-- >>> parseTest v4sequence "1..2;8" +-- IPv4SequenceOptions (IPv4SequenceMemberOctetRange (IPv4Octet 1) (IPv4Octet 2)) (IPv4SequenceSingleMember (IPv4SequenceMemberOctet (IPv4Octet 8))) -- v4sequence :: Parser IPv4Sequence v4sequence = try both <|> just_one -- Maybe sepBy is appropriate here? @@ -111,11 +112,11 @@ v4sequence = try both <|> just_one -- Maybe sepBy is appropriate here? -- -- ==== _Examples_ -- --- >>> parse v4field "" "127" --- Right (IPv4FieldOctet (IPv4Octet 127)) +-- >>> parseTest v4field "127" +-- IPv4FieldOctet (IPv4Octet 127) -- --- >>> parse v4field "" "[127]" --- Right (IPv4FieldSequence (IPv4SequenceSingleMember (IPv4SequenceMemberOctet (IPv4Octet 127)))) +-- >>> parseTest v4field "[127]" +-- IPv4FieldSequence (IPv4SequenceSingleMember (IPv4SequenceMemberOctet (IPv4Octet 127))) -- v4field :: Parser IPv4Field v4field = just_octet <|> brackets @@ -130,15 +131,67 @@ v4field = just_octet <|> brackets -- | Parse an IPv4 octet, which should contain a string of digits. +-- Should fail if the parsed integer does not lie between @0@ and +-- @255@ inclusive. -- -- ==== _Examples_ -- --- >>> parse v4octet "" "127" --- Right (IPv4Octet 127) +-- Standard octets are parsed correctly: +-- +-- parseTest v4octet "0" +-- IPv4Octet 0 +-- +-- >>> parseTest v4octet "127" +-- IPv4Octet 127 +-- +-- >>> parseTest v4octet "255" +-- IPv4Octet 255 +-- +-- Non-digit input throws an error: +-- +-- >>> parseTest v4octet "Hello, World!" +-- parse error at (line 1, column 1): +-- unexpected "H" +-- expecting digit +-- +-- If we're given an integer outside the range @0..255@ (i.e. not a +-- valid octet), we fail: +-- +-- >>> parseTest v4octet "9000" +-- parse error at (line 1, column 5): +-- unexpected end of input +-- expecting digit +-- Octet "9000" must be between 0 and 255. -- v4octet :: Parser IPv4Octet -v4octet = fmap (IPv4Octet . read) $ many1 digit +v4octet = do + s <- many1 digit + case ( readMaybe s :: Maybe Int ) of + -- If "many1 digit" gives us a list of digits, we should be able + -- to convert that to an Int! It will overflow rather than fail + -- if the input is too big/small, so it should really always + -- succeed. + Nothing -> unexpected "readMaybe failed on a sequence of digits!" + -- If we got an Int, make sure it's actually a representation of + -- an octet. + Just k -> if 0 <= k && k <= 255 + then return (IPv4Octet k) + else fail ("Octet \"" ++ (show k) + ++ "\" must be between 0 and 255.") + + +-- | Parse an ipv4 address pattern. This consists of four fields, +-- separated by periods, where a field is either a simple octet or a +-- sequence. +-- +-- See also: 'v4field', 'v4sequence'. +-- +-- ==== _Examples_ +-- +-- >>> parseTest v4pattern "127.0.0.1" +-- IPv4Pattern (IPv4FieldOctet (IPv4Octet 127)) (IPv4FieldOctet (IPv4Octet 0)) (IPv4FieldOctet (IPv4Octet 0)) (IPv4FieldOctet (IPv4Octet 1)) +-- v4pattern :: Parser IPv4Pattern v4pattern = do field1 <- v4field @@ -151,13 +204,53 @@ v4pattern = do return $ IPv4Pattern field1 field2 field3 field4 +-- | Enumerate the members of an 'IPv4SequenceMember'. A sequence +-- member is either an octet, which is easy to enumerate -- we just +-- print it -- or an octet range whose members can be enumerated +-- from least to greatest. +-- +-- We enumerate strings instead of integers because the big picture +-- is that we will be listing out patterns of ipv4 addresses, and +-- those are represented as strings (dotted quad format). +-- +-- ==== _Examples_ +-- +-- >>> let (Right r) = parse v4seq_member "" "127" +-- >>> sequence_members r +-- ["127"] +-- +-- >>> let (Right r) = parse v4seq_member "" "127..135" +-- >>> sequence_members r +-- ["127","128","129","130","131","132","133","134","135"] +-- sequence_members :: IPv4SequenceMember -> [String] sequence_members (IPv4SequenceMemberOctet (IPv4Octet i)) = [show i] -sequence_members (IPv4SequenceMemberOctetRange (IPv4Octet start) (IPv4Octet end)) = - [show x | x <- [start..end]] +sequence_members (IPv4SequenceMemberOctetRange (IPv4Octet s) (IPv4Octet t)) = + [show x | x <- [s .. t]] + +-- | Enumerate the members of an ipv4 sequence. These consist of +-- either a single sequence member (in which case we delegate to +-- 'sequence_members'), or an \"option\" which is enumerated +-- recursively. +-- +-- ==== _Examples_ +-- +-- >>> let (Right r) = parse v4sequence "" "1" +-- >>> sequences r +-- ["1"] +-- +-- >>> let (Right r) = parse v4sequence "" "1..2" +-- >>> sequences r +-- ["1","2"] +-- +-- >>> let (Right r) = parse v4sequence "" "1..3;4;5..9" +-- >>> sequences r +-- ["1","2","3","4","5","6","7","8","9"] +-- sequences :: IPv4Sequence -> [String] -sequences (IPv4SequenceSingleMember sm) = sequence_members sm +sequences (IPv4SequenceSingleMember sm) = + sequence_members sm sequences (IPv4SequenceOptions sm s) = (sequence_members sm) ++ (sequences s) -- 2.43.2