1use super::ApeTag;
2use super::item::ApeItem;
3use crate::ape::APE_PICTURE_TYPES;
4use crate::ape::constants::{APE_PREAMBLE, INVALID_KEYS};
5use crate::ape::header::{self, ApeHeader};
6use crate::config::ParseOptions;
7use crate::error::Result;
8use crate::macros::{decode_err, err, try_vec};
9use crate::tag::ItemValue;
10use crate::util::text::utf8_decode;
11
12use std::io::{Read, Seek, SeekFrom};
13
14use byteorder::{LittleEndian, ReadBytesExt};
15
16pub(crate) fn read_ape_tag_with_header<R>(
17 data: &mut R,
18 header: ApeHeader,
19 parse_options: ParseOptions,
20) -> Result<ApeTag>
21where
22 R: Read + Seek,
23{
24 let mut tag = ApeTag::default();
25 let mut remaining_size = header.size;
26
27 for _ in 0..header.item_count {
28 if remaining_size < 11 {
29 break;
30 }
31
32 let value_size = data.read_u32::<LittleEndian>()?;
33 if value_size > remaining_size {
34 err!(SizeMismatch);
35 }
36
37 remaining_size -= 4;
38 let flags = data.read_u32::<LittleEndian>()?;
39
40 let mut key = Vec::new();
41 let mut key_char = data.read_u8()?;
42
43 while key_char != 0 {
44 key.push(key_char);
45 key_char = data.read_u8()?;
46 }
47
48 let key = utf8_decode(key)
49 .map_err(|_| decode_err!(Ape, "APE tag item contains a non UTF-8 key"))?;
50
51 if INVALID_KEYS.contains(&&*key.to_uppercase()) {
52 decode_err!(@BAIL Ape, "APE tag item contains an illegal key");
53 }
54
55 if APE_PICTURE_TYPES.contains(&&*key) && !parse_options.read_cover_art {
56 data.seek(SeekFrom::Current(i64::from(value_size)))?;
57 continue;
58 }
59
60 let read_only = (flags & 1) == 1;
61 let item_type = (flags >> 1) & 3;
62
63 if value_size == 0 || key.len() < 2 || key.len() > 255 {
64 log::warn!("APE: Encountered invalid item key '{}'", key);
65 data.seek(SeekFrom::Current(i64::from(value_size)))?;
66 continue;
67 }
68
69 let mut value = try_vec![0; value_size as usize];
70 data.read_exact(&mut value)?;
71
72 let parsed_value = match item_type {
73 0 => ItemValue::Text(utf8_decode(value).map_err(|_| {
74 decode_err!(Ape, "Failed to convert text item into a UTF-8 string")
75 })?),
76 1 => ItemValue::Binary(value),
77 2 => ItemValue::Locator(utf8_decode(value).map_err(|_| {
78 decode_err!(Ape, "Failed to convert locator item into a UTF-8 string")
79 })?),
80 _ => decode_err!(@BAIL Ape, "APE tag item contains an invalid item type"),
81 };
82
83 let mut item = ApeItem::new(key, parsed_value)?;
84
85 if read_only {
86 item.read_only = true;
87 }
88
89 tag.insert(item);
90 }
91
92 data.seek(SeekFrom::Current(32))?;
94
95 Ok(tag)
96}
97
98pub(crate) fn read_ape_tag<R: Read + Seek>(
99 reader: &mut R,
100 footer: bool,
101 parse_options: ParseOptions,
102) -> Result<(Option<ApeTag>, Option<ApeHeader>)> {
103 let mut ape_preamble = [0; 8];
104 reader.read_exact(&mut ape_preamble)?;
105
106 let mut ape_tag = None;
107 if &ape_preamble == APE_PREAMBLE {
108 let ape_header = header::read_ape_header(reader, footer)?;
109 if parse_options.read_tags {
110 ape_tag = Some(read_ape_tag_with_header(reader, ape_header, parse_options)?);
111 }
112
113 return Ok((ape_tag, Some(ape_header)));
114 }
115
116 Ok((None, None))
117}