summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/id3v2.rs111
1 files 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<usize> {
+ 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<Vec<u8>> {
@@ -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