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)