summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSho Sakuma <me@m1sk9.dev>2026-08-05 02:23:04 +0900
committerSho Sakuma <me@m1sk9.dev>2026-08-05 02:23:04 +0900
commitc29d1621b26f9c1dfbf0a26f4433996060babf10 (patch)
treed1c9212600bc111031ac8a8a16b8f2fac0e08d6d
parent6a45b5bb4fe3ab660eb92dc92d38b3d6c8c1c0fa (diff)
downloadLunaticChat-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.kt15
-rw-r--r--platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/config/ConfigManagerTest.kt21
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()