1 -- | An IPv4 address pattern has four fields separated by ".". Each
2 -- field is either a decimal number, or a sequence inside "[]" that
3 -- contains one or more ";"-separated decimal numbers or
4 -- number..number ranges.
6 -- Thus, any pattern field can be a sequence inside "[]", but a "[]"
7 -- sequence cannot span multiple address fields, and a pattern field
8 -- cannot contain both a number and a "[]" sequence at the same
11 -- This means that the pattern 1.2.[3.4] is not valid (the sequence
12 -- [3.4] cannot span two address fields) and the pattern
13 -- 1.2.3.3[6..9] is also not valid (the last field cannot be both
14 -- number 3 and sequence [6..9] at the same time).
16 -- The syntax for IPv4 patterns is as follows:
18 -- v4pattern = v4field "." v4field "." v4field "." v4field
19 -- v4field = v4octet | "[" v4sequence "]"
20 -- v4octet = any decimal number in the range 0 through 255
21 -- v4sequence = v4seq_member | v4sequence ";" v4seq_member
22 -- v4seq_member = v4octet | v4octet ".." v4octet
24 module Network.DNS.RBL.IPv4Pattern (
32 import Test.Tasty ( TestTree, testGroup )
33 import Test.Tasty.HUnit ( (@?=), testCase )
43 import Text.Parsec.String ( Parser )
44 import Text.Read ( readMaybe )
46 import Network.DNS.RBL.Pretty ( Pretty(..) )
51 -- | An ipv4 octet; that is, an integer between @0@ and @255@
52 -- inclusive. This is the data type corresponding to a \"v4octet\"
53 -- in the postscreen parser.
55 newtype IPv4Octet = IPv4Octet Int
59 instance Pretty IPv4Octet where
60 pretty_show (IPv4Octet x) = show x
63 -- | Parse an IPv4 octet, which should contain a string of digits.
64 -- Should fail if the parsed integer does not lie between @0@ and
69 -- >>> import Text.Parsec ( parseTest )
71 -- Standard octets are parsed correctly:
73 -- >>> parseTest v4octet "0"
76 -- >>> parseTest v4octet "127"
79 -- >>> parseTest v4octet "255"
82 -- Non-digit input throws an error:
84 -- >>> parseTest v4octet "Hello, World!"
85 -- parse error at (line 1, column 1):
89 -- If we're given an integer outside the range @0..255@ (i.e. not a
90 -- valid octet), we fail:
92 -- >>> parseTest v4octet "9000"
93 -- parse error at (line 1, column 5):
94 -- unexpected end of input
96 -- Octet "9000" must be between 0 and 255.
98 v4octet :: Parser IPv4Octet
101 case ( readMaybe s :: Maybe Int ) of
102 -- If "many1 digit" gives us a list of digits, we should be able
103 -- to convert that to an Int! It will overflow rather than fail
104 -- if the input is too big/small, so it should really always
106 Nothing -> unexpected "v4octet: readMaybe failed on a sequence of digits!"
108 -- If we got an Int, make sure it's actually a representation of
110 Just k -> if 0 <= k && k <= 255
111 then return (IPv4Octet k)
112 else fail ("Octet \"" ++ (show k)
113 ++ "\" must be between 0 and 255.")
118 -- * Sequence members
121 -- | An ipv4 \"sequence member\". A sequence member is either an
122 -- integer (an octet) or a range of integers (contained in an
123 -- octet). This data type corresponds to \"v4seq_member\" in the
124 -- postscreen parser.
126 data IPv4SequenceMember =
127 IPv4SequenceMemberOctet IPv4Octet
128 | IPv4SequenceMemberOctetRange IPv4Octet IPv4Octet
132 instance Pretty IPv4SequenceMember where
133 pretty_show (IPv4SequenceMemberOctet octet) = pretty_show octet
134 pretty_show (IPv4SequenceMemberOctetRange octet1 octet2) =
135 (pretty_show octet1) ++ ".." ++ (pretty_show octet2)
138 -- | Parse an IPv4 \"sequence member\". A sequence member is either an
139 -- octet, or a start..end sequence (like an enumeration, in Haskell).
143 -- >>> import Text.Parsec ( parseTest )
145 -- >>> parseTest v4seq_member "127"
146 -- IPv4SequenceMemberOctet (IPv4Octet 127)
148 -- >>> parseTest v4seq_member "1..5"
149 -- IPv4SequenceMemberOctetRange (IPv4Octet 1) (IPv4Octet 5)
151 v4seq_member :: Parser IPv4SequenceMember
152 v4seq_member = try both <|> just_one
158 return $ IPv4SequenceMemberOctetRange oct1 oct2
160 just_one = fmap IPv4SequenceMemberOctet v4octet
166 -- | An ipv4 \"sequence\". A sequence contains either a single
167 -- \"sequence member\" (see 'IPv4SequenceMember'), or a sequence
168 -- member along with another sequence. So, this is a potentially
169 -- recursive definition. This type corresponds to \"v4sequence\" in
170 -- the postscreen parser.
173 IPv4SequenceSingleMember IPv4SequenceMember
174 | IPv4SequenceOptions IPv4SequenceMember IPv4Sequence
178 instance Pretty IPv4Sequence where
179 pretty_show (IPv4SequenceSingleMember member) = pretty_show member
180 pretty_show (IPv4SequenceOptions member subsequence) =
181 (pretty_show member) ++ ";" ++ (pretty_show subsequence)
184 -- | Parse an IPv4 \"sequence\". A sequence is whatever is allowed
185 -- within square brackets. Basically it can be three things:
187 -- * An octet (number).
188 -- * A range of addresses in start..end format.
189 -- * An alternative, separated by a semicolon, where each side
190 -- contains one of the previous two options.
194 -- >>> import Text.Parsec ( parseTest )
195 -- >>> parseTest v4sequence "1"
196 -- IPv4SequenceSingleMember (IPv4SequenceMemberOctet (IPv4Octet 1))
198 -- >>> pretty_print $ parse v4sequence "" "1..2"
201 -- >>> pretty_print $ parse v4sequence "" "1..2;8"
204 v4sequence :: Parser IPv4Sequence
205 v4sequence = try both <|> just_one
211 return $ IPv4SequenceOptions sm s
213 just_one = fmap IPv4SequenceSingleMember v4seq_member
219 data IPv4Field = IPv4FieldOctet IPv4Octet | IPv4FieldSequence IPv4Sequence
223 instance Pretty IPv4Field where
224 pretty_show (IPv4FieldOctet octet) = pretty_show octet
225 pretty_show (IPv4FieldSequence s) = "[" ++ (pretty_show s) ++ "]"
228 -- | Parse an IPv4 \"field\", which is either a boring old octet, or a
229 -- 'v4sequence' within square brackets.
233 -- >>> import Text.Parsec ( parseTest )
234 -- >>> parseTest v4field "127"
235 -- IPv4FieldOctet (IPv4Octet 127)
237 -- >>> pretty_print $ parse v4field "" "[127]"
240 v4field :: Parser IPv4Field
241 v4field = just_octet <|> brackets
243 just_octet = fmap IPv4FieldOctet v4octet
249 return $ IPv4FieldSequence s
256 IPv4Pattern IPv4Field IPv4Field IPv4Field IPv4Field
260 instance Pretty IPv4Pattern where
261 pretty_show (IPv4Pattern f1 f2 f3 f4) =
262 (pretty_show f1) ++ "."
270 -- | Parse an ipv4 address pattern. This consists of four fields,
271 -- separated by periods, where a field is either a simple octet or a
274 -- See also: 'v4field', 'v4sequence'.
278 -- >>> pretty_print $ parse v4pattern "" "127.0.0.1"
281 -- >>> pretty_print $ parse v4pattern "" "127.0.[1..3].1"
284 -- >>> pretty_print $ parse v4pattern "" "127.0.[1..3;8].1"
287 -- In the module intro, it is mentioned that this is invalid:
289 -- >>> import Text.Parsec ( parseTest )
290 -- >>> parseTest v4pattern "1.2.[3.4]"
291 -- parse error at (line 1, column 7):
293 -- expecting digit or "]"
295 -- This one is /also/ invalid; however, we'll parse the valid part off
298 -- >>> pretty_print $ parse v4pattern "" "1.2.3.3[6..9]"
301 v4pattern :: Parser IPv4Pattern
310 return $ IPv4Pattern field1 field2 field3 field4
316 -- | Enumerate the members of an 'IPv4SequenceMember'. A sequence
317 -- member is either an octet, which is easy to enumerate -- we just
318 -- print it -- or an octet range whose members can be enumerated
319 -- from least to greatest.
321 -- We enumerate strings instead of integers because the big picture
322 -- is that we will be listing out patterns of ipv4 addresses, and
323 -- those are represented as strings (dotted quad format).
327 -- >>> let (Right r) = parse v4seq_member "" "127"
328 -- >>> sequence_members r
331 -- >>> let (Right r) = parse v4seq_member "" "127..135"
332 -- >>> sequence_members r
333 -- ["127","128","129","130","131","132","133","134","135"]
335 sequence_members :: IPv4SequenceMember -> [String]
336 sequence_members (IPv4SequenceMemberOctet (IPv4Octet i)) = [show i]
337 sequence_members (IPv4SequenceMemberOctetRange (IPv4Octet s) (IPv4Octet t)) =
338 [show x | x <- [s .. t]]
341 -- | Enumerate the members of an ipv4 sequence. These consist of
342 -- either a single sequence member (in which case we delegate to
343 -- 'sequence_members'), or an \"option\" which is enumerated
348 -- >>> let (Right r) = parse v4sequence "" "1"
352 -- >>> let (Right r) = parse v4sequence "" "1..2"
356 -- >>> let (Right r) = parse v4sequence "" "1..3;4;5..9"
358 -- ["1","2","3","4","5","6","7","8","9"]
360 sequences :: IPv4Sequence -> [String]
361 sequences (IPv4SequenceSingleMember sm) =
363 sequences (IPv4SequenceOptions sm s) =
364 (sequence_members sm) ++ (sequences s)
367 -- | Enumerate the members of an 'IPv4Field'. If the field contains a
368 -- single 'IPv4Octet', we simply 'show' it. Otherwise it contains an
369 -- 'IPv4FieldSequence', and we enumerate that recursively using
374 -- >>> let (Right r) = parse v4field "" "1"
378 -- >>> let (Right r) = parse v4field "" "[127..135]"
380 -- ["127","128","129","130","131","132","133","134","135"]
382 fields :: IPv4Field -> [String]
383 fields (IPv4FieldOctet (IPv4Octet i)) = [show i]
384 fields (IPv4FieldSequence s) = sequences s
387 -- | Enumerate the addresses represented by a given 'IPv4Pattern'.
389 -- A pattern contains four fields, sepearated by period
390 -- characters. We want to list all possible combinations of
391 -- addresses where the first octet comes from the first field, the
392 -- second octet comes from the second field... and so on. To do
393 -- this, we take advantage of the List monad and the fact that
394 -- 'fields' returns a list of 'String's.
400 -- >>> let (Right r) = parse v4pattern "" "127.0.0.1"
404 -- Anything between 127.0.0.2 and 127.0.0.4, and either 127.0.0.10
407 -- >>> let (Right r) = parse v4pattern "" "127.0.0.[2..4;10;11]"
409 -- ["127.0.0.2","127.0.0.3","127.0.0.4","127.0.0.10","127.0.0.11"]
411 addresses :: IPv4Pattern -> [String]
412 addresses (IPv4Pattern field1 field2 field3 field4) = do
417 return $ f1 ++ "." ++ f2 ++ "." ++ f3 ++ "." ++ f4
423 ipv4pattern_tests :: TestTree
425 testGroup "IPv4Pattern Tests" [ v4octet_tests ]
428 v4octet_tests :: TestTree
432 [ test_v4octet_single_digit_parsed ]
434 test_v4octet_single_digit_parsed :: TestTree
435 test_v4octet_single_digit_parsed =
436 testCase "a single digit is parsed as a v4octet" $ do
437 -- Whatever, it's a test.
438 let (Right actual) = parse v4octet "" "1"
439 let expected = IPv4Octet 1