HostAndPort (empty) → 0.1.0
raw patch · 6 files changed
+391/−0 lines, 6 filesdep +HostAndPortdep +basedep +doctestsetup-changed
Dependencies added: HostAndPort, base, doctest, hspec, parsec
Files
- HostAndPort.cabal +62/−0
- LICENSE +21/−0
- Setup.hs +2/−0
- src/Network/HostAndPort.hs +209/−0
- tests/Doctests.hs +2/−0
- tests/Spec.hs +95/−0
+ HostAndPort.cabal view
@@ -0,0 +1,62 @@+name: HostAndPort+version: 0.1.0+license: MIT+license-file: LICENSE+author: Slava Bacherikov+maintainer: slava@bacher09.org+homepage: https://github.com/bacher09/hostandport+bug-reports: https://github.com/bacher09/hostandport/issues+copyright: (c) Slava Bacherikov 2015+category: Network+build-type: Simple+cabal-version: >=1.10+synopsis: Parser host and port pairs like localhost:22+description:++ Simple parser for parsing host and port pairs.+ Host can be either ipv4, ipv6 or domain name and port are+ optional.+ .+ IPv6 address should be surrounded by square brackets.+ .+ Examples:+ .+ * localhost+ * localhost:8080+ * 127.0.0.1+ * 127.0.0.1:8080+ * [::1]+ * [::1]:8080++library+ exposed-modules: Network.HostAndPort+ build-depends: base >=4.5 && <5,+ parsec >=3.1+ hs-source-dirs: src+ ghc-options: -O2 -funbox-strict-fields -Wall -fno-warn-name-shadowing+ default-language: Haskell2010++test-suite tests+ default-language: Haskell2010+ type: exitcode-stdio-1.0+ hs-source-dirs: tests+ main-is: Spec.hs+ ghc-options: -O2 -funbox-strict-fields -Wall -fno-warn-name-shadowing+ build-depends: base >=4.5 && <5,+ HostAndPort,+ hspec++test-suite doctests+ default-language: Haskell2010+ type: exitcode-stdio-1.0+ hs-source-dirs: tests+ main-is: Doctests.hs+ ghc-options: -threaded+ build-depends: base >=4.5 && <5,+ HostAndPort,+ doctest+++source-repository head+ type: git+ location: https://github.com/bacher09/darkplaces-rcon.git
+ LICENSE view
@@ -0,0 +1,21 @@+The MIT License (MIT)++Copyright (c) 2015 Slava Bacherikov++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in+all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN+THE SOFTWARE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ src/Network/HostAndPort.hs view
@@ -0,0 +1,209 @@+module Network.HostAndPort (+ isIPv4Address,+ isIPv6Address,+ hostAndPort,+ maybeHostAndPort,+ defaultHostAndPort+) where+import Text.Parsec+import Control.Applicative hiding((<|>), many)+import Control.Monad+import Data.Maybe+++type Parser = Parsec String ()+++countMinMax :: (Stream s m t) => Int -> Int -> ParsecT s u m a -> ParsecT s u m [a]+countMinMax m x p+ | m > 0 = do+ f <- p+ end <- countMinMax (m - 1) (x - 1) p+ return $ f : end+ | x <= 0 = return []+ | otherwise = option [] $ do+ f <- p+ end <- countMinMax 0 (x - 1) p+ return $ f : end+++intDigits :: Int -> Int+intDigits = length . show+++limitedInt :: Int -> String -> Parser String+limitedInt x e = do+ b <- countMinMax 1 (intDigits x) digit+ if (read b :: Int) > x+ then fail e+ else return b+++byteNum :: Parser String+byteNum = limitedInt 255 "Value to large"+++consequence :: (Monad m) => [m [a]] -> m [a]+consequence v = liftM concat $ sequence v+++ipv4address :: Parser String+ipv4address = consequence [+ byteNum, string ".",+ byteNum, string ".",+ byteNum, string ".", byteNum] <?> "bad IPv4 address"+++hexShortNum :: Parser String+hexShortNum = countMinMax 1 4 hexDigit+++port :: Parser String+port = limitedInt 65535 "Port number to large"+++ipv6address :: Parser String+ipv6address = do+ let ipv6variants = (try <$> skippedAtBegin)+ ++ [try full]+ ++ (try <$> skippedAtMiddle)+ ++ (try <$> skippedAtEnd)+ ++ [last2 False]+ choice ipv6variants <?> "bad IPv6 address"+ where+ h4s = (++) <$> hexShortNum <*> string ":"+ sh4 = (++) <$> string ":" <*> hexShortNum+ execNum 0 = return ""+ execNum n = concat <$> count n h4s+ partNum 0 = return ""+ partNum n = do+ f <- hexShortNum+ e <- countMinMax 0 (n - 1) (try sh4)+ return $ f ++ concat e++ maybeNum n = concat <$> countMinMax 0 n h4s+ last2f = try ipv4address <|> consequence [h4s, hexShortNum]+ last2 f = if f+ then last2f+ else choice [try $ last2f,+ try $ consequence [string "::", hexShortNum],+ consequence [hexShortNum, string "::"]]++ skippedAtBegin =+ map (\i -> consequence [string "::", execNum i, last2 True]) [5,4..0]++ skippedAtMiddle = [+ consequence [partNum 1, string "::", maybeNum 4, last2 True],+ consequence [partNum 2, string "::", maybeNum 3, last2 True],+ consequence [partNum 3, string "::", maybeNum 2, last2 True],+ consequence [partNum 4, string "::", maybeNum 1, last2 True],+ consequence [partNum 5, string "::", last2 True],+ consequence [partNum 6, string "::", hexShortNum]]++ skippedAtEnd = [consequence [partNum 7, string "::"]]++ full = consequence [concat <$> count 6 h4s, last2 True]+++ipv6addressWithScope :: Parser String+ipv6addressWithScope = consequence [ipv6address, option "" scope]+ where+ scope = consequence [string "%", many1 asciiAlphaNum]+++hostname :: Parser String+hostname = many1 $ alphaNum <|> oneOf ".-_"+++isParsed :: Parser a -> String -> Bool+isParsed p s = case runParser p () "" s of+ (Right _) -> True+ (Left _) -> False+++-- | This function will validate ipv4 address+-- and return True if string is valie adress+isIPv4Address :: String -> Bool+isIPv4Address = isParsed $ ipv4address <* eof+++-- | Function validates ipv6 address+isIPv6Address :: String -> Bool+isIPv6Address = isParsed $ ipv6addressWithScope <* eof+++isAsciiAlpha :: Char -> Bool+isAsciiAlpha c = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')+++isAsciiNum :: Char -> Bool+isAsciiNum c = (c >= '0' && c <= '9')+++isAsciiAlphaNum :: Char -> Bool+isAsciiAlphaNum c = isAsciiAlpha c || isAsciiNum c+++asciiAlphaNum :: Parser Char+asciiAlphaNum = satisfy isAsciiAlphaNum+++connectionStr :: Parser (String, Maybe String)+connectionStr = do+ addr <- try ipv6str <|> try ipv4address <|> hostname+ p <- maybePort+ return (addr, p)+ where+ ipv6str = do+ void $ char '['+ ipv6 <- ipv6addressWithScope+ void $ char ']'+ return ipv6+ maybePort = option Nothing $ char ':' >> Just <$> port++-- | This function will parse it's argument and return either+-- `String` (`Left`) with info about error or (Host, `Maybe` Port)+-- tuple (`Right`).+--+-- Examples:+--+-- >>> hostAndPort "localhost"+-- Right ("localhost",Nothing)+-- >>> hostAndPort "[::1]:3030"+-- Right ("::1",Just "3030")+hostAndPort :: String -> Either String (String, Maybe String)+hostAndPort s = case runParser (connectionStr <* eof) () "" s of+ (Right v) -> Right v+ (Left e) -> Left $ show $ e+++-- | Function will parse argument and return Maybe (Host, Maybe Port)+--+-- Examples:+--+-- >>> maybeHostAndPort "192.168.10.12"+-- Just ("192.168.10.12",Nothing)+-- >>> maybeHostAndPort "192.168.10.12:7272"+-- Just ("192.168.10.12",Just "7272")+maybeHostAndPort :: String -> Maybe (String, Maybe String)+maybeHostAndPort s = case hostAndPort s of+ (Right v) -> Just v+ (Left _) -> Nothing+++-- | Function will take default port and connection string+-- and returns Just (Host, Port) for valid input and+-- Nothing for invalid.+--+-- Examples:+--+-- >>> defaultHostAndPort "22" "my.server.com"+-- Just ("my.server.com","22")+-- >>> defaultHostAndPort "22" "my.otherserver.com:54022"+-- Just ("my.otherserver.com","54022")+-- >>> defaultHostAndPort "22" "porttobig.com:500022"+-- Nothing+defaultHostAndPort :: String -- ^ default Port number+ -> String -- ^ connection string+ -> Maybe (String, String) -- ^ Maybe (Host, Port)+defaultHostAndPort p s = (\(h, mp) -> (h, fromMaybe p mp)) <$> maybeHostAndPort s
+ tests/Doctests.hs view
@@ -0,0 +1,2 @@+import Test.DocTest+main = doctest ["-isrc", "src/Network/HostAndPort.hs"]
+ tests/Spec.hs view
@@ -0,0 +1,95 @@+module Main where+import Test.Hspec+import Network.HostAndPort+import Text.Printf+++main :: IO ()+main = hspec spec+++spec :: Spec+spec = do+ describe "isIPv4Address" $ do+ valid isIPv4Address "127.0.0.1"+ valid isIPv4Address "255.255.255.255"++ invalid isIPv4Address " 127.0.0.1"+ invalid isIPv4Address "127.0.0.1 "++ invalid isIPv4Address "9.9.256.1"++ describe "isIPv6Address" $ do+ valid isIPv6Address "::1"+ valid isIPv6Address "::1:2"+ valid isIPv6Address "::1:2:3"+ valid isIPv6Address "::1:2:3:4"+ valid isIPv6Address "::1:2:3:4:5"+ valid isIPv6Address "::1:2:3:4:5:6"+ valid isIPv6Address "::1:2:3:4:5:6:7"+ valid isIPv6Address "1:2:3:4:5:6:7:8"+ valid isIPv6Address "::192.168.1.10"+ valid isIPv6Address "1::192.168.1.10"+ valid isIPv6Address "1:2::192.168.1.10"+ valid isIPv6Address "1:2:3::192.168.1.10"+ valid isIPv6Address "1:2:3:4::192.168.1.10"+ valid isIPv6Address "1:2:3:4:5::192.168.1.10"+ valid isIPv6Address "::1:192.168.1.10"+ valid isIPv6Address "::1:2:192.168.1.10"+ valid isIPv6Address "::1:2:3:192.168.1.10"+ valid isIPv6Address "::1:2:3:4:192.168.1.10"+ valid isIPv6Address "::1:2:3:4:5:192.168.1.10"+ valid isIPv6Address "::1:2:3:4:5:192.168.1.10"+ valid isIPv6Address "ff::192.168.1.10"+ valid isIPv6Address "1:2:3:4:5:6:127.0.0.1"+ valid isIPv6Address "1::"+ valid isIPv6Address "1:2::"+ valid isIPv6Address "1:2:3::"+ valid isIPv6Address "1:2:3:4::"+ valid isIPv6Address "1:2:3:4:5::"+ valid isIPv6Address "1:2:3:4:5:6::"+ valid isIPv6Address "1:2:3:4:5:6:7::"+ valid isIPv6Address "1::f"+ valid isIPv6Address "1:2::f"+ valid isIPv6Address "1:2:3::f"+ valid isIPv6Address "1:2:3:4::f"+ valid isIPv6Address "1:2:3:4:5::f"+ valid isIPv6Address "1:2:3:4:5:6::f"+ valid isIPv6Address "1:2:3:4:5::6:f"+ valid isIPv6Address "1:2:3:4::5:6:f"+ valid isIPv6Address "1:2:3::4:5:6:f"+ valid isIPv6Address "1:2::3:4:5:6:f"+ valid isIPv6Address "1::2:3:4:5:6:f"+ valid isIPv6Address "::1:2:3:4:5:6:f"+ valid isIPv6Address "::ffff:0:255.255.255.255"+ valid isIPv6Address "fe80::7:8%eth0"+ valid isIPv6Address "fe80::7:8%11"++ invalid isIPv6Address "::"+ invalid isIPv6Address "1:2:3:4:5:6:7:8:9"+ invalid isIPv6Address "1:2:3:4:5:6:7:"+ invalid isIPv6Address "1:2:3:4:5:6:7"+ invalid isIPv6Address ":1:2:3:4:5:6:7"+ invalid isIPv6Address "1:2:3:4:5:6:7:10.0.0.1"+ invalid isIPv6Address "::1:2:3:4:5:6:7:8"+ invalid isIPv6Address "1:2:3:4:5:6::192.168.1.10"+ invalid isIPv6Address "1:2:3:4:5:6:7:8::"++ describe "defaultHostAndPort" $ do+ it "parsing hosts" $ do+ defaultHostAndPort "40" "localhost:25" `shouldBe` Just ("localhost", "25")++ it "parsing ipv4" $ do+ defaultHostAndPort "80" "127.0.0.1:8080" `shouldBe` Just ("127.0.0.1", "8080")+ defaultHostAndPort "80" "127.0.0.1" `shouldBe` Just ("127.0.0.1", "80")++ it "parsing ipv6" $ do+ defaultHostAndPort "26000" "[f08e::7:8:1]" `shouldBe` Just ("f08e::7:8:1", "26000")+ defaultHostAndPort "26000" "[f08e::7:8:1]:27000" `shouldBe` Just ("f08e::7:8:1", "27000")++ it "testing invalid" $ do+ defaultHostAndPort "60" "localhost:99999" `shouldBe` Nothing+ defaultHostAndPort "60" "[bad]:25" `shouldBe` Nothing+ where+ valid f s = it (printf "valid \"%s\"" s) (f s `shouldBe` True)+ invalid f s = it (printf "invalid \"%s\"" s) (f s `shouldBe` False)