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_keyword<'buf, R: Iterator<Item = u8>>(
336 r: &mut TextReader<R>,
337 buf: &'buf mut [u8],
338 part: XpmPart,
339) -> Result<&'buf [u8], XpmDecodeError> {
340 let mut len = 0;
341
342 while let Some(b) = r.peek() {
343 if matches!(b, b'_' | b'a'..=b'z' | b'A'..=b'Z') {
344 if len >= buf.len() {
345 return Err(XpmDecodeError::Parse(part, r.loc()));
347 }
348 buf[len] = b;
349 len += 1;
350 r.next();
351 } else {
352 break;
353 }
354 }
355
356 Ok(&buf[..len])
357}
358fn read_fixed_string<R: Iterator<Item = u8>>(
360 r: &mut TextReader<R>,
361 s: &[u8],
362 part: XpmPart,
363) -> Result<(), XpmDecodeError> {
364 for c in s {
365 if let Some(b) = r.next() {
366 if b != *c {
367 return Err(XpmDecodeError::Parse(part, r.loc()));
368 }
369 } else {
370 return Err(XpmDecodeError::Parse(part, r.loc()));
371 };
372 }
373 Ok(())
374}
375fn read_byte<R: Iterator<Item = u8>>(
377 r: &mut TextReader<R>,
378 part: XpmPart,
379) -> Result<u8, XpmDecodeError> {
380 match r.next() {
381 None => Err(XpmDecodeError::Parse(part, r.loc())),
382 Some(b) => Ok(b),
383 }
384}
385
386fn read_whitespace_gap<R: Iterator<Item = u8>>(
389 r: &mut TextReader<R>,
390 part: XpmPart,
391) -> Result<(), XpmDecodeError> {
392 let b = read_byte(r, part)?;
393 if !(b == b' ' || b == b'\t') {
394 return Err(XpmDecodeError::Parse(part, r.loc()));
395 }
396 while let Some(b) = r.peek() {
397 if b == b' ' || b == b'\t' {
398 r.next();
399 continue;
400 } else {
401 return Ok(());
402 }
403 }
404 Ok(())
405}
406
407fn skip_whitespace_and_comments<R: Iterator<Item = u8>>(
410 r: &mut TextReader<R>,
411 part: XpmPart,
412) -> Result<usize, XpmDecodeError> {
413 let mut nbytes = 0;
414
415 let mut has_first_char = false;
417 let mut in_comment = false;
418
419 while let Some(b) = r.peek() {
420 if !in_comment {
421 if has_first_char {
422 if b != b'*' {
423 return Err(XpmDecodeError::Parse(part, r.loc()));
424 } else {
425 in_comment = true;
426 has_first_char = false;
427 }
428 }
429 if b == b'/' {
430 has_first_char = true;
431 }
432 }
433 if b == b' ' || b == b'\t' || b == b'\n' || b == b'/' || in_comment {
434 if in_comment {
435 if has_first_char && b == b'/' {
436 in_comment = false;
437 }
438 has_first_char = b == b'*';
439 }
440 nbytes += 1;
441 r.next();
442 continue;
443 } else {
444 break;
445 }
446 }
447 if !in_comment && has_first_char {
448 return Err(XpmDecodeError::Parse(part, r.loc()));
450 }
451
452 Ok(nbytes)
453}
454
455fn skip_non_empty_whitespace_and_comments<R: Iterator<Item = u8>>(
457 r: &mut TextReader<R>,
458 part: XpmPart,
459) -> Result<(), XpmDecodeError> {
460 let spaces = skip_whitespace_and_comments(r, part)?;
461 if spaces == 0 {
462 return Err(XpmDecodeError::Parse(part, r.loc()));
463 }
464 Ok(())
465}
466
467fn skip_spaces_and_tabs<R: Iterator<Item = u8>>(
468 r: &mut TextReader<R>,
469) -> Result<usize, XpmDecodeError> {
470 let mut nbytes = 0;
471 while let Some(b) = r.peek() {
472 if b == b' ' || b == b'\t' {
473 nbytes += 1;
474 r.next();
475 continue;
476 } else {
477 break;
478 }
479 }
480 Ok(nbytes)
481}
482
483fn read_to_newline<R: Iterator<Item = u8>>(
485 r: &mut TextReader<R>,
486 part: XpmPart,
487) -> Result<(), XpmDecodeError> {
488 while let Some(b) = r.peek() {
489 if b == b' ' || b == b'\t' {
490 r.next();
491 continue;
492 } else {
493 break;
494 }
495 }
496 if read_byte(r, part)? != b'\n' {
497 Err(XpmDecodeError::Parse(part, r.loc()))
498 } else {
499 Ok(())
500 }
501}
502fn read_until_whitespace_or_eos<'a, R: Iterator<Item = u8>>(
505 r: &mut TextReader<R>,
506 buf: &'a mut [u8],
507 part: XpmPart,
508) -> Result<&'a mut [u8], XpmDecodeError> {
509 let mut len = 0;
510 while let Some(b) = r.peek() {
511 if b == b' ' || b == b'\t' || b == b'"' {
512 return Ok(&mut buf[..len]);
513 } else if b == b'\\' {
514 r.next();
515 return Err(XpmDecodeError::Parse(part, r.loc()));
516 } else {
517 if len >= buf.len() {
518 return Err(XpmDecodeError::Parse(part, r.loc()));
520 }
521 buf[len] = b;
522 len += 1;
523 r.next();
524 }
525 }
526 Ok(&mut buf[..len])
527}
528
529fn read_all_except_eos<R: Iterator<Item = u8>>(
531 r: &mut TextReader<R>,
532 buf: &mut [u8],
533 part: XpmPart,
534) -> Result<(), XpmDecodeError> {
535 let mut len = 0;
536 while let Some(b) = r.peek() {
537 if b == b'"' || b == b'\\' {
538 r.next();
539 return Err(XpmDecodeError::Parse(part, r.loc()));
540 } else {
541 buf[len] = b;
542 len += 1;
543 r.next();
544 if len >= buf.len() {
545 return Ok(());
546 }
547 }
548 }
549 Err(XpmDecodeError::Parse(part, r.loc()))
550}
551
552fn read_name<R: Iterator<Item = u8>>(
556 r: &mut TextReader<R>,
557 part: XpmPart,
558) -> Result<(), XpmDecodeError> {
559 let mut empty = true;
560 while let Some(b) = r.peek() {
561 match b {
562 b'/' | b' ' | b'\t' | b'\n' | b'[' => {
563 break;
564 }
565 _ => (),
566 }
567 r.next();
568 empty = false;
569 }
570 if empty {
571 return Err(XpmDecodeError::Parse(part, r.loc()));
572 }
573
574 Ok(())
575}
576
577fn parse_i32(data: &[u8]) -> Option<i32> {
579 if data.starts_with(b"-") {
580 (-(parse_u32(&data[1..])? as i64)).try_into().ok()
581 } else {
582 parse_u32(data)?.try_into().ok()
583 }
584}
585
586fn parse_u32(data: &[u8]) -> Option<u32> {
588 let Some(c1) = data.first() else {
589 return None;
591 };
592 if *c1 == b'0' && data.len() > 1 {
593 return None;
595 }
596 let mut x: u32 = 0;
597 for c in data {
598 if b'0' <= *c && *c <= b'9' {
599 x = x.checked_mul(10)?.checked_add((*c - b'0') as u32)?;
600 } else {
601 return None;
602 }
603 }
604 Some(x)
605}
606fn parse_hex(b: u8) -> Option<u8> {
607 match b {
608 b'0'..=b'9' => Some(b - b'0'),
609 b'A'..=b'F' => Some(b - b'A' + 10),
610 b'a'..=b'f' => Some(b - b'a' + 10),
611 _ => None,
612 }
613}
614fn parse_hex1(x1: u8) -> Option<u16> {
615 let x = parse_hex(x1)? as u16;
616 Some(x | (x << 4) | (x << 8) | (x << 12))
617}
618fn parse_hex2(x2: u8, x1: u8) -> Option<u16> {
619 let x = ((parse_hex(x2)? as u16) << 4) | (parse_hex(x1)? as u16);
620 Some(x | (x << 8))
621}
622fn parse_hex3(x3: u8, x2: u8, x1: u8) -> Option<u16> {
623 let x =
624 ((parse_hex(x3)? as u16) << 8) | ((parse_hex(x2)? as u16) << 4) | (parse_hex(x1)? as u16);
625 Some((((x as u32) * 65535 + 2047) / 4095) as u16)
629}
630fn parse_hex4(x4: u8, x3: u8, x2: u8, x1: u8) -> Option<u16> {
631 Some(
632 (parse_hex(x1)? as u16)
633 | ((parse_hex(x2)? as u16) << 4)
634 | ((parse_hex(x3)? as u16) << 8)
635 | ((parse_hex(x4)? as u16) << 12),
636 )
637}
638fn scale_u8_to_u16(x: u8) -> u16 {
639 (x as u16) << 8 | (x as u16)
640}
641
642fn parse_hex_color(data: &[u8]) -> Option<[u16; 4]> {
646 Some(match data {
647 [r, g, b] => [parse_hex1(*r)?, parse_hex1(*g)?, parse_hex1(*b)?, 0xffff],
648 [r2, r1, g2, g1, b2, b1] => [
649 parse_hex2(*r2, *r1)?,
650 parse_hex2(*g2, *g1)?,
651 parse_hex2(*b2, *b1)?,
652 0xffff,
653 ],
654 [r3, r2, r1, g3, g2, g1, b3, b2, b1] => [
655 parse_hex3(*r3, *r2, *r1)?,
656 parse_hex3(*g3, *g2, *g1)?,
657 parse_hex3(*b3, *b2, *b1)?,
658 0xffff,
659 ],
660 [r4, r3, r2, r1, g4, g3, g2, g1, b4, b3, b2, b1] => [
661 parse_hex4(*r4, *r3, *r2, *r1)?,
662 parse_hex4(*g4, *g3, *g2, *g1)?,
663 parse_hex4(*b4, *b3, *b2, *b1)?,
664 0xffff,
665 ],
666 _ => {
667 return None;
668 }
669 })
670}
671
672fn parse_color(data: &[u8]) -> Result<[u16; 4], XpmDecodeError> {
673 if data.starts_with(b"#") {
674 parse_hex_color(&data[1..]).ok_or(XpmDecodeError::BadHexColor)
675 } else {
676 if data == b"none" {
677 return Ok([0, 0, 0, 0]);
678 }
679
680 if let Ok(idx) = x11r6colors::COLORS.binary_search_by(|entry| entry.0.as_bytes().cmp(data))
681 {
682 let entry = x11r6colors::COLORS[idx];
683 Ok([
684 scale_u8_to_u16(entry.1),
685 scale_u8_to_u16(entry.2),
686 scale_u8_to_u16(entry.3),
687 0xffff,
688 ])
689 } else {
690 assert!(data.len() <= MAX_COLOR_NAME_LEN);
693 let mut tmp = [0u8; MAX_COLOR_NAME_LEN];
694 tmp[..data.len()].copy_from_slice(data);
695 Err(XpmDecodeError::UnknownColor((tmp, data.len() as u8)))
696 }
697 }
698}
699
700fn read_xpm_header<R: Iterator<Item = u8>>(
702 r: &mut TextReader<R>,
703) -> Result<XpmHeaderInfo, XpmDecodeError> {
704 let keyword_buf = &mut [0u8; 16];
705
706 read_fixed_string(r, b"/* XPM */", XpmPart::Header)?;
708 read_to_newline(r, XpmPart::Header)?;
709
710 skip_whitespace_and_comments(r, XpmPart::ArrayStart)?;
711 read_fixed_string(r, b"static", XpmPart::ArrayStart)?;
712 skip_non_empty_whitespace_and_comments(r, XpmPart::ArrayStart)?;
713
714 let keyword = read_keyword(r, keyword_buf, XpmPart::ArrayStart)?;
720 match keyword {
721 b"const" => {
722 skip_non_empty_whitespace_and_comments(r, XpmPart::ArrayStart)?;
723 read_fixed_string(r, b"char", XpmPart::ArrayStart)?;
724 }
725 b"char" => (),
726 _ => return Err(XpmDecodeError::Parse(XpmPart::ArrayStart, r.loc())),
727 }
728
729 skip_whitespace_and_comments(r, XpmPart::ArrayStart)?;
730 read_fixed_string(r, b"*", XpmPart::ArrayStart)?;
731 skip_whitespace_and_comments(r, XpmPart::ArrayStart)?;
732 read_name(r, XpmPart::ArrayStart)?;
733 skip_whitespace_and_comments(r, XpmPart::ArrayStart)?;
734 read_fixed_string(r, b"[", XpmPart::ArrayStart)?;
735 skip_whitespace_and_comments(r, XpmPart::ArrayStart)?;
736 read_fixed_string(r, b"]", XpmPart::ArrayStart)?;
737 skip_whitespace_and_comments(r, XpmPart::ArrayStart)?;
738 read_fixed_string(r, b"=", XpmPart::ArrayStart)?;
739 skip_whitespace_and_comments(r, XpmPart::ArrayStart)?;
740 read_fixed_string(r, b"{", XpmPart::ArrayStart)?;
741 skip_whitespace_and_comments(r, XpmPart::ArrayStart)?;
742
743 read_fixed_string(r, b"\"", XpmPart::FirstLine)?;
745
746 let mut int_buf = [0u8; 10]; skip_spaces_and_tabs(r)?; let int = read_until_whitespace_or_eos(r, &mut int_buf, XpmPart::FirstLine)?;
750 let width = parse_u32(int).ok_or(XpmDecodeError::Parse(XpmPart::FirstLine, r.loc()))?;
751 if width == 0 {
752 return Err(XpmDecodeError::ZeroWidth);
753 }
754
755 read_whitespace_gap(r, XpmPart::FirstLine)?;
756 let int = read_until_whitespace_or_eos(r, &mut int_buf, XpmPart::FirstLine)?;
757 let height = parse_u32(int).ok_or(XpmDecodeError::Parse(XpmPart::FirstLine, r.loc()))?;
758 if height == 0 {
759 return Err(XpmDecodeError::ZeroHeight);
760 }
761
762 read_whitespace_gap(r, XpmPart::FirstLine)?;
763 let int = read_until_whitespace_or_eos(r, &mut int_buf, XpmPart::FirstLine)?;
764 let ncolors = parse_u32(int).ok_or(XpmDecodeError::Parse(XpmPart::FirstLine, r.loc()))?;
765 read_whitespace_gap(r, XpmPart::FirstLine)?;
766 let int = read_until_whitespace_or_eos(r, &mut int_buf, XpmPart::FirstLine)?;
767 let cpp = parse_u32(int).ok_or(XpmDecodeError::Parse(XpmPart::FirstLine, r.loc()))?;
768 skip_spaces_and_tabs(r)?;
769
770 let _hotspot = if let Some(b'"') = r.peek() {
771 None
773 } else {
774 let int = read_until_whitespace_or_eos(r, &mut int_buf, XpmPart::FirstLine)?;
775 let hotspot_x = parse_i32(int).ok_or(XpmDecodeError::Parse(XpmPart::FirstLine, r.loc()))?;
776 read_whitespace_gap(r, XpmPart::FirstLine)?;
777 let int = read_until_whitespace_or_eos(r, &mut int_buf, XpmPart::FirstLine)?;
778 let hotspot_y = parse_i32(int).ok_or(XpmDecodeError::Parse(XpmPart::FirstLine, r.loc()))?;
779 skip_spaces_and_tabs(r)?;
780
781 Some((hotspot_x, hotspot_y))
783 };
784 read_fixed_string(r, b"\"", XpmPart::FirstLine)?;
787 skip_whitespace_and_comments(r, XpmPart::FirstLine)?;
788 read_fixed_string(r, b",", XpmPart::FirstLine)?;
789 skip_whitespace_and_comments(r, XpmPart::FirstLine)?;
790
791 if ncolors == 0 {
792 return Err(XpmDecodeError::ZeroColors);
793 }
794 if cpp == 0 || cpp > 8 {
795 return Err(XpmDecodeError::BadCharsPerColor(cpp));
798 }
799
800 Ok(XpmHeaderInfo {
801 width,
802 height,
803 ncolors,
804 cpp,
805 })
806}
807fn read_xpm_palette<R: Iterator<Item = u8>>(
809 r: &mut TextReader<R>,
810 info: &XpmHeaderInfo,
811) -> Result<XpmPalette, XpmDecodeError> {
812 assert!(1 <= info.cpp && info.cpp <= 8);
813
814 assert!(x11r6colors::COLORS.windows(2).all(|p| p[0].0 < p[1].0));
816
817 let mut color_table: Vec<XpmColorCodeEntry> = Vec::new();
824
825 for _col in 0..info.ncolors {
826 read_fixed_string(r, b"\"", XpmPart::Palette)?;
827
828 let mut code = [0_u8; 8];
829 read_all_except_eos(r, &mut code[..info.cpp as usize], XpmPart::Palette)?;
830 read_whitespace_gap(r, XpmPart::Palette)?;
831
832 let mut color_name_buf = [0_u8; MAX_COLOR_NAME_LEN];
845 let mut color_name_len = 0;
846 let mut next_buf = [0_u8; MAX_COLOR_NAME_LEN];
847
848 let mut key: Option<XpmVisual> = None;
849
850 let mut cvis_color = None;
851 loop {
852 if r.peek().unwrap_or(b'"') == b'"' {
853 let Some(ref k) = key else {
854 return Err(XpmDecodeError::MissingEntry);
856 };
857 if color_name_len == 0 {
858 return Err(XpmDecodeError::MissingColorAfterKey);
860 }
861
862 let color = handle_key_color(k, &color_name_buf[..color_name_len])?;
863 cvis_color = color.or(cvis_color);
864 break;
865 }
866
867 let next = read_until_whitespace_or_eos(r, &mut next_buf, XpmPart::Palette)?;
868 skip_spaces_and_tabs(r)?;
869
870 let this_key = match &next[..] {
871 b"m" => Some(XpmVisual::Mono),
872 b"s" => Some(XpmVisual::Symbolic),
873 b"g4" => Some(XpmVisual::Grayscale4),
874 b"g" => Some(XpmVisual::Grayscale),
875 b"c" => Some(XpmVisual::Color),
876 _ => None,
877 };
878
879 let Some(ref k) = key else {
880 if this_key.is_none() {
882 return Err(XpmDecodeError::MissingKeyBeforeColor);
884 };
885
886 key = this_key;
887 continue;
888 };
889
890 if this_key.is_some() {
891 if color_name_len == 0 {
893 return Err(XpmDecodeError::TwoKeysInARow);
894 }
895
896 let color = handle_key_color(k, &color_name_buf[..color_name_len])?;
897 cvis_color = color.or(cvis_color);
898 color_name_len = 0;
899 key = this_key;
900 continue;
901 }
902
903 if color_name_len > 0 {
906 if color_name_len < MAX_COLOR_NAME_LEN {
907 color_name_buf[color_name_len] = b' ';
908 color_name_len += 1;
909 } else {
910 return Err(XpmDecodeError::ColorNameTooLong);
911 }
912 }
913 for c in next {
914 if !valid_name_char(*c) {
915 return Err(XpmDecodeError::InvalidColorName);
916 }
917 if color_name_len < MAX_COLOR_NAME_LEN {
920 color_name_buf[color_name_len] = fold_to_lower(*c);
921 color_name_len += 1;
922 } else {
923 return Err(XpmDecodeError::ColorNameTooLong);
924 }
925 }
926 }
927
928 let Some(color) = cvis_color else {
929 return Err(XpmDecodeError::NoColorModeColorSpecified);
930 };
931
932 color_table.push(XpmColorCodeEntry {
933 code: u64::from_le_bytes(code),
934 value: color,
935 });
936
937 read_fixed_string(r, b"\"", XpmPart::Palette)?;
938 skip_whitespace_and_comments(r, XpmPart::Palette)?;
939 read_fixed_string(r, b",", XpmPart::Palette)?;
940 skip_whitespace_and_comments(r, XpmPart::Palette)?;
941 }
942
943 color_table.sort_unstable_by_key(|x| x.code);
945 for w in color_table.windows(2) {
946 if w[0].code.cmp(&w[1].code) != Ordering::Less {
947 return Err(XpmDecodeError::DuplicateCode);
948 }
949 }
950
951 read_fixed_string(r, b"\"", XpmPart::Body)?;
952
953 Ok(XpmPalette { table: color_table })
954}
955fn read_xpm_pixel<R: Iterator<Item = u8>>(
957 r: &mut TextReader<R>,
958 info: &XpmHeaderInfo,
959 palette: &XpmPalette,
960 chunk: &mut [u8; 8],
961) -> Result<(), XpmDecodeError> {
962 let mut code = [0_u8; 8];
963 read_all_except_eos(r, &mut code[..info.cpp as usize], XpmPart::Palette)?;
964 let code = u64::from_le_bytes(code);
965
966 let Ok(index) = palette
967 .table
968 .binary_search_by(|entry| entry.code.cmp(&code))
969 else {
970 return Err(XpmDecodeError::UnknownCode);
971 };
972
973 let color = palette.table[index].value;
974 chunk[0..2].copy_from_slice(&color[0].to_ne_bytes());
976 chunk[2..4].copy_from_slice(&color[1].to_ne_bytes());
977 chunk[4..6].copy_from_slice(&color[2].to_ne_bytes());
978 chunk[6..8].copy_from_slice(&color[3].to_ne_bytes());
979 Ok(())
980}
981fn read_xpm_row_transition<R: Iterator<Item = u8>>(
984 r: &mut TextReader<R>,
985) -> Result<(), XpmDecodeError> {
986 read_fixed_string(r, b"\"", XpmPart::Body)?;
988
989 skip_whitespace_and_comments(r, XpmPart::Body)?;
990 read_fixed_string(r, b",", XpmPart::Body)?;
991 skip_whitespace_and_comments(r, XpmPart::Body)?;
992 read_fixed_string(r, b"\"", XpmPart::Body)?;
994 Ok(())
995}
996fn read_xpm_trailing<R: Iterator<Item = u8>>(r: &mut TextReader<R>) -> Result<(), XpmDecodeError> {
998 read_fixed_string(r, b"\"", XpmPart::Body)?;
1000
1001 skip_whitespace_and_comments(r, XpmPart::Trailing)?;
1003 let next = read_byte(r, XpmPart::Trailing)?;
1004 if next == b',' {
1005 skip_whitespace_and_comments(r, XpmPart::Trailing)?;
1006 read_fixed_string(r, b"}", XpmPart::Trailing)?;
1007 } else if next != b'}' {
1008 return Err(XpmDecodeError::Parse(XpmPart::Trailing, r.loc()));
1009 }
1010 skip_whitespace_and_comments(r, XpmPart::Trailing)?;
1011 read_fixed_string(r, b";", XpmPart::Trailing)?;
1012
1013 skip_whitespace_and_comments(r, XpmPart::AfterEnd)?;
1014 if r.next().is_some() {
1015 Err(XpmDecodeError::Parse(XpmPart::AfterEnd, r.loc()))
1017 } else {
1018 Ok(())
1019 }
1020}
1021
1022impl<R> XpmDecoder<R>
1023where
1024 R: BufRead,
1025{
1026 pub fn new(reader: R) -> Result<XpmDecoder<R>, ImageError> {
1028 let mut r = TextReader::new(IoAdapter {
1029 reader: reader.bytes(),
1030 error: None,
1031 });
1032
1033 let info = read_xpm_header(&mut r).apply_after(&mut r.inner.error)?;
1034
1035 Ok(XpmDecoder { r, info })
1036 }
1037}
1038
1039fn handle_key_color(key: &XpmVisual, color: &[u8]) -> Result<Option<[u16; 4]>, XpmDecodeError> {
1041 if matches!(key, XpmVisual::Symbolic) {
1042 return Ok(None);
1043 }
1044 let color = parse_color(color)?;
1045 if matches!(key, XpmVisual::Color) {
1046 Ok(Some(color))
1047 } else {
1048 Ok(None)
1049 }
1050}
1051
1052impl<R: BufRead> ImageDecoder for XpmDecoder<R> {
1053 fn dimensions(&self) -> (u32, u32) {
1054 (self.info.width, self.info.height)
1055 }
1056 fn color_type(&self) -> ColorType {
1057 ColorType::Rgba16
1060 }
1061 fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()>
1062 where
1063 Self: Sized,
1064 {
1065 assert!(1 <= self.info.cpp && self.info.cpp <= 8);
1066
1067 let palette =
1068 read_xpm_palette(&mut self.r, &self.info).apply_after(&mut self.r.inner.error)?;
1069
1070 let stride = (self.info.width as usize).checked_mul(8).unwrap();
1072 for (i, row) in buf.chunks_exact_mut(stride).enumerate() {
1073 for chunk in row.chunks_exact_mut(8) {
1074 read_xpm_pixel(&mut self.r, &self.info, &palette, chunk.try_into().unwrap())
1075 .apply_after(&mut self.r.inner.error)?;
1076 }
1077
1078 if i >= (self.info.height - 1) as usize {
1079 } else {
1081 read_xpm_row_transition(&mut self.r).apply_after(&mut self.r.inner.error)?;
1082 }
1083 }
1084
1085 read_xpm_trailing(&mut self.r).apply_after(&mut self.r.inner.error)?;
1086
1087 Ok(())
1088 }
1089 fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
1090 (*self).read_image(buf)
1091 }
1092
1093 fn set_limits(&mut self, limits: Limits) -> ImageResult<()> {
1094 limits.check_support(&LimitSupport::default())?;
1095 let (width, height) = self.dimensions();
1096 limits.check_dimensions(width, height)?;
1097
1098 let max_pixels = u64::from(self.info.width) * u64::from(self.info.height);
1099 let max_image_bytes =
1100 max_pixels
1101 .checked_mul(8)
1102 .ok_or(ImageError::Limits(LimitError::from_kind(
1103 LimitErrorKind::DimensionError,
1104 )))?;
1105
1106 let max_table_bytes = (self.info.ncolors as u64) * (size_of::<XpmColorCodeEntry>() as u64);
1107 let max_bytes = max_image_bytes
1108 .checked_add(max_table_bytes)
1109 .ok_or(ImageError::Limits(LimitError::from_kind(
1110 LimitErrorKind::InsufficientMemory,
1111 )))?;
1112
1113 let max_alloc = limits.max_alloc.unwrap_or(u64::MAX);
1114 if max_alloc < max_bytes {
1115 return Err(ImageError::Limits(LimitError::from_kind(
1116 LimitErrorKind::InsufficientMemory,
1117 )));
1118 }
1119 Ok(())
1120 }
1121}
1122
1123#[cfg(test)]
1124mod tests {
1125 use super::*;
1126
1127 #[test]
1128 fn image_missing_body() {
1129 let data = b"/* XPM */
1130static char *test[] = {
1131\"20 5 10 1\",
1132};
1133";
1134 let decoder = XpmDecoder::new(&data[..]).unwrap();
1135 let mut image = vec![0; decoder.total_bytes() as usize];
1136 assert!(decoder.read_image(&mut image).is_err());
1137 }
1138
1139 #[test]
1140 fn invalid_color_name() {
1141 let data = b"/* XPM */
1142static char *test[] = {
1143 \"1 1 1 1\",
1144 \" c Antique White1\",
1145 \" \",
1146};";
1147 let decoder = XpmDecoder::new(&data[..]).unwrap();
1148 let mut image = vec![0; decoder.total_bytes() as usize];
1149 assert!(decoder.read_image(&mut image).is_err());
1150 }
1151
1152 #[test]
1153 fn trailing_semicolon_required() {
1154 let data = b"/* XPM */
1155 static char *test[] = {
1156 \"1 1 1 1\",
1157 \" c none\",
1158 \" \",
1159 };";
1160 let decoder = XpmDecoder::new(&data[..data.len() - 1]).unwrap();
1161 let mut image = vec![0; decoder.total_bytes() as usize];
1162 assert!(decoder.read_image(&mut image).is_err());
1163
1164 let decoder = XpmDecoder::new(&data[..]).unwrap();
1165 let mut image = vec![0; decoder.total_bytes() as usize];
1166 assert!(decoder.read_image(&mut image).is_ok());
1167 }
1168}