]> gitweb.michael.orlitzky.com - hath.git/blob - src/Main.hs
Separate a few functions out in to a ListUtils module.
[hath.git] / src / Main.hs
1 import qualified Data.Char as DC
2 import qualified Data.List as DL
3 import qualified Numeric as N
4 import System.Exit (exitFailure)
5 import Text.Regex.Posix
6
7 import ListUtils
8
9 -- Takes an IP address in CIDR notation, and returns a list of its
10 -- octets (converted to Int).
11 octets :: String -> [Int]
12 octets cidr = map read (take 4 (splitWith (`elem` "./") cidr))
13
14
15 -- Returns the mask portion of a CIDR address. That is, everything
16 -- after the trailing slash.
17 maskbits :: String -> Int
18 maskbits cidr = read ((splitWith (`elem` "/") cidr) !! 1)
19
20
21 -- Takes an Int, and returns its base-two representation as a String.
22 base_two :: Int -> String
23 base_two n = N.showIntAtBase 2 DC.intToDigit n ""
24
25
26 -- Takes a set of octets, and converts them to base-two
27 -- individually. The results are then zero-padded on the left to 8
28 -- characters, and concatenated together.
29 octets_base_two :: [Int] -> String
30 octets_base_two octet_list =
31 DL.concatMap ((pad_left_to 8 '0') .base_two) octet_list
32
33
34 -- Returns the minimum address (as a base-two string) satisfying the
35 -- given CIDR string.
36 min_base_two_address :: String -> String
37 min_base_two_address cidr =
38 pad_right_to 32 '0' netpart
39 where
40 netpart = take (maskbits cidr) (octets_base_two (octets cidr))
41
42
43 -- Returns the maximum address (as a base-two string) satisfying the
44 -- given CIDR string.
45 max_base_two_address :: String -> String
46 max_base_two_address cidr =
47 pad_right_to 32 '1' netpart
48 where
49 netpart = take (maskbits cidr) (octets_base_two (octets cidr))
50
51
52 -- The octet components of min_base_two_address, as a base-two String.
53 min_base_two_octets :: String -> [String]
54 min_base_two_octets cidr =
55 [octet1, octet2, octet3, octet4]
56 where
57 addr = min_base_two_address cidr
58 octet1 = fst (DL.splitAt 8 addr)
59 octet2 = fst (DL.splitAt 8 (snd (DL.splitAt 8 addr)))
60 octet3 = fst (DL.splitAt 8 (snd (DL.splitAt 16 addr)))
61 octet4 = snd (DL.splitAt 24 addr)
62
63
64 -- The octet components of max_base_two_address, as a base-two String.
65 max_base_two_octets :: String -> [String]
66 max_base_two_octets cidr =
67 [octet1, octet2, octet3, octet4]
68 where
69 addr = max_base_two_address cidr
70 octet1 = fst (DL.splitAt 8 addr)
71 octet2 = fst (DL.splitAt 8 (snd (DL.splitAt 8 addr)))
72 octet3 = fst (DL.splitAt 8 (snd (DL.splitAt 16 addr)))
73 octet4 = snd (DL.splitAt 24 addr)
74
75
76 -- The octet components of min_base_two_address, as Ints.
77 min_octets :: String -> [Int]
78 min_octets cidr =
79 map base_two_to_base_ten (min_base_two_octets cidr)
80
81
82 -- The octet components of max_base_two_address, as Ints.
83 max_octets :: String -> [Int]
84 max_octets cidr =
85 map base_two_to_base_ten (max_base_two_octets cidr)
86
87
88 -- The base_two_to_base_ten function requires a way to determine
89 -- whether or not the character it's currently parsing is valid. This
90 -- should do it.
91 is_binary_digit :: Char -> Bool
92 is_binary_digit c =
93 if c `elem` ['0','1'] then
94 True
95 else
96 False
97
98
99 -- Convert a base-two String to an Int.
100 base_two_to_base_ten :: String -> Int
101 base_two_to_base_ten s =
102 if (length parsed) == 0 then
103 0
104 else
105 fst (parsed !! 0)
106 where
107 parsed = N.readInt 2 is_binary_digit DC.digitToInt s
108
109
110 -- A regular expression that matches a non-address character.
111 non_addr_char :: String
112 non_addr_char = "[^\\.0-9]"
113
114
115 -- Add non_addr_chars on either side of the given String. This
116 -- prevents (for example) the regex '127.0.0.1' from matching
117 -- '127.0.0.100'.
118 addr_barrier :: String -> String
119 addr_barrier x = non_addr_char ++ x ++ non_addr_char
120
121
122 -- The magic happens here. We take a CIDR String as an argument, and
123 -- return the equivalent regular expression. We do this as follows:
124 --
125 -- 1. Compute the minimum possible value of each octet.
126 -- 2. Compute the maximum possible value of each octet.
127 -- 3. Generate a regex matching every value between those min and
128 -- max values.
129 -- 4. Join the regexes from step 3 with regexes matching periods.
130 -- 5. Stick an address boundary on either side of the result.
131 cidr_to_regex :: String -> String
132 cidr_to_regex cidr =
133 addr_barrier (DL.intercalate "\\." [range1, range2, range3, range4])
134 where
135 range1 = numeric_range min1 max1
136 range2 = numeric_range min2 max2
137 range3 = numeric_range min3 max3
138 range4 = numeric_range min4 max4
139 min1 = (min_octets cidr) !! 0
140 min2 = (min_octets cidr) !! 1
141 min3 = (min_octets cidr) !! 2
142 min4 = (min_octets cidr) !! 3
143 max1 = (max_octets cidr) !! 0
144 max2 = (max_octets cidr) !! 1
145 max3 = (max_octets cidr) !! 2
146 max4 = (max_octets cidr) !! 3
147
148
149 -- Will return True if the passed String is in CIDR notation, False
150 -- otherwise.
151 is_valid_cidr :: String -> Bool
152 is_valid_cidr cidr = cidr =~ "([0-9]{1,3}\\.){3}[0-9]{1,3}/[0-9]{1,2}"
153
154
155 -- Take a list of Strings, and return a regular expression matching
156 -- any of them.
157 alternate :: [String] -> String
158 alternate terms = "(" ++ (concat (DL.intersperse "|" terms)) ++ ")"
159
160
161 -- Take two Ints as parameters, and return a regex matching any
162 -- integer between them (inclusive).
163 numeric_range :: Int -> Int -> String
164 numeric_range x y =
165 alternate (map show [lower..upper])
166 where
167 lower = minimum [x,y]
168 upper = maximum [x,y]
169
170
171 -- Take a CIDR String, and exitFailure if it's invalid.
172 validate_or_die :: String -> IO ()
173 validate_or_die cidr = do
174 if (is_valid_cidr cidr)
175 then do
176 return ()
177 else do
178 putStrLn "Error: not valid CIDR notation."
179 exitFailure
180
181
182 main :: IO ()
183 main = do
184 input <- getContents
185 let cidrs = lines input
186 mapM validate_or_die cidrs
187 let regexes = map cidr_to_regex cidrs
188 putStrLn $ alternate regexes
189