From 66a74e38bdcb87552504d4b32334d13f1d647faa Mon Sep 17 00:00:00 2001 From: Marius Meisenzahl Date: Wed, 2 Oct 2024 23:00:56 +0200 Subject: [PATCH] Utils: Port hostname validation from distinst and add tests --- meson.build | 1 + src/HostnameValidator.vala | 43 +++++++++++++++++++++++++++++++++ src/Utils.vala | 2 +- src/meson.build | 1 + test/HostnameValidatorTest.vala | 24 ++++++++++++++++++ test/Test.vala | 5 ++++ test/meson.build | 13 ++++++++++ 7 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 src/HostnameValidator.vala create mode 100644 test/HostnameValidatorTest.vala create mode 100644 test/Test.vala create mode 100644 test/meson.build diff --git a/meson.build b/meson.build index 8f1a847ff..662a778b4 100644 --- a/meson.build +++ b/meson.build @@ -36,3 +36,4 @@ subdir('common') subdir('daemon') subdir('src') subdir('data') +subdir('test') diff --git a/src/HostnameValidator.vala b/src/HostnameValidator.vala new file mode 100644 index 000000000..27e7e9fb5 --- /dev/null +++ b/src/HostnameValidator.vala @@ -0,0 +1,43 @@ +/* + * Copyright 2018-2022 System76 + * Copyright 2024 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: MIT + */ + +namespace Utils { + private bool hostname_is_valid_char (char c) { + return (c.isalnum () || + c == '-' || + c == '.'); + } + + // Based on https://github.com/pop-os/hostname-validator/blob/458fa5a1df98cb663f0456dffb542e1a907861c9/src/lib.rs#L29 + /// Validate a hostname according to [IETF RFC 1123](https://tools.ietf.org/html/rfc1123). + /// + /// A hostname is valid if the following condition are true: + /// + /// - It does not start or end with `-` or `.`. + /// - It does not contain any characters outside of the alphanumeric range, except for `-` and `.`. + /// - It is not empty. + /// - It is 253 or fewer characters. + /// - Its labels (characters separated by `.`) are not empty. + /// - Its labels are 63 or fewer characters. + /// - Its labels do not start or end with '-' or '.'. + public bool hostname_is_valid (string hostname) { + for (int i = 0; i < hostname.char_count (); i++) { + char c = hostname[i]; + if (!hostname_is_valid_char (c)) { + return false; + } + } + + string[] labels = hostname.split (".", -1); + foreach (string label in labels) { + if (label.char_count () == 0 || label.length > 63 || label[0] == '-' || label[label.length - 1] == '-') { + return false; + } + } + + return !(hostname.char_count () == 0 || hostname.length > 253); + } +} diff --git a/src/Utils.vala b/src/Utils.vala index 8368ab2cf..c8b59a0a4 100644 --- a/src/Utils.vala +++ b/src/Utils.vala @@ -267,7 +267,7 @@ namespace Utils { // If the automatic hostname logic fails in some way, it's possible we may generate an invalid // hostname. We could fix this by trimming traling/leading hyphens or other invalid characters. // But it's probably a bad hostname anyway, so just fallback - if (!Distinst.validate_hostname (hostname)) { + if (!hostname_is_valid (hostname)) { hostname = "elementary-os"; } diff --git a/src/meson.build b/src/meson.build index 504555f76..3ecf0f994 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,5 +1,6 @@ vala_files = [ 'Application.vala', + 'HostnameValidator.vala', 'MainWindow.vala', 'Utils.vala', 'Helpers/InstallerDaemon.vala', diff --git a/test/HostnameValidatorTest.vala b/test/HostnameValidatorTest.vala new file mode 100644 index 000000000..7399c553e --- /dev/null +++ b/test/HostnameValidatorTest.vala @@ -0,0 +1,24 @@ +void add_hostname_validator_tests () { + Test.add_func ("/valid", () => { + assert (Utils.hostname_is_valid ("VaLiD-HoStNaMe")); + assert (Utils.hostname_is_valid ("50-name")); + assert (Utils.hostname_is_valid ("235235")); + assert (Utils.hostname_is_valid ("example.com")); + assert (Utils.hostname_is_valid ("VaLid.HoStNaMe")); + assert (Utils.hostname_is_valid ("123.456")); + }); + + Test.add_func ("/invalid", () => { + assert (!Utils.hostname_is_valid ("-invalid-name")); + assert (!Utils.hostname_is_valid ("also-invalid-")); + assert (!Utils.hostname_is_valid ("asdf@fasd")); + assert (!Utils.hostname_is_valid ("@asdfl")); + assert (!Utils.hostname_is_valid ("asd f@")); + assert (!Utils.hostname_is_valid (".invalid")); + assert (!Utils.hostname_is_valid ("invalid.name.")); + assert (!Utils.hostname_is_valid ("foo.label-is-way-to-longgggggggggggggggggggggggggggggggggggggggggggg.org")); + assert (!Utils.hostname_is_valid ("invalid.-starting.char")); + assert (!Utils.hostname_is_valid ("invalid.ending-.char")); + assert (!Utils.hostname_is_valid ("empty..label")); + }); +} diff --git a/test/Test.vala b/test/Test.vala new file mode 100644 index 000000000..de40a0434 --- /dev/null +++ b/test/Test.vala @@ -0,0 +1,5 @@ +void main (string[] args) { + Test.init (ref args); + add_hostname_validator_tests (); + Test.run (); +} diff --git a/test/meson.build b/test/meson.build new file mode 100644 index 000000000..1ef64043d --- /dev/null +++ b/test/meson.build @@ -0,0 +1,13 @@ +test_dependencies = [ + glib_dep, +] + +tests = executable( + meson.project_name() + '-tests', + 'HostnameValidatorTest.vala', + 'Test.vala', + meson.project_source_root() + '/src/HostnameValidator.vala', + dependencies: test_dependencies +) + +test('Test', tests)