const TAG_ORIENTATION: u16 = 0x0112;
const TIFF_TYPE_SHORT: u16 = 3;
const TIFF_HEADER_MIN: usize = 8;
pub(crate) fn parse_orientation(data: &[u8]) -> Option<u8> {
let tiff = if data.len() >= 6 && &data[..6] == b"Exif\0\0" {
&data[6..]
} else {
data
};
if tiff.len() < TIFF_HEADER_MIN {
return None;
}
let big_endian = match &tiff[0..2] {
b"MM" => true,
b"II" => false,
_ => return None,
};
if read_u16(tiff, 2, big_endian) != 42 {
return None;
}
let ifd_offset = read_u32(tiff, 4, big_endian) as usize;
if ifd_offset.checked_add(2)? > tiff.len() {
return None;
}
let entry_count = read_u16(tiff, ifd_offset, big_endian) as usize;
let entries_start = ifd_offset + 2;
for i in 0..entry_count {
let offset = entries_start + i * 12;
if offset + 12 > tiff.len() {
break;
}
let tag = read_u16(tiff, offset, big_endian);
if tag == TAG_ORIENTATION {
if read_u16(tiff, offset + 2, big_endian) != TIFF_TYPE_SHORT {
return None;
}
let value = read_u16(tiff, offset + 8, big_endian);
return if (1..=8).contains(&value) {
Some(value as u8)
} else {
None
};
}
if tag > TAG_ORIENTATION {
break;
}
}
None
}
fn read_u16(data: &[u8], offset: usize, big_endian: bool) -> u16 {
let b = &data[offset..offset + 2];
if big_endian {
u16::from_be_bytes([b[0], b[1]])
} else {
u16::from_le_bytes([b[0], b[1]])
}
}
fn read_u32(data: &[u8], offset: usize, big_endian: bool) -> u32 {
let b = &data[offset..offset + 4];
if big_endian {
u32::from_be_bytes([b[0], b[1], b[2], b[3]])
} else {
u32::from_le_bytes([b[0], b[1], b[2], b[3]])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(unused_imports)]
use alloc::{vec, vec::Vec};
fn make_tiff_with_orientation(orientation: u16, big_endian: bool) -> Vec<u8> {
let mut buf = Vec::new();
let write_u16 = |buf: &mut Vec<u8>, v: u16| {
if big_endian {
buf.extend_from_slice(&v.to_be_bytes());
} else {
buf.extend_from_slice(&v.to_le_bytes());
}
};
let write_u32 = |buf: &mut Vec<u8>, v: u32| {
if big_endian {
buf.extend_from_slice(&v.to_be_bytes());
} else {
buf.extend_from_slice(&v.to_le_bytes());
}
};
if big_endian {
buf.extend_from_slice(b"MM");
} else {
buf.extend_from_slice(b"II");
}
write_u16(&mut buf, 42);
write_u32(&mut buf, 8);
write_u16(&mut buf, 1);
write_u16(&mut buf, TAG_ORIENTATION);
write_u16(&mut buf, TIFF_TYPE_SHORT);
write_u32(&mut buf, 1); write_u16(&mut buf, orientation);
write_u16(&mut buf, 0);
buf
}
#[test]
fn parse_little_endian() {
let tiff = make_tiff_with_orientation(6, false);
assert_eq!(parse_orientation(&tiff), Some(6));
}
#[test]
fn parse_big_endian() {
let tiff = make_tiff_with_orientation(3, true);
assert_eq!(parse_orientation(&tiff), Some(3));
}
#[test]
fn parse_with_exif_prefix() {
let tiff = make_tiff_with_orientation(8, false);
let mut with_prefix = b"Exif\0\0".to_vec();
with_prefix.extend_from_slice(&tiff);
assert_eq!(parse_orientation(&with_prefix), Some(8));
}
#[test]
fn normal_orientation() {
let tiff = make_tiff_with_orientation(1, false);
assert_eq!(parse_orientation(&tiff), Some(1));
}
#[test]
fn invalid_orientation_zero() {
let tiff = make_tiff_with_orientation(0, false);
assert_eq!(parse_orientation(&tiff), None);
}
#[test]
fn invalid_orientation_nine() {
let tiff = make_tiff_with_orientation(9, false);
assert_eq!(parse_orientation(&tiff), None);
}
#[test]
fn empty_data() {
assert_eq!(parse_orientation(&[]), None);
}
#[test]
fn truncated_header() {
assert_eq!(parse_orientation(b"II*\0"), None);
}
#[test]
fn no_orientation_tag() {
let mut buf = Vec::new();
buf.extend_from_slice(b"II");
buf.extend_from_slice(&42u16.to_le_bytes());
buf.extend_from_slice(&8u32.to_le_bytes());
buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&0x0100u16.to_le_bytes()); buf.extend_from_slice(&3u16.to_le_bytes()); buf.extend_from_slice(&1u32.to_le_bytes()); buf.extend_from_slice(&640u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); assert_eq!(parse_orientation(&buf), None);
}
}