aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/ui/syntax/highlighter.go
diff options
context:
space:
mode:
Diffstat (limited to 'ui/syntax/highlighter.go')
-rw-r--r--ui/syntax/highlighter.go631
1 files changed, 631 insertions, 0 deletions
diff --git a/ui/syntax/highlighter.go b/ui/syntax/highlighter.go
new file mode 100644
index 00000000..099a23b0
--- /dev/null
+++ b/ui/syntax/highlighter.go
@@ -0,0 +1,631 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
+ *
+ * This is a direct translation of the original C, and for that reason, it's pretty unusual Go code:
+ * https://git.zx2c4.com/wireguard-tools/tree/contrib/highlighter/highlighter.c
+ */
+
+package syntax
+
+import "unsafe"
+
+type highlight int
+
+const (
+ highlightSection highlight = iota
+ highlightField
+ highlightPrivateKey
+ highlightPublicKey
+ highlightPresharedKey
+ highlightIP
+ highlightCidr
+ highlightHost
+ highlightPort
+ highlightMTU
+ highlightKeepalive
+ highlightComment
+ highlightDelimiter
+ highlightTable
+ highlightCmd
+ highlightError
+)
+
+func validateHighlight(isValid bool, t highlight) highlight {
+ if isValid {
+ return t
+ }
+ return highlightError
+}
+
+type highlightSpan struct {
+ t highlight
+ s int
+ len int
+}
+
+func isDecimal(c byte) bool {
+ return c >= '0' && c <= '9'
+}
+
+func isHexadecimal(c byte) bool {
+ return isDecimal(c) || (c|32) >= 'a' && (c|32) <= 'f'
+}
+
+func isAlphabet(c byte) bool {
+ return (c|32) >= 'a' && (c|32) <= 'z'
+}
+
+type stringSpan struct {
+ s *byte
+ len int
+}
+
+func (s stringSpan) at(i int) *byte {
+ return (*byte)(unsafe.Add(unsafe.Pointer(s.s), uintptr(i)))
+}
+
+func (s stringSpan) isSame(c string) bool {
+ if s.len != len(c) {
+ return false
+ }
+ cb := ([]byte)(c)
+ for i := 0; i < s.len; i++ {
+ if *s.at(i) != cb[i] {
+ return false
+ }
+ }
+ return true
+}
+
+func (s stringSpan) isCaselessSame(c string) bool {
+ if s.len != len(c) {
+ return false
+ }
+ cb := ([]byte)(c)
+ for i := 0; i < s.len; i++ {
+ a := *s.at(i)
+ b := cb[i]
+ if a-'a' < 26 {
+ a &= 95
+ }
+ if b-'a' < 26 {
+ b &= 95
+ }
+ if a != b {
+ return false
+ }
+ }
+ return true
+}
+
+func (s stringSpan) isValidKey() bool {
+ if s.len != 44 || *s.at(43) != '=' {
+ return false
+ }
+ for i := 0; i < 42; i++ {
+ if !isDecimal(*s.at(i)) && !isAlphabet(*s.at(i)) && *s.at(i) != '/' && *s.at(i) != '+' {
+ return false
+ }
+ }
+ switch *s.at(42) {
+ case 'A', 'E', 'I', 'M', 'Q', 'U', 'Y', 'c', 'g', 'k', 'o', 's', 'w', '4', '8', '0':
+ return true
+ }
+ return false
+}
+
+func (s stringSpan) isValidHostname() bool {
+ numDigit := 0
+ numEntity := s.len
+ if s.len > 63 || s.len == 0 {
+ return false
+ }
+ if *s.s == '-' || *s.at(s.len - 1) == '-' {
+ return false
+ }
+ if *s.s == '.' || *s.at(s.len - 1) == '.' {
+ return false
+ }
+ for i := 0; i < s.len; i++ {
+ if isDecimal(*s.at(i)) {
+ numDigit++
+ continue
+ }
+ if *s.at(i) == '.' {
+ numEntity--
+ continue
+ }
+ if !isAlphabet(*s.at(i)) && *s.at(i) != '-' {
+ return false
+ }
+ if i != 0 && *s.at(i) == '.' && *s.at(i - 1) == '.' {
+ return false
+ }
+ }
+ return numDigit != numEntity
+}
+
+func (s stringSpan) isValidIPv4() bool {
+ pos := 0
+ for i := 0; i < 4 && pos < s.len; i++ {
+ val := 0
+ j := 0
+ for ; j < 3 && pos+j < s.len && isDecimal(*s.at(pos + j)); j++ {
+ val = 10*val + int(*s.at(pos + j)-'0')
+ }
+ if j == 0 || j > 1 && *s.at(pos) == '0' || val > 255 {
+ return false
+ }
+ if pos+j == s.len && i == 3 {
+ return true
+ }
+ if *s.at(pos + j) != '.' {
+ return false
+ }
+ pos += j + 1
+ }
+ return false
+}
+
+func (s stringSpan) isValidIPv6() bool {
+ if s.len < 2 {
+ return false
+ }
+ pos := 0
+ if *s.at(0) == ':' {
+ if *s.at(1) != ':' {
+ return false
+ }
+ pos = 1
+ }
+ if *s.at(s.len - 1) == ':' && *s.at(s.len - 2) != ':' {
+ return false
+ }
+ seenColon := false
+ for i := 0; pos < s.len; i++ {
+ if *s.at(pos) == ':' && !seenColon {
+ seenColon = true
+ pos++
+ if pos == s.len {
+ break
+ }
+ if i == 7 {
+ return false
+ }
+ continue
+ }
+ j := 0
+ for ; ; j++ {
+ if j < 4 && pos+j < s.len && isHexadecimal(*s.at(pos + j)) {
+ continue
+ }
+ break
+ }
+ if j == 0 {
+ return false
+ }
+ if pos+j == s.len && (seenColon || i == 7) {
+ break
+ }
+ if i == 7 {
+ return false
+ }
+ if *s.at(pos + j) != ':' {
+ if *s.at(pos + j) != '.' || i < 6 && !seenColon {
+ return false
+ }
+ return stringSpan{s.at(pos), s.len - pos}.isValidIPv4()
+ }
+ pos += j + 1
+ }
+ return true
+}
+
+func (s stringSpan) isValidUint(supportHex bool, min, max uint64) bool {
+ // Bound this around 32 bits, so that we don't have to write overflow logic.
+ if s.len > 10 || s.len == 0 {
+ return false
+ }
+ val := uint64(0)
+ if supportHex && s.len > 2 && *s.s == '0' && *s.at(1) == 'x' {
+ for i := 2; i < s.len; i++ {
+ if *s.at(i)-'0' < 10 {
+ val = 16*val + uint64(*s.at(i)-'0')
+ } else if (*s.at(i))|32-'a' < 6 {
+ val = 16*val + uint64((*s.at(i)|32)-'a'+10)
+ } else {
+ return false
+ }
+ }
+ } else {
+ for i := 0; i < s.len; i++ {
+ if !isDecimal(*s.at(i)) {
+ return false
+ }
+ val = 10*val + uint64(*s.at(i)-'0')
+ }
+ }
+ return val <= max && val >= min
+}
+
+func (s stringSpan) isValidPort() bool {
+ return s.isValidUint(false, 0, 65535)
+}
+
+func (s stringSpan) isValidMTU() bool {
+ return s.isValidUint(false, 576, 65535)
+}
+
+func (s stringSpan) isValidTable() bool {
+ return s.isSame("off") || s.isSame("auto") || s.isSame("main") || s.isValidUint(false, 0, (1<<32)-1)
+}
+
+func (s stringSpan) isValidPersistentKeepAlive() bool {
+ if s.isSame("off") {
+ return true
+ }
+ return s.isValidUint(false, 0, 65535)
+}
+
+// It's probably not worthwhile to try to validate a bash expression. So instead we just demand non-zero length.
+func (s stringSpan) isValidPrePostUpDown() bool {
+ return s.len != 0
+}
+
+func (s stringSpan) isValidScope() bool {
+ if s.len > 64 || s.len == 0 {
+ return false
+ }
+ for i := 0; i < s.len; i++ {
+ if isAlphabet(*s.at(i)) && !isDecimal(*s.at(i)) && *s.at(i) != '_' && *s.at(i) != '=' && *s.at(i) != '+' && *s.at(i) != '.' && *s.at(i) != '-' {
+ return false
+ }
+ }
+ return true
+}
+
+func (s stringSpan) isValidEndpoint() bool {
+ if s.len == 0 {
+ return false
+ }
+ if *s.s == '[' {
+ seenScope := false
+ hostspan := stringSpan{s.at(1), 0}
+ for i := 1; i < s.len; i++ {
+ if *s.at(i) == '%' {
+ if seenScope {
+ return false
+ }
+ seenScope = true
+ if !hostspan.isValidIPv6() {
+ return false
+ }
+ hostspan = stringSpan{s.at(i + 1), 0}
+ } else if *s.at(i) == ']' {
+ if seenScope {
+ if !hostspan.isValidScope() {
+ return false
+ }
+ } else if !hostspan.isValidIPv6() {
+ return false
+ }
+ if i == s.len-1 || *s.at((i + 1)) != ':' {
+ return false
+ }
+ return stringSpan{s.at(i + 2), s.len - i - 2}.isValidPort()
+ } else {
+ hostspan.len++
+ }
+ }
+ return false
+ }
+ for i := 0; i < s.len; i++ {
+ if *s.at(i) == ':' {
+ host := stringSpan{s.s, i}
+ port := stringSpan{s.at(i + 1), s.len - i - 1}
+ return port.isValidPort() && (host.isValidIPv4() || host.isValidHostname())
+ }
+ }
+ return false
+}
+
+func (s stringSpan) isValidNetwork() bool {
+ for i := 0; i < s.len; i++ {
+ if *s.at(i) == '/' {
+ ip := stringSpan{s.s, i}
+ cidr := stringSpan{s.at(i + 1), s.len - i - 1}
+ cidrval := uint16(0)
+ if cidr.len > 3 || cidr.len == 0 {
+ return false
+ }
+ for j := 0; j < cidr.len; j++ {
+ if !isDecimal(*cidr.at(j)) {
+ return false
+ }
+ cidrval = 10*cidrval + uint16(*cidr.at(j)-'0')
+ }
+ if ip.isValidIPv4() {
+ return cidrval <= 32
+ } else if ip.isValidIPv6() {
+ return cidrval <= 128
+ }
+ return false
+ }
+ }
+ return s.isValidIPv4() || s.isValidIPv6()
+}
+
+type field int32
+
+const (
+ fieldInterfaceSection field = iota
+ fieldPrivateKey
+ fieldListenPort
+ fieldAddress
+ fieldDNS
+ fieldMTU
+ fieldTable
+ fieldPreUp
+ fieldPostUp
+ fieldPreDown
+ fieldPostDown
+ fieldPeerSection
+ fieldPublicKey
+ fieldPresharedKey
+ fieldAllowedIPs
+ fieldEndpoint
+ fieldPersistentKeepalive
+ fieldInvalid
+)
+
+func sectionForField(t field) field {
+ if t > fieldInterfaceSection && t < fieldPeerSection {
+ return fieldInterfaceSection
+ }
+ if t > fieldPeerSection && t < fieldInvalid {
+ return fieldPeerSection
+ }
+ return fieldInvalid
+}
+
+func (s stringSpan) field() field {
+ switch {
+ case s.isCaselessSame("PrivateKey"):
+ return fieldPrivateKey
+ case s.isCaselessSame("ListenPort"):
+ return fieldListenPort
+ case s.isCaselessSame("Address"):
+ return fieldAddress
+ case s.isCaselessSame("DNS"):
+ return fieldDNS
+ case s.isCaselessSame("MTU"):
+ return fieldMTU
+ case s.isCaselessSame("Table"):
+ return fieldTable
+ case s.isCaselessSame("PublicKey"):
+ return fieldPublicKey
+ case s.isCaselessSame("PresharedKey"):
+ return fieldPresharedKey
+ case s.isCaselessSame("AllowedIPs"):
+ return fieldAllowedIPs
+ case s.isCaselessSame("Endpoint"):
+ return fieldEndpoint
+ case s.isCaselessSame("PersistentKeepalive"):
+ return fieldPersistentKeepalive
+ case s.isCaselessSame("PreUp"):
+ return fieldPreUp
+ case s.isCaselessSame("PostUp"):
+ return fieldPostUp
+ case s.isCaselessSame("PreDown"):
+ return fieldPreDown
+ case s.isCaselessSame("PostDown"):
+ return fieldPostDown
+ }
+ return fieldInvalid
+}
+
+func (s stringSpan) sectionType() field {
+ switch {
+ case s.isCaselessSame("[Peer]"):
+ return fieldPeerSection
+ case s.isCaselessSame("[Interface]"):
+ return fieldInterfaceSection
+ }
+ return fieldInvalid
+}
+
+type highlightSpanArray []highlightSpan
+
+func (hsa *highlightSpanArray) append(o *byte, s stringSpan, t highlight) {
+ if s.len == 0 {
+ return
+ }
+ *hsa = append(*hsa, highlightSpan{t, int((uintptr(unsafe.Pointer(s.s))) - (uintptr(unsafe.Pointer(o)))), s.len})
+}
+
+func (hsa *highlightSpanArray) highlightMultivalueValue(parent, s stringSpan, section field) {
+ switch section {
+ case fieldDNS:
+ if s.isValidIPv4() || s.isValidIPv6() {
+ hsa.append(parent.s, s, highlightIP)
+ } else if s.isValidHostname() {
+ hsa.append(parent.s, s, highlightHost)
+ } else {
+ hsa.append(parent.s, s, highlightError)
+ }
+ case fieldAddress, fieldAllowedIPs:
+ if !s.isValidNetwork() {
+ hsa.append(parent.s, s, highlightError)
+ break
+ }
+ slash := 0
+ for ; slash < s.len; slash++ {
+ if *s.at(slash) == '/' {
+ break
+ }
+ }
+ if slash == s.len {
+ hsa.append(parent.s, s, highlightIP)
+ } else {
+ hsa.append(parent.s, stringSpan{s.s, slash}, highlightIP)
+ hsa.append(parent.s, stringSpan{s.at(slash), 1}, highlightDelimiter)
+ hsa.append(parent.s, stringSpan{s.at(slash + 1), s.len - slash - 1}, highlightCidr)
+ }
+ default:
+ hsa.append(parent.s, s, highlightError)
+ }
+}
+
+func (hsa *highlightSpanArray) highlightMultivalue(parent, s stringSpan, section field) {
+ currentSpan := stringSpan{s.s, 0}
+ lenAtLastSpace := 0
+ for i := 0; i < s.len; i++ {
+ if *s.at(i) == ',' {
+ currentSpan.len = lenAtLastSpace
+ hsa.highlightMultivalueValue(parent, currentSpan, section)
+ hsa.append(parent.s, stringSpan{s.at(i), 1}, highlightDelimiter)
+ lenAtLastSpace = 0
+ currentSpan = stringSpan{s.at(i + 1), 0}
+ } else if *s.at(i) == ' ' || *s.at(i) == '\t' {
+ if s.at(i) == currentSpan.s && currentSpan.len == 0 {
+ currentSpan.s = currentSpan.at(1)
+ } else {
+ currentSpan.len++
+ }
+ } else {
+ currentSpan.len++
+ lenAtLastSpace = currentSpan.len
+ }
+ }
+ currentSpan.len = lenAtLastSpace
+ if currentSpan.len != 0 {
+ hsa.highlightMultivalueValue(parent, currentSpan, section)
+ } else if (*hsa)[len(*hsa)-1].t == highlightDelimiter {
+ (*hsa)[len(*hsa)-1].t = highlightError
+ }
+}
+
+func (hsa *highlightSpanArray) highlightValue(parent, s stringSpan, section field) {
+ switch section {
+ case fieldPrivateKey:
+ hsa.append(parent.s, s, validateHighlight(s.isValidKey(), highlightPrivateKey))
+ case fieldPublicKey:
+ hsa.append(parent.s, s, validateHighlight(s.isValidKey(), highlightPublicKey))
+ case fieldPresharedKey:
+ hsa.append(parent.s, s, validateHighlight(s.isValidKey(), highlightPresharedKey))
+ case fieldMTU:
+ hsa.append(parent.s, s, validateHighlight(s.isValidMTU(), highlightMTU))
+ case fieldTable:
+ hsa.append(parent.s, s, validateHighlight(s.isValidTable(), highlightTable))
+ case fieldPreUp, fieldPostUp, fieldPreDown, fieldPostDown:
+ hsa.append(parent.s, s, validateHighlight(s.isValidPrePostUpDown(), highlightCmd))
+ case fieldListenPort:
+ hsa.append(parent.s, s, validateHighlight(s.isValidPort(), highlightPort))
+ case fieldPersistentKeepalive:
+ hsa.append(parent.s, s, validateHighlight(s.isValidPersistentKeepAlive(), highlightKeepalive))
+ case fieldEndpoint:
+ if !s.isValidEndpoint() {
+ hsa.append(parent.s, s, highlightError)
+ break
+ }
+ colon := s.len
+ for colon > 0 {
+ colon--
+ if *s.at(colon) == ':' {
+ break
+ }
+ }
+ hsa.append(parent.s, stringSpan{s.s, colon}, highlightHost)
+ hsa.append(parent.s, stringSpan{s.at(colon), 1}, highlightDelimiter)
+ hsa.append(parent.s, stringSpan{s.at(colon + 1), s.len - colon - 1}, highlightPort)
+ case fieldAddress, fieldDNS, fieldAllowedIPs:
+ hsa.highlightMultivalue(parent, s, section)
+ default:
+ hsa.append(parent.s, s, highlightError)
+ }
+}
+
+func highlightConfig(config string) []highlightSpan {
+ var ret highlightSpanArray
+ b := append([]byte(config), 0)
+ s := stringSpan{&b[0], len(b) - 1}
+ currentSpan := stringSpan{s.s, 0}
+ currentSection := fieldInvalid
+ currentField := fieldInvalid
+ const (
+ onNone = iota
+ onKey
+ onValue
+ onComment
+ onSection
+ )
+ state := onNone
+ lenAtLastSpace := 0
+ equalsLocation := 0
+ for i := 0; i <= s.len; i++ {
+ if i == s.len || *s.at(i) == '\n' || state != onComment && *s.at(i) == '#' {
+ if state == onKey {
+ currentSpan.len = lenAtLastSpace
+ ret.append(s.s, currentSpan, highlightError)
+ } else if state == onValue {
+ if currentSpan.len != 0 {
+ ret.append(s.s, stringSpan{s.at(equalsLocation), 1}, highlightDelimiter)
+ currentSpan.len = lenAtLastSpace
+ ret.highlightValue(s, currentSpan, currentField)
+ } else {
+ ret.append(s.s, stringSpan{s.at(equalsLocation), 1}, highlightError)
+ }
+ } else if state == onSection {
+ currentSpan.len = lenAtLastSpace
+ currentSection = currentSpan.sectionType()
+ ret.append(s.s, currentSpan, validateHighlight(currentSection != fieldInvalid, highlightSection))
+ } else if state == onComment {
+ ret.append(s.s, currentSpan, highlightComment)
+ }
+ if i == s.len {
+ break
+ }
+ lenAtLastSpace = 0
+ currentField = fieldInvalid
+ if *s.at(i) == '#' {
+ currentSpan = stringSpan{s.at(i), 1}
+ state = onComment
+ } else {
+ currentSpan = stringSpan{s.at(i + 1), 0}
+ state = onNone
+ }
+ } else if state == onComment {
+ currentSpan.len++
+ } else if *s.at(i) == ' ' || *s.at(i) == '\t' {
+ if s.at(i) == currentSpan.s && currentSpan.len == 0 {
+ currentSpan.s = currentSpan.at(1)
+ } else {
+ currentSpan.len++
+ }
+ } else if *s.at(i) == '=' && state == onKey {
+ currentSpan.len = lenAtLastSpace
+ currentField = currentSpan.field()
+ section := sectionForField(currentField)
+ if section == fieldInvalid || currentField == fieldInvalid || section != currentSection {
+ ret.append(s.s, currentSpan, highlightError)
+ } else {
+ ret.append(s.s, currentSpan, highlightField)
+ }
+ equalsLocation = i
+ currentSpan = stringSpan{s.at(i + 1), 0}
+ state = onValue
+ } else {
+ if state == onNone {
+ if *s.at(i) == '[' {
+ state = onSection
+ } else {
+ state = onKey
+ }
+ }
+ currentSpan.len++
+ lenAtLastSpace = currentSpan.len
+ }
+ }
+ return ([]highlightSpan)(ret)
+}