mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
fix: prevent server crash on unsupported Shift-JIS characters (#116)
UTF8ToSJIS panicked when encountering characters outside the Shift-JIS range (emoji, Lenny faces, cuneiform, etc.), crashing the server when such characters were sent via the Discord relay channel. Replace the panic with graceful filtering that drops unmappable runes and preserves valid content. Also fix ToNGWord index-out-of-range panic on empty encoder output. Closes #116
This commit is contained in:
@@ -38,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Fixed double-save bug in logout flow that caused unnecessary database operations
|
||||
- Fixed save operation ordering - now saves data before session cleanup instead of after
|
||||
- Fixed stale transmog/armor appearance shown to other players - user binary cache now invalidated when plate data is saved
|
||||
- Fixed server crash when Discord relay receives messages with unsupported Shift-JIS characters (emoji, Lenny faces, cuneiform, etc.)
|
||||
|
||||
### Security
|
||||
|
||||
|
||||
@@ -15,7 +15,15 @@ func UTF8ToSJIS(x string) []byte {
|
||||
e := japanese.ShiftJIS.NewEncoder()
|
||||
xt, _, err := transform.String(e, x)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
// Filter out runes that can't be encoded to Shift-JIS instead of
|
||||
// crashing the server (see PR #116).
|
||||
var filtered []rune
|
||||
for _, r := range x {
|
||||
if _, _, err := transform.String(japanese.ShiftJIS.NewEncoder(), string(r)); err == nil {
|
||||
filtered = append(filtered, r)
|
||||
}
|
||||
}
|
||||
xt, _, _ = transform.String(japanese.ShiftJIS.NewEncoder(), string(filtered))
|
||||
}
|
||||
return []byte(xt)
|
||||
}
|
||||
@@ -36,9 +44,10 @@ func ToNGWord(x string) []uint16 {
|
||||
t := UTF8ToSJIS(string(r))
|
||||
if len(t) > 1 {
|
||||
w = append(w, uint16(t[1])<<8|uint16(t[0]))
|
||||
} else {
|
||||
} else if len(t) == 1 {
|
||||
w = append(w, uint16(t[0]))
|
||||
}
|
||||
// Skip runes that produced no SJIS output (unsupported characters)
|
||||
} else {
|
||||
w = append(w, uint16(r))
|
||||
}
|
||||
|
||||
@@ -458,6 +458,80 @@ func BenchmarkCSVElems(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUTF8ToSJIS_UnsupportedCharacters(t *testing.T) {
|
||||
// Regression test for PR #116: Characters outside the Shift-JIS range
|
||||
// (e.g. Lenny face, cuneiform) previously caused a panic in UTF8ToSJIS,
|
||||
// crashing the server when relayed from Discord.
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
}{
|
||||
{"lenny_face", "( ͡° ͜ʖ ͡°)"},
|
||||
{"cuneiform", "𒀜"},
|
||||
{"emoji", "Hello 🎮 World"},
|
||||
{"mixed_unsupported", "Test ͡° message 𒀜 here"},
|
||||
{"zalgo_text", "H̷e̸l̵l̶o̷"},
|
||||
{"only_unsupported", "🎮🎲🎯"},
|
||||
{"cyrillic", "Привет"},
|
||||
{"arabic", "مرحبا"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Must not panic - the old code would panic here
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("UTF8ToSJIS panicked on input %q: %v", tt.input, r)
|
||||
}
|
||||
}()
|
||||
result := UTF8ToSJIS(tt.input)
|
||||
if result == nil {
|
||||
t.Error("UTF8ToSJIS returned nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUTF8ToSJIS_PreservesValidContent(t *testing.T) {
|
||||
// Verify that valid Shift-JIS content is preserved when mixed with
|
||||
// unsupported characters.
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"ascii_with_emoji", "Hello 🎮 World", "Hello World"},
|
||||
{"japanese_with_emoji", "テスト🎮データ", "テストデータ"},
|
||||
{"only_valid", "Hello World", "Hello World"},
|
||||
{"only_invalid", "🎮🎲🎯", ""},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
sjis := UTF8ToSJIS(tt.input)
|
||||
roundTripped := SJISToUTF8(sjis)
|
||||
if roundTripped != tt.expected {
|
||||
t.Errorf("UTF8ToSJIS(%q) round-tripped to %q, want %q", tt.input, roundTripped, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestToNGWord_UnsupportedCharacters(t *testing.T) {
|
||||
// ToNGWord also calls UTF8ToSJIS internally, so it must not panic either.
|
||||
inputs := []string{"( ͡° ͜ʖ ͡°)", "🎮", "Hello 🎮 World"}
|
||||
for _, input := range inputs {
|
||||
t.Run(input, func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("ToNGWord panicked on input %q: %v", input, r)
|
||||
}
|
||||
}()
|
||||
_ = ToNGWord(input)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUTF8ToSJIS(b *testing.B) {
|
||||
text := "Hello World テスト"
|
||||
b.ResetTimer()
|
||||
|
||||
Reference in New Issue
Block a user