From ee880389866b44acb4fae64f9885ada7d964b738 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Sat, 5 Jan 2019 17:02:14 +0100 Subject: [PATCH] contrib: introduce simple highlighter library Signed-off-by: Jason A. Donenfeld --- contrib/highlighter/Makefile | 25 ++ contrib/highlighter/README | 22 + contrib/highlighter/fuzz.c | 22 + contrib/highlighter/gui/highlight.cpp | 90 ++++ contrib/highlighter/gui/highlight.pro | 5 + contrib/highlighter/highlight.c | 83 ++++ contrib/highlighter/highlighter.c | 620 ++++++++++++++++++++++++++ contrib/highlighter/highlighter.h | 37 ++ 8 files changed, 904 insertions(+) create mode 100644 contrib/highlighter/Makefile create mode 100644 contrib/highlighter/README create mode 100644 contrib/highlighter/fuzz.c create mode 100644 contrib/highlighter/gui/highlight.cpp create mode 100644 contrib/highlighter/gui/highlight.pro create mode 100644 contrib/highlighter/highlight.c create mode 100644 contrib/highlighter/highlighter.c create mode 100644 contrib/highlighter/highlighter.h diff --git a/contrib/highlighter/Makefile b/contrib/highlighter/Makefile new file mode 100644 index 0000000..29e1402 --- /dev/null +++ b/contrib/highlighter/Makefile @@ -0,0 +1,25 @@ +CFLAGS ?= -O3 -march=native +CFLAGS += -std=gnu99 +CFLAGS += -Wall +CFLAGS += -MMD -MP + +highlight: highlight.o highlighter.o + +fuzz: CC := clang +fuzz: CFLAGS += -fsanitize=fuzzer +fuzz: fuzz.c highlighter.c + +gui/Makefile: gui/highlight.pro + cd gui && qmake +gui: gui/Makefile + @$(MAKE) -C gui + +clean: + rm -f highlight fuzz *.o *.d + @if [ -f gui/Makefile ]; then $(MAKE) -C gui distclean; fi + +.PHONY: clean gui +.DEFAULT_GOAL: highlight +MAKEFLAGS += --no-print-directory + +-include *.d diff --git a/contrib/highlighter/README b/contrib/highlighter/README new file mode 100644 index 0000000..2ea5141 --- /dev/null +++ b/contrib/highlighter/README @@ -0,0 +1,22 @@ +wg(8) and wg-quick(8) syntax highlighter library +================================================ + +highlighter.c contains a simple portable highlighter for the wg(8) and +wg-quick(8) configuration files. Simply copy `highlight.c` and +`highlight.h` into your project wholesale. + +As a demo, a simple console highlighter program is included, alongside a +simple Qt5 GUI app to show its usage in realtime. + +There is also a basic fuzzer, because why not? + +Usage: + + $ make + $ ./highlight < path/to/tunnel.conf + + $ make gui + $ ./gui/highlight + + $ make fuzz + $ ./fuzz -workers=$(nproc) -jobs=$(nproc) ./corpus diff --git a/contrib/highlighter/fuzz.c b/contrib/highlighter/fuzz.c new file mode 100644 index 0000000..e308157 --- /dev/null +++ b/contrib/highlighter/fuzz.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2015-2019 Jason A. Donenfeld . All Rights Reserved. + */ + +#include +#include +#include "highlighter.h" + +int LLVMFuzzerTestOneInput(const char *data, size_t size) +{ + char *str = strndup(data, size); + if (!str) + return 0; + struct highlight_span *spans = highlight_config(str); + if (!spans) + return 0; + for (struct highlight_span *span = spans; span->type != HighlightEnd; ++span); + free(spans); + free(str); + return 0; +} diff --git a/contrib/highlighter/gui/highlight.cpp b/contrib/highlighter/gui/highlight.cpp new file mode 100644 index 0000000..a95857b --- /dev/null +++ b/contrib/highlighter/gui/highlight.cpp @@ -0,0 +1,90 @@ +#include +#include +#include +#include +#include +#include + +extern "C" { +#include "../highlighter.h" +} + +static QColor colormap[] = { + [HighlightSection] = QColor("#ababab"), + [HighlightField] = QColor("#70c0b1"), + [HighlightPrivateKey] = QColor("#7aa6da"), + [HighlightPublicKey] = QColor("#7aa6da"), + [HighlightPresharedKey] = QColor("#7aa6da"), + [HighlightIP] = QColor("#b9ca4a"), + [HighlightCidr] = QColor("#e78c45"), + [HighlightHost] = QColor("#b9ca4a"), + [HighlightPort] = QColor("#e78c45"), + [HighlightMTU] = QColor("#c397d8"), + [HighlightKeepalive] = QColor("#c397d8"), + [HighlightComment] = QColor("#969896"), + [HighlightDelimiter] = QColor("#7aa6da"), +#ifndef MOBILE_WGQUICK_SUBSET + [HighlightTable] = QColor("#c397d8"), + [HighlightFwMark] = QColor("#c397d8"), + [HighlightSaveConfig] = QColor("#c397d8"), + [HighlightCmd] = QColor("#969896"), +#endif + [HighlightError] = QColor("#d54e53") +}; + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + QWidget w; + w.setWindowTitle(QObject::tr("WireGuard Configuration Highlighter")); + QVBoxLayout v; + w.setLayout(&v); + QPlainTextEdit e; + v.addWidget(&e); + QPalette p(e.palette()); + p.setColor(QPalette::Base, QColor("#010101")); + p.setColor(QPalette::Text, QColor("#eaeaea")); + e.setPalette(p); + QFont f(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + f.setPointSize(16); + e.setFont(f); + e.setMinimumSize(400, 500); + bool guard = false; + QObject::connect(&e, &QPlainTextEdit::textChanged, [&]() { + if (guard) + return; + struct highlight_span *spans = highlight_config(e.toPlainText().toLatin1().data()); + if (!spans) + return; + QTextCursor cursor(e.document()); + QTextCharFormat format; + cursor.beginEditBlock(); + cursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor); + cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); + format.setForeground(p.color(QPalette::Text)); + format.setUnderlineStyle(QTextCharFormat::NoUnderline); + cursor.mergeCharFormat(format); + for (struct highlight_span *span = spans; span->type != HighlightEnd; ++span) { + cursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor); + cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, span->start); + cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, span->len); + format.setForeground(colormap[span->type]); + format.setUnderlineStyle(span->type == HighlightError ? QTextCharFormat::SpellCheckUnderline : QTextCharFormat::NoUnderline); + cursor.mergeCharFormat(format); + } + free(spans); + guard = true; + cursor.endEditBlock(); + guard = false; + }); + QPushButton b; + v.addWidget(&b); + b.setText(QObject::tr("&Randomize colors")); + QObject::connect(&b, &QPushButton::clicked, [&]() { + for (size_t i = 0; i < sizeof(colormap) / sizeof(colormap[0]); ++i) + colormap[i] = QColor::fromHsl(qrand() % 360, qrand() % 192 + 64, qrand() % 128 + 128); + e.setPlainText(e.toPlainText()); + }); + w.show(); + return a.exec(); +} diff --git a/contrib/highlighter/gui/highlight.pro b/contrib/highlighter/gui/highlight.pro new file mode 100644 index 0000000..f25667c --- /dev/null +++ b/contrib/highlighter/gui/highlight.pro @@ -0,0 +1,5 @@ +QT += core gui widgets +TEMPLATE = app +TARGET = highlight +SOURCES += highlight.cpp ../highlighter.c +HEADERS += ../highlighter.h diff --git a/contrib/highlighter/highlight.c b/contrib/highlighter/highlight.c new file mode 100644 index 0000000..b03f792 --- /dev/null +++ b/contrib/highlighter/highlight.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2015-2019 Jason A. Donenfeld . All Rights Reserved. + */ + +#include +#include +#include +#include "highlighter.h" + +#define TERMINAL_FG_BLACK "\x1b[30m" +#define TERMINAL_FG_RED "\x1b[31m" +#define TERMINAL_FG_GREEN "\x1b[32m" +#define TERMINAL_FG_YELLOW "\x1b[33m" +#define TERMINAL_FG_BLUE "\x1b[34m" +#define TERMINAL_FG_MAGENTA "\x1b[35m" +#define TERMINAL_FG_CYAN "\x1b[36m" +#define TERMINAL_FG_WHITE "\x1b[37m" +#define TERMINAL_FG_DEFAULT "\x1b[39m" + +#define TERMINAL_BG_BLACK "\x1b[40m" +#define TERMINAL_BG_RED "\x1b[41m" +#define TERMINAL_BG_GREEN "\x1b[42m" +#define TERMINAL_BG_YELLOW "\x1b[43m" +#define TERMINAL_BG_BLUE "\x1b[44m" +#define TERMINAL_BG_MAGENTA "\x1b[45m" +#define TERMINAL_BG_CYAN "\x1b[46m" +#define TERMINAL_BG_WHITE "\x1b[47m" +#define TERMINAL_BG_DEFAULT "\x1b[49m" + +#define TERMINAL_BOLD "\x1b[1m" +#define TERMINAL_NO_BOLD "\x1b[22m" +#define TERMINAL_UNDERLINE "\x1b[4m" +#define TERMINAL_NO_UNDERLINE "\x1b[24m" + +#define TERMINAL_RESET "\x1b[0m" + +static const char *colormap[] = { + [HighlightSection] = TERMINAL_FG_BLACK TERMINAL_BOLD, + [HighlightField] = TERMINAL_FG_BLUE TERMINAL_BOLD, + [HighlightPrivateKey] = TERMINAL_FG_YELLOW TERMINAL_BOLD, + [HighlightPublicKey] = TERMINAL_FG_YELLOW TERMINAL_BOLD, + [HighlightPresharedKey] = TERMINAL_FG_YELLOW TERMINAL_BOLD, + [HighlightIP] = TERMINAL_FG_GREEN, + [HighlightCidr] = TERMINAL_FG_YELLOW, + [HighlightHost] = TERMINAL_FG_GREEN TERMINAL_BOLD, + [HighlightPort] = TERMINAL_FG_MAGENTA, + [HighlightMTU] = TERMINAL_FG_BLUE, + [HighlightKeepalive] = TERMINAL_FG_BLUE, + [HighlightComment] = TERMINAL_FG_CYAN, + [HighlightDelimiter] = TERMINAL_FG_CYAN, +#ifndef MOBILE_WGQUICK_SUBSET + [HighlightTable] = TERMINAL_FG_BLUE, + [HighlightFwMark] = TERMINAL_FG_BLUE, + [HighlightSaveConfig] = TERMINAL_FG_BLUE, + [HighlightCmd] = TERMINAL_FG_WHITE, +#endif + [HighlightError] = TERMINAL_FG_RED TERMINAL_UNDERLINE +}; + +int main(int argc, char *argv[]) +{ + char input[1024 * 1024]; + struct highlight_span *spans; + size_t last = 0, total_len; + + total_len = fread(input, 1, sizeof(input) - 1, stdin); + input[total_len] = '\0'; + spans = highlight_config(input); + + fputs(TERMINAL_RESET, stdout); + for (struct highlight_span *span = spans; span->type != HighlightEnd; ++span) { + fwrite(input + last, 1, span->start - last, stdout); + fputs(colormap[span->type], stdout); + fwrite(input + span->start, 1, span->len, stdout); + fputs(TERMINAL_RESET, stdout); + last = span->start + span->len; + } + fwrite(input + last, 1, total_len - last, stdout); + + free(spans); + return 0; +} diff --git a/contrib/highlighter/highlighter.c b/contrib/highlighter/highlighter.c new file mode 100644 index 0000000..171a84c --- /dev/null +++ b/contrib/highlighter/highlighter.c @@ -0,0 +1,620 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2015-2019 Jason A. Donenfeld . All Rights Reserved. + */ + +#include +#include +#include +#include +#include +#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 < 43; ++i) { + if (!is_decimal(s.s[i]) && !is_alphabet(s.s[i]) && + s.s[i] != '/' && 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 (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 (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 (!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; +} diff --git a/contrib/highlighter/highlighter.h b/contrib/highlighter/highlighter.h new file mode 100644 index 0000000..c004e12 --- /dev/null +++ b/contrib/highlighter/highlighter.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2015-2019 Jason A. Donenfeld . All Rights Reserved. + */ + +#include + +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);