diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..9fd5f77 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,75 @@ +// +// 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::error; +use std::fmt; +use std::io; + +/// An error type returned when parsing Exif data. +#[derive(Debug)] +pub enum Error { + /// Input data was malformed or truncated. + InvalidFormat(&'static str), + /// Input data could not be read due to an I/O error and + /// a `std::io::Error` value is associated with this variant. + Io(io::Error), + /// Exif attribute information was not found. + NotFound(&'static str), +} + +impl From for Error { + fn from(err: io::Error) -> Error { + Error::Io(err) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Error::InvalidFormat(msg) => f.write_str(msg), + Error::Io(ref err) => err.fmt(f), + Error::NotFound(msg) => f.write_str(msg), + } + } +} + +impl error::Error for Error { + fn description(&self) -> &str { + match *self { + Error::InvalidFormat(msg) => msg, + Error::Io(ref err) => err.description(), + Error::NotFound(msg) => msg, + } + } + + fn cause(&self) -> Option<&error::Error> { + match *self { + Error::InvalidFormat(_) => None, + Error::Io(ref err) => Some(err), + Error::NotFound(_) => None, + } + } +} diff --git a/src/jpeg.rs b/src/jpeg.rs new file mode 100644 index 0000000..209814d --- /dev/null +++ b/src/jpeg.rs @@ -0,0 +1,100 @@ +// +// 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; +use std::io::Read; + +use error::Error; +use util::read8; +use util::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; +} + +// 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(reader: &mut R) + -> Result, Error> where R: io::BufRead { + match get_exif_attr_sub(reader) { + Err(Error::Io(ref e)) if e.kind() == io::ErrorKind::UnexpectedEof => + Err(Error::InvalidFormat("Broken JPEG file")), + r => r, + } +} + +fn get_exif_attr_sub(reader: &mut R) + -> Result, Error> where R: io::BufRead { + let mut soi = [0u8; 2]; + let mut code; + try!(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. + try!(reader.read_until(marker::P, &mut Vec::new())); + // Get a marker code. + loop { + code = try!(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("No Exif data found")), + _ => {}, + } + // Read marker segments. + let seglen = try!(read16(reader)); + if seglen < 2 { + return Err(Error::InvalidFormat("Invalid segment length")); + } + let mut seg = Vec::new(); + try!(reader.by_ref().take(seglen as u64 - 2).read_to_end(&mut seg)); + if code == marker::APP1 && seg.starts_with(&EXIF_ID) { + return Ok(seg.split_off(EXIF_ID.len())); + } + if code == marker::SOS { + // Skipping the scan data is handled in the main loop, + // so there is nothing to do here. + } + } +} diff --git a/src/lib.rs b/src/lib.rs index cdfbe1a..9c1df62 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,34 @@ -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - } -} +// +// 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. +// + +//! Exif parsing library written in pure Rust. + +pub use error::Error; +pub use jpeg::get_exif_attr as get_exif_attr_from_jpeg; + +mod error; +mod jpeg; +mod util; diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..7aa7772 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,38 @@ +// +// 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; + +pub fn read8(reader: &mut R) -> Result where R: io::Read { + let mut buf: [u8; 1] = unsafe { ::std::mem::uninitialized() }; + reader.read_exact(&mut buf).and(Ok(buf[0])) +} + +pub fn read16(reader: &mut R) -> Result where R: io::Read { + let mut buf: [u8; 2] = unsafe { ::std::mem::uninitialized() }; + try!(reader.read_exact(&mut buf)); + Ok(((buf[0] as u16) << 8) + buf[1] as u16) +}