@@ -63,14 +63,26 @@ export function ServerForm({ onSubmit, initialData, isEditing = false, onCancel
6363 return true ;
6464 }
6565
66+ // Check for IPv6 with zone identifier (link-local addresses like fe80::...%eth0)
67+ let ipv6Address = trimmed ;
68+ const zoneIdMatch = trimmed . match ( / ^ ( .+ ) % ( [ a - z A - Z 0 - 9 _ \- ] + ) $ / ) ;
69+ if ( zoneIdMatch ) {
70+ ipv6Address = zoneIdMatch [ 1 ] ;
71+ // Zone identifier should be a valid interface name (alphanumeric, underscore, hyphen)
72+ const zoneId = zoneIdMatch [ 2 ] ;
73+ if ( ! / ^ [ a - z A - Z 0 - 9 _ \- ] + $ / . test ( zoneId ) ) {
74+ return false ;
75+ }
76+ }
77+
6678 // IPv6 validation (supports compressed format like ::1 and full format)
6779 // Matches: 2001:0db8:85a3:0000:0000:8a2e:0370:7334, ::1, 2001:db8::1, etc.
6880 // Also supports IPv4-mapped IPv6 addresses like ::ffff:192.168.1.1
6981 // Simplified validation: check for valid hex segments separated by colons
7082 const ipv6Pattern = / ^ (?: [ 0 - 9 a - f A - F ] { 1 , 4 } : ) { 7 } [ 0 - 9 a - f A - F ] { 1 , 4 } $ | ^ : : 1 $ | ^ : : $ | ^ (?: [ 0 - 9 a - f A - F ] { 1 , 4 } : ) * : : (?: [ 0 - 9 a - f A - F ] { 1 , 4 } : ) * [ 0 - 9 a - f A - F ] { 1 , 4 } $ | ^ (?: [ 0 - 9 a - f A - F ] { 1 , 4 } : ) * : : [ 0 - 9 a - f A - F ] { 1 , 4 } $ | ^ : : (?: [ 0 - 9 a - f A - F ] { 1 , 4 } : ) + [ 0 - 9 a - f A - F ] { 1 , 4 } $ | ^ : : f f f f : (?: (?: 2 5 [ 0 - 5 ] | 2 [ 0 - 4 ] [ 0 - 9 ] | [ 0 1 ] ? [ 0 - 9 ] [ 0 - 9 ] ? ) \. ) { 3 } (?: 2 5 [ 0 - 5 ] | 2 [ 0 - 4 ] [ 0 - 9 ] | [ 0 1 ] ? [ 0 - 9 ] [ 0 - 9 ] ? ) $ | ^ (?: [ 0 - 9 a - f A - F ] { 1 , 4 } : ) { 1 , 4 } : (?: (?: 2 5 [ 0 - 5 ] | 2 [ 0 - 4 ] [ 0 - 9 ] | [ 0 1 ] ? [ 0 - 9 ] [ 0 - 9 ] ? ) \. ) { 3 } (?: 2 5 [ 0 - 5 ] | 2 [ 0 - 4 ] [ 0 - 9 ] | [ 0 1 ] ? [ 0 - 9 ] [ 0 - 9 ] ? ) $ / ;
71- if ( ipv6Pattern . test ( trimmed ) ) {
83+ if ( ipv6Pattern . test ( ipv6Address ) ) {
7284 // Additional validation: ensure only one :: compression exists
73- const compressionCount = ( trimmed . match ( / : : / g) || [ ] ) . length ;
85+ const compressionCount = ( ipv6Address . match ( / : : / g) || [ ] ) . length ;
7486 if ( compressionCount <= 1 ) {
7587 return true ;
7688 }
@@ -273,7 +285,7 @@ export function ServerForm({ onSubmit, initialData, isEditing = false, onCancel
273285 className = { `w-full px-3 py-2 border rounded-md shadow-sm bg-card text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring ${
274286 errors . ip ? 'border-destructive' : 'border-border'
275287 } `}
276- placeholder = "e.g., 192.168.1.100, server.example.com, or 2001:db8::1"
288+ placeholder = "e.g., 192.168.1.100, server.example.com, 2001:db8::1, or fe80::...%eth0 "
277289 />
278290 { errors . ip && < p className = "mt-1 text-sm text-destructive" > { errors . ip } </ p > }
279291 </ div >
0 commit comments