Added syntax highlighting conf textview
Signed-off-by: Eric Kuck <eric@bluelinelabs.com>
This commit is contained in:
parent
c2633987c3
commit
59bfa7f1df
|
@ -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 */,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
|
@ -3,3 +3,4 @@
|
|||
#include "zip.h"
|
||||
#include "wireguard-go-version.h"
|
||||
#include "ringlogger.h"
|
||||
#include "highlighter.h"
|
||||
|
|
Loading…
Reference in New Issue