12

I have an NSTextField where I am asking a user to input a string that is either in IPv4 format, or a domain name such as www.example.com. Currently, my code is:

@IBAction func verifyTarget(sender: NSTextFieldCell) {
    var txtTarget: NSTextFieldCell = sender

    var strRawTarget: String? = txtTarget.stringValue
    println("Input: " + strRawTarget!)
    var URLTarget: NSURL?

    URLTarget = NSURL.URLWithString(strRawTarget)
    if URLTarget {
        println("URL \(URLTarget) is valid!")
    }
    else {
        println("URL \(strRawTarget) is not valid!")
    }
}

Some example output:

Input: 
URL  is valid!
Input: adsfasdf
URL adsfasdf is valid!
Input: afe12389hfs. . afopadsf
URL afe12389hfs. . afopadsf is not valid!
Input: 192.292.111.3
URL 192.292.111.3 is valid!
Input: 0.a.0.a
URL 0.a.0.a is valid!
Input: %2
URL %2 is not valid!
Input: %20
URL %20 is valid!

Am I doing something wrong?

Matt
  • 2,576
  • 6
  • 34
  • 52

8 Answers8

42

Check if IP address is IPv4 or IPv6 in Swift

func validateIpAddress(ipToValidate: String) -> Bool {

    var sin = sockaddr_in()
    var sin6 = sockaddr_in6()

    if ipToValidate.withCString({ cstring in inet_pton(AF_INET6, cstring, &sin6.sin6_addr) }) == 1 {
        // IPv6 peer.
        return true
    }
    else if ipToValidate.withCString({ cstring in inet_pton(AF_INET, cstring, &sin.sin_addr) }) == 1 {
        // IPv4 peer.
        return true
    }

    return false;
}
Alin Golumbeanu
  • 579
  • 1
  • 7
  • 18
11

NSURL.URLWithString evaluates the URL string you pass it based on the criteria for decoding a relative or absolute address as laid out in these not-all-that-readable documents: RFCs 2396, 1738, and 1808. That is to say, what you're hoping to validate is only a small subset of what NSURL can handle. You're better off using a RegEx or two, perhaps from this answer:

@IBAction func verifyTarget(sender: NSTextFieldCell) {
    var txtTarget: NSTextFieldCell = sender

    var strRawTarget: String? = txtTarget.stringValue
    println("Input: " + strRawTarget!)

    let validIpAddressRegex = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"
    let validHostnameRegex = "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$"
    
    if strRawTarget == nil {
        println("no input!")
    } else if strRawTarget!.rangeOfString(validIpAddressRegex, options: .RegularExpressionSearch) {
        println("\(strRawTarget) is a valid IP address")
    } else if strRawTarget!.rangeOfString(validHostnameRegex, options: .RegularExpressionSearch) {
        println("\(strRawTarget) is a valid hostname")
    } else {
        println("\(strRawTarget) is not valid")
    }
}
Community
  • 1
  • 1
Nate Cook
  • 92,417
  • 32
  • 217
  • 178
  • No, it still doesn't work (??) I copy/pasted your code exactly. In fact, I can't seem to get ANY string that isn't valid. They all say "Valid IP address." – Matt Jun 30 '14 at 07:11
  • 1
    Got a little careless with the optional unwrapping -- updated the code with a working version. – Nate Cook Jun 30 '14 at 09:48
  • Hi, can you please provide this answer in Swift 4.2 version? – Neeraj Shukla Aug 21 '19 at 11:23
10

The code from @Alin in a more compact form:

extension String {
func isIPv4() -> Bool {
    var sin = sockaddr_in()
    return self.withCString({ cstring in inet_pton(AF_INET, cstring, &sin.sin_addr) }) == 1
}

func isIPv6() -> Bool {
    var sin6 = sockaddr_in6()
    return self.withCString({ cstring in inet_pton(AF_INET6, cstring, &sin6.sin6_addr) }) == 1
}

func isIpAddress() -> Bool { return self.isIPv6() || self.isIPv4() }
}

Usage:

let ipv6 = "FE80:0000:0000:0000:0202:B3FF:FE1E:8329"
let ipv6Collapsed = "FE80::0202:B3FF:FE1E:8329"
let ipv4 = "19.117.63.126"

