diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5bd86320b..359b907fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,7 @@ jobs: run: | meson setup build ninja -C build install + ninja -C build test lint: runs-on: ubuntu-latest diff --git a/meson.build b/meson.build index 2da8f0ec7..85c73b6b3 100644 --- a/meson.build +++ b/meson.build @@ -42,3 +42,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..64c9193d3 --- /dev/null +++ b/src/HostnameValidator.vala @@ -0,0 +1,47 @@ +// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- +/*- + * Copyright 2024 elementary, Inc. (https://elementary.io) + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Authored by: Marius Meisenzahl + */ + +namespace Utils { + private bool hostname_is_valid_char (char c) { + return ((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + c == '-' || + c == '.'); + } + + // Based on https://github.com/pop-os/hostname-validator/blob/458fa5a1df98cb663f0456dffb542e1a907861c9/src/lib.rs#L29 + 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 8c7afcc34..c8b59a0a4 100644 --- a/src/Utils.vala +++ b/src/Utils.vala @@ -264,6 +264,13 @@ namespace Utils { string hostname = get_ubiquity_compatible_hostname () ?? ("elementary-os" + "-" + get_chassis ()); hostname += "-" + get_machine_id ().substring (0, 8); + // 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 (!hostname_is_valid (hostname)) { + hostname = "elementary-os"; + } + return hostname; } } diff --git a/src/meson.build b/src/meson.build index 1ae8f0018..13bf7e43a 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)