Skip to main content

mcproto_types/
text_component.rs

1use std::{
2    collections::HashMap,
3    error::Error,
4    fmt,
5    io::{self, Read, Write},
6};
7
8use fastnbt::{
9    SerOpts, Tag,
10    stream::{Parser, Value as StreamValue},
11};
12use mcproto_codec::{
13    error::{CodecError, CodecKind, CodecOperation, InvalidEncodingReason},
14    io::write_all_counted,
15};
16
17use crate::{TypeCodec, component::NbtComponent};
18
19pub use fastnbt::Value as NbtValue;
20
21/// A network NBT representation of the current Java text component schema.
22#[derive(Debug, Clone, PartialEq)]
23pub struct TextComponent(pub NbtComponent);
24
25impl TextComponent {
26    const MAX_COMPONENT_DEPTH: usize = 512;
27
28    pub fn text(value: impl Into<String>) -> Self {
29        Self(NbtComponent::text(value))
30    }
31}
32
33impl Default for TextComponent {
34    fn default() -> Self {
35        Self::text("")
36    }
37}
38
39impl From<NbtComponent> for TextComponent {
40    fn from(value: NbtComponent) -> Self {
41        Self(value)
42    }
43}
44
45impl From<String> for TextComponent {
46    fn from(value: String) -> Self {
47        Self::text(value)
48    }
49}
50
51impl From<&str> for TextComponent {
52    fn from(value: &str) -> Self {
53        Self::text(value)
54    }
55}
56
57impl TypeCodec for TextComponent {
58    fn encode(&self, writer: &mut impl Write) -> Result<(), CodecError> {
59        self.0
60            .validate_depth(Self::MAX_COMPONENT_DEPTH)
61            .map_err(|source| invalid_nbt(CodecOperation::Write, 0, source))?;
62        let normalized = self.0.normalized_root_for_nbt();
63        let value = fastnbt::to_value(&normalized)
64            .map_err(|source| invalid_nbt(CodecOperation::Write, 0, source))?;
65        validate_nbt_strings(&value, CodecOperation::Write, 0)?;
66        encode_root(&value, writer)
67    }
68
69    fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
70        let (value, bytes_processed) = decode_root(reader)?;
71        let component: NbtComponent = fastnbt::from_value(&value)
72            .map_err(|source| invalid_nbt(CodecOperation::Read, bytes_processed, source))?;
73        component
74            .validate_depth(Self::MAX_COMPONENT_DEPTH)
75            .map_err(|source| invalid_nbt(CodecOperation::Read, bytes_processed, source))?;
76        Ok(Self(component.normalized_root_for_nbt()))
77    }
78}
79
80fn encode_root(value: &NbtValue, writer: &mut impl Write) -> Result<(), CodecError> {
81    let wrapper = HashMap::from([("", value)]);
82    let encoded = fastnbt::to_bytes_with_opts(&wrapper, SerOpts::network_nbt())
83        .map_err(|source| invalid_nbt(CodecOperation::Write, 0, source))?;
84
85    if encoded.len() < 5
86        || encoded[0] != Tag::Compound as u8
87        || encoded[2..4] != [0, 0]
88        || encoded.last() != Some(&(Tag::End as u8))
89        || !matches!(encoded[1], 8 | 10)
90    {
91        return Err(CodecError::invalid_encoding_for_operation(
92            CodecKind::TextComponent,
93            CodecOperation::Write,
94            0,
95            InvalidEncodingReason::InvalidNbt,
96        ));
97    }
98
99    write_all_counted(writer, &encoded[1..2], CodecKind::TextComponent, 0)?;
100    write_all_counted(
101        writer,
102        &encoded[4..encoded.len() - 1],
103        CodecKind::TextComponent,
104        1,
105    )
106}
107
108fn decode_root(reader: &mut impl Read) -> Result<(NbtValue, usize), CodecError> {
109    let mut reader = CountedReader::new(reader);
110    let mut root_tag = [0_u8; 1];
111    reader.read_exact(&mut root_tag).map_err(|source| {
112        CodecError::from_read_error(CodecKind::TextComponent, reader.processed, source)
113    })?;
114    if !matches!(root_tag[0], 8 | 10) {
115        return Err(CodecError::invalid_encoding(
116            CodecKind::TextComponent,
117            reader.processed,
118            InvalidEncodingReason::InvalidTextComponentRootTag { tag: root_tag[0] },
119        ));
120    }
121
122    // The stream parser requires a root name; network text components omit it.
123    let mut prefixed = PrefixReader {
124        prefix: &[root_tag[0], 0, 0],
125        inner: &mut reader,
126    };
127    let result = (|| {
128        let mut parser = Parser::new(&mut prefixed);
129        let first = parser.next().map_err(NbtTreeError::Parser)?;
130        parse_stream_value(&mut parser, first, 1)
131    })();
132
133    match result {
134        Ok(value) => Ok((value, prefixed.inner.processed)),
135        Err(error) => {
136            let processed = prefixed.inner.processed;
137            match prefixed.inner.failure.take() {
138                Some(source) => Err(CodecError::from_read_error(
139                    CodecKind::TextComponent,
140                    processed,
141                    source,
142                )),
143                None => Err(invalid_nbt(CodecOperation::Read, processed, error)),
144            }
145        }
146    }
147}
148
149fn parse_stream_value<R: Read>(
150    parser: &mut Parser<R>,
151    value: StreamValue,
152    depth: usize,
153) -> Result<NbtValue, NbtTreeError> {
154    if depth > TextComponent::MAX_COMPONENT_DEPTH {
155        return Err(NbtTreeError::TooDeep);
156    }
157    match value {
158        StreamValue::Byte(_, value) => Ok(NbtValue::Byte(value)),
159        StreamValue::Short(_, value) => Ok(NbtValue::Short(value)),
160        StreamValue::Int(_, value) => Ok(NbtValue::Int(value)),
161        StreamValue::Long(_, value) => Ok(NbtValue::Long(value)),
162        StreamValue::Float(_, value) => Ok(NbtValue::Float(value)),
163        StreamValue::Double(_, value) => Ok(NbtValue::Double(value)),
164        StreamValue::ByteArray(_, value) => Ok(NbtValue::ByteArray(fastnbt::ByteArray::new(value))),
165        StreamValue::String(_, value) => Ok(NbtValue::String(value)),
166        StreamValue::IntArray(_, value) => Ok(NbtValue::IntArray(fastnbt::IntArray::new(value))),
167        StreamValue::LongArray(_, value) => Ok(NbtValue::LongArray(fastnbt::LongArray::new(value))),
168        StreamValue::List(_, _, length) => {
169            let length = usize::try_from(length).map_err(|_| NbtTreeError::InvalidStructure)?;
170            // A declared length is untrusted and can exceed the remaining packet.
171            let mut values = Vec::with_capacity(length.min(1024));
172            for _ in 0..length {
173                let value = parser.next().map_err(NbtTreeError::Parser)?;
174                values.push(parse_stream_value(parser, value, depth + 1)?);
175            }
176            if !matches!(parser.next(), Ok(StreamValue::ListEnd)) {
177                return Err(NbtTreeError::InvalidStructure);
178            }
179            Ok(NbtValue::List(values))
180        }
181        StreamValue::Compound(_) => {
182            let mut values = HashMap::new();
183            loop {
184                let value = parser.next().map_err(NbtTreeError::Parser)?;
185                if matches!(value, StreamValue::CompoundEnd) {
186                    break;
187                }
188                let name = stream_name(&value).ok_or(NbtTreeError::InvalidStructure)?;
189                values.insert(
190                    name.to_owned(),
191                    parse_stream_value(parser, value, depth + 1)?,
192                );
193            }
194            Ok(NbtValue::Compound(values))
195        }
196        StreamValue::ListEnd | StreamValue::CompoundEnd => Err(NbtTreeError::InvalidStructure),
197    }
198}
199
200fn stream_name(value: &StreamValue) -> Option<&str> {
201    match value {
202        StreamValue::Byte(name, _)
203        | StreamValue::Short(name, _)
204        | StreamValue::Int(name, _)
205        | StreamValue::Long(name, _)
206        | StreamValue::Float(name, _)
207        | StreamValue::Double(name, _)
208        | StreamValue::ByteArray(name, _)
209        | StreamValue::String(name, _)
210        | StreamValue::List(name, _, _)
211        | StreamValue::Compound(name)
212        | StreamValue::IntArray(name, _)
213        | StreamValue::LongArray(name, _) => name.as_deref(),
214        StreamValue::ListEnd | StreamValue::CompoundEnd => None,
215    }
216}
217
218#[derive(Debug)]
219enum NbtTreeError {
220    Parser(fastnbt::stream::Error),
221    InvalidStructure,
222    TooDeep,
223}
224
225impl fmt::Display for NbtTreeError {
226    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
227        match self {
228            Self::Parser(source) => source.fmt(formatter),
229            Self::InvalidStructure => formatter.write_str("invalid NBT tree structure"),
230            Self::TooDeep => formatter.write_str("NBT tree is nested too deeply"),
231        }
232    }
233}
234
235impl Error for NbtTreeError {
236    fn source(&self) -> Option<&(dyn Error + 'static)> {
237        match self {
238            Self::Parser(source) => Some(source),
239            _ => None,
240        }
241    }
242}
243
244fn invalid_nbt(
245    operation: CodecOperation,
246    bytes_processed: usize,
247    source: impl Error + Send + Sync + 'static,
248) -> CodecError {
249    CodecError::invalid_encoding_for_operation_with_source(
250        CodecKind::TextComponent,
251        operation,
252        bytes_processed,
253        InvalidEncodingReason::InvalidNbt,
254        source,
255    )
256}
257
258fn validate_nbt_strings(
259    root: &NbtValue,
260    operation: CodecOperation,
261    bytes_processed: usize,
262) -> Result<(), CodecError> {
263    let mut pending = vec![root];
264    while let Some(value) = pending.pop() {
265        match value {
266            NbtValue::String(value) => validate_nbt_string(value, operation, bytes_processed)?,
267            NbtValue::List(values) => pending.extend(values),
268            NbtValue::Compound(values) => {
269                for (name, value) in values {
270                    validate_nbt_string(name, operation, bytes_processed)?;
271                    pending.push(value);
272                }
273            }
274            _ => {}
275        }
276    }
277    Ok(())
278}
279
280fn validate_nbt_string(
281    value: &str,
282    operation: CodecOperation,
283    bytes_processed: usize,
284) -> Result<(), CodecError> {
285    let encoded_len = value.chars().try_fold(0_usize, |length, character| {
286        let width = match character as u32 {
287            0 => 2,
288            1..=0x7f => 1,
289            0x80..=0x7ff => 2,
290            0x800..=0xffff => 3,
291            _ => 6,
292        };
293        length.checked_add(width)
294    });
295    if !matches!(encoded_len, Some(length) if length <= u16::MAX as usize) {
296        return Err(CodecError::invalid_encoding_for_operation(
297            CodecKind::TextComponent,
298            operation,
299            bytes_processed,
300            InvalidEncodingReason::StringTooLong {
301                max_bytes: u16::MAX as usize,
302            },
303        ));
304    }
305    Ok(())
306}
307
308struct CountedReader<'a, R: ?Sized> {
309    inner: &'a mut R,
310    processed: usize,
311    failure: Option<io::Error>,
312}
313
314impl<'a, R: ?Sized> CountedReader<'a, R> {
315    fn new(inner: &'a mut R) -> Self {
316        Self {
317            inner,
318            processed: 0,
319            failure: None,
320        }
321    }
322}
323
324impl<R: Read + ?Sized> Read for CountedReader<'_, R> {
325    fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
326        if buffer.is_empty() {
327            return Ok(0);
328        }
329        match self.inner.read(buffer) {
330            Ok(0) => {
331                let error = io::Error::new(io::ErrorKind::UnexpectedEof, "unexpected end of NBT");
332                self.failure = Some(error);
333                Err(io::Error::new(
334                    io::ErrorKind::UnexpectedEof,
335                    "unexpected end of NBT",
336                ))
337            }
338            Ok(read) => {
339                self.processed += read;
340                Ok(read)
341            }
342            Err(error) => {
343                let returned = clone_io_error(&error);
344                self.failure = Some(error);
345                Err(returned)
346            }
347        }
348    }
349}
350
351struct PrefixReader<'a, 'b, R: ?Sized> {
352    prefix: &'a [u8],
353    inner: &'a mut CountedReader<'b, R>,
354}
355
356impl<R: Read + ?Sized> Read for PrefixReader<'_, '_, R> {
357    fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
358        if buffer.is_empty() {
359            return Ok(0);
360        }
361        if !self.prefix.is_empty() {
362            let read = buffer.len().min(self.prefix.len());
363            buffer[..read].copy_from_slice(&self.prefix[..read]);
364            self.prefix = &self.prefix[read..];
365            return Ok(read);
366        }
367        self.inner.read(buffer)
368    }
369}
370
371fn clone_io_error(error: &io::Error) -> io::Error {
372    match error.raw_os_error() {
373        Some(code) => io::Error::from_raw_os_error(code),
374        None => io::Error::new(error.kind(), error.to_string()),
375    }
376}