golang.zx2c4.com/wireguard/windows@v0.5.4-0.20230123132234-dcc0eb72a04b/ui/syntax/highlighter.go (about)

     1  /* SPDX-License-Identifier: MIT
     2   *
     3   * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
     4   *
     5   * This is a direct translation of the original C, and for that reason, it's pretty unusual Go code:
     6   * https://git.zx2c4.com/wireguard-tools/tree/contrib/highlighter/highlighter.c
     7   */
     8  
     9  package syntax
    10  
    11  import "unsafe"
    12  
    13  type highlight int
    14  
    15  const (
    16  	highlightSection highlight = iota
    17  	highlightField
    18  	highlightPrivateKey
    19  	highlightPublicKey
    20  	highlightPresharedKey
    21  	highlightIP
    22  	highlightCidr
    23  	highlightHost
    24  	highlightPort
    25  	highlightMTU
    26  	highlightKeepalive
    27  	highlightComment
    28  	highlightDelimiter
    29  	highlightTable
    30  	highlightCmd
    31  	highlightError
    32  )
    33  
    34  func validateHighlight(isValid bool, t highlight) highlight {
    35  	if isValid {
    36  		return t
    37  	}
    38  	return highlightError
    39  }
    40  
    41  type highlightSpan struct {
    42  	t   highlight
    43  	s   int
    44  	len int
    45  }
    46  
    47  func isDecimal(c byte) bool {
    48  	return c >= '0' && c <= '9'
    49  }
    50  
    51  func isHexadecimal(c byte) bool {
    52  	return isDecimal(c) || (c|32) >= 'a' && (c|32) <= 'f'
    53  }
    54  
    55  func isAlphabet(c byte) bool {
    56  	return (c|32) >= 'a' && (c|32) <= 'z'
    57  }
    58  
    59  type stringSpan struct {
    60  	s   *byte
    61  	len int
    62  }
    63  
    64  func (s stringSpan) at(i int) *byte {
    65  	return (*byte)(unsafe.Add(unsafe.Pointer(s.s), uintptr(i)))
    66  }
    67  
    68  func (s stringSpan) isSame(c string) bool {
    69  	if s.len != len(c) {
    70  		return false
    71  	}
    72  	cb := ([]byte)(c)
    73  	for i := 0; i < s.len; i++ {
    74  		if *s.at(i) != cb[i] {
    75  			return false
    76  		}
    77  	}
    78  	return true
    79  }
    80  
    81  func (s stringSpan) isCaselessSame(c string) bool {
    82  	if s.len != len(c) {
    83  		return false
    84  	}
    85  	cb := ([]byte)(c)
    86  	for i := 0; i < s.len; i++ {
    87  		a := *s.at(i)
    88  		b := cb[i]
    89  		if a-'a' < 26 {
    90  			a &= 95
    91  		}
    92  		if b-'a' < 26 {
    93  			b &= 95
    94  		}
    95  		if a != b {
    96  			return false
    97  		}
    98  	}
    99  	return true
   100  }
   101  
   102  func (s stringSpan) isValidKey() bool {
   103  	if s.len != 44 || *s.at(43) != '=' {
   104  		return false
   105  	}
   106  	for i := 0; i < 42; i++ {
   107  		if !isDecimal(*s.at(i)) && !isAlphabet(*s.at(i)) && *s.at(i) != '/' && *s.at(i) != '+' {
   108  			return false
   109  		}
   110  	}
   111  	switch *s.at(42) {
   112  	case 'A', 'E', 'I', 'M', 'Q', 'U', 'Y', 'c', 'g', 'k', 'o', 's', 'w', '4', '8', '0':
   113  		return true
   114  	}
   115  	return false
   116  }
   117  
   118  func (s stringSpan) isValidHostname() bool {
   119  	numDigit := 0
   120  	numEntity := s.len
   121  	if s.len > 63 || s.len == 0 {
   122  		return false
   123  	}
   124  	if *s.s == '-' || *s.at(s.len - 1) == '-' {
   125  		return false
   126  	}
   127  	if *s.s == '.' || *s.at(s.len - 1) == '.' {
   128  		return false
   129  	}
   130  	for i := 0; i < s.len; i++ {
   131  		if isDecimal(*s.at(i)) {
   132  			numDigit++
   133  			continue
   134  		}
   135  		if *s.at(i) == '.' {
   136  			numEntity--
   137  			continue
   138  		}
   139  		if !isAlphabet(*s.at(i)) && *s.at(i) != '-' {
   140  			return false
   141  		}
   142  		if i != 0 && *s.at(i) == '.' && *s.at(i - 1) == '.' {
   143  			return false
   144  		}
   145  	}
   146  	return numDigit != numEntity
   147  }
   148  
   149  func (s stringSpan) isValidIPv4() bool {
   150  	pos := 0
   151  	for i := 0; i < 4 && pos < s.len; i++ {
   152  		val := 0
   153  		j := 0
   154  		for ; j < 3 && pos+j < s.len && isDecimal(*s.at(pos + j)); j++ {
   155  			val = 10*val + int(*s.at(pos + j)-'0')
   156  		}
   157  		if j == 0 || j > 1 && *s.at(pos) == '0' || val > 255 {
   158  			return false
   159  		}
   160  		if pos+j == s.len && i == 3 {
   161  			return true
   162  		}
   163  		if *s.at(pos + j) != '.' {
   164  			return false
   165  		}
   166  		pos += j + 1
   167  	}
   168  	return false
   169  }
   170  
   171  func (s stringSpan) isValidIPv6() bool {
   172  	if s.len < 2 {
   173  		return false
   174  	}
   175  	pos := 0
   176  	if *s.at(0) == ':' {
   177  		if *s.at(1) != ':' {
   178  			return false
   179  		}
   180  		pos = 1
   181  	}
   182  	if *s.at(s.len - 1) == ':' && *s.at(s.len - 2) != ':' {
   183  		return false
   184  	}
   185  	seenColon := false
   186  	for i := 0; pos < s.len; i++ {
   187  		if *s.at(pos) == ':' && !seenColon {
   188  			seenColon = true
   189  			pos++
   190  			if pos == s.len {
   191  				break
   192  			}
   193  			if i == 7 {
   194  				return false
   195  			}
   196  			continue
   197  		}
   198  		j := 0
   199  		for ; ; j++ {
   200  			if j < 4 && pos+j < s.len && isHexadecimal(*s.at(pos + j)) {
   201  				continue
   202  			}
   203  			break
   204  		}
   205  		if j == 0 {
   206  			return false
   207  		}
   208  		if pos+j == s.len && (seenColon || i == 7) {
   209  			break
   210  		}
   211  		if i == 7 {
   212  			return false
   213  		}
   214  		if *s.at(pos + j) != ':' {
   215  			if *s.at(pos + j) != '.' || i < 6 && !seenColon {
   216  				return false
   217  			}
   218  			return stringSpan{s.at(pos), s.len - pos}.isValidIPv4()
   219  		}
   220  		pos += j + 1
   221  	}
   222  	return true
   223  }
   224  
   225  func (s stringSpan) isValidUint(supportHex bool, min, max uint64) bool {
   226  	// Bound this around 32 bits, so that we don't have to write overflow logic.
   227  	if s.len > 10 || s.len == 0 {
   228  		return false
   229  	}
   230  	val := uint64(0)
   231  	if supportHex && s.len > 2 && *s.s == '0' && *s.at(1) == 'x' {
   232  		for i := 2; i < s.len; i++ {
   233  			if *s.at(i)-'0' < 10 {
   234  				val = 16*val + uint64(*s.at(i)-'0')
   235  			} else if (*s.at(i))|32-'a' < 6 {
   236  				val = 16*val + uint64((*s.at(i)|32)-'a'+10)
   237  			} else {
   238  				return false
   239  			}
   240  		}
   241  	} else {
   242  		for i := 0; i < s.len; i++ {
   243  			if !isDecimal(*s.at(i)) {
   244  				return false
   245  			}
   246  			val = 10*val + uint64(*s.at(i)-'0')
   247  		}
   248  	}
   249  	return val <= max && val >= min
   250  }
   251  
   252  func (s stringSpan) isValidPort() bool {
   253  	return s.isValidUint(false, 0, 65535)
   254  }
   255  
   256  func (s stringSpan) isValidMTU() bool {
   257  	return s.isValidUint(false, 576, 65535)
   258  }
   259  
   260  func (s stringSpan) isValidTable() bool {
   261  	return s.isSame("off") || s.isSame("auto") || s.isSame("main") || s.isValidUint(false, 0, (1<<32)-1)
   262  }
   263  
   264  func (s stringSpan) isValidPersistentKeepAlive() bool {
   265  	if s.isSame("off") {
   266  		return true
   267  	}
   268  	return s.isValidUint(false, 0, 65535)
   269  }
   270  
   271  // It's probably not worthwhile to try to validate a bash expression. So instead we just demand non-zero length.
   272  func (s stringSpan) isValidPrePostUpDown() bool {
   273  	return s.len != 0
   274  }
   275  
   276  func (s stringSpan) isValidScope() bool {
   277  	if s.len > 64 || s.len == 0 {
   278  		return false
   279  	}
   280  	for i := 0; i < s.len; i++ {
   281  		if isAlphabet(*s.at(i)) && !isDecimal(*s.at(i)) && *s.at(i) != '_' && *s.at(i) != '=' && *s.at(i) != '+' && *s.at(i) != '.' && *s.at(i) != '-' {
   282  			return false
   283  		}
   284  	}
   285  	return true
   286  }
   287  
   288  func (s stringSpan) isValidEndpoint() bool {
   289  	if s.len == 0 {
   290  		return false
   291  	}
   292  	if *s.s == '[' {
   293  		seenScope := false
   294  		hostspan := stringSpan{s.at(1), 0}
   295  		for i := 1; i < s.len; i++ {
   296  			if *s.at(i) == '%' {
   297  				if seenScope {
   298  					return false
   299  				}
   300  				seenScope = true
   301  				if !hostspan.isValidIPv6() {
   302  					return false
   303  				}
   304  				hostspan = stringSpan{s.at(i + 1), 0}
   305  			} else if *s.at(i) == ']' {
   306  				if seenScope {
   307  					if !hostspan.isValidScope() {
   308  						return false
   309  					}
   310  				} else if !hostspan.isValidIPv6() {
   311  					return false
   312  				}
   313  				if i == s.len-1 || *s.at((i + 1)) != ':' {
   314  					return false
   315  				}
   316  				return stringSpan{s.at(i + 2), s.len - i - 2}.isValidPort()
   317  			} else {
   318  				hostspan.len++
   319  			}
   320  		}
   321  		return false
   322  	}
   323  	for i := 0; i < s.len; i++ {
   324  		if *s.at(i) == ':' {
   325  			host := stringSpan{s.s, i}
   326  			port := stringSpan{s.at(i + 1), s.len - i - 1}
   327  			return port.isValidPort() && (host.isValidIPv4() || host.isValidHostname())
   328  		}
   329  	}
   330  	return false
   331  }
   332  
   333  func (s stringSpan) isValidNetwork() bool {
   334  	for i := 0; i < s.len; i++ {
   335  		if *s.at(i) == '/' {
   336  			ip := stringSpan{s.s, i}
   337  			cidr := stringSpan{s.at(i + 1), s.len - i - 1}
   338  			cidrval := uint16(0)
   339  			if cidr.len > 3 || cidr.len == 0 {
   340  				return false
   341  			}
   342  			for j := 0; j < cidr.len; j++ {
   343  				if !isDecimal(*cidr.at(j)) {
   344  					return false
   345  				}
   346  				cidrval = 10*cidrval + uint16(*cidr.at(j)-'0')
   347  			}
   348  			if ip.isValidIPv4() {
   349  				return cidrval <= 32
   350  			} else if ip.isValidIPv6() {
   351  				return cidrval <= 128
   352  			}
   353  			return false
   354  		}
   355  	}
   356  	return s.isValidIPv4() || s.isValidIPv6()
   357  }
   358  
   359  type field int32
   360  
   361  const (
   362  	fieldInterfaceSection field = iota
   363  	fieldPrivateKey
   364  	fieldListenPort
   365  	fieldAddress
   366  	fieldDNS
   367  	fieldMTU
   368  	fieldTable
   369  	fieldPreUp
   370  	fieldPostUp
   371  	fieldPreDown
   372  	fieldPostDown
   373  	fieldPeerSection
   374  	fieldPublicKey
   375  	fieldPresharedKey
   376  	fieldAllowedIPs
   377  	fieldEndpoint
   378  	fieldPersistentKeepalive
   379  	fieldInvalid
   380  )
   381  
   382  func sectionForField(t field) field {
   383  	if t > fieldInterfaceSection && t < fieldPeerSection {
   384  		return fieldInterfaceSection
   385  	}
   386  	if t > fieldPeerSection && t < fieldInvalid {
   387  		return fieldPeerSection
   388  	}
   389  	return fieldInvalid
   390  }
   391  
   392  func (s stringSpan) field() field {
   393  	switch {
   394  	case s.isCaselessSame("PrivateKey"):
   395  		return fieldPrivateKey
   396  	case s.isCaselessSame("ListenPort"):
   397  		return fieldListenPort
   398  	case s.isCaselessSame("Address"):
   399  		return fieldAddress
   400  	case s.isCaselessSame("DNS"):
   401  		return fieldDNS
   402  	case s.isCaselessSame("MTU"):
   403  		return fieldMTU
   404  	case s.isCaselessSame("Table"):
   405  		return fieldTable
   406  	case s.isCaselessSame("PublicKey"):
   407  		return fieldPublicKey
   408  	case s.isCaselessSame("PresharedKey"):
   409  		return fieldPresharedKey
   410  	case s.isCaselessSame("AllowedIPs"):
   411  		return fieldAllowedIPs
   412  	case s.isCaselessSame("Endpoint"):
   413  		return fieldEndpoint
   414  	case s.isCaselessSame("PersistentKeepalive"):
   415  		return fieldPersistentKeepalive
   416  	case s.isCaselessSame("PreUp"):
   417  		return fieldPreUp
   418  	case s.isCaselessSame("PostUp"):
   419  		return fieldPostUp
   420  	case s.isCaselessSame("PreDown"):
   421  		return fieldPreDown
   422  	case s.isCaselessSame("PostDown"):
   423  		return fieldPostDown
   424  	}
   425  	return fieldInvalid
   426  }
   427  
   428  func (s stringSpan) sectionType() field {
   429  	switch {
   430  	case s.isCaselessSame("[Peer]"):
   431  		return fieldPeerSection
   432  	case s.isCaselessSame("[Interface]"):
   433  		return fieldInterfaceSection
   434  	}
   435  	return fieldInvalid
   436  }
   437  
   438  type highlightSpanArray []highlightSpan
   439  
   440  func (hsa *highlightSpanArray) append(o *byte, s stringSpan, t highlight) {
   441  	if s.len == 0 {
   442  		return
   443  	}
   444  	*hsa = append(*hsa, highlightSpan{t, int((uintptr(unsafe.Pointer(s.s))) - (uintptr(unsafe.Pointer(o)))), s.len})
   445  }
   446  
   447  func (hsa *highlightSpanArray) highlightMultivalueValue(parent, s stringSpan, section field) {
   448  	switch section {
   449  	case fieldDNS:
   450  		if s.isValidIPv4() || s.isValidIPv6() {
   451  			hsa.append(parent.s, s, highlightIP)
   452  		} else if s.isValidHostname() {
   453  			hsa.append(parent.s, s, highlightHost)
   454  		} else {
   455  			hsa.append(parent.s, s, highlightError)
   456  		}
   457  	case fieldAddress, fieldAllowedIPs:
   458  		if !s.isValidNetwork() {
   459  			hsa.append(parent.s, s, highlightError)
   460  			break
   461  		}
   462  		slash := 0
   463  		for ; slash < s.len; slash++ {
   464  			if *s.at(slash) == '/' {
   465  				break
   466  			}
   467  		}
   468  		if slash == s.len {
   469  			hsa.append(parent.s, s, highlightIP)
   470  		} else {
   471  			hsa.append(parent.s, stringSpan{s.s, slash}, highlightIP)
   472  			hsa.append(parent.s, stringSpan{s.at(slash), 1}, highlightDelimiter)
   473  			hsa.append(parent.s, stringSpan{s.at(slash + 1), s.len - slash - 1}, highlightCidr)
   474  		}
   475  	default:
   476  		hsa.append(parent.s, s, highlightError)
   477  	}
   478  }
   479  
   480  func (hsa *highlightSpanArray) highlightMultivalue(parent, s stringSpan, section field) {
   481  	currentSpan := stringSpan{s.s, 0}
   482  	lenAtLastSpace := 0
   483  	for i := 0; i < s.len; i++ {
   484  		if *s.at(i) == ',' {
   485  			currentSpan.len = lenAtLastSpace
   486  			hsa.highlightMultivalueValue(parent, currentSpan, section)
   487  			hsa.append(parent.s, stringSpan{s.at(i), 1}, highlightDelimiter)
   488  			lenAtLastSpace = 0
   489  			currentSpan = stringSpan{s.at(i + 1), 0}
   490  		} else if *s.at(i) == ' ' || *s.at(i) == '\t' {
   491  			if s.at(i) == currentSpan.s && currentSpan.len == 0 {
   492  				currentSpan.s = currentSpan.at(1)
   493  			} else {
   494  				currentSpan.len++
   495  			}
   496  		} else {
   497  			currentSpan.len++
   498  			lenAtLastSpace = currentSpan.len
   499  		}
   500  	}
   501  	currentSpan.len = lenAtLastSpace
   502  	if currentSpan.len != 0 {
   503  		hsa.highlightMultivalueValue(parent, currentSpan, section)
   504  	} else if (*hsa)[len(*hsa)-1].t == highlightDelimiter {
   505  		(*hsa)[len(*hsa)-1].t = highlightError
   506  	}
   507  }
   508  
   509  func (hsa *highlightSpanArray) highlightValue(parent, s stringSpan, section field) {
   510  	switch section {
   511  	case fieldPrivateKey:
   512  		hsa.append(parent.s, s, validateHighlight(s.isValidKey(), highlightPrivateKey))
   513  	case fieldPublicKey:
   514  		hsa.append(parent.s, s, validateHighlight(s.isValidKey(), highlightPublicKey))
   515  	case fieldPresharedKey:
   516  		hsa.append(parent.s, s, validateHighlight(s.isValidKey(), highlightPresharedKey))
   517  	case fieldMTU:
   518  		hsa.append(parent.s, s, validateHighlight(s.isValidMTU(), highlightMTU))
   519  	case fieldTable:
   520  		hsa.append(parent.s, s, validateHighlight(s.isValidTable(), highlightTable))
   521  	case fieldPreUp, fieldPostUp, fieldPreDown, fieldPostDown:
   522  		hsa.append(parent.s, s, validateHighlight(s.isValidPrePostUpDown(), highlightCmd))
   523  	case fieldListenPort:
   524  		hsa.append(parent.s, s, validateHighlight(s.isValidPort(), highlightPort))
   525  	case fieldPersistentKeepalive:
   526  		hsa.append(parent.s, s, validateHighlight(s.isValidPersistentKeepAlive(), highlightKeepalive))
   527  	case fieldEndpoint:
   528  		if !s.isValidEndpoint() {
   529  			hsa.append(parent.s, s, highlightError)
   530  			break
   531  		}
   532  		colon := s.len
   533  		for colon > 0 {
   534  			colon--
   535  			if *s.at(colon) == ':' {
   536  				break
   537  			}
   538  		}
   539  		hsa.append(parent.s, stringSpan{s.s, colon}, highlightHost)
   540  		hsa.append(parent.s, stringSpan{s.at(colon), 1}, highlightDelimiter)
   541  		hsa.append(parent.s, stringSpan{s.at(colon + 1), s.len - colon - 1}, highlightPort)
   542  	case fieldAddress, fieldDNS, fieldAllowedIPs:
   543  		hsa.highlightMultivalue(parent, s, section)
   544  	default:
   545  		hsa.append(parent.s, s, highlightError)
   546  	}
   547  }
   548  
   549  func highlightConfig(config string) []highlightSpan {
   550  	var ret highlightSpanArray
   551  	b := append([]byte(config), 0)
   552  	s := stringSpan{&b[0], len(b) - 1}
   553  	currentSpan := stringSpan{s.s, 0}
   554  	currentSection := fieldInvalid
   555  	currentField := fieldInvalid
   556  	const (
   557  		onNone = iota
   558  		onKey
   559  		onValue
   560  		onComment
   561  		onSection
   562  	)
   563  	state := onNone
   564  	lenAtLastSpace := 0
   565  	equalsLocation := 0
   566  	for i := 0; i <= s.len; i++ {
   567  		if i == s.len || *s.at(i) == '\n' || state != onComment && *s.at(i) == '#' {
   568  			if state == onKey {
   569  				currentSpan.len = lenAtLastSpace
   570  				ret.append(s.s, currentSpan, highlightError)
   571  			} else if state == onValue {
   572  				if currentSpan.len != 0 {
   573  					ret.append(s.s, stringSpan{s.at(equalsLocation), 1}, highlightDelimiter)
   574  					currentSpan.len = lenAtLastSpace
   575  					ret.highlightValue(s, currentSpan, currentField)
   576  				} else {
   577  					ret.append(s.s, stringSpan{s.at(equalsLocation), 1}, highlightError)
   578  				}
   579  			} else if state == onSection {
   580  				currentSpan.len = lenAtLastSpace
   581  				currentSection = currentSpan.sectionType()
   582  				ret.append(s.s, currentSpan, validateHighlight(currentSection != fieldInvalid, highlightSection))
   583  			} else if state == onComment {
   584  				ret.append(s.s, currentSpan, highlightComment)
   585  			}
   586  			if i == s.len {
   587  				break
   588  			}
   589  			lenAtLastSpace = 0
   590  			currentField = fieldInvalid
   591  			if *s.at(i) == '#' {
   592  				currentSpan = stringSpan{s.at(i), 1}
   593  				state = onComment
   594  			} else {
   595  				currentSpan = stringSpan{s.at(i + 1), 0}
   596  				state = onNone
   597  			}
   598  		} else if state == onComment {
   599  			currentSpan.len++
   600  		} else if *s.at(i) == ' ' || *s.at(i) == '\t' {
   601  			if s.at(i) == currentSpan.s && currentSpan.len == 0 {
   602  				currentSpan.s = currentSpan.at(1)
   603  			} else {
   604  				currentSpan.len++
   605  			}
   606  		} else if *s.at(i) == '=' && state == onKey {
   607  			currentSpan.len = lenAtLastSpace
   608  			currentField = currentSpan.field()
   609  			section := sectionForField(currentField)
   610  			if section == fieldInvalid || currentField == fieldInvalid || section != currentSection {
   611  				ret.append(s.s, currentSpan, highlightError)
   612  			} else {
   613  				ret.append(s.s, currentSpan, highlightField)
   614  			}
   615  			equalsLocation = i
   616  			currentSpan = stringSpan{s.at(i + 1), 0}
   617  			state = onValue
   618  		} else {
   619  			if state == onNone {
   620  				if *s.at(i) == '[' {
   621  					state = onSection
   622  				} else {
   623  					state = onKey
   624  				}
   625  			}
   626  			currentSpan.len++
   627  			lenAtLastSpace = currentSpan.len
   628  		}
   629  	}
   630  	return ([]highlightSpan)(ret)
   631  }