Move all source files to Sources/ and rename WireGuardKit targets

Signed-off-by: Andrej Mihajlov <and@mullvad.net>
This commit is contained in:
Andrej Mihajlov
2020-12-02 12:27:39 +01:00
parent 9c38a1b897
commit ec57408570
209 changed files with 54 additions and 58 deletions
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa
class ButtonRow: NSView {
let button: NSButton = {
let button = NSButton()
button.title = ""
button.setButtonType(.momentaryPushIn)
button.bezelStyle = .rounded
return button
}()
var buttonTitle: String {
get { return button.title }
set(value) { button.title = value }
}
var isButtonEnabled: Bool {
get { return button.isEnabled }
set(value) { button.isEnabled = value }
}
var buttonToolTip: String {
get { return button.toolTip ?? "" }
set(value) { button.toolTip = value }
}
var onButtonClicked: (() -> Void)?
var observationToken: AnyObject?
override var intrinsicContentSize: NSSize {
return NSSize(width: NSView.noIntrinsicMetric, height: button.intrinsicContentSize.height)
}
init() {
super.init(frame: CGRect.zero)
button.target = self
button.action = #selector(buttonClicked)
addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.centerYAnchor.constraint(equalTo: self.centerYAnchor),
button.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 155),
button.widthAnchor.constraint(greaterThanOrEqualToConstant: 100)
])
}
required init?(coder decoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func buttonClicked() {
onButtonClicked?()
}
override func prepareForReuse() {
buttonTitle = ""
buttonToolTip = ""
onButtonClicked = nil
observationToken = nil
}
}
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa
protocol ConfTextColorTheme {
static var defaultColor: NSColor { get }
static var colorMap: [UInt32: NSColor] { get }
}
struct ConfTextAquaColorTheme: ConfTextColorTheme {
static let defaultColor = NSColor(hex: "#000000")
static let colorMap: [UInt32: NSColor] = [
HighlightSection.rawValue: NSColor(hex: "#326D74"), // Class name in Xcode
HighlightField.rawValue: NSColor(hex: "#9B2393"), // Keywords in Xcode
HighlightPublicKey.rawValue: NSColor(hex: "#643820"), // Preprocessor directives in Xcode
HighlightPrivateKey.rawValue: NSColor(hex: "#643820"), // Preprocessor directives in Xcode
HighlightPresharedKey.rawValue: NSColor(hex: "#643820"), // Preprocessor directives in Xcode
HighlightIP.rawValue: NSColor(hex: "#0E0EFF"), // URLs in Xcode
HighlightHost.rawValue: NSColor(hex: "#0E0EFF"), // URLs in Xcode
HighlightCidr.rawValue: NSColor(hex: "#815F03"), // Attributes in Xcode
HighlightPort.rawValue: NSColor(hex: "#815F03"), // Attributes in Xcode
HighlightMTU.rawValue: NSColor(hex: "#1C00CF"), // Numbers in Xcode
HighlightKeepalive.rawValue: NSColor(hex: "#1C00CF"), // Numbers in Xcode
HighlightComment.rawValue: NSColor(hex: "#536579"), // Comments in Xcode
HighlightError.rawValue: NSColor(hex: "#C41A16") // Strings in Xcode
]
}
struct ConfTextDarkAquaColorTheme: ConfTextColorTheme {
static let defaultColor = NSColor(hex: "#FFFFFF") // Plain text in Xcode
static let colorMap: [UInt32: NSColor] = [
HighlightSection.rawValue: NSColor(hex: "#91D462"), // Class name in Xcode
HighlightField.rawValue: NSColor(hex: "#FC5FA3"), // Keywords in Xcode
HighlightPublicKey.rawValue: NSColor(hex: "#FD8F3F"), // Preprocessor directives in Xcode
HighlightPrivateKey.rawValue: NSColor(hex: "#FD8F3F"), // Preprocessor directives in Xcode
HighlightPresharedKey.rawValue: NSColor(hex: "#FD8F3F"), // Preprocessor directives in Xcode
HighlightIP.rawValue: NSColor(hex: "#53A5FB"), // URLs in Xcode
HighlightHost.rawValue: NSColor(hex: "#53A5FB"), // URLs in Xcode
HighlightCidr.rawValue: NSColor(hex: "#75B492"), // Attributes in Xcode
HighlightPort.rawValue: NSColor(hex: "#75B492"), // Attributes in Xcode
HighlightMTU.rawValue: NSColor(hex: "#9686F5"), // Numbers in Xcode
HighlightKeepalive.rawValue: NSColor(hex: "#9686F5"), // Numbers in Xcode
HighlightComment.rawValue: NSColor(hex: "#6C7986"), // Comments in Xcode
HighlightError.rawValue: NSColor(hex: "#FF4C4C") // Strings in Xcode
]
}
@@ -0,0 +1,180 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa
private let fontSize: CGFloat = 15
class ConfTextStorage: NSTextStorage {
let defaultFont = NSFontManager.shared.convertWeight(true, of: NSFont.systemFont(ofSize: fontSize))
private let boldFont = NSFont.boldSystemFont(ofSize: fontSize)
private lazy var italicFont = NSFontManager.shared.convert(defaultFont, toHaveTrait: .italicFontMask)
private var textColorTheme: ConfTextColorTheme.Type?
private let backingStore: NSMutableAttributedString
private(set) var hasError = false
private(set) var privateKeyString: String?
private(set) var hasOnePeer: Bool = false
private(set) var lastOnePeerAllowedIPs = [String]()
private(set) var lastOnePeerDNSServers = [String]()
private(set) var lastOnePeerHasPublicKey = 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")
}
func nonColorAttributes(for highlightType: highlight_type) -> [NSAttributedString.Key: Any] {
switch highlightType.rawValue {
case HighlightSection.rawValue, HighlightField.rawValue:
return [.font: boldFont]
case HighlightPublicKey.rawValue, HighlightPrivateKey.rawValue, HighlightPresharedKey.rawValue,
HighlightIP.rawValue, HighlightCidr.rawValue, HighlightHost.rawValue, HighlightPort.rawValue,
HighlightMTU.rawValue, HighlightKeepalive.rawValue, HighlightDelimiter.rawValue:
return [.font: defaultFont]
case HighlightComment.rawValue:
return [.font: italicFont]
case HighlightError.rawValue:
return [.font: defaultFont, .underlineStyle: 1]
default:
return [:]
}
}
func updateAttributes(for textColorTheme: ConfTextColorTheme.Type) {
self.textColorTheme = textColorTheme
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 resetLastPeer() {
hasOnePeer = false
lastOnePeerAllowedIPs = []
lastOnePeerDNSServers = []
lastOnePeerHasPublicKey = false
}
func evaluateExcludePrivateIPs(highlightSpans: UnsafePointer<highlight_span>) {
var spans = highlightSpans
enum FieldType: String {
case dns
case allowedips
}
var fieldType: FieldType?
resetLastPeer()
while spans.pointee.type != HighlightEnd {
let span = spans.pointee
var substring = backingStore.attributedSubstring(from: NSRange(location: span.start, length: span.len)).string.lowercased()
if span.type == HighlightError {
resetLastPeer()
return
} else if span.type == HighlightSection {
if substring == "[peer]" {
if hasOnePeer {
resetLastPeer()
return
}
hasOnePeer = true
}
} else if span.type == HighlightField {
fieldType = FieldType(rawValue: substring)
} else if span.type == HighlightIP && fieldType == .dns {
lastOnePeerDNSServers.append(substring)
} else if span.type == HighlightIP && fieldType == .allowedips {
let next = spans.successor()
let nextnext = next.successor()
if next.pointee.type == HighlightDelimiter && nextnext.pointee.type == HighlightCidr {
substring += backingStore.attributedSubstring(from: NSRange(location: next.pointee.start, length: next.pointee.len)).string +
backingStore.attributedSubstring(from: NSRange(location: nextnext.pointee.start, length: nextnext.pointee.len)).string
}
lastOnePeerAllowedIPs.append(substring)
} else if span.type == HighlightPublicKey {
lastOnePeerHasPublicKey = true
}
spans = spans.successor()
}
}
func highlightSyntax() {
guard let textColorTheme = textColorTheme else { return }
hasError = false
privateKeyString = nil
let fullTextRange = NSRange(location: 0, length: (backingStore.string as NSString).length)
backingStore.beginEditing()
let defaultAttributes: [NSAttributedString.Key: Any] = [
.foregroundColor: textColorTheme.defaultColor,
.font: defaultFont
]
backingStore.setAttributes(defaultAttributes, range: fullTextRange)
var spans = highlight_config(backingStore.string)!
evaluateExcludePrivateIPs(highlightSpans: spans)
let spansStart = spans
while spans.pointee.type != HighlightEnd {
let span = spans.pointee
let range = NSRange(location: span.start, length: span.len)
backingStore.setAttributes(nonColorAttributes(for: span.type), range: range)
let color = textColorTheme.colorMap[span.type.rawValue, default: textColorTheme.defaultColor]
backingStore.addAttribute(.foregroundColor, value: color, range: range)
if span.type == HighlightError {
hasError = true
}
if span.type == HighlightPrivateKey {
privateKeyString = backingStore.attributedSubstring(from: NSRange(location: span.start, length: span.len)).string
}
spans = spans.successor()
}
backingStore.endEditing()
free(spansStart)
beginEditing()
edited(.editedAttributes, range: fullTextRange, changeInLength: 0)
endEditing()
}
}
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa
class ConfTextView: NSTextView {
private let confTextStorage = ConfTextStorage()
@objc dynamic var hasError: Bool = false
@objc dynamic var privateKeyString: String?
@objc dynamic var singlePeerAllowedIPs: [String]?
override var string: String {
didSet {
confTextStorage.highlightSyntax()
updateConfigData()
}
}
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
allowsUndo = true
isAutomaticSpellingCorrectionEnabled = false
isAutomaticDataDetectionEnabled = false
isAutomaticLinkDetectionEnabled = false
isAutomaticTextCompletionEnabled = false
isAutomaticTextReplacementEnabled = false
isAutomaticDashSubstitutionEnabled = false
isAutomaticQuoteSubstitutionEnabled = false
updateTheme()
delegate = self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidChangeEffectiveAppearance() {
updateTheme()
}
private func updateTheme() {
switch effectiveAppearance.bestMatch(from: [.aqua, .darkAqua]) ?? .aqua {
case .darkAqua:
confTextStorage.updateAttributes(for: ConfTextDarkAquaColorTheme.self)
default:
confTextStorage.updateAttributes(for: ConfTextAquaColorTheme.self)
}
}
private func updateConfigData() {
if hasError != confTextStorage.hasError {
hasError = confTextStorage.hasError
}
if privateKeyString != confTextStorage.privateKeyString {
privateKeyString = confTextStorage.privateKeyString
}
let hasSyntaxError = confTextStorage.hasError
let hasSemanticError = confTextStorage.privateKeyString == nil || !confTextStorage.lastOnePeerHasPublicKey
let updatedSinglePeerAllowedIPs = confTextStorage.hasOnePeer && !hasSyntaxError && !hasSemanticError ? confTextStorage.lastOnePeerAllowedIPs : nil
if singlePeerAllowedIPs != updatedSinglePeerAllowedIPs {
singlePeerAllowedIPs = updatedSinglePeerAllowedIPs
}
}
func setConfText(_ text: String) {
let fullTextRange = NSRange(location: 0, length: (string as NSString).length)
if shouldChangeText(in: fullTextRange, replacementString: text) {
replaceCharacters(in: fullTextRange, with: text)
didChangeText()
}
}
}
extension ConfTextView: NSTextViewDelegate {
func textDidChange(_ notification: Notification) {
confTextStorage.highlightSyntax()
updateConfigData()
needsDisplay = true
}
}
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa
class DeleteTunnelsConfirmationAlert: NSAlert {
var alertDeleteButton: NSButton?
var alertCancelButton: NSButton?
var onDeleteClicked: ((_ completionHandler: @escaping () -> Void) -> Void)?
override init() {
super.init()
let alertDeleteButton = addButton(withTitle: tr("macDeleteTunnelConfirmationAlertButtonTitleDelete"))
alertDeleteButton.target = self
alertDeleteButton.action = #selector(removeTunnelAlertDeleteClicked)
self.alertDeleteButton = alertDeleteButton
self.alertCancelButton = addButton(withTitle: tr("macDeleteTunnelConfirmationAlertButtonTitleCancel"))
}
@objc func removeTunnelAlertDeleteClicked() {
alertDeleteButton?.title = tr("macDeleteTunnelConfirmationAlertButtonTitleDeleting")
alertDeleteButton?.isEnabled = false
alertCancelButton?.isEnabled = false
if let onDeleteClicked = onDeleteClicked {
onDeleteClicked { [weak self] in
guard let self = self else { return }
self.window.sheetParent?.endSheet(self.window)
}
}
}
func beginSheetModal(for sheetWindow: NSWindow) {
beginSheetModal(for: sheetWindow) { _ in }
}
}
@@ -0,0 +1,139 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa
class EditableKeyValueRow: NSView {
let keyLabel: NSTextField = {
let keyLabel = NSTextField()
keyLabel.isEditable = false
keyLabel.isSelectable = false
keyLabel.isBordered = false
keyLabel.alignment = .right
keyLabel.maximumNumberOfLines = 1
keyLabel.lineBreakMode = .byTruncatingTail
keyLabel.backgroundColor = .clear
return keyLabel
}()
let valueLabel: NSTextField = {
let valueLabel = NSTextField()
valueLabel.isSelectable = true
valueLabel.maximumNumberOfLines = 1
valueLabel.lineBreakMode = .byTruncatingTail
return valueLabel
}()
let valueImageView: NSImageView?
var key: String {
get { return keyLabel.stringValue }
set(value) { keyLabel.stringValue = value }
}
var value: String {
get { return valueLabel.stringValue }
set(value) { valueLabel.stringValue = value }
}
var isKeyInBold: Bool {
get { return keyLabel.font == NSFont.boldSystemFont(ofSize: 0) }
set(value) {
if value {
keyLabel.font = NSFont.boldSystemFont(ofSize: 0)
} else {
keyLabel.font = NSFont.systemFont(ofSize: 0)
}
}
}
var valueImage: NSImage? {
get { return valueImageView?.image }
set(value) { valueImageView?.image = value }
}
var observationToken: AnyObject?
override var intrinsicContentSize: NSSize {
let height = max(keyLabel.intrinsicContentSize.height, valueLabel.intrinsicContentSize.height)
return NSSize(width: NSView.noIntrinsicMetric, height: height)
}
convenience init() {
self.init(hasValueImage: false)
}
fileprivate init(hasValueImage: Bool) {
valueImageView = hasValueImage ? NSImageView() : nil
super.init(frame: CGRect.zero)
addSubview(keyLabel)
addSubview(valueLabel)
keyLabel.translatesAutoresizingMaskIntoConstraints = false
valueLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
keyLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor),
keyLabel.firstBaselineAnchor.constraint(equalTo: valueLabel.firstBaselineAnchor),
self.leadingAnchor.constraint(equalTo: keyLabel.leadingAnchor),
valueLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor)
])
let spacing: CGFloat = 5
if let valueImageView = valueImageView {
addSubview(valueImageView)
valueImageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
valueImageView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
valueImageView.leadingAnchor.constraint(equalTo: keyLabel.trailingAnchor, constant: spacing),
valueLabel.leadingAnchor.constraint(equalTo: valueImageView.trailingAnchor)
])
} else {
NSLayoutConstraint.activate([
valueLabel.leadingAnchor.constraint(equalTo: keyLabel.trailingAnchor, constant: spacing)
])
}
keyLabel.setContentCompressionResistancePriority(.defaultHigh + 2, for: .horizontal)
keyLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
valueLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
let widthConstraint = keyLabel.widthAnchor.constraint(equalToConstant: 150)
widthConstraint.priority = .defaultHigh + 1
widthConstraint.isActive = true
}
required init?(coder decoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepareForReuse() {
key = ""
value = ""
isKeyInBold = false
observationToken = nil
}
}
class KeyValueRow: EditableKeyValueRow {
init() {
super.init(hasValueImage: false)
valueLabel.isEditable = false
valueLabel.isBordered = false
valueLabel.backgroundColor = .clear
}
required init?(coder decoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class KeyValueImageRow: EditableKeyValueRow {
init() {
super.init(hasValueImage: true)
valueLabel.isEditable = false
valueLabel.isBordered = false
valueLabel.backgroundColor = .clear
}
required init?(coder decoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa
class LogViewCell: NSTableCellView {
var text: String = "" {
didSet { textField?.stringValue = text }
}
init() {
super.init(frame: .zero)
let textField = NSTextField(wrappingLabelWithString: "")
addSubview(textField)
textField.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
textField.leadingAnchor.constraint(equalTo: self.leadingAnchor),
textField.trailingAnchor.constraint(equalTo: self.trailingAnchor),
textField.topAnchor.constraint(equalTo: self.topAnchor),
textField.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
self.textField = textField
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepareForReuse() {
textField?.stringValue = ""
}
}
class LogViewTimestampCell: LogViewCell {
override init() {
super.init()
if let textField = textField {
textField.maximumNumberOfLines = 1
textField.lineBreakMode = .byClipping
textField.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
textField.setContentHuggingPriority(.defaultLow, for: .vertical)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class LogViewMessageCell: LogViewCell {
override init() {
super.init()
if let textField = textField {
textField.maximumNumberOfLines = 0
textField.lineBreakMode = .byWordWrapping
textField.setContentCompressionResistancePriority(.required, for: .vertical)
textField.setContentHuggingPriority(.required, for: .vertical)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
@@ -0,0 +1,171 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa
import CoreWLAN
class OnDemandControlsRow: NSView {
let keyLabel: NSTextField = {
let keyLabel = NSTextField()
keyLabel.stringValue = tr("macFieldOnDemand")
keyLabel.isEditable = false
keyLabel.isSelectable = false
keyLabel.isBordered = false
keyLabel.alignment = .right
keyLabel.maximumNumberOfLines = 1
keyLabel.lineBreakMode = .byTruncatingTail
keyLabel.backgroundColor = .clear
return keyLabel
}()
let onDemandEthernetCheckbox: NSButton = {
let checkbox = NSButton()
checkbox.title = tr("tunnelOnDemandEthernet")
checkbox.setButtonType(.switch)
checkbox.state = .off
return checkbox
}()
let onDemandWiFiCheckbox: NSButton = {
let checkbox = NSButton()
checkbox.title = tr("tunnelOnDemandWiFi")
checkbox.setButtonType(.switch)
checkbox.state = .off
return checkbox
}()
static let onDemandSSIDOptions: [ActivateOnDemandViewModel.OnDemandSSIDOption] = [
.anySSID, .onlySpecificSSIDs, .exceptSpecificSSIDs
]
let onDemandSSIDOptionsPopup = NSPopUpButton()
let onDemandSSIDsField: NSTokenField = {
let tokenField = NSTokenField()
tokenField.tokenizingCharacterSet = CharacterSet([])
tokenField.tokenStyle = .squared
NSLayoutConstraint.activate([
tokenField.widthAnchor.constraint(greaterThanOrEqualToConstant: 180)
])
return tokenField
}()
override var intrinsicContentSize: NSSize {
let minHeight: CGFloat = 22
let height = max(minHeight, keyLabel.intrinsicContentSize.height,
onDemandEthernetCheckbox.intrinsicContentSize.height, onDemandWiFiCheckbox.intrinsicContentSize.height,
onDemandSSIDOptionsPopup.intrinsicContentSize.height, onDemandSSIDsField.intrinsicContentSize.height)
return NSSize(width: NSView.noIntrinsicMetric, height: height)
}
var onDemandViewModel: ActivateOnDemandViewModel? {
didSet { updateControls() }
}
var currentSSIDs: [String]
init() {
currentSSIDs = getCurrentSSIDs()
super.init(frame: CGRect.zero)
onDemandSSIDOptionsPopup.addItems(withTitles: OnDemandControlsRow.onDemandSSIDOptions.map { $0.localizedUIString })
let stackView = NSStackView()
stackView.setViews([onDemandEthernetCheckbox, onDemandWiFiCheckbox, onDemandSSIDOptionsPopup, onDemandSSIDsField], in: .leading)
stackView.orientation = .horizontal
addSubview(keyLabel)
addSubview(stackView)
keyLabel.translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
keyLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor),
stackView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
self.leadingAnchor.constraint(equalTo: keyLabel.leadingAnchor),
stackView.leadingAnchor.constraint(equalTo: keyLabel.trailingAnchor, constant: 5),
stackView.trailingAnchor.constraint(equalTo: self.trailingAnchor)
])
keyLabel.setContentCompressionResistancePriority(.defaultHigh + 2, for: .horizontal)
keyLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
let widthConstraint = keyLabel.widthAnchor.constraint(equalToConstant: 150)
widthConstraint.priority = .defaultHigh + 1
widthConstraint.isActive = true
NSLayoutConstraint.activate([
onDemandEthernetCheckbox.centerYAnchor.constraint(equalTo: stackView.centerYAnchor),
onDemandWiFiCheckbox.lastBaselineAnchor.constraint(equalTo: onDemandEthernetCheckbox.lastBaselineAnchor),
onDemandSSIDOptionsPopup.lastBaselineAnchor.constraint(equalTo: onDemandEthernetCheckbox.lastBaselineAnchor),
onDemandSSIDsField.lastBaselineAnchor.constraint(equalTo: onDemandEthernetCheckbox.lastBaselineAnchor)
])
onDemandSSIDsField.setContentHuggingPriority(.defaultLow, for: .horizontal)
onDemandEthernetCheckbox.target = self
onDemandEthernetCheckbox.action = #selector(ethernetCheckboxToggled)
onDemandWiFiCheckbox.target = self
onDemandWiFiCheckbox.action = #selector(wiFiCheckboxToggled)
onDemandSSIDOptionsPopup.target = self
onDemandSSIDOptionsPopup.action = #selector(ssidOptionsPopupValueChanged)
onDemandSSIDsField.delegate = self
updateControls()
}
required init?(coder decoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func saveToViewModel() {
guard let onDemandViewModel = onDemandViewModel else { return }
onDemandViewModel.isNonWiFiInterfaceEnabled = onDemandEthernetCheckbox.state == .on
onDemandViewModel.isWiFiInterfaceEnabled = onDemandWiFiCheckbox.state == .on
onDemandViewModel.ssidOption = OnDemandControlsRow.onDemandSSIDOptions[onDemandSSIDOptionsPopup.indexOfSelectedItem]
onDemandViewModel.selectedSSIDs = (onDemandSSIDsField.objectValue as? [String]) ?? []
}
func updateControls() {
guard let onDemandViewModel = onDemandViewModel else { return }
onDemandEthernetCheckbox.state = onDemandViewModel.isNonWiFiInterfaceEnabled ? .on : .off
onDemandWiFiCheckbox.state = onDemandViewModel.isWiFiInterfaceEnabled ? .on : .off
let optionIndex = OnDemandControlsRow.onDemandSSIDOptions.firstIndex(of: onDemandViewModel.ssidOption)
onDemandSSIDOptionsPopup.selectItem(at: optionIndex ?? 0)
onDemandSSIDsField.objectValue = onDemandViewModel.selectedSSIDs
onDemandSSIDOptionsPopup.isHidden = !onDemandViewModel.isWiFiInterfaceEnabled
onDemandSSIDsField.isHidden = !onDemandViewModel.isWiFiInterfaceEnabled || onDemandViewModel.ssidOption == .anySSID
}
@objc func ethernetCheckboxToggled() {
onDemandViewModel?.isNonWiFiInterfaceEnabled = onDemandEthernetCheckbox.state == .on
}
@objc func wiFiCheckboxToggled() {
onDemandViewModel?.isWiFiInterfaceEnabled = onDemandWiFiCheckbox.state == .on
updateControls()
}
@objc func ssidOptionsPopupValueChanged() {
let selectedIndex = onDemandSSIDOptionsPopup.indexOfSelectedItem
onDemandViewModel?.ssidOption = OnDemandControlsRow.onDemandSSIDOptions[selectedIndex]
onDemandViewModel?.selectedSSIDs = (onDemandSSIDsField.objectValue as? [String]) ?? []
updateControls()
if !onDemandSSIDsField.isHidden {
onDemandSSIDsField.becomeFirstResponder()
}
}
}
extension OnDemandControlsRow: NSTokenFieldDelegate {
func tokenField(_ tokenField: NSTokenField, completionsForSubstring substring: String, indexOfToken tokenIndex: Int, indexOfSelectedItem selectedIndex: UnsafeMutablePointer<Int>?) -> [Any]? {
return currentSSIDs.filter { $0.hasPrefix(substring) }
}
}
private func getCurrentSSIDs() -> [String] {
return CWWiFiClient.shared().interfaces()?.compactMap { $0.ssid() } ?? []
}
@@ -0,0 +1,75 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa
class TunnelListRow: NSView {
var tunnel: TunnelContainer? {
didSet(value) {
// Bind to the tunnel's name
nameLabel.stringValue = tunnel?.name ?? ""
nameObservationToken = tunnel?.observe(\TunnelContainer.name) { [weak self] tunnel, _ in
self?.nameLabel.stringValue = tunnel.name
}
// Bind to the tunnel's status
statusImageView.image = TunnelListRow.image(for: tunnel?.status)
statusObservationToken = tunnel?.observe(\TunnelContainer.status) { [weak self] tunnel, _ in
self?.statusImageView.image = TunnelListRow.image(for: tunnel.status)
}
}
}
let nameLabel: NSTextField = {
let nameLabel = NSTextField()
nameLabel.isEditable = false
nameLabel.isSelectable = false
nameLabel.isBordered = false
nameLabel.maximumNumberOfLines = 1
nameLabel.lineBreakMode = .byTruncatingTail
return nameLabel
}()
let statusImageView = NSImageView()
private var statusObservationToken: AnyObject?
private var nameObservationToken: AnyObject?
init() {
super.init(frame: CGRect.zero)
addSubview(statusImageView)
addSubview(nameLabel)
statusImageView.translatesAutoresizingMaskIntoConstraints = false
nameLabel.translatesAutoresizingMaskIntoConstraints = false
nameLabel.backgroundColor = .clear
NSLayoutConstraint.activate([
self.leadingAnchor.constraint(equalTo: statusImageView.leadingAnchor),
statusImageView.trailingAnchor.constraint(equalTo: nameLabel.leadingAnchor),
statusImageView.widthAnchor.constraint(equalToConstant: 20),
nameLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor),
statusImageView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
nameLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor)
])
}
required init?(coder decoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
static func image(for status: TunnelStatus?) -> NSImage? {
guard let status = status else { return nil }
switch status {
case .active, .restarting, .reasserting:
return NSImage(named: NSImage.statusAvailableName)
case .activating, .waiting, .deactivating:
return NSImage(named: NSImage.statusPartiallyAvailableName)
case .inactive:
return NSImage(named: NSImage.statusNoneName)
}
}
override func prepareForReuse() {
nameLabel.stringValue = ""
statusImageView.image = nil
}
}
@@ -0,0 +1,641 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "highlighter.h"
typedef struct {
const char *s;
size_t len;
} string_span_t;
static bool is_decimal(char c)
{
return c >= '0' && c <= '9';
}
static bool is_hexadecimal(char c)
{
return is_decimal(c) || ((c | 32) >= 'a' && (c | 32) <= 'f');
}
static bool is_alphabet(char c)
{
return (c | 32) >= 'a' && (c | 32) <= 'z';
}
static bool is_same(string_span_t s, const char *c)
{
size_t len = strlen(c);
if (len != s.len)
return false;
return !memcmp(s.s, c, len);
}
static bool is_caseless_same(string_span_t s, const char *c)
{
size_t len = strlen(c);
if (len != s.len)
return false;
for (size_t i = 0; i < len; ++i) {
char a = c[i], b = s.s[i];
if ((unsigned)a - 'a' < 26)
a &= 95;
if ((unsigned)b - 'a' < 26)
b &= 95;
if (a != b)
return false;
}
return true;
}
static bool is_valid_key(string_span_t s)
{
if (s.len != 44 || s.s[43] != '=')
return false;
for (size_t i = 0; i < 42; ++i) {
if (!is_decimal(s.s[i]) && !is_alphabet(s.s[i]) &&
s.s[i] != '/' && s.s[i] != '+')
return false;
}
switch (s.s[42]) {
case 'A':
case 'E':
case 'I':
case 'M':
case 'Q':
case 'U':
case 'Y':
case 'c':
case 'g':
case 'k':
case 'o':
case 's':
case 'w':
case '4':
case '8':
case '0':
break;
default:
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 (is_decimal(s.s[i])) {
++num_digit;
continue;
}
if (s.s[i] == '.') {
--num_entity;
continue;
}
if (!is_alphabet(s.s[i]) && 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 && is_decimal(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 && is_hexadecimal(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_uint(string_span_t s, bool support_hex, uint64_t min, uint64_t max)
{
uint64_t val = 0;
/* Bound this around 32 bits, so that we don't have to write overflow logic. */
if (s.len > 10 || !s.len)
return false;
if (support_hex && s.len > 2 && s.s[0] == '0' && s.s[1] == 'x') {
for (size_t i = 2; i < s.len; ++i) {
if ((unsigned)s.s[i] - '0' < 10)
val = 16 * val + (s.s[i] - '0');
else if (((unsigned)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 (!is_decimal(s.s[i]))
return false;
val = 10 * val + s.s[i] - '0';
}
}
return val <= max && val >= min;
}
static bool is_valid_port(string_span_t s)
{
return is_valid_uint(s, false, 0, 65535);
}
static bool is_valid_mtu(string_span_t s)
{
return is_valid_uint(s, false, 576, 65535);
}
static bool is_valid_persistentkeepalive(string_span_t s)
{
if (is_same(s, "off"))
return true;
return is_valid_uint(s, false, 0, 65535);
}
#ifndef MOBILE_WGQUICK_SUBSET
static bool is_valid_fwmark(string_span_t s)
{
if (is_same(s, "off"))
return true;
return is_valid_uint(s, true, 0, 4294967295);
}
static bool is_valid_table(string_span_t s)
{
if (is_same(s, "auto"))
return true;
if (is_same(s, "off"))
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_uint(s, false, 0, 4294967295);
}
static bool is_valid_saveconfig(string_span_t s)
{
return is_same(s, "true") || is_same(s, "false");
}
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;
}
#endif
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 (!is_alphabet(s.s[i]) && !is_decimal(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 (!is_decimal(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);
}
enum field {
InterfaceSection,
PrivateKey,
ListenPort,
Address,
DNS,
MTU,
#ifndef MOBILE_WGQUICK_SUBSET
FwMark,
Table,
PreUp, PostUp, PreDown, PostDown,
SaveConfig,
#endif
PeerSection,
PublicKey,
PresharedKey,
AllowedIPs,
Endpoint,
PersistentKeepalive,
Invalid
};
static enum field section_for_field(enum field t)
{
if (t > InterfaceSection && t < PeerSection)
return InterfaceSection;
if (t > PeerSection && t < Invalid)
return PeerSection;
return Invalid;
}
static enum field get_field(string_span_t s)
{
#define check_enum(t) do { if (is_caseless_same(s, #t)) return t; } while (0)
check_enum(PrivateKey);
check_enum(ListenPort);
check_enum(Address);
check_enum(DNS);
check_enum(MTU);
check_enum(PublicKey);
check_enum(PresharedKey);
check_enum(AllowedIPs);
check_enum(Endpoint);
check_enum(PersistentKeepalive);
#ifndef MOBILE_WGQUICK_SUBSET
check_enum(FwMark);
check_enum(Table);
check_enum(PreUp);
check_enum(PostUp);
check_enum(PreDown);
check_enum(PostDown);
check_enum(SaveConfig);
#endif
return Invalid;
#undef check_enum
}
static enum field get_sectiontype(string_span_t s)
{
if (is_caseless_same(s, "[Peer]"))
return PeerSection;
if (is_caseless_same(s, "[Interface]"))
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 field 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 field 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 field section)
{
switch (section) {
case PrivateKey:
append_highlight_span(ret, parent.s, s, is_valid_key(s) ? HighlightPrivateKey : HighlightError);
break;
case PublicKey:
append_highlight_span(ret, parent.s, s, is_valid_key(s) ? HighlightPublicKey : HighlightError);
break;
case PresharedKey:
append_highlight_span(ret, parent.s, s, is_valid_key(s) ? HighlightPresharedKey : HighlightError);
break;
case MTU:
append_highlight_span(ret, parent.s, s, is_valid_mtu(s) ? HighlightMTU : HighlightError);
break;
#ifndef MOBILE_WGQUICK_SUBSET
case SaveConfig:
append_highlight_span(ret, parent.s, s, is_valid_saveconfig(s) ? HighlightSaveConfig : 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 PreUp:
case PostUp:
case PreDown:
case PostDown:
append_highlight_span(ret, parent.s, s, is_valid_prepostupdown(s) ? HighlightCmd : HighlightError);
break;
#endif
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 field current_section = Invalid, current_field = 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_field);
} 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_field = 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_field = get_field(current_span);
enum field section = section_for_field(current_field);
if (section == Invalid || current_field == Invalid || section != current_section)
append_highlight_span(&ret, s.s, current_span, HighlightError);
else
append_highlight_span(&ret, s.s, current_span, HighlightField);
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,38 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
#include <sys/types.h>
#define MOBILE_WGQUICK_SUBSET
enum highlight_type {
HighlightSection,
HighlightField,
HighlightPrivateKey,
HighlightPublicKey,
HighlightPresharedKey,
HighlightIP,
HighlightCidr,
HighlightHost,
HighlightPort,
HighlightMTU,
HighlightKeepalive,
HighlightComment,
HighlightDelimiter,
#ifndef MOBILE_WGQUICK_SUBSET
HighlightTable,
HighlightFwMark,
HighlightSaveConfig,
HighlightCmd,
#endif
HighlightError,
HighlightEnd
};
struct highlight_span {
enum highlight_type type;
size_t start, len;
};
struct highlight_span *highlight_config(const char *config);