1mod x11r6colors;
51
52use std::cmp::Ordering;
53use std::fmt;
54use std::io::{BufRead, Bytes};
55
56use image::error::{
57 DecodingError, ImageError, ImageFormatHint, ImageResult, LimitError, LimitErrorKind,
58};
59use image::{ColorType, ImageDecoder, LimitSupport, Limits};
60
61const MAX_COLOR_NAME_LEN: usize = 32;
63
64#[derive(Clone, Copy, Debug)]
69struct TextLocation {
70 byte: u64,
71 line: u64,
72 column: u64,
73}
74
75struct TextReader<R> {
77 inner: R,
78
79 current: Option<u8>,
80
81 location: TextLocation,
82}
83
84impl<R> TextReader<R>
85where
86 R: Iterator<Item = u8>,
87{
88 fn new(mut r: R) -> TextReader<R> {
90 let current = r.next();
91 TextReader {
92 inner: r,
93 current,
94 location: TextLocation {
95 byte: 0,
96 line: 1,
97 column: 0,
98 },
99 }
100 }
101
102 fn next(&mut self) -> Option<u8> {
104 self.current?;
105
106 let mut current = self.inner.next();
107 std::mem::swap(&mut self.current, &mut current);
108
109 self.location.byte += 1;
110 self.location.column += 1;
111 if let Some(b'\n') = current {
112 self.location.line += 1;
113 self.location.column = 0;
114 }
115 current
116 }
117 fn peek(&self) -> Option<u8> {
119 self.current
120 }
121 fn loc(&self) -> TextLocation {
123 self.location
124 }
125}
126
127struct IoAdapter<R> {
131 reader: Bytes<R>,
132 error: Option<std::io::Error>,
133}
134
135impl<R> Iterator for IoAdapter<R>
136where
137 R: BufRead,
138{
139 type Item = u8;
140 #[inline(always)]
141 fn next(&mut self) -> Option<Self::Item> {
142 if self.error.is_some() {
143 return None;
144 }
145 match self.reader.next() {
146 None => None,
147 Some(Ok(v)) => Some(v),
148 Some(Err(e)) => {
149 self.error = Some(e);
150 None
151 }
152 }
153 }
154}
155
156pub struct XpmDecoder<R> {
158 r: TextReader<IoAdapter<R>>,
159 info: XpmHeaderInfo,
160}
161
162struct XpmHeaderInfo {
164 width: u32,
165 height: u32,
166 ncolors: u32,
167 cpp: u32,
169}
170
171struct XpmPalette {
173 table: Vec<XpmColorCodeEntry>,
179}
180
181struct XpmColorCodeEntry {
183 code: u64,
184 value: [u16; 4],
186}
187
188#[derive(Debug, Clone, Copy)]
189enum XpmPart {
190 Header,
191 ArrayStart,
192 FirstLine,
193 Palette,
194 Body,
195 Trailing,
196 AfterEnd,
197}
198
199#[derive(Debug)]
200enum XpmDecodeError {
201 Parse(XpmPart, TextLocation),
202 ZeroWidth,
203 ZeroHeight,
204 ZeroColors,
205 BadCharsPerColor(u32),
206 UnknownColor(([u8; MAX_COLOR_NAME_LEN], u8)),
209 NoColorModeColorSpecified,
211 BadHexColor,
212 DuplicateCode,
213 UnknownCode,
214 TwoKeysInARow,
215 MissingEntry,
216 MissingColorAfterKey,
217 MissingKeyBeforeColor,
218 InvalidColorName,
219 ColorNameTooLong,
220}
221
222#[derive(Debug)]
224enum XpmVisual {
225 Mono,
226 Symbolic,
227 Grayscale4,
228 Grayscale,
229 Color,
230}
231
232impl fmt::Display for TextLocation {
233 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234 f.write_fmt(format_args!(
235 "byte={},line={}:col={}",
236 self.byte, self.line, self.column
237 ))
238 }
239}
240
241impl fmt::Display for XpmPart {
242 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243 match self {
244 Self::Header => f.write_str("header"),
245 Self::ArrayStart => f.write_str("array definition"),
246 Self::FirstLine => f.write_str("<Values> section"),
247 Self::Palette => f.write_str("<Colors> section"),
248 Self::Body => f.write_str("<Pixels> section"),
249 Self::Trailing => f.write_str("array end"),
250 Self::AfterEnd => f.write_str("after final semicolon"),
251 }
252 }
253}
254
255impl fmt::Display for XpmDecodeError {
256 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257 match self {
258 Self::Parse(part, loc) => f.write_fmt(format_args!("Failed to parse {}, at {}", part, loc)),
259 Self::ZeroWidth => f.write_str("Invalid (zero) image width"),
260 Self::ZeroHeight => f.write_str("Invalid (zero) image height"),
261 Self::ZeroColors => f.write_str("Invalid (zero) number of colors"),
262 Self::BadCharsPerColor(c) => f.write_fmt(format_args!(
263 "Invalid number of characters per color: {} is not in [1,8]",
264 c
265 )),
266 Self::UnknownColor((buf, len)) => {
267 let s = std::str::from_utf8(&buf[..*len as usize]).ok().unwrap_or("");
268 assert!(s.chars().all(|x| x.is_ascii_alphanumeric()));
269 f.write_fmt(format_args!("Unknown color name \"{}\"; is not an X11R6 color.", s))
270 }
271 Self::NoColorModeColorSpecified => {
272 f.write_str("Color entry has no specified value for color visual")
273 }
274 Self::BadHexColor => f.write_str("Invalid hex RGB color"),
275 Self::DuplicateCode => f.write_str("Duplicate color code"),
276 Self::UnknownCode => f.write_str("Unknown color code"),
277
278 Self::ColorNameTooLong => f.write_str("Invalid color name, too long"),
279 Self::TwoKeysInARow => f.write_str("Invalid color specification, two keys in a row"),
280 Self::MissingEntry => f.write_str("Invalid color specification, must contain at least one key-color pair"),
281 Self::MissingColorAfterKey => f.write_str("Invalid color specification, no color name after key"),
282 Self::MissingKeyBeforeColor => f.write_str("Invalid color specification, no key before color name or could not parse value as key (m|s|g4|g|c)"),
283 Self::InvalidColorName => f.write_str("Invalid color name, contains non-alphanumeric or non-whitespace characters"),
284 }
285 }
286}
287
288impl std::error::Error for XpmDecodeError {}
289
290impl From<XpmDecodeError> for ImageError {
291 fn from(e: XpmDecodeError) -> ImageError {
292 ImageError::Decoding(DecodingError::new(ImageFormatHint::Name("XPM".into()), e))
293 }
294}
295
296trait XpmDecoderIoInjectionExt {
299 type Value;
300 fn apply_after(self, err: &mut Option<std::io::Error>) -> Result<Self::Value, ImageError>;
301}
302
303impl<X> XpmDecoderIoInjectionExt for Result<X, XpmDecodeError> {
304 type Value = X;
305 fn apply_after(self, err: &mut Option<std::io::Error>) -> Result<Self::Value, ImageError> {
306 if let Some(err) = err.take() {
307 return Err(ImageError::IoError(err));
308 }
309 match self {
310 Self::Ok(x) => Ok(x),
311 Self::Err(e) => Err(e.into()),
312 }
313 }
314}
315
316fn valid_name_char(x: u8) -> bool {
318 matches!(x, b'#' | b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z' | b'_')
320}
321fn fold_to_lower(x: u8) -> u8 {
323 match x {
324 b'A'..=b'Z' => (x - b'A') + b'a',
325 _ => x,
326 }
327}
328
329fn read_fixed_string<R: Iterator<Item = u8>>(
331 r: &mut TextReader<R>,
332 s: &[u8],
333 part: XpmPart,
334) -> Result<(), XpmDecodeError> {
335 for c in s {
336 if let Some(b) = r.next() {
337 if b != *c {
338 return Err(XpmDecodeError::Parse(part, r.loc()));
339 }
340 } else {
341 return Err(XpmDecodeError::Parse(part, r.loc()));
342 };
343 }
344 Ok(())
345}
346fn read_byte<R: Iterator<Item = u8>>(
348 r: &mut TextReader<R>,
349 part: XpmPart,
350) -> Result<u8, XpmDecodeError> {
351 match r.next() {
352 None => Err(XpmDecodeError::Parse(part, r.loc())),
353 Some(b) => Ok(b),
354 }
355}
356
357fn read_whitespace_gap<R: Iterator<Item = u8>>(
360 r: &mut TextReader<R>,
361 part: XpmPart,
362) -> Result<(), XpmDecodeError> {
363 let b = read_byte(r, part)?;
364 if !(b == b' ' || b == b'\t') {
365 return Err(XpmDecodeError::Parse(part, r.loc()));
366 }
367 while let Some(b) = r.peek() {
368 if b == b' ' || b == b'\t' {
369 r.next();
370 continue;
371 } else {
372 return Ok(());
373 }
374 }
375 Ok(())
376}
377
378fn skip_whitespace_and_comments<R: Iterator<Item = u8>>(
381 r: &mut TextReader<R>,
382 part: XpmPart,
383) -> Result<usize, XpmDecodeError> {
384 let mut nbytes = 0;
385
386 let mut has_first_char = false;
388 let mut in_comment = false;
389
390 while let Some(b) = r.peek() {
391 if !in_comment {
392 if has_first_char {
393 if b != b'*' {
394 return Err(XpmDecodeError::Parse(part, r.loc()));
395 } else {
396 in_comment = true;
397 has_first_char = false;
398 }
399 }
400 if b == b'/' {
401 has_first_char = true;
402 }
403 }
404 if b == b' ' || b == b'\t' || b == b'\n' || b == b'/' || in_comment {
405 if in_comment {
406 if has_first_char && b == b'/' {
407 in_comment = false;
408 }
409 has_first_char = b == b'*';
410 }
411 nbytes += 1;
412 r.next();
413 continue;
414 } else {
415 break;
416 }
417 }
418 if !in_comment && has_first_char {
419 return Err(XpmDecodeError::Parse(part, r.loc()));
421 }
422
423 Ok(nbytes)
424}
425
426fn skip_spaces_and_tabs<R: Iterator<Item = u8>>(
427 r: &mut TextReader<R>,
428) -> Result<usize, XpmDecodeError> {
429 let mut nbytes = 0;
430 while let Some(b) = r.peek() {
431 if b == b' ' || b == b'\t' {
432 nbytes += 1;
433 r.next();
434 continue;
435 } else {
436 break;
437 }
438 }
439 Ok(nbytes)
440}
441
442fn read_to_newline<R: Iterator<Item = u8>>(
444 r: &mut TextReader<R>,
445 part: XpmPart,
446) -> Result<(), XpmDecodeError> {
447 while let Some(b) = r.peek() {
448 if b == b' ' || b == b'\t' {
449 r.next();
450 continue;
451 } else {
452 break;
453 }
454 }
455 if read_byte(r, part)? != b'\n' {
456 Err(XpmDecodeError::Parse(part, r.loc()))
457 } else {
458 Ok(())
459 }
460}
461fn read_until_whitespace_or_eos<'a, R: Iterator<Item = u8>>(
464 r: &mut TextReader<R>,
465 buf: &'a mut [u8],
466 part: XpmPart,
467) -> Result<&'a mut [u8], XpmDecodeError> {
468 let mut len = 0;
469 while let Some(b) = r.peek() {
470 if b == b' ' || b == b'\t' || b == b'"' {
471 return Ok(&mut buf[..len]);
472 } else if b == b'\\' {
473 r.next();
474 return Err(XpmDecodeError::Parse(part, r.loc()));
475 } else {
476 if len >= buf.len() {
477 return Err(XpmDecodeError::Parse(part, r.loc()));
479 }
480 buf[len] = b;
481 len += 1;
482 r.next();
483 }
484 }
485 Ok(&mut buf[..len])
486}
487
488fn read_all_except_eos<R: Iterator<Item = u8>>(
490 r: &mut TextReader<R>,
491 buf: &mut [u8],
492 part: XpmPart,
493) -> Result<(), XpmDecodeError> {
494 let mut len = 0;
495 while let Some(b) = r.peek() {
496 if b == b'"' || b == b'\\' {
497 r.next();
498 return Err(XpmDecodeError::Parse(part, r.loc()));
499 } else {
500 buf[len] = b;
501 len += 1;
502 r.next();
503 if len >= buf.len() {
504 return Ok(());
505 }
506 }
507 }
508 Err(XpmDecodeError::Parse(part, r.loc()))
509}
510
511fn read_name<R: Iterator<Item = u8>>(
515 r: &mut TextReader<R>,
516 part: XpmPart,
517) -> Result<(), XpmDecodeError> {
518 let mut empty = true;
519 while let Some(b) = r.peek() {
520 match b {
521 b'/' | b' ' | b'\t' | b'\n' | b'[' => {
522 break;
523 }
524 _ => (),
525 }
526 r.next();
527 empty = false;
528 }
529 if empty {
530 return Err(XpmDecodeError::Parse(part, r.loc()));
531 }
532
533 Ok(())
534}
535
536fn parse_i32(data: &[u8]) -> Option<i32> {
538 if data.starts_with(b"-") {
539 (-(parse_u32(&data[1..])? as i64)).try_into().ok()
540 } else {
541 parse_u32(data)?.try_into().ok()
542 }
543}
544
545fn parse_u32(data: &[u8]) -> Option<u32> {
547 let Some(c1) = data.first() else {
548 return None;
550 };
551 if *c1 == b'0' && data.len() > 1 {
552 return None;
554 }
555 let mut x: u32 = 0;
556 for c in data {
557 if b'0' <= *c && *c <= b'9' {
558 x = x.checked_mul(10)?.checked_add((*c - b'0') as u32)?;
559 } else {
560 return None;
561 }
562 }
563 Some(x)
564}
565fn parse_hex(b: u8) -> Option<u8> {
566 match b {
567 b'0'..=b'9' => Some(b - b'0'),
568 b'A'..=b'F' => Some(b - b'A' + 10),
569 b'a'..=b'f' => Some(b - b'a' + 10),
570 _ => None,
571 }
572}
573fn parse_hex1(x1: u8) -> Option<u16> {
574 let x = parse_hex(x1)? as u16;
575 Some(x | (x << 4) | (x << 8) | (x << 12))
576}
577fn parse_hex2(x2: u8, x1: u8) -> Option<u16> {
578 let x = ((parse_hex(x2)? as u16) << 4) | (parse_hex(x1)? as u16);
579 Some(x | (x << 8))
580}
581fn parse_hex3(x3: u8, x2: u8, x1: u8) -> Option<u16> {
582 let x =
583 ((parse_hex(x3)? as u16) << 8) | ((parse_hex(x2)? as u16) << 4) | (parse_hex(x1)? as u16);
584 Some((((x as u32) * 65535 + 2047) / 4095) as u16)
588}
589fn parse_hex4(x4: u8, x3: u8, x2: u8, x1: u8) -> Option<u16> {
590 Some(
591 (parse_hex(x1)? as u16)
592 | ((parse_hex(x2)? as u16) << 4)
593 | ((parse_hex(x3)? as u16) << 8)
594 | ((parse_hex(x4)? as u16) << 12),
595 )
596}
597fn scale_u8_to_u16(x: u8) -> u16 {
598 (x as u16) << 8 | (x as u16)
599}
600
601fn parse_hex_color(data: &[u8]) -> Option<[u16; 4]> {
605 Some(match data {
606 [r, g, b] => [parse_hex1(*r)?, parse_hex1(*g)?, parse_hex1(*b)?, 0xffff],
607 [r2, r1, g2, g1, b2, b1] => [
608 parse_hex2(*r2, *r1)?,
609 parse_hex2(*g2, *g1)?,
610 parse_hex2(*b2, *b1)?,
611 0xffff,
612 ],
613 [r3, r2, r1, g3, g2, g1, b3, b2, b1] => [
614 parse_hex3(*r3, *r2, *r1)?,
615 parse_hex3(*g3, *g2, *g1)?,
616 parse_hex3(*b3, *b2, *b1)?,
617 0xffff,
618 ],
619 [r4, r3, r2, r1, g4, g3, g2, g1, b4, b3, b2, b1] => [
620 parse_hex4(*r4, *r3, *r2, *r1)?,
621 parse_hex4(*g4, *g3, *g2, *g1)?,
622 parse_hex4(*b4, *b3, *b2, *b1)?,
623 0xffff,
624 ],
625 _ => {
626 return None;
627 }
628 })
629}
630
631fn parse_color(data: &[u8]) -> Result<[u16; 4], XpmDecodeError> {
632 if data.starts_with(b"#") {
633 parse_hex_color(&data[1..]).ok_or(XpmDecodeError::BadHexColor)
634 } else {
635 if data == b"none" {
636 return Ok([0, 0, 0, 0]);
637 }
638
639 if let Ok(idx) = x11r6colors::COLORS.binary_search_by(|entry| entry.0.as_bytes().cmp(data))
640 {
641 let entry = x11r6colors::COLORS[idx];
642 Ok([
643 scale_u8_to_u16(entry.1),
644 scale_u8_to_u16(entry.2),
645 scale_u8_to_u16(entry.3),
646 0xffff,
647 ])
648 } else {
649 assert!(data.len() <= MAX_COLOR_NAME_LEN);
652 let mut tmp = [0u8; MAX_COLOR_NAME_LEN];
653 tmp[..data.len()].copy_from_slice(data);
654 Err(XpmDecodeError::UnknownColor((tmp, data.len() as u8)))
655 }
656 }
657}
658
659fn read_xpm_header<R: Iterator<Item = u8>>(
661 r: &mut TextReader<R>,
662) -> Result<XpmHeaderInfo, XpmDecodeError> {
663 read_fixed_string(r, b"/* XPM */", XpmPart::Header)?;
665 read_to_newline(r, XpmPart::Header)?;
666
667 skip_whitespace_and_comments(r, XpmPart::ArrayStart)?;
668 read_fixed_string(r, b"static", XpmPart::ArrayStart)?;
669 if skip_whitespace_and_comments(r, XpmPart::ArrayStart)? == 0 {
670 return Err(XpmDecodeError::Parse(XpmPart::ArrayStart, r.loc()));
672 }
673 read_fixed_string(r, b"char", XpmPart::ArrayStart)?;
674 skip_whitespace_and_comments(r, XpmPart::ArrayStart)?;
675 read_fixed_string(r, b"*", XpmPart::ArrayStart)?;
676 skip_whitespace_and_comments(r, XpmPart::ArrayStart)?;
677 read_name(r, XpmPart::ArrayStart)?;
678 skip_whitespace_and_comments(r, XpmPart::ArrayStart)?;
679 read_fixed_string(r, b"[", XpmPart::ArrayStart)?;
680 skip_whitespace_and_comments(r, XpmPart::ArrayStart)?;
681 read_fixed_string(r, b"]", XpmPart::ArrayStart)?;
682 skip_whitespace_and_comments(r, XpmPart::ArrayStart)?;
683 read_fixed_string(r, b"=", XpmPart::ArrayStart)?;
684 skip_whitespace_and_comments(r, XpmPart::ArrayStart)?;
685 read_fixed_string(r, b"{", XpmPart::ArrayStart)?;
686 skip_whitespace_and_comments(r, XpmPart::ArrayStart)?;
687
688 read_fixed_string(r, b"\"", XpmPart::FirstLine)?;
690
691 let mut int_buf = [0u8; 10]; skip_spaces_and_tabs(r)?; let int = read_until_whitespace_or_eos(r, &mut int_buf, XpmPart::FirstLine)?;
695 let width = parse_u32(int).ok_or(XpmDecodeError::Parse(XpmPart::FirstLine, r.loc()))?;
696 if width == 0 {
697 return Err(XpmDecodeError::ZeroWidth);
698 }
699
700 read_whitespace_gap(r, XpmPart::FirstLine)?;
701 let int = read_until_whitespace_or_eos(r, &mut int_buf, XpmPart::FirstLine)?;
702 let height = parse_u32(int).ok_or(XpmDecodeError::Parse(XpmPart::FirstLine, r.loc()))?;
703 if height == 0 {
704 return Err(XpmDecodeError::ZeroHeight);
705 }
706
707 read_whitespace_gap(r, XpmPart::FirstLine)?;
708 let int = read_until_whitespace_or_eos(r, &mut int_buf, XpmPart::FirstLine)?;
709 let ncolors = parse_u32(int).ok_or(XpmDecodeError::Parse(XpmPart::FirstLine, r.loc()))?;
710 read_whitespace_gap(r, XpmPart::FirstLine)?;
711 let int = read_until_whitespace_or_eos(r, &mut int_buf, XpmPart::FirstLine)?;
712 let cpp = parse_u32(int).ok_or(XpmDecodeError::Parse(XpmPart::FirstLine, r.loc()))?;
713 skip_spaces_and_tabs(r)?;
714
715 let _hotspot = if let Some(b'"') = r.peek() {
716 None
718 } else {
719 let int = read_until_whitespace_or_eos(r, &mut int_buf, XpmPart::FirstLine)?;
720 let hotspot_x = parse_i32(int).ok_or(XpmDecodeError::Parse(XpmPart::FirstLine, r.loc()))?;
721 read_whitespace_gap(r, XpmPart::FirstLine)?;
722 let int = read_until_whitespace_or_eos(r, &mut int_buf, XpmPart::FirstLine)?;
723 let hotspot_y = parse_i32(int).ok_or(XpmDecodeError::Parse(XpmPart::FirstLine, r.loc()))?;
724 skip_spaces_and_tabs(r)?;
725
726 Some((hotspot_x, hotspot_y))
728 };
729 read_fixed_string(r, b"\"", XpmPart::FirstLine)?;
732 skip_whitespace_and_comments(r, XpmPart::FirstLine)?;
733 read_fixed_string(r, b",", XpmPart::FirstLine)?;
734 skip_whitespace_and_comments(r, XpmPart::FirstLine)?;
735
736 if ncolors == 0 {
737 return Err(XpmDecodeError::ZeroColors);
738 }
739 if cpp == 0 || cpp > 8 {
740 return Err(XpmDecodeError::BadCharsPerColor(cpp));
743 }
744
745 Ok(XpmHeaderInfo {
746 width,
747 height,
748 ncolors,
749 cpp,
750 })
751}
752fn read_xpm_palette<R: Iterator<Item = u8>>(
754 r: &mut TextReader<R>,
755 info: &XpmHeaderInfo,
756) -> Result<XpmPalette, XpmDecodeError> {
757 assert!(1 <= info.cpp && info.cpp <= 8);
758
759 assert!(x11r6colors::COLORS.windows(2).all(|p| p[0].0 < p[1].0));
761
762 let mut color_table: Vec<XpmColorCodeEntry> = Vec::new();
769
770 for _col in 0..info.ncolors {
771 read_fixed_string(r, b"\"", XpmPart::Palette)?;
772
773 let mut code = [0_u8; 8];
774 read_all_except_eos(r, &mut code[..info.cpp as usize], XpmPart::Palette)?;
775 read_whitespace_gap(r, XpmPart::Palette)?;
776
777 let mut color_name_buf = [0_u8; MAX_COLOR_NAME_LEN];
790 let mut color_name_len = 0;
791 let mut next_buf = [0_u8; MAX_COLOR_NAME_LEN];
792
793 let mut key: Option<XpmVisual> = None;
794
795 let mut cvis_color = None;
796 loop {
797 if r.peek().unwrap_or(b'"') == b'"' {
798 let Some(ref k) = key else {
799 return Err(XpmDecodeError::MissingEntry);
801 };
802 if color_name_len == 0 {
803 return Err(XpmDecodeError::MissingColorAfterKey);
805 }
806
807 let color = handle_key_color(k, &color_name_buf[..color_name_len])?;
808 cvis_color = color.or(cvis_color);
809 break;
810 }
811
812 let next = read_until_whitespace_or_eos(r, &mut next_buf, XpmPart::Palette)?;
813 skip_spaces_and_tabs(r)?;
814
815 let this_key = match &next[..] {
816 b"m" => Some(XpmVisual::Mono),
817 b"s" => Some(XpmVisual::Symbolic),
818 b"g4" => Some(XpmVisual::Grayscale4),
819 b"g" => Some(XpmVisual::Grayscale),
820 b"c" => Some(XpmVisual::Color),
821 _ => None,
822 };
823
824 let Some(ref k) = key else {
825 if this_key.is_none() {
827 return Err(XpmDecodeError::MissingKeyBeforeColor);
829 };
830
831 key = this_key;
832 continue;
833 };
834
835 if this_key.is_some() {
836 if color_name_len == 0 {
838 return Err(XpmDecodeError::TwoKeysInARow);
839 }
840
841 let color = handle_key_color(k, &color_name_buf[..color_name_len])?;
842 cvis_color = color.or(cvis_color);
843 color_name_len = 0;
844 key = this_key;
845 continue;
846 }
847
848 if color_name_len > 0 {
851 if color_name_len < MAX_COLOR_NAME_LEN {
852 color_name_buf[color_name_len] = b' ';
853 color_name_len += 1;
854 } else {
855 return Err(XpmDecodeError::ColorNameTooLong);
856 }
857 }
858 for c in next {
859 if !valid_name_char(*c) {
860 return Err(XpmDecodeError::InvalidColorName);
861 }
862 if color_name_len < MAX_COLOR_NAME_LEN {
865 color_name_buf[color_name_len] = fold_to_lower(*c);
866 color_name_len += 1;
867 } else {
868 return Err(XpmDecodeError::ColorNameTooLong);
869 }
870 }
871 }
872
873 let Some(color) = cvis_color else {
874 return Err(XpmDecodeError::NoColorModeColorSpecified);
875 };
876
877 color_table.push(XpmColorCodeEntry {
878 code: u64::from_le_bytes(code),
879 value: color,
880 });
881
882 read_fixed_string(r, b"\"", XpmPart::Palette)?;
883 skip_whitespace_and_comments(r, XpmPart::Palette)?;
884 read_fixed_string(r, b",", XpmPart::Palette)?;
885 skip_whitespace_and_comments(r, XpmPart::Palette)?;
886 }
887
888 color_table.sort_unstable_by(|x, y| x.code.cmp(&y.code));
890 for w in color_table.windows(2) {
891 if w[0].code.cmp(&w[1].code) != Ordering::Less {
892 return Err(XpmDecodeError::DuplicateCode);
893 }
894 }
895
896 read_fixed_string(r, b"\"", XpmPart::Body)?;
897
898 Ok(XpmPalette { table: color_table })
899}
900fn read_xpm_pixel<R: Iterator<Item = u8>>(
902 r: &mut TextReader<R>,
903 info: &XpmHeaderInfo,
904 palette: &XpmPalette,
905 chunk: &mut [u8; 8],
906) -> Result<(), XpmDecodeError> {
907 let mut code = [0_u8; 8];
908 read_all_except_eos(r, &mut code[..info.cpp as usize], XpmPart::Palette)?;
909 let code = u64::from_le_bytes(code);
910
911 let Ok(index) = palette
912 .table
913 .binary_search_by(|entry| entry.code.cmp(&code))
914 else {
915 return Err(XpmDecodeError::UnknownCode);
916 };
917
918 let color = palette.table[index].value;
919 chunk[0..2].copy_from_slice(&color[0].to_ne_bytes());
921 chunk[2..4].copy_from_slice(&color[1].to_ne_bytes());
922 chunk[4..6].copy_from_slice(&color[2].to_ne_bytes());
923 chunk[6..8].copy_from_slice(&color[3].to_ne_bytes());
924 Ok(())
925}
926fn read_xpm_row_transition<R: Iterator<Item = u8>>(
929 r: &mut TextReader<R>,
930) -> Result<(), XpmDecodeError> {
931 read_fixed_string(r, b"\"", XpmPart::Body)?;
933
934 skip_whitespace_and_comments(r, XpmPart::Body)?;
935 read_fixed_string(r, b",", XpmPart::Body)?;
936 skip_whitespace_and_comments(r, XpmPart::Body)?;
937 read_fixed_string(r, b"\"", XpmPart::Body)?;
939 Ok(())
940}
941fn read_xpm_trailing<R: Iterator<Item = u8>>(r: &mut TextReader<R>) -> Result<(), XpmDecodeError> {
943 read_fixed_string(r, b"\"", XpmPart::Body)?;
945
946 skip_whitespace_and_comments(r, XpmPart::Trailing)?;
948 let next = read_byte(r, XpmPart::Trailing)?;
949 if next == b',' {
950 skip_whitespace_and_comments(r, XpmPart::Trailing)?;
951 read_fixed_string(r, b"}", XpmPart::Trailing)?;
952 } else if next != b'}' {
953 return Err(XpmDecodeError::Parse(XpmPart::Trailing, r.loc()));
954 }
955 skip_whitespace_and_comments(r, XpmPart::Trailing)?;
956 read_fixed_string(r, b";", XpmPart::Trailing)?;
957
958 skip_whitespace_and_comments(r, XpmPart::AfterEnd)?;
959 if r.next().is_some() {
960 Err(XpmDecodeError::Parse(XpmPart::AfterEnd, r.loc()))
962 } else {
963 Ok(())
964 }
965}
966
967impl<R> XpmDecoder<R>
968where
969 R: BufRead,
970{
971 pub fn new(reader: R) -> Result<XpmDecoder<R>, ImageError> {
973 let mut r = TextReader::new(IoAdapter {
974 reader: reader.bytes(),
975 error: None,
976 });
977
978 let info = read_xpm_header(&mut r).apply_after(&mut r.inner.error)?;
979
980 Ok(XpmDecoder { r, info })
981 }
982}
983
984fn handle_key_color(key: &XpmVisual, color: &[u8]) -> Result<Option<[u16; 4]>, XpmDecodeError> {
986 if matches!(key, XpmVisual::Symbolic) {
987 return Ok(None);
988 }
989 let color = parse_color(color)?;
990 if matches!(key, XpmVisual::Color) {
991 Ok(Some(color))
992 } else {
993 Ok(None)
994 }
995}
996
997impl<R: BufRead> ImageDecoder for XpmDecoder<R> {
998 fn dimensions(&self) -> (u32, u32) {
999 (self.info.width, self.info.height)
1000 }
1001 fn color_type(&self) -> ColorType {
1002 ColorType::Rgba16
1005 }
1006 fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()>
1007 where
1008 Self: Sized,
1009 {
1010 assert!(1 <= self.info.cpp && self.info.cpp <= 8);
1011
1012 let palette =
1013 read_xpm_palette(&mut self.r, &self.info).apply_after(&mut self.r.inner.error)?;
1014
1015 let stride = (self.info.width as usize).checked_mul(8).unwrap();
1017 for (i, row) in buf.chunks_exact_mut(stride).enumerate() {
1018 for chunk in row.chunks_exact_mut(8) {
1019 read_xpm_pixel(&mut self.r, &self.info, &palette, chunk.try_into().unwrap())
1020 .apply_after(&mut self.r.inner.error)?;
1021 }
1022
1023 if i >= (self.info.height - 1) as usize {
1024 } else {
1026 read_xpm_row_transition(&mut self.r).apply_after(&mut self.r.inner.error)?;
1027 }
1028 }
1029
1030 read_xpm_trailing(&mut self.r).apply_after(&mut self.r.inner.error)?;
1031
1032 Ok(())
1033 }
1034 fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
1035 (*self).read_image(buf)
1036 }
1037
1038 fn set_limits(&mut self, limits: Limits) -> ImageResult<()> {
1039 limits.check_support(&LimitSupport::default())?;
1040 let (width, height) = self.dimensions();
1041 limits.check_dimensions(width, height)?;
1042
1043 let max_pixels = u64::from(self.info.width) * u64::from(self.info.height);
1044 let max_image_bytes =
1045 max_pixels
1046 .checked_mul(8)
1047 .ok_or(ImageError::Limits(LimitError::from_kind(
1048 LimitErrorKind::DimensionError,
1049 )))?;
1050
1051 let max_table_bytes = (self.info.ncolors as u64) * (size_of::<XpmColorCodeEntry>() as u64);
1052 let max_bytes = max_image_bytes
1053 .checked_add(max_table_bytes)
1054 .ok_or(ImageError::Limits(LimitError::from_kind(
1055 LimitErrorKind::InsufficientMemory,
1056 )))?;
1057
1058 let max_alloc = limits.max_alloc.unwrap_or(u64::MAX);
1059 if max_alloc < max_bytes {
1060 return Err(ImageError::Limits(LimitError::from_kind(
1061 LimitErrorKind::InsufficientMemory,
1062 )));
1063 }
1064 Ok(())
1065 }
1066}
1067
1068#[cfg(test)]
1069mod tests {
1070 use super::*;
1071
1072 #[test]
1073 fn image_missing_body() {
1074 let data = b"/* XPM */
1075static char *test[] = {
1076\"20 5 10 1\",
1077};
1078";
1079 let decoder = XpmDecoder::new(&data[..]).unwrap();
1080 let mut image = vec![0; decoder.total_bytes() as usize];
1081 assert!(decoder.read_image(&mut image).is_err());
1082 }
1083
1084 #[test]
1085 fn invalid_color_name() {
1086 let data = b"/* XPM */
1087static char *test[] = {
1088 \"1 1 1 1\",
1089 \" c Antique White1\",
1090 \" \",
1091};";
1092 let decoder = XpmDecoder::new(&data[..]).unwrap();
1093 let mut image = vec![0; decoder.total_bytes() as usize];
1094 assert!(decoder.read_image(&mut image).is_err());
1095 }
1096
1097 #[test]
1098 fn trailing_semicolon_required() {
1099 let data = b"/* XPM */
1100 static char *test[] = {
1101 \"1 1 1 1\",
1102 \" c none\",
1103 \" \",
1104 };";
1105 let decoder = XpmDecoder::new(&data[..data.len() - 1]).unwrap();
1106 let mut image = vec![0; decoder.total_bytes() as usize];
1107 assert!(decoder.read_image(&mut image).is_err());
1108
1109 let decoder = XpmDecoder::new(&data[..]).unwrap();
1110 let mut image = vec![0; decoder.total_bytes() as usize];
1111 assert!(decoder.read_image(&mut image).is_ok());
1112 }
1113}