|
| 1 | +use crate::error::{Id3v2Error, Id3v2ErrorKind, Result}; |
| 2 | +use crate::id3::v2::restrictions::TagRestrictions; |
| 3 | +use crate::id3::v2::util::synchsafe::SynchsafeInteger; |
| 4 | +use crate::macros::err; |
| 5 | + |
| 6 | +use std::io::Read; |
| 7 | + |
| 8 | +use byteorder::{BigEndian, ByteOrder, ReadBytesExt}; |
| 9 | + |
| 10 | +/// The ID3v2 version |
| 11 | +#[derive(PartialEq, Eq, Debug, Clone, Copy)] |
| 12 | +pub enum Id3v2Version { |
| 13 | + /// ID3v2.2 |
| 14 | + V2, |
| 15 | + /// ID3v2.3 |
| 16 | + V3, |
| 17 | + /// ID3v2.4 |
| 18 | + V4, |
| 19 | +} |
| 20 | + |
| 21 | +/// Flags that apply to the entire tag |
| 22 | +#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)] |
| 23 | +#[allow(clippy::struct_excessive_bools)] |
| 24 | +pub struct Id3v2TagFlags { |
| 25 | + /// Whether or not all frames are unsynchronised. See [`FrameFlags::unsynchronisation`](crate::id3::v2::FrameFlags::unsynchronisation) |
| 26 | + pub unsynchronisation: bool, |
| 27 | + /// Indicates if the tag is in an experimental stage |
| 28 | + pub experimental: bool, |
| 29 | + /// Indicates that the tag includes a footer |
| 30 | + /// |
| 31 | + /// A footer will be created if the tag is written |
| 32 | + pub footer: bool, |
| 33 | + /// Whether or not to include a CRC-32 in the extended header |
| 34 | + /// |
| 35 | + /// This is calculated if the tag is written |
| 36 | + pub crc: bool, |
| 37 | + /// Restrictions on the tag, written in the extended header |
| 38 | + /// |
| 39 | + /// In addition to being setting this flag, all restrictions must be provided. See [`TagRestrictions`] |
| 40 | + pub restrictions: Option<TagRestrictions>, |
| 41 | +} |
| 42 | + |
| 43 | +#[derive(Copy, Clone, Debug)] |
| 44 | +pub(crate) struct Id3v2Header { |
| 45 | + pub version: Id3v2Version, |
| 46 | + pub flags: Id3v2TagFlags, |
| 47 | + pub size: u32, |
| 48 | + pub extended_size: u32, |
| 49 | +} |
| 50 | + |
| 51 | +impl Id3v2Header { |
| 52 | + pub(crate) fn parse<R>(bytes: &mut R) -> Result<Self> |
| 53 | + where |
| 54 | + R: Read, |
| 55 | + { |
| 56 | + let mut header = [0; 10]; |
| 57 | + bytes.read_exact(&mut header)?; |
| 58 | + |
| 59 | + if &header[..3] != b"ID3" { |
| 60 | + err!(FakeTag); |
| 61 | + } |
| 62 | + |
| 63 | + // Version is stored as [major, minor], but here we don't care about minor revisions unless there's an error. |
| 64 | + let version = match header[3] { |
| 65 | + 2 => Id3v2Version::V2, |
| 66 | + 3 => Id3v2Version::V3, |
| 67 | + 4 => Id3v2Version::V4, |
| 68 | + major => { |
| 69 | + return Err( |
| 70 | + Id3v2Error::new(Id3v2ErrorKind::BadId3v2Version(major, header[4])).into(), |
| 71 | + ) |
| 72 | + }, |
| 73 | + }; |
| 74 | + |
| 75 | + let flags = header[5]; |
| 76 | + |
| 77 | + // Compression was a flag only used in ID3v2.2 (bit 2). |
| 78 | + // At the time the ID3v2.2 specification was written, a compression scheme wasn't decided. |
| 79 | + // The spec recommends just ignoring the tag in this case. |
| 80 | + if version == Id3v2Version::V2 && flags & 0x40 == 0x40 { |
| 81 | + return Err(Id3v2Error::new(Id3v2ErrorKind::V2Compression).into()); |
| 82 | + } |
| 83 | + |
| 84 | + let mut flags_parsed = Id3v2TagFlags { |
| 85 | + unsynchronisation: flags & 0x80 == 0x80, |
| 86 | + experimental: (version == Id3v2Version::V4 || version == Id3v2Version::V3) |
| 87 | + && flags & 0x20 == 0x20, |
| 88 | + footer: (version == Id3v2Version::V4 || version == Id3v2Version::V3) |
| 89 | + && flags & 0x10 == 0x10, |
| 90 | + crc: false, // Retrieved later if applicable |
| 91 | + restrictions: None, // Retrieved later if applicable |
| 92 | + }; |
| 93 | + |
| 94 | + let size = BigEndian::read_u32(&header[6..]).unsynch(); |
| 95 | + let mut extended_size = 0; |
| 96 | + |
| 97 | + let extended_header = |
| 98 | + (version == Id3v2Version::V4 || version == Id3v2Version::V3) && flags & 0x40 == 0x40; |
| 99 | + |
| 100 | + if extended_header { |
| 101 | + extended_size = bytes.read_u32::<BigEndian>()?.unsynch(); |
| 102 | + |
| 103 | + if extended_size < 6 { |
| 104 | + return Err(Id3v2Error::new(Id3v2ErrorKind::BadExtendedHeaderSize).into()); |
| 105 | + } |
| 106 | + |
| 107 | + // Useless byte since there's only 1 byte for flags |
| 108 | + let _num_flag_bytes = bytes.read_u8()?; |
| 109 | + |
| 110 | + let extended_flags = bytes.read_u8()?; |
| 111 | + |
| 112 | + // The only flags we care about here are the CRC and restrictions |
| 113 | + |
| 114 | + if extended_flags & 0x20 == 0x20 { |
| 115 | + flags_parsed.crc = true; |
| 116 | + |
| 117 | + // We don't care about the existing CRC (5) or its length byte (1) |
| 118 | + let mut crc = [0; 6]; |
| 119 | + bytes.read_exact(&mut crc)?; |
| 120 | + } |
| 121 | + |
| 122 | + if extended_flags & 0x10 == 0x10 { |
| 123 | + // We don't care about the length byte, it is always 1 |
| 124 | + let _data_length = bytes.read_u8()?; |
| 125 | + |
| 126 | + flags_parsed.restrictions = Some(TagRestrictions::from_byte(bytes.read_u8()?)); |
| 127 | + } |
| 128 | + } |
| 129 | + |
| 130 | + if extended_size > 0 && extended_size >= size { |
| 131 | + return Err(Id3v2Error::new(Id3v2ErrorKind::BadExtendedHeaderSize).into()); |
| 132 | + } |
| 133 | + |
| 134 | + Ok(Id3v2Header { |
| 135 | + version, |
| 136 | + flags: flags_parsed, |
| 137 | + size, |
| 138 | + extended_size, |
| 139 | + }) |
| 140 | + } |
| 141 | +} |
0 commit comments