Skip to content

Commit c990ba8

Browse files
authored
Handle big and deep messages (#107)
* Handle big and deep messages * Update index.js
1 parent 0cd3a82 commit c990ba8

File tree

2 files changed

+49
-13
lines changed

2 files changed

+49
-13
lines changed

index.js

+21-13
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ const vsprintf = require('./format')
33
const debug = require('debug')('minecraft-protocol')
44
const nbt = require('prismarine-nbt')
55
const getValueSafely = (obj, key, def) => Object.hasOwn(obj, key) ? obj[key] : def
6+
const MAX_CHAT_DEPTH = 8
7+
const MAX_CHAT_LENGTH = 4096
68

79
function loader (registryOrVersion) {
810
const registry = typeof registryOrVersion === 'string' ? require('prismarine-registry')(registryOrVersion) : registryOrVersion
@@ -296,27 +298,29 @@ function loader (registryOrVersion) {
296298
* Flattens the message in to plain-text
297299
* @return {String}
298300
*/
299-
toString (lang = defaultLang) {
301+
toString (lang = defaultLang, _depth = 0) {
302+
if (_depth > MAX_CHAT_DEPTH) return ''
300303
let message = ''
301304
if (typeof this.text === 'string' || typeof this.text === 'number') message += this.text
302305
else if (this.translate !== undefined) {
303306
const _with = this.with ?? []
304307

305-
const args = _with.map(entry => entry.toString(lang))
308+
const args = _with.map(entry => entry.toString(lang, _depth + 1))
306309
const format = getValueSafely(lang, this.translate, this.translate)
307310
message += vsprintf(format, args)
308311
}
309312
if (this.extra) {
310-
message += this.extra.map((entry) => entry.toString(lang)).join('')
313+
message += this.extra.map((entry) => entry.toString(lang, _depth + 1)).join('')
311314
}
312-
return message.replace(/§[0-9a-flnmokr]/g, '')
315+
return message.replace(/§[0-9a-flnmokr]/g, '').slice(0, MAX_CHAT_LENGTH)
313316
}
314317

315318
valueOf () {
316319
return this.toString()
317320
}
318321

319-
toMotd (lang = defaultLang, parent = {}) {
322+
toMotd (lang = defaultLang, parent = {}, _depth = 0) {
323+
if (_depth > MAX_CHAT_DEPTH) return ''
320324
const codes = {
321325
color: {
322326
black: '§0',
@@ -360,16 +364,16 @@ function loader (registryOrVersion) {
360364
const _with = this.with ?? []
361365

362366
const args = _with.map(entry => {
363-
const entryAsMotd = entry.toMotd(lang, this)
367+
const entryAsMotd = entry.toMotd(lang, this, _depth + 1)
364368
return entryAsMotd + (entryAsMotd.includes('§') ? '§r' + message : '')
365369
})
366370
const format = getValueSafely(lang, this.translate, this.translate)
367371
message += vsprintf(format, args)
368372
}
369373
if (this.extra) {
370-
message += this.extra.map(entry => entry.toMotd(lang, this)).join('')
374+
message += this.extra.map(entry => entry.toMotd(lang, this, _depth + 1)).join('')
371375
}
372-
return message
376+
return message.slice(0, MAX_CHAT_LENGTH)
373377
}
374378

375379
toAnsi (lang = defaultLang, codes = defaultAnsiCodes) {
@@ -388,11 +392,12 @@ function loader (registryOrVersion) {
388392
// ANSI from https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797#rgb-colors
389393
message = message.replace(hexRegex, `\u001b[38;2;${red};${green};${blue}m`)
390394
}
391-
return codes['§r'] + message + codes['§r']
395+
return codes['§r'] + message.slice(0, MAX_CHAT_LENGTH) + codes['§r']
392396
}
393397

394398
// NOTE : Have to be be mindful here as bad HTML gen may lead to arbitrary code execution from server
395-
toHTML (lang = registry.language, styles = cssDefaultStyles, allowedFormats = formatMembers) {
399+
toHTML (lang = registry.language, styles = cssDefaultStyles, allowedFormats = formatMembers, _depth = 0) {
400+
if (_depth > MAX_CHAT_DEPTH) return ''
396401
let str = ''
397402
if (allowedFormats.some(member => this[member])) {
398403
const cssProps = allowedFormats.reduce((acc, cur) => this[cur]
@@ -412,18 +417,21 @@ function loader (registryOrVersion) {
412417
const params = []
413418
if (this.with) {
414419
for (const param of this.with) {
415-
params.push(param.toHTML(lang, styles, allowedFormats))
420+
params.push(param.toHTML(lang, styles, allowedFormats, _depth + 1))
416421
}
417422
}
418423
const format = getValueSafely(lang, this.translate, this.translate)
419424
str += vsprintf(escapeHtml(format), params)
420425
}
421426

422427
if (this.extra) {
423-
str += this.extra.map(entry => entry.toHTML(lang, styles, allowedFormats)).join('')
428+
str += this.extra.map(entry => entry.toHTML(lang, styles, allowedFormats, _depth + 1)).join('')
424429
}
425430
str += '</span>'
426-
return str
431+
// It's not safe to truncate HTML so just return unformatted text
432+
return str > MAX_CHAT_LENGTH
433+
? escapeHtml(this.toString())
434+
: str
427435
}
428436

429437
static fromNotch (msg) {

test/basic.test.js

+28
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,31 @@ describe('Client-side chat formatting', function () {
126126
assert.strictEqual(msg.toString(), '💬 [Admin] Player » hello world ! ⏎')
127127
})
128128
})
129+
130+
describe('Big message parsing', function () {
131+
const ChatMessage = require('prismarine-chat')('1.16')
132+
const translate = '%1$s'.repeat(32)
133+
const format = {
134+
text: 'a',
135+
color: 'dark_red',
136+
bold: true,
137+
italic: true,
138+
strikethrough: true,
139+
underlined: true,
140+
obfuscated: true
141+
}
142+
it('handles big messages', function () {
143+
const _with = [format]
144+
const big = { translate, with: _with }
145+
for (let i = 0; i < 7; i++) _with[0] = structuredClone(big)
146+
const message = new ChatMessage(big)
147+
assert.strictEqual(message.toString().length, 4096)
148+
})
149+
it('handles too deep messages', function () {
150+
const _with = [format]
151+
const big = { translate, with: _with }
152+
for (let i = 0; i < 10; i++) _with[0] = structuredClone(big)
153+
const message = new ChatMessage(big)
154+
assert.strictEqual(message.toString().length, 0)
155+
})
156+
})

0 commit comments

Comments
 (0)