exif-rs/src/jpeg.rs

158 lines
5.3 KiB
Rust

//
// Copyright (c) 2016 KAMADA Ken'ichi.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
use std::io::{BufRead, ErrorKind};
use crate::error::Error;
use crate::util::{read8, read16};
mod marker {
// The first byte of a marker.
pub const P: u8 = 0xff;
// Marker codes.
pub const Z: u8 = 0x00; // Not a marker but a byte stuffing.
pub const TEM: u8 = 0x01;
pub const RST0: u8 = 0xd0;
pub const RST7: u8 = 0xd7;
pub const SOI: u8 = 0xd8;
pub const EOI: u8 = 0xd9;
pub const SOS: u8 = 0xda;
pub const APP1: u8 = 0xe1;
}
// SOI marker as the JPEG header.
const JPEG_SIG: [u8; 2] = [marker::P, marker::SOI];
// Exif identifier code "Exif\0\0". [EXIF23 4.7.2]
const EXIF_ID: [u8; 6] = [0x45, 0x78, 0x69, 0x66, 0x00, 0x00];
/// Get the Exif attribute information segment from a JPEG file.
pub fn get_exif_attr<R>(reader: &mut R)
-> Result<Vec<u8>, Error> where R: BufRead {
match get_exif_attr_sub(reader) {
Err(Error::Io(ref e)) if e.kind() == ErrorKind::UnexpectedEof =>
Err(Error::InvalidFormat("Broken JPEG file")),
r => r,
}
}
fn get_exif_attr_sub<R>(reader: &mut R)
-> Result<Vec<u8>, Error> where R: BufRead {
let mut soi = [0u8; 2];
reader.read_exact(&mut soi)?;
if soi != [marker::P, marker::SOI] {
return Err(Error::InvalidFormat("Not a JPEG file"));
}
loop {
// Find a marker prefix. Discard non-ff bytes, which appear if
// we are in the scan data after SOS or we are out of sync.
reader.read_until(marker::P, &mut Vec::new())?;
// Get a marker code.
let mut code;
loop {
code = read8(reader)?;
if code != marker::P { break; }
}
// Continue or return early on stand-alone markers.
match code {
marker::Z | marker::TEM | marker::RST0..=marker::RST7 => continue,
marker::SOI => return Err(Error::InvalidFormat("Unexpected SOI")),
marker::EOI => return Err(Error::NotFound("JPEG")),
_ => {},
}
// Read marker segments.
let len = read16(reader)?.checked_sub(2)
.ok_or(Error::InvalidFormat("Invalid segment length"))?;
let mut seg = vec![0; len.into()];
reader.read_exact(&mut seg)?;
if code == marker::APP1 && seg.starts_with(&EXIF_ID) {
seg.drain(..EXIF_ID.len());
return Ok(seg);
}
if code == marker::SOS {
// Skipping the scan data is handled in the main loop,
// so there is nothing to do here.
}
}
}
pub fn is_jpeg(buf: &[u8]) -> bool {
buf.starts_with(&JPEG_SIG)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn truncated() {
let sets: &[&[u8]] = &[
b"",
b"\xff",
b"\xff\xd8",
b"\xff\xd8\x00",
b"\xff\xd8\xff",
b"\xff\xd8\xff\xe1\x00\x08\x03\x04",
];
for &data in sets {
assert_err_pat!(get_exif_attr(&mut &data[..]),
Error::InvalidFormat("Broken JPEG file"));
}
let mut data = b"\xff\xd8\xff\xe1\x00\x08Exif\0\0".to_vec();
assert_eq!(get_exif_attr(&mut &data[..]).unwrap(), b"");
while let Some(_) = data.pop() {
get_exif_attr(&mut &data[..]).unwrap_err();
}
}
#[test]
fn no_exif() {
let data = b"\xff\xd8\xff\xd9";
assert_err_pat!(get_exif_attr(&mut &data[..]),
Error::NotFound(_));
}
#[test]
fn out_of_sync() {
let data = b"\xff\xd8\x01\x02\x03\xff\x00\xff\xd9";
assert_err_pat!(get_exif_attr(&mut &data[..]),
Error::NotFound(_));
}
#[test]
fn empty() {
let data = b"\xff\xd8\xff\xe1\x00\x08Exif\0\0\xff\xd9";
assert_ok!(get_exif_attr(&mut &data[..]), []);
}
#[test]
fn non_empty() {
let data = b"\xff\xd8\xff\xe1\x00\x0aExif\0\0\xbe\xad\xff\xd9";
assert_ok!(get_exif_attr(&mut &data[..]), [0xbe, 0xad]);
}
}