ipv6.isIpAddress()  //true
ipv6.isIPv6()       //true
ipv6.isIPv4()       //false

ipv6Collapsed.isIpAddress() //true
ipv6Collapsed.isIPv6()      //true
ipv6Collapsed.isIPv4()      //false

ipv4.isIpAddress()  //true
ipv4.isIPv6()       //false
ipv4.isIPv4()       //true
Darkwonder
  • 1,149
  • 1
  • 13
  • 26
5

The new Network framework has failable initializers for struct IPv4Address and struct IPv6Address which handle the IP address portion very easily. Doing this in IPv6 with a regex is tough with all the shortening rules.

Unfortunately I can't address the domain name part.

Note that Network framework is recent, so it may force you to compile for recent OS versions.

    import Network
let tests = ["192.168.4.4","fkjhwojfw","192.168.4.4.4","2620:3","2620::33"]

for test in tests {
    if let _ = IPv4Address(test) {
        debugPrint("\(test) is valid ipv4 address")
    } else if let _ = IPv6Address(test) {
        debugPrint("\(test) is valid ipv6 address")
    } else {
        debugPrint("\(test) is not a valid IP address")
    }
}

output:
"192.168.4.4 is valid ipv4 address"
"fkjhwojfw is not a valid IP address"
"192.168.4.4.4 is not a valid IP address"
"2620:3 is not a valid IP address"
"2620::33 is valid ipv6 address"
Darrell Root
  • 724
  • 8
  • 19
2

Updated to Swift 5.1 based on @Nate Cook response.

@IBAction func verifyTarget(sender: NSTextFieldCell) {
    guard let strRawTarget = sender.stringValue else { print("no input!"); return }
    print("Input: " + strRawTarget)

    if strRawTarget.isValidIpAddress {
        print("\(strRawTarget) is a valid IP address")
    } else if strRawTarget.isValidHostname {
        print("\(strRawTarget) is a valid hostname")
    } else {
        print("\(strRawTarget) is not valid")
    }
}

enum Regex {
    static let ipAddress = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"
    static let hostname = "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$"
}

extension String {
    var isValidIpAddress: Bool {
        return self.matches(pattern: Regex.ipAddress)
    }
    
    var isValidHostname: Bool {
        return self.matches(pattern: Regex.hostname)
    }
    
    private func matches(pattern: String) -> Bool {
        return self.range(of: pattern,
                          options: .regularExpression,
                          range: nil,
                          locale: nil) != nil
    }
}
0
@IBAction func verifyTarget(sender: NSTextFieldCell) -> Bool {
    let validIP = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"
    if ((sender.stringValue.count == 0) || (sender.stringValue!.range(of: validIP, options: .regularExpression) == nil)) { 
        return false 
    }
    return true
}
Zigii Wong
  • 7,766
  • 8
  • 51
  • 79
Lefki
  • 1
  • 1
  • Can you elaborate how this resolves the question? Code-only answers are often discouraged. – Litty Mar 08 '18 at 22:25
0

Try something like:

private static func validate(ipAddress: String) -> Bool {
    return ipAddress.withCString({ cstring in
        var addressV6 = sockaddr_in6()
        var address = sockaddr_in()
        return inet_pton(AF_INET6, cstring, &addressV6.sin6_addr) == 1 // IPv6.
            || inet_pton(AF_INET, cstring, &address.sin_addr) == 1 // IPv4.
    });
}

Simply checks if it's accepted by sockaddr_in function as an IPv4 address, or by sockaddr_in6 as IPv6 address.

Top-Master
  • 7,611
  • 5
  • 39
  • 71
0

I wanted to check whether the string is an IP address or not, I found the following solution and it worked fine.

    extension String {
    
    /// Property tells whether a string is a valid IP or not
    var isValidIpAddress: Bool {
        let validIpAddressRegex = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"
        let predicate = NSPredicate.init(format: "SELF MATCHES %@", validIpAddressRegex)
        let matches = predicate.evaluate(with: self)
        return matches
    }
}

Usage:

string.isValidIpAddress