diff options
-rw-r--r-- | WireGuard/WireGuard.xcodeproj/project.pbxproj | 18 | ||||
-rw-r--r-- | WireGuard/WireGuard/UI/macOS/NSColor+Hex.swift | 25 | ||||
-rw-r--r-- | WireGuard/WireGuard/UI/macOS/View/ConfTextStorage.swift | 174 | ||||
-rw-r--r-- | WireGuard/WireGuard/UI/macOS/View/ConfTextView.swift | 57 | ||||
-rw-r--r-- | WireGuard/WireGuard/UI/macOS/ViewController/TunnelEditViewController.swift | 4 | ||||
-rw-r--r-- | WireGuard/WireGuard/UI/macOS/highlighter.c | 588 | ||||
-rw-r--r-- | WireGuard/WireGuard/UI/macOS/highlighter.h | 33 | ||||
-rw-r--r-- | WireGuard/WireGuard/WireGuard-Bridging-Header.h | 1 |
8 files changed, 898 insertions, 2 deletions
diff --git a/WireGuard/WireGuard.xcodeproj/project.pbxproj b/WireGuard/WireGuard.xcodeproj/project.pbxproj index 402fb29..44785c0 100644 --- a/WireGuard/WireGuard.xcodeproj/project.pbxproj +++ b/WireGuard/WireGuard.xcodeproj/project.pbxproj @@ -17,6 +17,10 @@ 5F4541A621C4449E00994C13 /* ButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4541A521C4449E00994C13 /* ButtonCell.swift */; }; 5F4541A921C451D100994C13 /* TunnelStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4541A821C451D100994C13 /* TunnelStatus.swift */; }; 5F4541B221CBFAEE00994C13 /* String+ArrayConversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4541B121CBFAEE00994C13 /* String+ArrayConversion.swift */; }; + 5F52D0BB21E3781B00283CEA /* ConfTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F52D0BA21E3781B00283CEA /* ConfTextView.swift */; }; + 5F52D0BD21E3785C00283CEA /* ConfTextStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F52D0BC21E3785C00283CEA /* ConfTextStorage.swift */; }; + 5F52D0BF21E3788900283CEA /* NSColor+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F52D0BE21E3788900283CEA /* NSColor+Hex.swift */; }; + 5F52D0C221E378C000283CEA /* highlighter.c in Sources */ = {isa = PBXBuildFile; fileRef = 5F52D0C121E378C000283CEA /* highlighter.c */; }; 5F9696AA21CD6AE6008063FE /* LegacyConfigMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F9696A921CD6AE6008063FE /* LegacyConfigMigration.swift */; }; 5F9696AB21CD6AE6008063FE /* LegacyConfigMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F9696A921CD6AE6008063FE /* LegacyConfigMigration.swift */; }; 5F9696AE21CD6F72008063FE /* String+ArrayConversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4541B121CBFAEE00994C13 /* String+ArrayConversion.swift */; }; @@ -218,6 +222,11 @@ 5F4541A521C4449E00994C13 /* ButtonCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonCell.swift; sourceTree = "<group>"; }; 5F4541A821C451D100994C13 /* TunnelStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelStatus.swift; sourceTree = "<group>"; }; 5F4541B121CBFAEE00994C13 /* String+ArrayConversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+ArrayConversion.swift"; sourceTree = "<group>"; }; + 5F52D0BA21E3781B00283CEA /* ConfTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfTextView.swift; sourceTree = "<group>"; }; + 5F52D0BC21E3785C00283CEA /* ConfTextStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfTextStorage.swift; sourceTree = "<group>"; }; + 5F52D0BE21E3788900283CEA /* NSColor+Hex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSColor+Hex.swift"; sourceTree = "<group>"; }; + 5F52D0C021E378C000283CEA /* highlighter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = highlighter.h; sourceTree = "<group>"; }; + 5F52D0C121E378C000283CEA /* highlighter.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = highlighter.c; sourceTree = "<group>"; }; 5F9696A921CD6AE6008063FE /* LegacyConfigMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyConfigMigration.swift; sourceTree = "<group>"; }; 5F9696AF21CD7128008063FE /* TunnelConfiguration+WgQuickConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelConfiguration+WgQuickConfig.swift"; sourceTree = "<group>"; }; 5FF7B96121CC95DE00A7DD74 /* InterfaceConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterfaceConfiguration.swift; sourceTree = "<group>"; }; @@ -374,6 +383,8 @@ children = ( 6F4DD16A21DA558800690EAE /* TunnelListRow.swift */, 6F613D9A21DE33B8004B217A /* KeyValueRow.swift */, + 5F52D0BA21E3781B00283CEA /* ConfTextView.swift */, + 5F52D0BC21E3785C00283CEA /* ConfTextStorage.swift */, ); path = View; sourceTree = "<group>"; @@ -503,6 +514,9 @@ 6FB1BD6621D2607E00A991BF /* Info.plist */, 6FB1BD6721D2607E00A991BF /* WireGuard.entitlements */, 6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */, + 5F52D0BE21E3788900283CEA /* NSColor+Hex.swift */, + 5F52D0C021E378C000283CEA /* highlighter.h */, + 5F52D0C121E378C000283CEA /* highlighter.c */, ); path = macOS; sourceTree = "<group>"; @@ -1077,6 +1091,8 @@ 6FB1BDD621D50F5300A991BF /* zip.c in Sources */, 6FDB3C3B21DCF47400A0C0BF /* TunnelDetailTableViewController.swift in Sources */, 6FB1BDD721D50F5300A991BF /* WireGuardAppError.swift in Sources */, + 5F52D0BD21E3785C00283CEA /* ConfTextStorage.swift in Sources */, + 5F52D0C221E378C000283CEA /* highlighter.c in Sources */, 6F4DD16E21DBEA0700690EAE /* ManageTunnelsRootViewController.swift in Sources */, 6F4DD16C21DA558F00690EAE /* NSTableView+Reuse.swift in Sources */, 6FB1BDD821D50F5300A991BF /* WireGuardResult.swift in Sources */, @@ -1089,12 +1105,14 @@ 6FB1BDBD21D50F0200A991BF /* ringlogger.h in Sources */, 6FBA103F21D6B6FF0051C35F /* TunnelImporter.swift in Sources */, 6F4DD16B21DA558800690EAE /* TunnelListRow.swift in Sources */, + 5F52D0BF21E3788900283CEA /* NSColor+Hex.swift in Sources */, 6FB1BDBE21D50F0200A991BF /* Logger.swift in Sources */, 6FB1BDBF21D50F0200A991BF /* TunnelConfiguration+WgQuickConfig.swift in Sources */, 6FB1BDC021D50F0200A991BF /* NETunnelProviderProtocol+Extension.swift in Sources */, 6FBA101821D656000051C35F /* StatusMenu.swift in Sources */, 6F613D9B21DE33B8004B217A /* KeyValueRow.swift in Sources */, 6FB1BDC121D50F0200A991BF /* String+ArrayConversion.swift in Sources */, + 5F52D0BB21E3781B00283CEA /* ConfTextView.swift in Sources */, 6FB1BDC221D50F0300A991BF /* LegacyConfigMigration.swift in Sources */, 6FBA104021D6B7040051C35F /* ErrorPresenterProtocol.swift in Sources */, 6FCD99AA21E0E14700BA4C82 /* NoTunnelsDetailViewController.swift in Sources */, 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 <Jason@zx2c4.com>. All Rights Reserved. + */ + +#include <string.h> +#include <strings.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <ctype.h> +#include <errno.h> +#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 <Jason@zx2c4.com>. All Rights Reserved. + */ + +#include <sys/types.h> + +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" |