From c1f987beef517e5983835e81e4dc56c6285cf992 Mon Sep 17 00:00:00 2001 From: Tom Smeding Date: Tue, 24 Mar 2020 21:24:50 +0100 Subject: Parse extended header (to an extent), and more v2.4 compat --- src/id3v2.rs | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 108 insertions(+), 3 deletions(-) diff --git a/src/id3v2.rs b/src/id3v2.rs index f65260d..d11d445 100644 --- a/src/id3v2.rs +++ b/src/id3v2.rs @@ -25,6 +25,78 @@ fn parse_id3v2_header(bytes: &[u8]) -> Option<(u16, u8, usize)> { } } +/// Returns the number of bytes consumed of the body. +fn parse_extended_header(body: &[u8], version_sub: u8) -> io::Result { + let extended_size = match version_sub { + 3 => 4 + read_big_endian(&body[0..4], 8), + 4 => read_big_endian(&body[0..4], 7), + _ => panic!("") + }; + + if body.len() < extended_size { + return Err("Header too small for extended header size field".ioerr()); + } + + match version_sub { + 3 => if extended_size != 10 && extended_size != 14 { + // Error message uses the v2.3 size format, which does not include the size + // number itself. + return Err( + format!("Extended header has unrecognised length {} (not 6 or 10)", + extended_size) + .ioerr() + ); + } + + 4 => if extended_size < 6 { + return Err(format!("Extended header size too small ({})", extended_size).ioerr()); + } + + _ => panic!("") + } + + match version_sub { + 3 => { + let flags = read_big_endian(&body[4..6], 8); + // First bit is "CRC present", which we do not care about (it's contained in + // the extended header, which we otherwise ignore anyhow). + if (flags & 0x7fff) != 0 { + return Err( + format!("Unknown extended header flags set (flags bytes 0x{:04x})", + flags) + .ioerr() + ); + } + } + + 4 => { + let num_flag_bytes = usize::from(body[4]); + if num_flag_bytes != 1 { + return Err( + format!("Unknown number of extended flag bytes {}", num_flag_bytes) + .ioerr() + ); + } + + let flags = body[5]; + // 0x40: "update tag"; ignored + // 0x20: "CRC present"; ignored + // 0x10: tag restructions; ignored + if (flags & 0x8f) != 0 { + return Err( + format!("Unknown extended flags (flags byte 0x{:02x})", flags) + .ioerr() + ); + } + } + + _ => panic!("") + } + + // Ignore the rest of the extended header + Ok(extended_size) +} + /// Encodes native string to ID3v2 string encoding (either Latin-1 or UCS-2 according to the /// characters used). fn encode_string(s: &str) -> io::Result> { @@ -110,9 +182,17 @@ impl RawFrame { } let id = String::from_utf8(data[0..4].to_vec()).unwrap(); - let size = read_big_endian(&data[4..8], 8); + let size = match tag_version_sub { + 3 => read_big_endian(&data[4..8], 8), + 4 => read_big_endian(&data[4..8], 7), + _ => panic!("") + }; let flags = read_big_endian(&data[8..10], 8) as u16; + if flags != 0 { + return Err(format!("Frame flags not supported (flags bytes {:04x})", flags)); + } + let body = data[10..10+size].to_vec(); Ok(Some((RawFrame { id, flags, tag_version_sub, body }, 10 + size))) @@ -259,8 +339,29 @@ impl ID3v2 { } }; - if flags != 0 { - return Err(format!("No ID3 header flags supported ({:x})", flags).ioerr()); + if (flags & 0x80) != 0 { + return Err(format!("ID3 unsynchronisation not supported").ioerr()); + } + + let extended_header = (flags & 0x40) != 0; + + if (flags & 0x20) != 0 { + return Err( + format!("Refusing to read ID3 tag in \"experimental\" stage, whatever that may mean") + .ioerr() + ); + } + + // ID3v2.4 only + if (flags & 0x10) != 0 { + return Err(format!("3DI footer unsupported (ID3v2.4 section 3.4)").ioerr()); + } + + if (flags & 0x0f) != 0 { + return Err( + format!("Unknown ID3 header flags found (flags byte: 0x{:02x})", flags) + .ioerr() + ); } let body = { @@ -273,6 +374,10 @@ impl ID3v2 { let mut frames = Vec::new(); let mut cursor = 0; + if extended_header { + cursor += parse_extended_header(&body, version_sub)?; + } + while cursor < body.len() { let tag = &body[cursor..cursor+4]; if tag.len() < 4 { break; } // not even enough bytes anymore -- cgit v1.2.3