diff options
author | Simon Rozman <simon@rozman.si> | 2020-10-28 17:30:58 +0100 |
---|---|---|
committer | Jason A. Donenfeld <Jason@zx2c4.com> | 2020-11-13 14:42:54 +0100 |
commit | ea200f82c3e855fcb2b5fd56fc5800374b80514f (patch) | |
tree | 3b9e7b8ff1792c4bb4f9b67d6e7edebba6201cea /ui/syntax | |
parent | build: remove duplicated ld flags (diff) | |
download | wireguard-windows-ea200f82c3e855fcb2b5fd56fc5800374b80514f.tar.xz wireguard-windows-ea200f82c3e855fcb2b5fd56fc5800374b80514f.zip |
syntax: port to go
Arm has no CGo support, so port the syntax editor C code to Go and hope
that it's fast enough. This is a pretty literal/unsafe translation from
the C.
Signed-off-by: Simon Rozman <simon@rozman.si>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
Diffstat (limited to 'ui/syntax')
-rw-r--r-- | ui/syntax/highlighter.c | 641 | ||||
-rw-r--r-- | ui/syntax/highlighter.go | 623 | ||||
-rw-r--r-- | ui/syntax/highlighter.h | 39 | ||||
-rw-r--r-- | ui/syntax/syntaxedit.c | 415 | ||||
-rw-r--r-- | ui/syntax/syntaxedit.go | 422 | ||||
-rw-r--r-- | ui/syntax/syntaxedit.h | 31 |
6 files changed, 1013 insertions, 1158 deletions
diff --git a/ui/syntax/highlighter.c b/ui/syntax/highlighter.c deleted file mode 100644 index d89feda1..00000000 --- a/ui/syntax/highlighter.c +++ /dev/null @@ -1,641 +0,0 @@ -// 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); -} - -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: - if (is_valid_ipv4(s) || is_valid_ipv6(s)) - append_highlight_span(ret, parent.s, s, HighlightIP); - else if (is_valid_hostname(s)) - append_highlight_span(ret, parent.s, s, HighlightHost); - else - append_highlight_span(ret, parent.s, s, 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/ui/syntax/highlighter.go b/ui/syntax/highlighter.go new file mode 100644 index 00000000..fa6a6530 --- /dev/null +++ b/ui/syntax/highlighter.go @@ -0,0 +1,623 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2020 WireGuard LLC. All Rights Reserved. + * + * This is a direct translation of the original C, and for that reason, it's pretty unusual Go code: + * https://git.zx2c4.com/wireguard-tools/tree/contrib/highlighter/highlighter.c + */ + +package syntax + +import "unsafe" + +type highlight int + +const ( + highlightSection highlight = iota + highlightField + highlightPrivateKey + highlightPublicKey + highlightPresharedKey + highlightIP + highlightCidr + highlightHost + highlightPort + highlightMTU + highlightKeepalive + highlightComment + highlightDelimiter + highlightCmd + highlightError +) + +func validateHighlight(isValid bool, t highlight) highlight { + if isValid { + return t + } + return highlightError +} + +type highlightSpan struct { + t highlight + s int + len int +} + +func isDecimal(c byte) bool { + return c >= '0' && c <= '9' +} + +func isHexadecimal(c byte) bool { + return isDecimal(c) || (c|32) >= 'a' && (c|32) <= 'f' +} + +func isAlphabet(c byte) bool { + return (c|32) >= 'a' && (c|32) <= 'z' +} + +type stringSpan struct { + s *byte + len int +} + +func (s stringSpan) at(i int) *byte { + return (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(s.s)) + uintptr(i))) +} + +func (s stringSpan) isSame(c string) bool { + if s.len != len(c) { + return false + } + cb := ([]byte)(c) + for i := 0; i < s.len; i++ { + if *s.at(i) != cb[i] { + return false + } + } + return true +} + +func (s stringSpan) isCaselessSame(c string) bool { + if s.len != len(c) { + return false + } + cb := ([]byte)(c) + for i := 0; i < s.len; i++ { + a := *s.at(i) + b := cb[i] + if a-'a' < 26 { + a &= 95 + } + if b-'a' < 26 { + b &= 95 + } + if a != b { + return false + } + } + return true +} + +func (s stringSpan) isValidKey() bool { + if s.len != 44 || *s.at(43) != '=' { + return false + } + for i := 0; i < 42; i++ { + if !isDecimal(*s.at(i)) && !isAlphabet(*s.at(i)) && *s.at(i) != '/' && *s.at(i) != '+' { + return false + } + } + switch *s.at(42) { + case 'A', 'E', 'I', 'M', 'Q', 'U', 'Y', 'c', 'g', 'k', 'o', 's', 'w', '4', '8', '0': + return true + } + return false +} + +func (s stringSpan) isValidHostname() bool { + numDigit := 0 + numEntity := s.len + if s.len > 63 || s.len == 0 { + return false + } + if *s.s == '-' || *s.at(s.len - 1) == '-' { + return false + } + if *s.s == '.' || *s.at(s.len - 1) == '.' { + return false + } + for i := 0; i < s.len; i++ { + if isDecimal(*s.at(i)) { + numDigit++ + continue + } + if *s.at(i) == '.' { + numEntity-- + continue + } + if !isAlphabet(*s.at(i)) && *s.at(i) != '-' { + return false + } + if i != 0 && *s.at(i) == '.' && *s.at(i - 1) == '.' { + return false + } + } + return numDigit != numEntity +} + +func (s stringSpan) isValidIPv4() bool { + pos := 0 + for i := 0; i < 4 && pos < s.len; i++ { + val := 0 + j := 0 + for ; j < 3 && pos+j < s.len && isDecimal(*s.at(pos + j)); j++ { + val = 10*val + int(*s.at(pos + j)-'0') + } + if j == 0 || j > 1 && *s.at(pos) == '0' || val > 255 { + return false + } + if pos+j == s.len && i == 3 { + return true + } + if *s.at(pos + j) != '.' { + return false + } + pos += j + 1 + } + return false +} + +func (s stringSpan) isValidIPv6() bool { + if s.len < 2 { + return false + } + pos := 0 + if *s.at(0) == ':' { + if *s.at(1) != ':' { + return false + } + pos = 1 + } + if *s.at(s.len - 1) == ':' && *s.at(s.len - 2) != ':' { + return false + } + seenColon := false + for i := 0; pos < s.len; i++ { + if *s.at(pos) == ':' && !seenColon { + seenColon = true + pos++ + if pos == s.len { + break + } + if i == 7 { + return false + } + continue + } + j := 0 + for ; ; j++ { + if j < 4 && pos+j < s.len && isHexadecimal(*s.at(pos + j)) { + continue + } + break + } + if j == 0 { + return false + } + if pos+j == s.len && (seenColon || i == 7) { + break + } + if i == 7 { + return false + } + if *s.at(pos + j) != ':' { + if *s.at(pos + j) != '.' || i < 6 && !seenColon { + return false + } + return stringSpan{s.at(pos), s.len - pos}.isValidIPv4() + } + pos += j + 1 + } + return true +} + +func (s stringSpan) isValidUint(supportHex bool, min uint64, max uint64) bool { + // Bound this around 32 bits, so that we don't have to write overflow logic. + if s.len > 10 || s.len == 0 { + return false + } + val := uint64(0) + if supportHex && s.len > 2 && *s.s == '0' && *s.at(1) == 'x' { + for i := 2; i < s.len; i++ { + if *s.at(i)-'0' < 10 { + val = 16*val + uint64(*s.at(i)-'0') + } else if (*s.at(i))|32-'a' < 6 { + val = 16*val + uint64((*s.at(i)|32)-'a'+10) + } else { + return false + } + } + } else { + for i := 0; i < s.len; i++ { + if !isDecimal(*s.at(i)) { + return false + } + val = 10*val + uint64(*s.at(i)-'0') + } + } + return val <= max && val >= min +} + +func (s stringSpan) isValidPort() bool { + return s.isValidUint(false, 0, 65535) +} + +func (s stringSpan) isValidMTU() bool { + return s.isValidUint(false, 576, 65535) +} + +func (s stringSpan) isValidPersistentKeepAlive() bool { + if s.isSame("off") { + return true + } + return s.isValidUint(false, 0, 65535) +} + +// It's probably not worthwhile to try to validate a bash expression. So instead we just demand non-zero length. +func (s stringSpan) isValidPrePostUpDown() bool { + return s.len != 0 +} + +func (s stringSpan) isValidScope() bool { + if s.len > 64 || s.len == 0 { + return false + } + for i := 0; i < s.len; i++ { + if isAlphabet(*s.at(i)) && !isDecimal(*s.at(i)) && *s.at(i) != '_' && *s.at(i) != '=' && *s.at(i) != '+' && *s.at(i) != '.' && *s.at(i) != '-' { + return false + } + } + return true +} + +func (s stringSpan) isValidEndpoint() bool { + if s.len == 0 { + return false + } + if *s.s == '[' { + seenScope := false + hostspan := stringSpan{s.at(1), 0} + for i := 1; i < s.len; i++ { + if *s.at(i) == '%' { + if seenScope { + return false + } + seenScope = true + if !hostspan.isValidIPv6() { + return false + } + hostspan = stringSpan{s.at(i + 1), 0} + } else if *s.at(i) == ']' { + if seenScope { + if !hostspan.isValidScope() { + return false + } + } else if !hostspan.isValidIPv6() { + return false + } + if i == s.len-1 || *s.at((i + 1)) != ':' { + return false + } + return stringSpan{s.at(i + 2), s.len - i - 2}.isValidPort() + } else { + hostspan.len++ + } + } + return false + } + for i := 0; i < s.len; i++ { + if *s.at(i) == ':' { + host := stringSpan{s.s, i} + port := stringSpan{s.at(i + 1), s.len - i - 1} + return port.isValidPort() && (host.isValidIPv4() || host.isValidHostname()) + } + } + return false +} + +func (s stringSpan) isValidNetwork() bool { + for i := 0; i < s.len; i++ { + if *s.at(i) == '/' { + ip := stringSpan{s.s, i} + cidr := stringSpan{s.at(i + 1), s.len - i - 1} + cidrval := uint16(0) + if cidr.len > 3 || cidr.len == 0 { + return false + } + for j := 0; j < cidr.len; j++ { + if !isDecimal(*cidr.at(j)) { + return false + } + cidrval = 10*cidrval + uint16(*cidr.at(j)-'0') + } + if ip.isValidIPv4() { + return cidrval <= 32 + } else if ip.isValidIPv6() { + return cidrval <= 128 + } + return false + } + } + return s.isValidIPv4() || s.isValidIPv6() +} + +type field int32 + +const ( + fieldInterfaceSection field = iota + fieldPrivateKey + fieldListenPort + fieldAddress + fieldDNS + fieldMTU + fieldPreUp + fieldPostUp + fieldPreDown + fieldPostDown + fieldPeerSection + fieldPublicKey + fieldPresharedKey + fieldAllowedIPs + fieldEndpoint + fieldPersistentKeepalive + fieldInvalid +) + +func sectionForField(t field) field { + if t > fieldInterfaceSection && t < fieldPeerSection { + return fieldInterfaceSection + } + if t > fieldPeerSection && t < fieldInvalid { + return fieldPeerSection + } + return fieldInvalid +} + +func (s stringSpan) field() field { + switch { + case s.isCaselessSame("PrivateKey"): + return fieldPrivateKey + case s.isCaselessSame("ListenPort"): + return fieldListenPort + case s.isCaselessSame("Address"): + return fieldAddress + case s.isCaselessSame("DNS"): + return fieldDNS + case s.isCaselessSame("MTU"): + return fieldMTU + case s.isCaselessSame("PublicKey"): + return fieldPublicKey + case s.isCaselessSame("PresharedKey"): + return fieldPresharedKey + case s.isCaselessSame("AllowedIPs"): + return fieldAllowedIPs + case s.isCaselessSame("Endpoint"): + return fieldEndpoint + case s.isCaselessSame("PersistentKeepalive"): + return fieldPersistentKeepalive + } + /* TODO: uncomment this once we support these in the client + case s.isCaselessSame("PreUp"): + return fieldPreUp + case s.isCaselessSame("PostUp"): + return fieldPostUp + case s.isCaselessSame("PreDown"): + return fieldPreDown + case s.isCaselessSame("PostDown"): + return fieldPostDown + */ + return fieldInvalid +} + +func (s stringSpan) sectionType() field { + switch { + case s.isCaselessSame("[Peer]"): + return fieldPeerSection + case s.isCaselessSame("[Interface]"): + return fieldInterfaceSection + } + return fieldInvalid +} + +type highlightSpanArray []highlightSpan + +func (hsa *highlightSpanArray) append(o *byte, s stringSpan, t highlight) { + if s.len == 0 { + return + } + *hsa = append(*hsa, highlightSpan{t, int((uintptr(unsafe.Pointer(s.s))) - (uintptr(unsafe.Pointer(o)))), s.len}) +} + +func (hsa *highlightSpanArray) highlightMultivalueValue(parent stringSpan, s stringSpan, section field) { + switch section { + case fieldDNS: + if s.isValidIPv4() || s.isValidIPv6() { + hsa.append(parent.s, s, highlightIP) + } else if s.isValidHostname() { + hsa.append(parent.s, s, highlightHost) + } else { + hsa.append(parent.s, s, highlightError) + } + case fieldAddress, fieldAllowedIPs: + if !s.isValidNetwork() { + hsa.append(parent.s, s, highlightError) + break + } + slash := 0 + for ; slash < s.len; slash++ { + if *s.at(slash) == '/' { + break + } + } + if slash == s.len { + hsa.append(parent.s, s, highlightIP) + } else { + hsa.append(parent.s, stringSpan{s.s, slash}, highlightIP) + hsa.append(parent.s, stringSpan{s.at(slash), 1}, highlightDelimiter) + hsa.append(parent.s, stringSpan{s.at(slash + 1), s.len - slash - 1}, highlightCidr) + } + default: + hsa.append(parent.s, s, highlightError) + } +} + +func (hsa *highlightSpanArray) highlightMultivalue(parent stringSpan, s stringSpan, section field) { + currentSpan := stringSpan{s.s, 0} + lenAtLastSpace := 0 + for i := 0; i < s.len; i++ { + if *s.at(i) == ',' { + currentSpan.len = lenAtLastSpace + hsa.highlightMultivalueValue(parent, currentSpan, section) + hsa.append(parent.s, stringSpan{s.at(i), 1}, highlightDelimiter) + lenAtLastSpace = 0 + currentSpan = stringSpan{s.at(i + 1), 0} + } else if *s.at(i) == ' ' || *s.at(i) == '\t' { + if s.at(i) == currentSpan.s && currentSpan.len == 0 { + currentSpan.s = currentSpan.at(1) + } else { + currentSpan.len++ + } + } else { + currentSpan.len++ + lenAtLastSpace = currentSpan.len + } + } + currentSpan.len = lenAtLastSpace + if currentSpan.len != 0 { + hsa.highlightMultivalueValue(parent, currentSpan, section) + } else if (*hsa)[len(*hsa)-1].t == highlightDelimiter { + (*hsa)[len(*hsa)-1].t = highlightError + } +} + +func (hsa *highlightSpanArray) highlightValue(parent stringSpan, s stringSpan, section field) { + switch section { + case fieldPrivateKey: + hsa.append(parent.s, s, validateHighlight(s.isValidKey(), highlightPrivateKey)) + case fieldPublicKey: + hsa.append(parent.s, s, validateHighlight(s.isValidKey(), highlightPublicKey)) + case fieldPresharedKey: + hsa.append(parent.s, s, validateHighlight(s.isValidKey(), highlightPresharedKey)) + case fieldMTU: + hsa.append(parent.s, s, validateHighlight(s.isValidMTU(), highlightMTU)) + case fieldPreUp, fieldPostUp, fieldPreDown, fieldPostDown: + hsa.append(parent.s, s, validateHighlight(s.isValidPrePostUpDown(), highlightCmd)) + case fieldListenPort: + hsa.append(parent.s, s, validateHighlight(s.isValidPort(), highlightPort)) + case fieldPersistentKeepalive: + hsa.append(parent.s, s, validateHighlight(s.isValidPersistentKeepAlive(), highlightKeepalive)) + case fieldEndpoint: + if !s.isValidEndpoint() { + hsa.append(parent.s, s, highlightError) + break + } + colon := s.len + for colon > 0 { + colon-- + if *s.at(colon) == ':' { + break + } + } + hsa.append(parent.s, stringSpan{s.s, colon}, highlightHost) + hsa.append(parent.s, stringSpan{s.at(colon), 1}, highlightDelimiter) + hsa.append(parent.s, stringSpan{s.at(colon + 1), s.len - colon - 1}, highlightPort) + case fieldAddress, fieldDNS, fieldAllowedIPs: + hsa.highlightMultivalue(parent, s, section) + default: + hsa.append(parent.s, s, highlightError) + } +} + +func highlightConfig(config string) []highlightSpan { + var ret highlightSpanArray + b := append([]byte(config), 0) + s := stringSpan{&b[0], len(b) - 1} + currentSpan := stringSpan{s.s, 0} + currentSection := fieldInvalid + currentField := fieldInvalid + const ( + onNone = iota + onKey + onValue + onComment + onSection + ) + state := onNone + lenAtLastSpace := 0 + equalsLocation := 0 + for i := 0; i <= s.len; i++ { + if i == s.len || *s.at(i) == '\n' || state != onComment && *s.at(i) == '#' { + if state == onKey { + currentSpan.len = lenAtLastSpace + ret.append(s.s, currentSpan, highlightError) + } else if state == onValue { + if currentSpan.len != 0 { + ret.append(s.s, stringSpan{s.at(equalsLocation), 1}, highlightDelimiter) + currentSpan.len = lenAtLastSpace + ret.highlightValue(s, currentSpan, currentField) + } else { + ret.append(s.s, stringSpan{s.at(equalsLocation), 1}, highlightError) + } + } else if state == onSection { + currentSpan.len = lenAtLastSpace + currentSection = currentSpan.sectionType() + ret.append(s.s, currentSpan, validateHighlight(currentSection != fieldInvalid, highlightSection)) + } else if state == onComment { + ret.append(s.s, currentSpan, highlightComment) + } + if i == s.len { + break + } + lenAtLastSpace = 0 + currentField = fieldInvalid + if *s.at(i) == '#' { + currentSpan = stringSpan{s.at(i), 1} + state = onComment + } else { + currentSpan = stringSpan{s.at(i + 1), 0} + state = onNone + } + } else if state == onComment { + currentSpan.len++ + } else if *s.at(i) == ' ' || *s.at(i) == '\t' { + if s.at(i) == currentSpan.s && currentSpan.len == 0 { + currentSpan.s = currentSpan.at(1) + } else { + currentSpan.len++ + } + } else if *s.at(i) == '=' && state == onKey { + currentSpan.len = lenAtLastSpace + currentField = currentSpan.field() + section := sectionForField(currentField) + if section == fieldInvalid || currentField == fieldInvalid || section != currentSection { + ret.append(s.s, currentSpan, highlightError) + } else { + ret.append(s.s, currentSpan, highlightField) + } + equalsLocation = i + currentSpan = stringSpan{s.at(i + 1), 0} + state = onValue + } else { + if state == onNone { + if *s.at(i) == '[' { + state = onSection + } else { + state = onKey + } + } + currentSpan.len++ + lenAtLastSpace = currentSpan.len + } + } + return ([]highlightSpan)(ret) +} diff --git a/ui/syntax/highlighter.h b/ui/syntax/highlighter.h deleted file mode 100644 index 0a86de78..00000000 --- a/ui/syntax/highlighter.h +++ /dev/null @@ -1,39 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Copyright (C) 2015-2020 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); diff --git a/ui/syntax/syntaxedit.c b/ui/syntax/syntaxedit.c deleted file mode 100644 index c18d496b..00000000 --- a/ui/syntax/syntaxedit.c +++ /dev/null @@ -1,415 +0,0 @@ -/* SPDX-License-Identifier: MIT - * - * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. - */ - -#include <stdlib.h> -#include <stdbool.h> -#include <stdint.h> -#include <string.h> -#include <assert.h> -#include <windows.h> -#include <windowsx.h> -#include <richedit.h> -#include <richole.h> -#include <tom.h> - -#include "syntaxedit.h" -#include "highlighter.h" - -const GUID CDECL IID_ITextDocument = { 0x8CC497C0, 0xA1DF, 0x11CE, { 0x80, 0x98, 0x00, 0xAA, 0x00, 0x47, 0xBE, 0x5D } }; - -struct syntaxedit_data { - IRichEditOle *irich; - ITextDocument *idoc; - enum block_state last_block_state; - LONG yheight; - bool highlight_guard; -}; - -static WNDPROC parent_proc; - -struct span_style { - COLORREF color; - DWORD effects; -}; - -static const struct span_style stylemap[] = { - [HighlightSection] = { .color = RGB(0x32, 0x6D, 0x74), .effects = CFE_BOLD }, - [HighlightField] = { .color = RGB(0x9B, 0x23, 0x93), .effects = CFE_BOLD }, - [HighlightPrivateKey] = { .color = RGB(0x64, 0x38, 0x20) }, - [HighlightPublicKey] = { .color = RGB(0x64, 0x38, 0x20) }, - [HighlightPresharedKey] = { .color = RGB(0x64, 0x38, 0x20) }, - [HighlightIP] = { .color = RGB(0x0E, 0x0E, 0xFF) }, - [HighlightCidr] = { .color = RGB(0x81, 0x5F, 0x03) }, - [HighlightHost] = { .color = RGB(0x0E, 0x0E, 0xFF) }, - [HighlightPort] = { .color = RGB(0x81, 0x5F, 0x03) }, - [HighlightMTU] = { .color = RGB(0x1C, 0x00, 0xCF) }, - [HighlightKeepalive] = { .color = RGB(0x1C, 0x00, 0xCF) }, - [HighlightComment] = { .color = RGB(0x53, 0x65, 0x79), .effects = CFE_ITALIC }, - [HighlightDelimiter] = { .color = RGB(0x00, 0x00, 0x00) }, -#ifndef MOBILE_WGQUICK_SUBSET - [HighlightTable] = { .color = RGB(0x1C, 0x00, 0xCF) }, - [HighlightFwMark] = { .color = RGB(0x1C, 0x00, 0xCF) }, - [HighlightSaveConfig] = { .color = RGB(0x81, 0x5F, 0x03) }, - [HighlightCmd] = { .color = RGB(0x63, 0x75, 0x89) }, -#endif - [HighlightError] = { .color = RGB(0xC4, 0x1A, 0x16), .effects = CFE_UNDERLINE } -}; - -static void evaluate_untunneled_blocking(struct syntaxedit_data *this, HWND hWnd, const char *msg, struct highlight_span *spans) -{ - enum block_state state = InevaluableBlockingUntunneledTraffic; - bool on_allowedips = false; - bool seen_peer = false; - bool seen_v6_00 = false, seen_v4_00 = false; - bool seen_v6_01 = false, seen_v6_80001 = false, seen_v4_01 = false, seen_v4_1281 = false; - - for (struct highlight_span *span = spans; span->type != HighlightEnd; ++span) { - switch (span->type) { - case HighlightError: - goto done; - case HighlightSection: - if (span->len != 6 || strncasecmp(&msg[span->start], "[peer]", 6)) - break; - if (!seen_peer) - seen_peer = true; - else - goto done; - break; - case HighlightField: - on_allowedips = span->len == 10 && !strncasecmp(&msg[span->start], "allowedips", 10); - break; - case HighlightIP: - if (!on_allowedips || !seen_peer) - break; - if ((span + 1)->type != HighlightDelimiter || (span + 2)->type != HighlightCidr) - break; - if ((span + 2)->len != 1) - break; - if (msg[(span + 2)->start] == '0') { - if (span->len == 7 && !strncmp(&msg[span->start], "0.0.0.0", 7)) - seen_v4_00 = true; - else if (span->len == 2 && !strncmp(&msg[span->start], "::", 2)) - seen_v6_00 = true; - } else if (msg[(span + 2)->start] == '1') { - if (span->len == 7 && !strncmp(&msg[span->start], "0.0.0.0", 7)) - seen_v4_01 = true; - else if (span->len == 9 && !strncmp(&msg[span->start], "128.0.0.0", 9)) - seen_v4_1281 = true; - else if (span->len == 2 && !strncmp(&msg[span->start], "::", 2)) - seen_v6_01 = true; - else if (span->len == 6 && !strncmp(&msg[span->start], "8000::", 6)) - seen_v6_80001 = true; - } - break; - } - } - - if (seen_v4_00 || seen_v6_00) - state = BlockingUntunneledTraffic; - else if ((seen_v4_01 && seen_v4_1281) || (seen_v6_01 && seen_v6_80001)) - state = NotBlockingUntunneledTraffic; - -done: - if (state != this->last_block_state) { - SendMessage(hWnd, SE_TRAFFIC_BLOCK, 0, state); - this->last_block_state = state; - } -} - -static void highlight_text(HWND hWnd) -{ - struct syntaxedit_data *this = (struct syntaxedit_data *)GetWindowLongPtr(hWnd, GWLP_USERDATA); - GETTEXTLENGTHEX gettextlengthex = { - .flags = GTL_NUMBYTES, - .codepage = CP_ACP /* Probably CP_UTF8 would be better, but (wine at least) returns utf32 sizes. */ - }; - GETTEXTEX gettextex = { - .flags = GT_NOHIDDENTEXT, - .codepage = gettextlengthex.codepage - }; - CHARFORMAT2 format = { - .cbSize = sizeof(CHARFORMAT2), - .dwMask = CFM_COLOR | CFM_CHARSET | CFM_SIZE | CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE, - .dwEffects = CFE_AUTOCOLOR, - .yHeight = this->yheight ?: 20 * 10, - .bCharSet = ANSI_CHARSET - }; - LRESULT msg_size; - char *msg = NULL; - struct highlight_span *spans = NULL; - CHARRANGE orig_selection; - POINT original_scroll; - bool found_private_key = false; - COLORREF bg_color, bg_inversion; - size_t num_spans; - - if (this->highlight_guard) - return; - this->highlight_guard = true; - - msg_size = SendMessage(hWnd, EM_GETTEXTLENGTHEX, (WPARAM)&gettextlengthex, 0); - if (msg_size == E_INVALIDARG) - return; - gettextex.cb = msg_size + 1; - - msg = malloc(msg_size + 1); - if (!msg) - goto out; - if (SendMessage(hWnd, EM_GETTEXTEX, (WPARAM)&gettextex, (LPARAM)msg) <= 0) - goto out; - - /* By default we get CR not CRLF, so just convert to LF. */ - for (size_t i = 0; i < msg_size; ++i) { - if (msg[i] == '\r') - msg[i] = '\n'; - } - - spans = highlight_config(msg); - if (!spans) - goto out; - - evaluate_untunneled_blocking(this, hWnd, msg, spans); - - this->idoc->lpVtbl->Undo(this->idoc, tomSuspend, NULL); - SendMessage(hWnd, EM_SETEVENTMASK, 0, 0); - SendMessage(hWnd, WM_SETREDRAW, FALSE, 0); - SendMessage(hWnd, EM_EXGETSEL, 0, (LPARAM)&orig_selection); - SendMessage(hWnd, EM_GETSCROLLPOS, 0, (LPARAM)&original_scroll); - SendMessage(hWnd, EM_HIDESELECTION, TRUE, 0); - SendMessage(hWnd, EM_SETCHARFORMAT, SCF_ALL, (LPARAM)&format); - bg_color = GetSysColor(COLOR_WINDOW); - bg_inversion = (bg_color & RGB(0xFF, 0xFF, 0xFF)) ^ RGB(0xFF, 0xFF, 0xFF); - SendMessage(hWnd, EM_SETBKGNDCOLOR, 0, bg_color); - num_spans = _msize(spans) / sizeof(spans[0]); - for (struct highlight_span *span = spans; span->type != HighlightEnd; ++span) { - if (num_spans <= 2048) { - CHARRANGE selection = { span->start, span->len + span->start }; - SendMessage(hWnd, EM_EXSETSEL, 0, (LPARAM)&selection); - format.crTextColor = stylemap[span->type].color ^ bg_inversion; - format.dwEffects = stylemap[span->type].effects; - SendMessage(hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&format); - } - if (span->type == HighlightPrivateKey && !found_private_key) { - /* Rather than allocating a new string, we mangle this one, since (for now) we don't use msg again. */ - msg[span->start + span->len] = '\0'; - SendMessage(hWnd, SE_PRIVATE_KEY, 0, (LPARAM)&msg[span->start]); - found_private_key = true; - } - } - SendMessage(hWnd, EM_SETSCROLLPOS, 0, (LPARAM)&original_scroll); - SendMessage(hWnd, EM_EXSETSEL, 0, (LPARAM)&orig_selection); - SendMessage(hWnd, EM_HIDESELECTION, FALSE, 0); - SendMessage(hWnd, WM_SETREDRAW, TRUE, 0); - RedrawWindow(hWnd, NULL, NULL, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN); - SendMessage(hWnd, EM_SETEVENTMASK, 0, ENM_CHANGE); - this->idoc->lpVtbl->Undo(this->idoc, tomResume, NULL); - if (!found_private_key) - SendMessage(hWnd, SE_PRIVATE_KEY, 0, 0); - -out: - free(spans); - free(msg); - this->highlight_guard = false; -} - -static void context_menu(HWND hWnd, INT x, INT y) -{ - GETTEXTLENGTHEX gettextlengthex = { - .flags = GTL_DEFAULT, - .codepage = CP_ACP - }; - /* This disturbing hack grabs the system edit menu normally used for the EDIT control. */ - HMENU popup, menu = LoadMenuW(GetModuleHandleW(L"comctl32.dll"), MAKEINTRESOURCEW(1)); - CHARRANGE selection = { 0 }; - bool has_selection, can_selectall, can_undo, can_paste; - UINT cmd; - - if (!menu) - return; - - SendMessage(hWnd, EM_EXGETSEL, 0, (LPARAM)&selection); - has_selection = selection.cpMax - selection.cpMin; - can_selectall = selection.cpMin || (selection.cpMax < SendMessage(hWnd, EM_GETTEXTLENGTHEX, (WPARAM)&gettextlengthex, 0)); - can_undo = SendMessage(hWnd, EM_CANUNDO, 0, 0); - can_paste = SendMessage(hWnd, EM_CANPASTE, CF_TEXT, 0); - - popup = GetSubMenu(menu, 0); - EnableMenuItem(popup, WM_UNDO, MF_BYCOMMAND | (can_undo ? MF_ENABLED : MF_GRAYED)); - EnableMenuItem(popup, WM_CUT, MF_BYCOMMAND | (has_selection ? MF_ENABLED : MF_GRAYED)); - EnableMenuItem(popup, WM_COPY, MF_BYCOMMAND | (has_selection ? MF_ENABLED : MF_GRAYED)); - EnableMenuItem(popup, WM_PASTE, MF_BYCOMMAND | (can_paste ? MF_ENABLED : MF_GRAYED)); - EnableMenuItem(popup, WM_CLEAR, MF_BYCOMMAND | (has_selection ? MF_ENABLED : MF_GRAYED)); - EnableMenuItem(popup, EM_SETSEL, MF_BYCOMMAND | (can_selectall ? MF_ENABLED : MF_GRAYED)); - - /* Delete items that we don't handle. */ - for (int ctl = GetMenuItemCount(popup) - 1; ctl >= 0; --ctl) { - MENUITEMINFOW menu_item = { - .cbSize = sizeof(MENUITEMINFOW), - .fMask = MIIM_FTYPE | MIIM_ID - }; - if (!GetMenuItemInfoW(popup, ctl, MF_BYPOSITION, &menu_item)) - continue; - if (menu_item.fType & MFT_SEPARATOR) - continue; - switch (menu_item.wID) { - case WM_UNDO: - case WM_CUT: - case WM_COPY: - case WM_PASTE: - case WM_CLEAR: - case EM_SETSEL: - continue; - } - DeleteMenu(popup, ctl, MF_BYPOSITION); - } - /* Delete trailing and adjacent separators. */ - for (int ctl = GetMenuItemCount(popup) - 1, end = true; ctl >= 0; --ctl) { - MENUITEMINFOW menu_item = { - .cbSize = sizeof(MENUITEMINFOW), - .fMask = MIIM_FTYPE - }; - if (!GetMenuItemInfoW(popup, ctl, MF_BYPOSITION, &menu_item)) - continue; - if (!(menu_item.fType & MFT_SEPARATOR)) { - end = false; - continue; - } - if (!end && ctl) { - if (!GetMenuItemInfoW(popup, ctl - 1, MF_BYPOSITION, &menu_item)) - continue; - if (!(menu_item.fType & MFT_SEPARATOR)) - continue; - } - DeleteMenu(popup, ctl, MF_BYPOSITION); - } - - if (x == -1 && y == -1) { - RECT rect; - GetWindowRect(hWnd, &rect); - x = (rect.left + rect.right) / 2; - y = (rect.top + rect.bottom) / 2; - } - - if (GetFocus() != hWnd) - SetFocus(hWnd); - - cmd = TrackPopupMenu(popup, TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, x, y, 0, hWnd, NULL); - if (cmd) - SendMessage(hWnd, cmd, 0, cmd == EM_SETSEL ? -1 : 0); - - DestroyMenu(menu); -} - -static LRESULT CALLBACK child_proc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) -{ - switch (Msg) { - case WM_CREATE: { - struct syntaxedit_data *this = calloc(1, sizeof(*this)); - SetWindowLong(hWnd, GWL_EXSTYLE, GetWindowLong(hWnd, GWL_EXSTYLE) & ~WS_EX_CLIENTEDGE); - assert(this); - SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)this); - SendMessage(hWnd, EM_GETOLEINTERFACE, 0, (LPARAM)&this->irich); - assert(this->irich); - this->irich->lpVtbl->QueryInterface(this->irich, &IID_ITextDocument, (void **)&this->idoc); - assert(this->idoc); - SendMessage(hWnd, EM_SETEVENTMASK, 0, ENM_CHANGE); - SendMessage(hWnd, EM_SETTEXTMODE, TM_SINGLECODEPAGE, 0); - break; - } - case WM_DESTROY: { - struct syntaxedit_data *this = (struct syntaxedit_data *)GetWindowLongPtr(hWnd, GWLP_USERDATA); - this->idoc->lpVtbl->Release(this->idoc); - this->irich->lpVtbl->Release(this->irich); - free(this); - } - case WM_SETTEXT: { - LRESULT ret = parent_proc(hWnd, Msg, wParam, lParam); - highlight_text(hWnd); - SendMessage(hWnd, EM_EMPTYUNDOBUFFER, 0, 0); - return ret; - } - case SE_SET_PARENT_DPI: { - struct syntaxedit_data *this = (struct syntaxedit_data *)GetWindowLongPtr(hWnd, GWLP_USERDATA); - HDC hdc = GetDC(hWnd); - if (this->yheight) - SendMessage(hWnd, EM_SETZOOM, GetDeviceCaps(hdc, LOGPIXELSY), wParam); - this->yheight = MulDiv(20 * 10, wParam, GetDeviceCaps(hdc, LOGPIXELSY)); - ReleaseDC(hWnd, hdc); - highlight_text(hWnd); - return 0; - } - case WM_REFLECT + WM_COMMAND: - case WM_COMMAND: - case WM_REFLECT + WM_NOTIFY: - case WM_NOTIFY: - switch (HIWORD(wParam)) { - case EN_CHANGE: - highlight_text(hWnd); - break; - } - break; - case WM_PASTE: - SendMessage(hWnd, EM_PASTESPECIAL, CF_TEXT, 0); - return 0; - case WM_KEYDOWN: { - WORD key = LOWORD(wParam); - if ((key == 'V' && GetKeyState(VK_CONTROL) < 0) || - (key == VK_INSERT && GetKeyState(VK_SHIFT) < 0)) { - SendMessage(hWnd, EM_PASTESPECIAL, CF_TEXT, 0); - return 0; - } - break; - } - case WM_CONTEXTMENU: - context_menu(hWnd, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); - return 0; - case WM_THEMECHANGED: - highlight_text(hWnd); - break; - case WM_GETDLGCODE: { - MSG *m = (MSG *)lParam; - LRESULT lres = parent_proc(hWnd, Msg, wParam, lParam); - lres &= ~DLGC_WANTTAB; - if (m && m->message == WM_KEYDOWN && m->wParam == VK_TAB && GetKeyState(VK_CONTROL) >= 0) - lres &= ~DLGC_WANTMESSAGE; - return lres; - } - } - return parent_proc(hWnd, Msg, wParam, lParam); -} - -static long has_loaded = 0; - -bool register_syntax_edit(void) -{ - WNDCLASSEXW class = { .cbSize = sizeof(WNDCLASSEXW) }; - WNDPROC pp; - HANDLE lib; - - if (InterlockedCompareExchange(&has_loaded, 1, 0) != 0) - return !!parent_proc; - - lib = LoadLibraryExW(L"msftedit.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); - if (!lib) - return false; - - if (!GetClassInfoExW(NULL, L"RICHEDIT50W", &class)) - goto err; - pp = class.lpfnWndProc; - if (!pp) - goto err; - class.cbSize = sizeof(WNDCLASSEXW); - class.hInstance = GetModuleHandleW(NULL); - class.lpszClassName = L"WgQuickSyntaxEdit"; - class.lpfnWndProc = child_proc; - if (!RegisterClassExW(&class)) - goto err; - parent_proc = pp; - return true; - -err: - FreeLibrary(lib); - return false; -} diff --git a/ui/syntax/syntaxedit.go b/ui/syntax/syntaxedit.go index 257bdbd2..3bbcfd9c 100644 --- a/ui/syntax/syntaxedit.go +++ b/ui/syntax/syntaxedit.go @@ -1,35 +1,41 @@ /* SPDX-License-Identifier: MIT * - * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + * Copyright (C) 2019-2020 WireGuard LLC. All Rights Reserved. */ package syntax import ( "errors" + "fmt" "strings" + "sync/atomic" "syscall" "unsafe" "github.com/lxn/walk" "github.com/lxn/win" + "golang.org/x/sys/windows" ) -// #cgo LDFLAGS: -lgdi32 -// #include "syntaxedit.h" -import "C" - type SyntaxEdit struct { walk.WidgetBase + irich *win.IRichEditOle + idoc *win.ITextDocument + lastBlockState BlockState + yheight int + highlightGuard uint32 textChangedPublisher walk.EventPublisher privateKeyPublisher walk.StringEventPublisher blockUntunneledTrafficPublisher walk.IntEventPublisher } +type BlockState int + const ( - InevaluableBlockingUntunneledTraffic = C.InevaluableBlockingUntunneledTraffic - BlockingUntunneledTraffic = C.BlockingUntunneledTraffic - NotBlockingUntunneledTraffic = C.NotBlockingUntunneledTraffic + InevaluableBlockingUntunneledTraffic BlockState = iota + BlockingUntunneledTraffic + NotBlockingUntunneledTraffic ) func (se *SyntaxEdit) LayoutFlags() walk.LayoutFlags { @@ -63,7 +69,6 @@ func (se *SyntaxEdit) SetText(text string) (err error) { if win.TRUE != se.SendMessage(win.WM_SETTEXT, 0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(text)))) { err = errors.New("WM_SETTEXT failed") } - se.textChangedPublisher.Publish() return } @@ -79,42 +84,388 @@ func (se *SyntaxEdit) BlockUntunneledTrafficStateChanged() *walk.IntEvent { return se.blockUntunneledTrafficPublisher.Event() } -func (se *SyntaxEdit) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { +type spanStyle struct { + color win.COLORREF + effects uint32 +} + +var stylemap = map[highlight]spanStyle{ + highlightSection: spanStyle{color: win.RGB(0x32, 0x6D, 0x74), effects: win.CFE_BOLD}, + highlightField: spanStyle{color: win.RGB(0x9B, 0x23, 0x93), effects: win.CFE_BOLD}, + highlightPrivateKey: spanStyle{color: win.RGB(0x64, 0x38, 0x20)}, + highlightPublicKey: spanStyle{color: win.RGB(0x64, 0x38, 0x20)}, + highlightPresharedKey: spanStyle{color: win.RGB(0x64, 0x38, 0x20)}, + highlightIP: spanStyle{color: win.RGB(0x0E, 0x0E, 0xFF)}, + highlightCidr: spanStyle{color: win.RGB(0x81, 0x5F, 0x03)}, + highlightHost: spanStyle{color: win.RGB(0x0E, 0x0E, 0xFF)}, + highlightPort: spanStyle{color: win.RGB(0x81, 0x5F, 0x03)}, + highlightMTU: spanStyle{color: win.RGB(0x1C, 0x00, 0xCF)}, + highlightKeepalive: spanStyle{color: win.RGB(0x1C, 0x00, 0xCF)}, + highlightComment: spanStyle{color: win.RGB(0x53, 0x65, 0x79), effects: win.CFE_ITALIC}, + highlightDelimiter: spanStyle{color: win.RGB(0x00, 0x00, 0x00)}, + highlightCmd: spanStyle{color: win.RGB(0x63, 0x75, 0x89)}, + highlightError: spanStyle{color: win.RGB(0xC4, 0x1A, 0x16), effects: win.CFE_UNDERLINE}, +} + +func (se *SyntaxEdit) evaluateUntunneledBlocking(cfg string, spans []highlightSpan) { + state := InevaluableBlockingUntunneledTraffic + var onAllowedIPs, + seenPeer, + seen00v6, + seen00v4, + seen01v6, + seen80001v6, + seen01v4, + seen1281v4 bool + + for i := range spans { + span := &spans[i] + switch span.t { + case highlightError: + goto done + case highlightSection: + if !strings.EqualFold(cfg[span.s:span.s+span.len], "[Peer]") { + break + } + if !seenPeer { + seenPeer = true + } else { + goto done + } + break + case highlightField: + onAllowedIPs = strings.EqualFold(cfg[span.s:span.s+span.len], "AllowedIPs") + break + case highlightIP: + if !onAllowedIPs || !seenPeer { + break + } + if i+2 >= len(spans) || spans[i+1].t != highlightDelimiter || spans[i+2].t != highlightCidr { + break + } + if spans[i+2].len != 1 { + break + } + switch cfg[spans[i+2].s] { + case '0': + switch cfg[span.s : span.s+span.len] { + case "0.0.0.0": + seen00v4 = true + case "::": + seen00v6 = true + } + case '1': + switch cfg[span.s : span.s+span.len] { + case "0.0.0.0": + seen01v4 = true + case "128.0.0.0": + seen1281v4 = true + case "::": + seen01v6 = true + case "8000::": + seen80001v6 = true + } + } + break + } + } + + if seen00v4 || seen00v6 { + state = BlockingUntunneledTraffic + } else if (seen01v4 && seen1281v4) || (seen01v6 && seen80001v6) { + state = NotBlockingUntunneledTraffic + } + +done: + if state != se.lastBlockState { + se.blockUntunneledTrafficPublisher.Publish(int(state)) + se.lastBlockState = state + } +} + +func (se *SyntaxEdit) highlightText() error { + if !atomic.CompareAndSwapUint32(&se.highlightGuard, 0, 1) { + return nil + } + defer atomic.StoreUint32(&se.highlightGuard, 0) + + hWnd := se.Handle() + gettextlengthex := win.GETTEXTLENGTHEX{ + Flags: win.GTL_NUMBYTES, + Codepage: win.CP_ACP, // Probably CP_UTF8 would be better, but (wine at least) returns utf32 sizes. + } + msgSize := uint32(win.SendMessage(hWnd, win.EM_GETTEXTLENGTHEX, uintptr(unsafe.Pointer(&gettextlengthex)), 0)) + if msgSize == win.E_INVALIDARG { + return errors.New("Failed to get text length") + } + + gettextex := win.GETTEXTEX{ + Flags: win.GT_NOHIDDENTEXT, + Codepage: gettextlengthex.Codepage, + Cb: msgSize + 1, + } + msg := make([]byte, msgSize+1) + msgCount := win.SendMessage(hWnd, win.EM_GETTEXTEX, uintptr(unsafe.Pointer(&gettextex)), uintptr(unsafe.Pointer(&msg[0]))) + if msgCount < 0 { + return errors.New("Failed to get text") + } + cfg := strings.Replace(string(msg[:msgCount]), "\r", "\n", -1) + + spans := highlightConfig(cfg) + se.evaluateUntunneledBlocking(cfg, spans) + + se.idoc.Undo(win.TomSuspend, nil) + win.SendMessage(hWnd, win.EM_SETEVENTMASK, 0, 0) + win.SendMessage(hWnd, win.WM_SETREDRAW, win.FALSE, 0) + var origSelection win.CHARRANGE + win.SendMessage(hWnd, win.EM_EXGETSEL, 0, uintptr(unsafe.Pointer(&origSelection))) + var origScroll win.POINT + win.SendMessage(hWnd, win.EM_GETSCROLLPOS, 0, uintptr(unsafe.Pointer(&origScroll))) + win.SendMessage(hWnd, win.EM_HIDESELECTION, win.TRUE, 0) + format := win.CHARFORMAT2{ + CHARFORMAT: win.CHARFORMAT{ + CbSize: uint32(unsafe.Sizeof(win.CHARFORMAT2{})), + DwMask: win.CFM_COLOR | win.CFM_CHARSET | win.CFM_SIZE | win.CFM_BOLD | win.CFM_ITALIC | win.CFM_UNDERLINE, + DwEffects: win.CFE_AUTOCOLOR, + BCharSet: win.ANSI_CHARSET, + }, + } + if se.yheight != 0 { + format.YHeight = 20 * 10 + } + win.SendMessage(hWnd, win.EM_SETCHARFORMAT, win.SCF_ALL, uintptr(unsafe.Pointer(&format))) + bgColor := win.COLORREF(win.GetSysColor(win.COLOR_WINDOW)) + bgInversion := (bgColor & win.RGB(0xFF, 0xFF, 0xFF)) ^ win.RGB(0xFF, 0xFF, 0xFF) + win.SendMessage(hWnd, win.EM_SETBKGNDCOLOR, 0, uintptr(bgColor)) + numSpans := len(spans) + foundPrivateKey := false + for i := range spans { + span := &spans[i] + if numSpans <= 2048 { + selection := win.CHARRANGE{int32(span.s), int32(span.s + span.len)} + win.SendMessage(hWnd, win.EM_EXSETSEL, 0, uintptr(unsafe.Pointer(&selection))) + format.CrTextColor = stylemap[span.t].color ^ bgInversion + format.DwEffects = stylemap[span.t].effects + win.SendMessage(hWnd, win.EM_SETCHARFORMAT, win.SCF_SELECTION, uintptr(unsafe.Pointer(&format))) + } + if span.t == highlightPrivateKey && !foundPrivateKey { + privateKey := cfg[span.s : span.s+span.len] + se.privateKeyPublisher.Publish(privateKey) + foundPrivateKey = true + } + } + win.SendMessage(hWnd, win.EM_SETSCROLLPOS, 0, uintptr(unsafe.Pointer(&origScroll))) + win.SendMessage(hWnd, win.EM_EXSETSEL, 0, uintptr(unsafe.Pointer(&origSelection))) + win.SendMessage(hWnd, win.EM_HIDESELECTION, win.FALSE, 0) + win.SendMessage(hWnd, win.WM_SETREDRAW, win.TRUE, 0) + win.RedrawWindow(hWnd, nil, 0, win.RDW_ERASE|win.RDW_FRAME|win.RDW_INVALIDATE|win.RDW_ALLCHILDREN) + win.SendMessage(hWnd, win.EM_SETEVENTMASK, 0, win.ENM_CHANGE) + se.idoc.Undo(win.TomResume, nil) + if !foundPrivateKey { + se.privateKeyPublisher.Publish("") + } + return nil +} + +func (se *SyntaxEdit) contextMenu(x, y int32) error { + /* This disturbing hack grabs the system edit menu normally used for the EDIT control. */ + comctl32UTF16, err := windows.UTF16PtrFromString("comctl32.dll") + if err != nil { + return err + } + comctl32Handle := win.GetModuleHandle(comctl32UTF16) + if comctl32Handle == 0 { + return errors.New("Failed to get comctl32.dll handle") + } + menu := win.LoadMenu(comctl32Handle, win.MAKEINTRESOURCE(1)) + if menu == 0 { + return errors.New("Failed to load menu") + } + defer win.DestroyMenu(menu) + + hWnd := se.Handle() + enableWhenSelected := uint32(win.MF_GRAYED) + var selection win.CHARRANGE + win.SendMessage(hWnd, win.EM_EXGETSEL, 0, uintptr(unsafe.Pointer(&selection))) + if selection.CpMin < selection.CpMax { + enableWhenSelected = win.MF_ENABLED + } + enableSelectAll := uint32(win.MF_GRAYED) + gettextlengthex := win.GETTEXTLENGTHEX{ + Flags: win.GTL_DEFAULT, + Codepage: win.CP_ACP, + } + if selection.CpMin != 0 || (selection.CpMax < int32(win.SendMessage(hWnd, win.EM_GETTEXTLENGTHEX, uintptr(unsafe.Pointer(&gettextlengthex)), 0))) { + enableSelectAll = win.MF_ENABLED + } + enableUndo := uint32(win.MF_GRAYED) + if win.SendMessage(hWnd, win.EM_CANUNDO, 0, 0) != 0 { + enableUndo = win.MF_ENABLED + } + enablePaste := uint32(win.MF_GRAYED) + if win.SendMessage(hWnd, win.EM_CANPASTE, win.CF_TEXT, 0) != 0 { + enablePaste = win.MF_ENABLED + } + + popup := win.GetSubMenu(menu, 0) + win.EnableMenuItem(popup, win.WM_UNDO, win.MF_BYCOMMAND|enableUndo) + win.EnableMenuItem(popup, win.WM_CUT, win.MF_BYCOMMAND|enableWhenSelected) + win.EnableMenuItem(popup, win.WM_COPY, win.MF_BYCOMMAND|enableWhenSelected) + win.EnableMenuItem(popup, win.WM_PASTE, win.MF_BYCOMMAND|enablePaste) + win.EnableMenuItem(popup, win.WM_CLEAR, win.MF_BYCOMMAND|enableWhenSelected) + win.EnableMenuItem(popup, win.EM_SETSEL, win.MF_BYCOMMAND|enableSelectAll) + + // Delete items that we don't handle. + for ctl := win.GetMenuItemCount(popup) - 1; ctl >= 0; ctl-- { + menuItem := win.MENUITEMINFO{ + CbSize: uint32(unsafe.Sizeof(win.MENUITEMINFO{})), + FMask: win.MIIM_FTYPE | win.MIIM_ID, + } + if !win.GetMenuItemInfo(popup, uint32(ctl), win.MF_BYPOSITION, &menuItem) { + continue + } + if (menuItem.FType & win.MFT_SEPARATOR) != 0 { + continue + } + switch menuItem.WID { + case win.WM_UNDO, win.WM_CUT, win.WM_COPY, win.WM_PASTE, win.WM_CLEAR, win.EM_SETSEL: + continue + } + win.DeleteMenu(popup, uint32(ctl), win.MF_BYPOSITION) + } + // Delete trailing and adjacent separators. + end := true + for ctl := win.GetMenuItemCount(popup) - 1; ctl >= 0; ctl-- { + menuItem := win.MENUITEMINFO{ + CbSize: uint32(unsafe.Sizeof(win.MENUITEMINFO{})), + FMask: win.MIIM_FTYPE, + } + if !win.GetMenuItemInfo(popup, uint32(ctl), win.MF_BYPOSITION, &menuItem) { + continue + } + if (menuItem.FType & win.MFT_SEPARATOR) == 0 { + end = false + continue + } + if !end && ctl > 0 { + if !win.GetMenuItemInfo(popup, uint32(ctl-1), win.MF_BYPOSITION, &menuItem) { + continue + } + if (menuItem.FType & win.MFT_SEPARATOR) == 0 { + continue + } + } + win.DeleteMenu(popup, uint32(ctl), win.MF_BYPOSITION) + } + + if x == -1 && y == -1 { + var rect win.RECT + win.GetWindowRect(hWnd, &rect) + x = (rect.Left + rect.Right) / 2 + y = (rect.Top + rect.Bottom) / 2 + } + + if win.GetFocus() != hWnd { + win.SetFocus(hWnd) + } + + cmd := win.TrackPopupMenu(popup, win.TPM_LEFTALIGN|win.TPM_RIGHTBUTTON|win.TPM_RETURNCMD|win.TPM_NONOTIFY, x, y, 0, hWnd, nil) + if cmd != 0 { + lParam := uintptr(0) + if cmd == win.EM_SETSEL { + lParam = ^uintptr(0) + } + win.SendMessage(hWnd, cmd, 0, lParam) + } + + return nil +} + +func (*SyntaxEdit) NeedsWmSize() bool { + return true +} + +func (se *SyntaxEdit) WndProc(hWnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { - case win.WM_NOTIFY, win.WM_COMMAND: + case win.WM_DESTROY: + if se.idoc != nil { + se.idoc.Release() + } + if se.irich != nil { + se.irich.Release() + } + + case win.WM_SETTEXT: + ret := se.WidgetBase.WndProc(hWnd, msg, wParam, lParam) + se.highlightText() + win.SendMessage(hWnd, win.EM_EMPTYUNDOBUFFER, 0, 0) + se.textChangedPublisher.Publish() + return ret + + case win.WM_COMMAND, win.WM_NOTIFY: switch win.HIWORD(uint32(wParam)) { case win.EN_CHANGE: + se.highlightText() se.textChangedPublisher.Publish() } - // This is a horrible trick from MFC where we reflect the event back to the child. - se.SendMessage(msg+C.WM_REFLECT, wParam, lParam) - case C.SE_PRIVATE_KEY: - if lParam == 0 { - se.privateKeyPublisher.Publish("") - } else { - se.privateKeyPublisher.Publish(C.GoString((*C.char)(unsafe.Pointer(lParam)))) + + case win.WM_PASTE: + win.SendMessage(hWnd, win.EM_PASTESPECIAL, win.CF_TEXT, 0) + return 0 + + case win.WM_KEYDOWN: + key := win.LOWORD(uint32(wParam)) + if key == 'V' && win.GetKeyState(win.VK_CONTROL) < 0 || + key == win.VK_INSERT && win.GetKeyState(win.VK_SHIFT) < 0 { + win.SendMessage(hWnd, win.EM_PASTESPECIAL, win.CF_TEXT, 0) + return 0 } - case C.SE_TRAFFIC_BLOCK: - se.blockUntunneledTrafficPublisher.Publish(int(lParam)) + + case win.WM_CONTEXTMENU: + se.contextMenu(win.GET_X_LPARAM(lParam), win.GET_Y_LPARAM(lParam)) + return 0 + + case win.WM_THEMECHANGED: + se.highlightText() + + case win.WM_GETDLGCODE: + m := (*win.MSG)(unsafe.Pointer(lParam)) + ret := se.WidgetBase.WndProc(hWnd, msg, wParam, lParam) + ret &^= win.DLGC_WANTTAB + if m != nil && m.Message == win.WM_KEYDOWN && m.WParam == win.VK_TAB && win.GetKeyState(win.VK_CONTROL) >= 0 { + ret &^= win.DLGC_WANTMESSAGE + } + return ret } - return se.WidgetBase.WndProc(hwnd, msg, wParam, lParam) + + return se.WidgetBase.WndProc(hWnd, msg, wParam, lParam) } func NewSyntaxEdit(parent walk.Container) (*SyntaxEdit, error) { - C.register_syntax_edit() + const LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800 + _, err := windows.LoadLibraryEx("msftedit.dll", 0, LOAD_LIBRARY_SEARCH_SYSTEM32) + if err != nil { + return nil, fmt.Errorf("Failed to load msftedit.dll: %v", err) + } + se := &SyntaxEdit{} - err := walk.InitWidget( + if err := walk.InitWidget( se, parent, - "WgQuickSyntaxEdit", - C.SYNTAXEDIT_STYLE, - C.SYNTAXEDIT_EXTSTYLE, - ) - if err != nil { + win.MSFTEDIT_CLASS, + win.WS_CHILD|win.ES_MULTILINE|win.WS_VISIBLE|win.WS_VSCROLL|win.WS_BORDER|win.WS_HSCROLL|win.WS_TABSTOP|win.ES_WANTRETURN|win.ES_NOOLEDRAGDROP, + 0); err != nil { return nil, err } - se.SendMessage(C.SE_SET_PARENT_DPI, uintptr(parent.DPI()), 0) - + hWnd := se.Handle() + win.SetWindowLong(hWnd, win.GWL_EXSTYLE, win.GetWindowLong(hWnd, win.GWL_EXSTYLE)&^win.WS_EX_CLIENTEDGE) + win.SendMessage(hWnd, win.EM_GETOLEINTERFACE, 0, uintptr(unsafe.Pointer(&se.irich))) + var idoc unsafe.Pointer + se.irich.QueryInterface(&win.IID_ITextDocument, &idoc) + se.idoc = (*win.ITextDocument)(idoc) + win.SendMessage(hWnd, win.EM_SETEVENTMASK, 0, win.ENM_CHANGE) + win.SendMessage(hWnd, win.EM_SETTEXTMODE, win.TM_SINGLECODEPAGE, 0) + se.ApplyDPI(parent.DPI()) se.GraphicsEffects().Add(walk.InteractionEffect) se.GraphicsEffects().Add(walk.FocusEffect) se.MustRegisterProperty("Text", walk.NewProperty( @@ -128,10 +479,17 @@ func NewSyntaxEdit(parent walk.Container) (*SyntaxEdit, error) { return se.SetText("") }, se.textChangedPublisher.Event())) - return se, nil } func (se *SyntaxEdit) ApplyDPI(dpi int) { - se.SendMessage(C.SE_SET_PARENT_DPI, uintptr(dpi), 0) + hWnd := se.Handle() + hdc := win.GetDC(hWnd) + logPixels := win.GetDeviceCaps(hdc, win.LOGPIXELSY) + if se.yheight != 0 { + win.SendMessage(hWnd, win.EM_SETZOOM, uintptr(logPixels), uintptr(dpi)) + } + se.yheight = 20 * 10 * dpi / int(logPixels) + win.ReleaseDC(hWnd, hdc) + se.highlightText() } diff --git a/ui/syntax/syntaxedit.h b/ui/syntax/syntaxedit.h deleted file mode 100644 index 048e7bc4..00000000 --- a/ui/syntax/syntaxedit.h +++ /dev/null @@ -1,31 +0,0 @@ -/* SPDX-License-Identifier: MIT - * - * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. - */ - -#ifndef SYNTAXEDIT_H -#define SYNTAXEDIT_H - -#include <stdbool.h> -#include <windows.h> -#include <richedit.h> - -#define SYNTAXEDIT_STYLE (WS_CHILD | ES_MULTILINE | WS_VISIBLE | WS_VSCROLL | WS_BORDER | WS_HSCROLL | WS_TABSTOP | ES_WANTRETURN | ES_NOOLEDRAGDROP) -#define SYNTAXEDIT_EXTSTYLE (0) - -/* The old MFC reflection trick. */ -#define WM_REFLECT (WM_USER + 0x1C00) - -#define SE_PRIVATE_KEY (WM_USER + 0x3100) -#define SE_TRAFFIC_BLOCK (WM_USER + 0x3101) -#define SE_SET_PARENT_DPI (WM_USER + 0x3102) - -enum block_state { - InevaluableBlockingUntunneledTraffic, - BlockingUntunneledTraffic, - NotBlockingUntunneledTraffic -}; - -extern bool register_syntax_edit(void); - -#endif |