import qualified Data.Char as DC import qualified Data.List as DL import qualified Numeric as N import System.Exit (exitFailure) import Text.Regex.Posix import ListUtils -- Takes an IP address in CIDR notation, and returns a list of its -- octets (converted to Int). octets :: String -> [Int] octets cidr = map read (take 4 (splitWith (`elem` "./") cidr)) -- Returns the mask portion of a CIDR address. That is, everything -- after the trailing slash. maskbits :: String -> Int maskbits cidr = read ((splitWith (`elem` "/") cidr) !! 1) -- Takes an Int, and returns its base-two representation as a String. base_two :: Int -> String base_two n = N.showIntAtBase 2 DC.intToDigit n "" -- Takes a set of octets, and converts them to base-two -- individually. The results are then zero-padded on the left to 8 -- characters, and concatenated together. octets_base_two :: [Int] -> String octets_base_two octet_list = DL.concatMap ((pad_left_to 8 '0') .base_two) octet_list -- Returns the minimum address (as a base-two string) satisfying the -- given CIDR string. min_base_two_address :: String -> String min_base_two_address cidr = pad_right_to 32 '0' netpart where netpart = take (maskbits cidr) (octets_base_two (octets cidr)) -- Returns the maximum address (as a base-two string) satisfying the -- given CIDR string. max_base_two_address :: String -> String max_base_two_address cidr = pad_right_to 32 '1' netpart where netpart = take (maskbits cidr) (octets_base_two (octets cidr)) -- The octet components of min_base_two_address, as a base-two String. min_base_two_octets :: String -> [String] min_base_two_octets cidr = [octet1, octet2, octet3, octet4] where addr = min_base_two_address cidr octet1 = fst (DL.splitAt 8 addr) octet2 = fst (DL.splitAt 8 (snd (DL.splitAt 8 addr))) octet3 = fst (DL.splitAt 8 (snd (DL.splitAt 16 addr))) octet4 = snd (DL.splitAt 24 addr) -- The octet components of max_base_two_address, as a base-two String. max_base_two_octets :: String -> [String] max_base_two_octets cidr = [octet1, octet2, octet3, octet4] where addr = max_base_two_address cidr octet1 = fst (DL.splitAt 8 addr) octet2 = fst (DL.splitAt 8 (snd (DL.splitAt 8 addr))) octet3 = fst (DL.splitAt 8 (snd (DL.splitAt 16 addr))) octet4 = snd (DL.splitAt 24 addr) -- The octet components of min_base_two_address, as Ints. min_octets :: String -> [Int] min_octets cidr = map base_two_to_base_ten (min_base_two_octets cidr) -- The octet components of max_base_two_address, as Ints. max_octets :: String -> [Int] max_octets cidr = map base_two_to_base_ten (max_base_two_octets cidr) -- The base_two_to_base_ten function requires a way to determine -- whether or not the character it's currently parsing is valid. This -- should do it. is_binary_digit :: Char -> Bool is_binary_digit c = if c `elem` ['0','1'] then True else False -- Convert a base-two String to an Int. base_two_to_base_ten :: String -> Int base_two_to_base_ten s = if (length parsed) == 0 then 0 else fst (parsed !! 0) where parsed = N.readInt 2 is_binary_digit DC.digitToInt s -- A regular expression that matches a non-address character. non_addr_char :: String non_addr_char = "[^\\.0-9]" -- Add non_addr_chars on either side of the given String. This -- prevents (for example) the regex '127.0.0.1' from matching -- '127.0.0.100'. addr_barrier :: String -> String addr_barrier x = non_addr_char ++ x ++ non_addr_char -- The magic happens here. We take a CIDR String as an argument, and -- return the equivalent regular expression. We do this as follows: -- -- 1. Compute the minimum possible value of each octet. -- 2. Compute the maximum possible value of each octet. -- 3. Generate a regex matching every value between those min and -- max values. -- 4. Join the regexes from step 3 with regexes matching periods. -- 5. Stick an address boundary on either side of the result. cidr_to_regex :: String -> String cidr_to_regex cidr = addr_barrier (DL.intercalate "\\." [range1, range2, range3, range4]) where range1 = numeric_range min1 max1 range2 = numeric_range min2 max2 range3 = numeric_range min3 max3 range4 = numeric_range min4 max4 min1 = (min_octets cidr) !! 0 min2 = (min_octets cidr) !! 1 min3 = (min_octets cidr) !! 2 min4 = (min_octets cidr) !! 3 max1 = (max_octets cidr) !! 0 max2 = (max_octets cidr) !! 1 max3 = (max_octets cidr) !! 2 max4 = (max_octets cidr) !! 3 -- Will return True if the passed String is in CIDR notation, False -- otherwise. is_valid_cidr :: String -> Bool is_valid_cidr cidr = cidr =~ "([0-9]{1,3}\\.){3}[0-9]{1,3}/[0-9]{1,2}" -- Take a list of Strings, and return a regular expression matching -- any of them. alternate :: [String] -> String alternate terms = "(" ++ (concat (DL.intersperse "|" terms)) ++ ")" -- Take two Ints as parameters, and return a regex matching any -- integer between them (inclusive). numeric_range :: Int -> Int -> String numeric_range x y = alternate (map show [lower..upper]) where lower = minimum [x,y] upper = maximum [x,y] -- Take a CIDR String, and exitFailure if it's invalid. validate_or_die :: String -> IO () validate_or_die cidr = do if (is_valid_cidr cidr) then do return () else do putStrLn "Error: not valid CIDR notation." exitFailure main :: IO () main = do input <- getContents let cidrs = lines input mapM validate_or_die cidrs let regexes = map cidr_to_regex cidrs putStrLn $ alternate regexes