diff options
| author | Sho Sakuma <me@m1sk9.dev> | 2026-08-05 02:23:04 +0900 |
|---|---|---|
| committer | Sho Sakuma <me@m1sk9.dev> | 2026-08-05 02:23:04 +0900 |
| commit | c29d1621b26f9c1dfbf0a26f4433996060babf10 (patch) | |
| tree | d1c9212600bc111031ac8a8a16b8f2fac0e08d6d | |
| parent | 6a45b5bb4fe3ab660eb92dc92d38b3d6c8c1c0fa (diff) | |
| download | LunaticChat-c29d1621b26f9c1dfbf0a26f4433996060babf10.tar.gz LunaticChat-c29d1621b26f9c1dfbf0a26f4433996060babf10.tar.bz2 LunaticChat-c29d1621b26f9c1dfbf0a26f4433996060babf10.zip | |
fix: keep a config.yml the YAML reader rejects from disabling the plugin
Catching YamlException at the parse step covered kaml but not the scanner
underneath it: a file saved as UTF-16, or one truncated with NUL padding after an
unclean shutdown, fails inside snakeyaml-engine's reader with an exception that
is not a YamlException. It escaped onEnable and Bukkit disabled the plugin over a
config file - the very failure reading per setting was meant to prevent, and one
the catch-all this replaced had handled. The same gap swallowed the serializer's
own non-YamlException fallback.
A UTF-8 BOM is also stripped before parsing. It otherwise stays on the first key,
which strictMode = false drops as an unknown setting without logging anything, so
the operator sees exactly one setting ignored and no reason why.
Co-Authored-By: Claude <noreply@anthropic.com>
| -rw-r--r-- | platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/config/ConfigManager.kt | 15 | ||||
| -rw-r--r-- | platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/config/ConfigManagerTest.kt | 21 |
2 files changed, 29 insertions, 7 deletions
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/config/ConfigManager.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/config/ConfigManager.kt index 00c1973..2ce24b6 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/config/ConfigManager.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/config/ConfigManager.kt @@ -41,12 +41,17 @@ class ConfigManager( fun loadConfiguration(contents: String): LunaticChatConfiguration { var document = try { - yaml.parseToYamlNode(contents) + // Editors that write a UTF-8 BOM would otherwise leave it on the first key, which + // strictMode = false then drops as an unknown setting without a word. + yaml.parseToYamlNode(contents.removePrefix("\uFEFF")) } catch (e: EmptyYamlDocumentException) { // A file that only holds comments is a valid way of saying "use the defaults", so it // is not reported as a failure the operator has to act on. return LunaticChatConfiguration() - } catch (e: YamlException) { + } catch (e: Exception) { + // Not YamlException: a file the reader rejects before it is YAML at all - one saved + // as UTF-16, or truncated with NUL padding - fails inside the scanner, and letting + // that out of onEnable would disable the plugin over a config file. return allDefaults("config.yml is not valid YAML", e) } @@ -65,13 +70,17 @@ class ConfigManager( ?: return allDefaults("config.yml could not be read", e) logger.warning("${setting.joinToString(".")} in config.yml fell back to its default: ${e.message}") document = remaining + } catch (e: Exception) { + // A serializer can fail without kaml turning it into a YamlException, and there is + // no path to prune a single setting by without one. + return allDefaults("config.yml could not be read", e) } } } private fun allDefaults( what: String, - cause: YamlException, + cause: Exception, ): LunaticChatConfiguration { logger.log( Level.SEVERE, diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/config/ConfigManagerTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/config/ConfigManagerTest.kt index dd9bd54..d415e97 100644 --- a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/config/ConfigManagerTest.kt +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/config/ConfigManagerTest.kt @@ -177,21 +177,34 @@ class ConfigManagerTest { } @Test - fun `a malformed file falls back to defaults instead of failing startup`() { - assertEquals(LunaticChatConfiguration(), load("features: [this is not a map")) + fun `a document that is not YAML at all says that every setting was reset`() { + val logger = TestUtils.TestLogger() + + val config = ConfigManager(logger).loadConfiguration("features: [this is not a map") + + assertEquals(LunaticChatConfiguration(), config) + assertTrue(logger.severeMessages.any { it.contains("EVERY setting") }) } @Test - fun `a document that is not YAML at all says that every setting was reset`() { + fun `a file the YAML reader rejects before parsing still leaves the plugin running`() { val logger = TestUtils.TestLogger() + // What a config.yml saved as UTF-16 looks like once it is read back as UTF-8: the scanner + // refuses it over the NUL bytes, before anything is YAML. + val notUtf8 = String("debug: true".toByteArray(Charsets.UTF_16), Charsets.UTF_8) - val config = ConfigManager(logger).loadConfiguration("features: [this is not a map") + val config = ConfigManager(logger).loadConfiguration(notUtf8) assertEquals(LunaticChatConfiguration(), config) assertTrue(logger.severeMessages.any { it.contains("EVERY setting") }) } @Test + fun `a leading byte order mark does not cost the first setting`() { + assertTrue(load("\uFEFFdebug: true").debug) + } + + @Test fun `a file holding only comments is not reported as a failure`() { val logger = TestUtils.TestLogger() |
