From 59bfa7f1df470af711187335f604422dfafca634 Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Mon, 7 Jan 2019 14:47:27 +0200 Subject: Added syntax highlighting conf textview Signed-off-by: Eric Kuck --- WireGuard/WireGuard/UI/macOS/NSColor+Hex.swift | 25 + .../WireGuard/UI/macOS/View/ConfTextStorage.swift | 174 ++++++ .../WireGuard/UI/macOS/View/ConfTextView.swift | 57 ++ .../ViewController/TunnelEditViewController.swift | 4 +- WireGuard/WireGuard/UI/macOS/highlighter.c | 588 +++++++++++++++++++++ WireGuard/WireGuard/UI/macOS/highlighter.h | 33 ++ WireGuard/WireGuard/WireGuard-Bridging-Header.h | 1 + 7 files changed, 880 insertions(+), 2 deletions(-) create mode 100644 WireGuard/WireGuard/UI/macOS/NSColor+Hex.swift create mode 100644 WireGuard/WireGuard/UI/macOS/View/ConfTextStorage.swift create mode 100644 WireGuard/WireGuard/UI/macOS/View/ConfTextView.swift create mode 100644 WireGuard/WireGuard/UI/macOS/highlighter.c create mode 100644 WireGuard/WireGuard/UI/macOS/highlighter.h (limited to 'WireGuard/WireGuard') diff --git a/WireGuard/WireGuard/UI/macOS/NSColor+Hex.swift b/WireGuard/WireGuard/UI/macOS/NSColor+Hex.swift new file mode 100644 index 0000000..abd5723 --- /dev/null +++ b/WireGuard/WireGuard/UI/macOS/NSColor+Hex.swift @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018 WireGuard LLC. All Rights Reserved. + +import AppKit + +extension NSColor { + + convenience init(hex: String) { + var hexString = hex.uppercased() + + if hexString.hasPrefix("#") { + hexString.remove(at: hexString.startIndex) + } + + if hexString.count != 6 { + fatalError("Invalid hex string \(hex)") + } + + var rgb: UInt32 = 0 + Scanner(string: hexString).scanHexInt32(&rgb) + + self.init(red: CGFloat((rgb & 0xFF0000) >> 16) / 255.0, green: CGFloat((rgb & 0x00FF00) >> 8) / 255.0, blue: CGFloat(rgb & 0x0000FF) / 255.0, alpha: 1) + } + +} diff --git a/WireGuard/WireGuard/UI/macOS/View/ConfTextStorage.swift b/WireGuard/WireGuard/UI/macOS/View/ConfTextStorage.swift new file mode 100644 index 0000000..2db3d87 --- /dev/null +++ b/WireGuard/WireGuard/UI/macOS/View/ConfTextStorage.swift @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018 WireGuard LLC. All Rights Reserved. + +import Cocoa + +class ConfTextStorage: NSTextStorage { + + struct TextColorTheme { + let black: NSColor + let red: NSColor + let green: NSColor + let yellow: NSColor + let blue: NSColor + let magenta: NSColor + let cyan: NSColor + let white: NSColor + let `default`: NSColor + } + + let defaultFont = NSFont.systemFont(ofSize: 16) + private let boldFont = NSFont.boldSystemFont(ofSize: 16) + + private var defaultAttributes: [NSAttributedString.Key: Any]! //swiftlint:disable:this implicitly_unwrapped_optional + private var highlightAttributes: [UInt32: [NSAttributedString.Key: Any]]! //swiftlint:disable:this implicitly_unwrapped_optional + + private let backingStore: NSMutableAttributedString + private(set) var hasError = false + + override init() { + backingStore = NSMutableAttributedString(string: "") + super.init() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init?(pasteboardPropertyList propertyList: Any, ofType type: NSPasteboard.PasteboardType) { + fatalError("init(pasteboardPropertyList:ofType:) has not been implemented") + } + + //swiftlint:disable:next function_body_length + func updateAttributes(for theme: TextColorTheme) { + self.defaultAttributes = [ + .foregroundColor: theme.default, + .font: defaultFont + ] + + self.highlightAttributes = [ + HighlightSection.rawValue: [ + .foregroundColor: theme.black, + .font: boldFont + ], + HighlightKeytype.rawValue: [ + .foregroundColor: theme.blue, + .font: boldFont + ], + HighlightKey.rawValue: [ + .foregroundColor: theme.yellow, + .font: boldFont + ], + HighlightCmd.rawValue: [ + .foregroundColor: theme.white, + .font: defaultFont + ], + HighlightIP.rawValue: [ + .foregroundColor: theme.green, + .font: defaultFont + ], + HighlightCidr.rawValue: [ + .foregroundColor: theme.yellow, + .font: defaultFont + ], + HighlightHost.rawValue: [ + .foregroundColor: theme.green, + .font: boldFont + ], + HighlightPort.rawValue: [ + .foregroundColor: theme.magenta, + .font: defaultFont + ], + HighlightTable.rawValue: [ + .foregroundColor: theme.blue, + .font: defaultFont + ], + HighlightFwMark.rawValue: [ + .foregroundColor: theme.blue, + .font: defaultFont + ], + HighlightMTU.rawValue: [ + .foregroundColor: theme.blue, + .font: defaultFont + ], + HighlightSaveConfig.rawValue: [ + .foregroundColor: theme.blue, + .font: defaultFont + ], + HighlightKeepalive.rawValue: [ + .foregroundColor: theme.blue, + .font: defaultFont + ], + HighlightComment.rawValue: [ + .foregroundColor: theme.cyan, + .font: defaultFont + ], + HighlightDelimiter.rawValue: [ + .foregroundColor: theme.cyan, + .font: defaultFont + ], + HighlightError.rawValue: [ + .foregroundColor: theme.red, + .font: defaultFont, + .underlineStyle: 1 + ] + ] + + highlightSyntax() + } + + override var string: String { + return backingStore.string + } + + override func attributes(at location: Int, effectiveRange range: NSRangePointer?) -> [NSAttributedString.Key: Any] { + return backingStore.attributes(at: location, effectiveRange: range) + } + + override func replaceCharacters(in range: NSRange, with str: String) { + beginEditing() + backingStore.replaceCharacters(in: range, with: str) + edited(.editedCharacters, range: range, changeInLength: str.count - range.length) + endEditing() + } + + override func replaceCharacters(in range: NSRange, with attrString: NSAttributedString) { + beginEditing() + backingStore.replaceCharacters(in: range, with: attrString) + edited(.editedCharacters, range: range, changeInLength: attrString.length - range.length) + endEditing() + } + + override func setAttributes(_ attrs: [NSAttributedString.Key: Any]?, range: NSRange) { + beginEditing() + backingStore.setAttributes(attrs, range: range) + edited(.editedAttributes, range: range, changeInLength: 0) + endEditing() + } + + func highlightSyntax() { + hasError = false + + backingStore.beginEditing() + var spans = highlight_config(backingStore.string.cString(using: String.Encoding.utf8))! + + while spans.pointee.type != HighlightEnd { + let span = spans.pointee + + let attributes = self.highlightAttributes[span.type.rawValue] ?? defaultAttributes + backingStore.setAttributes(attributes, range: NSRange(location: span.start, length: span.len)) + + if span.type == HighlightError { + hasError = true + } + + spans = spans.successor() + } + backingStore.endEditing() + + beginEditing() + edited(.editedAttributes, range: NSRange(location: 0, length: (backingStore.string as NSString).length), changeInLength: 0) + endEditing() + } + +} diff --git a/WireGuard/WireGuard/UI/macOS/View/ConfTextView.swift b/WireGuard/WireGuard/UI/macOS/View/ConfTextView.swift new file mode 100644 index 0000000..8526e6c --- /dev/null +++ b/WireGuard/WireGuard/UI/macOS/View/ConfTextView.swift @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018 WireGuard LLC. All Rights Reserved. + +import Cocoa + +class ConfTextView: NSTextView { + + private let confTextStorage = ConfTextStorage() + + var hasError: Bool { return confTextStorage.hasError } + + override var string: String { + didSet { + confTextStorage.highlightSyntax() + } + } + + init() { + let textContainer = NSTextContainer() + let layoutManager = NSLayoutManager() + layoutManager.addTextContainer(textContainer) + confTextStorage.addLayoutManager(layoutManager) + super.init(frame: CGRect(x: 0, y: 0, width: 1, height: 60), textContainer: textContainer) + font = confTextStorage.defaultFont + updateTheme() + delegate = self + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidChangeEffectiveAppearance() { + updateTheme() + } + + private func updateTheme() { + let theme: ConfTextStorage.TextColorTheme + switch effectiveAppearance.bestMatch(from: [.aqua, .darkAqua]) ?? .aqua { + case .darkAqua: + theme = ConfTextStorage.TextColorTheme(black: NSColor(hex: "#c7c7c7"), red: NSColor(hex: "#dc322f"), green: NSColor(hex: "#859900"), yellow: NSColor(hex: "#c7c400"), blue: NSColor(hex: "#268bd2"), magenta: NSColor(hex: "#d33682"), cyan: NSColor(hex: "#2aa198"), white: NSColor(hex: "#383838"), default: NSColor(hex: "#c7c7c7")) + default: + theme = ConfTextStorage.TextColorTheme(black: NSColor(hex: "#000000"), red: NSColor(hex: "#c91b00"), green: NSColor(hex: "#00c200"), yellow: NSColor(hex: "#c7c400"), blue: NSColor(hex: "#0225c7"), magenta: NSColor(hex: "#c930c7"), cyan: NSColor(hex: "#00c5c7"), white: NSColor(hex: "#c7c7c7"), default: NSColor(hex: "#000000")) + } + confTextStorage.updateAttributes(for: theme) + } + +} + +extension ConfTextView: NSTextViewDelegate { + + func textDidChange(_ notification: Notification) { + confTextStorage.highlightSyntax() + needsDisplay = true + } + +} diff --git a/WireGuard/WireGuard/UI/macOS/ViewController/TunnelEditViewController.swift b/WireGuard/WireGuard/UI/macOS/ViewController/TunnelEditViewController.swift index 52b80f6..47f488c 100644 --- a/WireGuard/WireGuard/UI/macOS/ViewController/TunnelEditViewController.swift +++ b/WireGuard/WireGuard/UI/macOS/ViewController/TunnelEditViewController.swift @@ -18,12 +18,12 @@ class TunnelEditViewController: NSViewController { }() let textView: NSTextView = { - let textView = NSTextView() + let textView = ConfTextView() let minWidth: CGFloat = 120 let minHeight: CGFloat = 60 textView.minSize = NSSize(width: 0, height: minHeight) textView.maxSize = NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) - textView.autoresizingMask = .width + textView.autoresizingMask = [.width, .height] textView.isHorizontallyResizable = true if let textContainer = textView.textContainer { textContainer.size = NSSize(width: minWidth, height: CGFloat.greatestFiniteMagnitude) diff --git a/WireGuard/WireGuard/UI/macOS/highlighter.c b/WireGuard/WireGuard/UI/macOS/highlighter.c new file mode 100644 index 0000000..6edf575 --- /dev/null +++ b/WireGuard/WireGuard/UI/macOS/highlighter.c @@ -0,0 +1,588 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2015-2019 Jason A. Donenfeld . All Rights Reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "highlighter.h" + +typedef struct { + const char *s; + size_t len; +} string_span_t; + +static bool is_valid_key(string_span_t s) +{ + if (s.len != 44 || s.s[43] != '=') + return false; + + for (size_t i = 0; i < 43; ++i) { + if (!((s.s[i] >= '/' && s.s[i] <= '9') || + (s.s[i] >= 'A' && s.s[i] <= 'Z') || + (s.s[i] >= 'a' && s.s[i] <= 'z') || + s.s[i] == '+')) + return false; + } + return true; +} + +static bool is_valid_hostname(string_span_t s) +{ + size_t num_digit = 0, num_entity = s.len; + + if (s.len > 63 || !s.len) + return false; + if (s.s[0] == '-' || s.s[s.len - 1] == '-') + return false; + if (s.s[0] == '.' || s.s[s.len - 1] == '.') + return false; + + for (size_t i = 0; i < s.len; ++i) { + if (isdigit(s.s[i])) { + ++num_digit; + continue; + } + if (s.s[i] == '.') { + --num_entity; + continue; + } + + if (!((s.s[i] >= 'A' && s.s[i] <= 'Z') || + (s.s[i] >= 'a' && s.s[i] <= 'z') || + s.s[i] == '-')) + return false; + + if (i && s.s[i] == '.' && s.s[i - 1] == '.') + return false; + } + return num_digit != num_entity; +} + +static bool is_valid_ipv4(string_span_t s) +{ + for (size_t j, i = 0, pos = 0; i < 4 && pos < s.len; ++i) { + uint32_t val = 0; + + for (j = 0; j < 3 && pos + j < s.len && isdigit(s.s[pos + j]); ++j) + val = 10 * val + s.s[pos + j] - '0'; + if (j == 0 || (j > 1 && s.s[pos] == '0') || val > 255) + return false; + if (pos + j == s.len && i == 3) + return true; + if (s.s[pos + j] != '.') + return false; + pos += j + 1; + } + return false; +} + +static bool is_valid_ipv6(string_span_t s) +{ + size_t pos = 0; + bool seen_colon = false; + + if (s.len < 2) + return false; + if (s.s[pos] == ':' && s.s[++pos] != ':') + return false; + if (s.s[s.len - 1] == ':' && s.s[s.len - 2] != ':') + return false; + + for (size_t j, i = 0; pos < s.len; ++i) { + if (s.s[pos] == ':' && !seen_colon) { + seen_colon = true; + if (++pos == s.len) + break; + if (i == 7) + return false; + continue; + } + for (j = 0; j < 4 && pos + j < s.len && isxdigit(s.s[pos + j]); ++j); + if (j == 0) + return false; + if (pos + j == s.len && (seen_colon || i == 7)) + break; + if (i == 7) + return false; + if (s.s[pos + j] != ':') { + if (s.s[pos + j] != '.' || (i < 6 && !seen_colon)) + return false; + return is_valid_ipv4((string_span_t){ s.s + pos, s.len - pos }); + } + pos += j + 1; + } + return true; +} + +static bool is_valid_u16(string_span_t s) +{ + uint32_t val = 0; + + if (s.len > 5 || !s.len) + return false; + + for (size_t i = 0; i < s.len; ++i) { + if (!isdigit(s.s[i])) + return false; + val = 10 * val + s.s[i] - '0'; + } + return val <= 65535; +} + +static bool is_valid_port(string_span_t s) +{ + return is_valid_u16(s); +} + +static bool is_valid_mtu(string_span_t s) +{ + return is_valid_u16(s); +} + +static bool is_valid_persistentkeepalive(string_span_t s) +{ + if (s.len == 3 && !memcmp(s.s, "off", 3)) + return true; + return is_valid_u16(s); +} + +static bool is_valid_u32(string_span_t s) +{ + uint64_t val = 0; + + if (s.len > 10 || !s.len) + return false; + + if (s.len > 2 && s.s[0] == '0' && s.s[1] == 'x') { + for (size_t i = 2; i < s.len; ++i) { + if (s.s[i] - '0' < 10) + val = 16 * val + (s.s[i] - '0'); + else if ((s.s[i] | 32) - 'a' < 6) + val = 16 * val + (s.s[i] | 32) - 'a' + 10; + else + return false; + } + } else { + for (size_t i = 0; i < s.len; ++i) { + if (!isdigit(s.s[i])) + return false; + val = 10 * val + s.s[i] - '0'; + } + } + return val <= 4294967295U; +} + +static bool is_valid_fwmark(string_span_t s) +{ + if (s.len == 3 && !memcmp(s.s, "off", 3)) + return true; + return is_valid_u32(s); +} + +static bool is_valid_table(string_span_t s) +{ + if (s.len == 4 && !memcmp(s.s, "auto", 3)) + return true; + if (s.len == 3 && !memcmp(s.s, "off", 3)) + return true; + /* This pretty much invalidates the other checks, but rt_names.c's + * fread_id_name does no validation aside from this. */ + if (s.len < 512) + return true; + return is_valid_u32(s); +} + +static bool is_valid_saveconfig(string_span_t s) +{ + return (s.len == 4 && !memcmp(s.s, "true", 4)) || + (s.len == 5 && !memcmp(s.s, "false", 5)); +} + +static bool is_valid_scope(string_span_t s) +{ + if (s.len > 64 || !s.len) + return false; + for (size_t i = 0; i < s.len; ++i) { + if (!((s.s[i] >= 'A' && s.s[i] <= 'Z') || + (s.s[i] >= 'a' && s.s[i] <= 'z') || + isdigit(s.s[i]) || s.s[i] == '_' || + s.s[i] == '=' || s.s[i] == '+' || + s.s[i] == '.' || s.s[i] == '-')) + return false; + } + return true; +} + +static bool is_valid_endpoint(string_span_t s) +{ + + if (!s.len) + return false; + + if (s.s[0] == '[') { + bool seen_scope = false; + string_span_t hostspan = { s.s + 1, 0 }; + + for (size_t i = 1; i < s.len; ++i) { + if (s.s[i] == '%') { + if (seen_scope) + return false; + seen_scope = true; + if (!is_valid_ipv6(hostspan)) + return false; + hostspan = (string_span_t){ s.s + i + 1, 0 }; + } else if (s.s[i] == ']') { + if (seen_scope) { + if (!is_valid_scope(hostspan)) + return false; + } else if (!is_valid_ipv6(hostspan)) { + return false; + } + if (i == s.len - 1 || s.s[i + 1] != ':') + return false; + return is_valid_port((string_span_t){ s.s + i + 2, s.len - i - 2 }); + } else { + ++hostspan.len; + } + } + return false; + } + for (size_t i = 0; i < s.len; ++i) { + if (s.s[i] == ':') { + string_span_t host = { s.s, i }, port = { s.s + i + 1, s.len - i - 1}; + return is_valid_port(port) && (is_valid_ipv4(host) || is_valid_hostname(host)); + } + } + return false; +} + +static bool is_valid_network(string_span_t s) +{ + for (size_t i = 0; i < s.len; ++i) { + if (s.s[i] == '/') { + string_span_t ip = { s.s, i }, cidr = { s.s + i + 1, s.len - i - 1}; + uint16_t cidrval = 0; + + if (cidr.len > 3 || !cidr.len) + return false; + + for (size_t j = 0; j < cidr.len; ++j) { + if (!isdigit(cidr.s[j])) + return false; + cidrval = 10 * cidrval + cidr.s[j] - '0'; + } + if (is_valid_ipv4(ip)) + return cidrval <= 32; + else if (is_valid_ipv6(ip)) + return cidrval <= 128; + return false; + } + } + return is_valid_ipv4(s) || is_valid_ipv6(s); +} + +static bool is_valid_dns(string_span_t s) +{ + return is_valid_ipv4(s) || is_valid_ipv6(s); +} + +static bool is_valid_prepostupdown(string_span_t s) +{ + /* It's probably not worthwhile to try to validate a bash expression. + * So instead we just demand non-zero length. */ + return s.len; +} + +enum keytype { + InterfaceSection, + PrivateKey, + ListenPort, + FwMark, + Address, + DNS, + MTU, + Table, + PreUp, PostUp, PreDown, PostDown, + SaveConfig, + + PeerSection, + PublicKey, + PresharedKey, + AllowedIPs, + Endpoint, + PersistentKeepalive, + + Invalid +}; + +static enum keytype section_for_keytype(enum keytype t) +{ + if (t > InterfaceSection && t < PeerSection) + return InterfaceSection; + if (t > PeerSection && t < Invalid) + return PeerSection; + return Invalid; +} + +static enum keytype get_keytype(string_span_t s) +{ +#define check_enum(t) do { if (s.len == strlen(#t) && !strncasecmp(#t, s.s, s.len)) return t; } while (0) + check_enum(PrivateKey); + check_enum(ListenPort); + check_enum(FwMark); + check_enum(Address); + check_enum(DNS); + check_enum(MTU); + check_enum(Table); + check_enum(PreUp); + check_enum(PostUp); + check_enum(PreDown); + check_enum(PostDown); + check_enum(PublicKey); + check_enum(PresharedKey); + check_enum(AllowedIPs); + check_enum(Endpoint); + check_enum(PersistentKeepalive); + check_enum(SaveConfig); + return Invalid; +#undef check_enum +} + +static enum keytype get_sectiontype(string_span_t s) +{ + if (s.len == 6 && !strncasecmp("[Peer]", s.s, 6)) + return PeerSection; + if (s.len == 11 && !strncasecmp("[Interface]", s.s, 11)) + return InterfaceSection; + return Invalid; +} + +struct highlight_span_array { + size_t len, capacity; + struct highlight_span *spans; +}; + +/* A useful OpenBSD-ism. */ +static void *realloc_array(void *optr, size_t nmemb, size_t size) +{ + if ((nmemb >= (size_t)1 << (sizeof(size_t) * 4) || + size >= (size_t)1 << (sizeof(size_t) * 4)) && + nmemb > 0 && SIZE_MAX / nmemb < size) { + errno = ENOMEM; + return NULL; + } + return realloc(optr, size * nmemb); +} + +static bool append_highlight_span(struct highlight_span_array *a, const char *o, string_span_t s, enum highlight_type t) +{ + if (!s.len) + return true; + if (a->len >= a->capacity) { + struct highlight_span *resized; + + a->capacity = a->capacity ? a->capacity * 2 : 64; + resized = realloc_array(a->spans, a->capacity, sizeof(*resized)); + if (!resized) { + free(a->spans); + memset(a, 0, sizeof(*a)); + return false; + } + a->spans = resized; + } + a->spans[a->len++] = (struct highlight_span){ t, s.s - o, s.len }; + return true; +} + +static void highlight_multivalue_value(struct highlight_span_array *ret, const string_span_t parent, const string_span_t s, enum keytype section) +{ + switch (section) { + case DNS: + append_highlight_span(ret, parent.s, s, is_valid_dns(s) ? HighlightIP : HighlightError); + break; + case Address: + case AllowedIPs: { + size_t slash; + + if (!is_valid_network(s)) { + append_highlight_span(ret, parent.s, s, HighlightError); + break; + } + for (slash = 0; slash < s.len; ++slash) { + if (s.s[slash] == '/') + break; + } + if (slash == s.len) { + append_highlight_span(ret, parent.s, s, HighlightIP); + } else { + append_highlight_span(ret, parent.s, (string_span_t){ s.s, slash }, HighlightIP); + append_highlight_span(ret, parent.s, (string_span_t){ s.s + slash, 1 }, HighlightDelimiter); + append_highlight_span(ret, parent.s, (string_span_t){ s.s + slash + 1, s.len - slash - 1 }, HighlightCidr); + } + break; + } + default: + append_highlight_span(ret, parent.s, s, HighlightError); + } +} + +static void highlight_multivalue(struct highlight_span_array *ret, const string_span_t parent, const string_span_t s, enum keytype section) +{ + string_span_t current_span = { s.s, 0 }; + size_t len_at_last_space = 0; + + for (size_t i = 0; i < s.len; ++i) { + if (s.s[i] == ',') { + current_span.len = len_at_last_space; + highlight_multivalue_value(ret, parent, current_span, section); + append_highlight_span(ret, parent.s, (string_span_t){ s.s + i, 1 }, HighlightDelimiter); + len_at_last_space = 0; + current_span = (string_span_t){ s.s + i + 1, 0 }; + } else if (s.s[i] == ' ' || s.s[i] == '\t') { + if (&s.s[i] == current_span.s && !current_span.len) + ++current_span.s; + else + ++current_span.len; + } else { + len_at_last_space = ++current_span.len; + } + } + current_span.len = len_at_last_space; + if (current_span.len) + highlight_multivalue_value(ret, parent, current_span, section); + else if (ret->spans[ret->len - 1].type == HighlightDelimiter) + ret->spans[ret->len - 1].type = HighlightError; +} + +static void highlight_value(struct highlight_span_array *ret, const string_span_t parent, const string_span_t s, enum keytype section) +{ + switch (section) { + case PrivateKey: + case PublicKey: + case PresharedKey: + append_highlight_span(ret, parent.s, s, is_valid_key(s) ? HighlightKey : HighlightError); + break; + case FwMark: + append_highlight_span(ret, parent.s, s, is_valid_fwmark(s) ? HighlightFwMark : HighlightError); + break; + case Table: + append_highlight_span(ret, parent.s, s, is_valid_table(s) ? HighlightTable : HighlightError); + break; + case MTU: + append_highlight_span(ret, parent.s, s, is_valid_mtu(s) ? HighlightMTU : HighlightError); + break; + case SaveConfig: + append_highlight_span(ret, parent.s, s, is_valid_saveconfig(s) ? HighlightSaveConfig : HighlightError); + break; + case PreUp: + case PostUp: + case PreDown: + case PostDown: + append_highlight_span(ret, parent.s, s, is_valid_prepostupdown(s) ? HighlightCmd : HighlightError); + break; + case ListenPort: + append_highlight_span(ret, parent.s, s, is_valid_port(s) ? HighlightPort : HighlightError); + break; + case PersistentKeepalive: + append_highlight_span(ret, parent.s, s, is_valid_persistentkeepalive(s) ? HighlightKeepalive : HighlightError); + break; + case Endpoint: { + size_t colon; + + if (!is_valid_endpoint(s)) { + append_highlight_span(ret, parent.s, s, HighlightError); + break; + } + for (colon = s.len; colon --> 0;) { + if (s.s[colon] == ':') + break; + } + append_highlight_span(ret, parent.s, (string_span_t){ s.s, colon }, HighlightHost); + append_highlight_span(ret, parent.s, (string_span_t){ s.s + colon, 1 }, HighlightDelimiter); + append_highlight_span(ret, parent.s, (string_span_t){ s.s + colon + 1, s.len - colon - 1 }, HighlightPort); + break; + } + case Address: + case DNS: + case AllowedIPs: + highlight_multivalue(ret, parent, s, section); + break; + default: + append_highlight_span(ret, parent.s, s, HighlightError); + } +} + +struct highlight_span *highlight_config(const char *config) +{ + struct highlight_span_array ret = { 0 }; + const string_span_t s = { config, strlen(config) }; + string_span_t current_span = { s.s, 0 }; + enum keytype current_section = Invalid, current_keytype = Invalid; + enum { OnNone, OnKey, OnValue, OnComment, OnSection } state = OnNone; + size_t len_at_last_space = 0, equals_location = 0; + + for (size_t i = 0; i <= s.len; ++i) { + if (i == s.len || s.s[i] == '\n' || (state != OnComment && s.s[i] == '#')) { + if (state == OnKey) { + current_span.len = len_at_last_space; + append_highlight_span(&ret, s.s, current_span, HighlightError); + } else if (state == OnValue) { + if (current_span.len) { + append_highlight_span(&ret, s.s, (string_span_t){ s.s + equals_location, 1 }, HighlightDelimiter); + current_span.len = len_at_last_space; + highlight_value(&ret, s, current_span, current_keytype); + } else { + append_highlight_span(&ret, s.s, (string_span_t){ s.s + equals_location, 1 }, HighlightError); + } + } else if (state == OnSection) { + current_span.len = len_at_last_space; + current_section = get_sectiontype(current_span); + append_highlight_span(&ret, s.s, current_span, current_section == Invalid ? HighlightError : HighlightSection); + } else if (state == OnComment) { + append_highlight_span(&ret, s.s, current_span, HighlightComment); + } + if (i == s.len) + break; + len_at_last_space = 0; + current_keytype = Invalid; + if (s.s[i] == '#') { + current_span = (string_span_t){ s.s + i, 1 }; + state = OnComment; + } else { + current_span = (string_span_t){ s.s + i + 1, 0 }; + state = OnNone; + } + } else if (state == OnComment) { + ++current_span.len; + } else if (s.s[i] == ' ' || s.s[i] == '\t') { + if (&s.s[i] == current_span.s && !current_span.len) + ++current_span.s; + else + ++current_span.len; + } else if (s.s[i] == '=' && state == OnKey) { + current_span.len = len_at_last_space; + current_keytype = get_keytype(current_span); + enum keytype section = section_for_keytype(current_keytype); + if (section == Invalid || current_keytype == Invalid || section != current_section) + append_highlight_span(&ret, s.s, current_span, HighlightError); + else + append_highlight_span(&ret, s.s, current_span, HighlightKeytype); + equals_location = i; + current_span = (string_span_t){ s.s + i + 1, 0 }; + state = OnValue; + } else { + if (state == OnNone) + state = s.s[i] == '[' ? OnSection : OnKey; + len_at_last_space = ++current_span.len; + } + } + + append_highlight_span(&ret, s.s, (string_span_t){ s.s, -1 }, HighlightEnd); + return ret.spans; +} diff --git a/WireGuard/WireGuard/UI/macOS/highlighter.h b/WireGuard/WireGuard/UI/macOS/highlighter.h new file mode 100644 index 0000000..9368af6 --- /dev/null +++ b/WireGuard/WireGuard/UI/macOS/highlighter.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2015-2019 Jason A. Donenfeld . All Rights Reserved. + */ + +#include + +enum highlight_type { + HighlightSection, + HighlightKeytype, + HighlightKey, + HighlightCmd, + HighlightIP, + HighlightCidr, + HighlightHost, + HighlightPort, + HighlightTable, + HighlightFwMark, + HighlightMTU, + HighlightSaveConfig, + HighlightKeepalive, + HighlightComment, + HighlightDelimiter, + HighlightError, + HighlightEnd +}; + +struct highlight_span { + enum highlight_type type; + size_t start, len; +}; + +struct highlight_span *highlight_config(const char *config); diff --git a/WireGuard/WireGuard/WireGuard-Bridging-Header.h b/WireGuard/WireGuard/WireGuard-Bridging-Header.h index 21cd2a2..95e712b 100644 --- a/WireGuard/WireGuard/WireGuard-Bridging-Header.h +++ b/WireGuard/WireGuard/WireGuard-Bridging-Header.h @@ -3,3 +3,4 @@ #include "zip.h" #include "wireguard-go-version.h" #include "ringlogger.h" +#include "highlighter.h" -- cgit v1.2.3-59-g8ed1b