#![allow(non_snake_case, non_upper_case_globals)]
use std::fmt;
use std::io::{self, Read, Write};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
#[derive(Clone, Copy, Default, Eq, PartialEq)]
pub struct OSType(pub [u8; 4]);
impl fmt::Debug for OSType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = String::from_utf8(self.0.to_vec());
write!(f, "{:?}", s)
}
}
#[allow(dead_code)]
pub mod constants {
use super::OSType;
pub const kIsOnDesk: u16 = 0x0001;
pub const kColor: u16 = 0x000e;
pub const kIsShared: u16 = 0x0040;
pub const kHasNoINITs: u16 = 0x0080;
pub const kHasBeenInited: u16 = 0x0100;
pub const kHasCustomIcon: u16 = 0x0400;
pub const kIsStationery: u16 = 0x0800;
pub const kNameLocked: u16 = 0x1000;
pub const kHasBundle: u16 = 0x2000;
pub const kIsInvisible: u16 = 0x4000;
pub const kIsAlias: u16 = 0x8000;
pub const kHideExtension: u16 = 0x0010;
pub const kExtendedFlagsAreInvalid: u16 = 0x8000;
pub const kExtendedFlagHasCustomBadge: u16 = 0x0100;
pub const kExtendedFlagHasRoutingInfo: u16 = 0x0004;
pub const kSymLinkFileType: OSType = OSType([0x73, 0x6c, 0x6e, 0x6b]);
pub const kSymLinkCreator: OSType = OSType([0x72, 0x68, 0x61, 0x70]);
}
#[derive(Clone, Debug, Default)]
#[repr(C)]
pub struct Point {
pub v: i16,
pub h: i16,
}
impl Point {
pub fn read<R: ReadBytesExt>(r: &mut R) -> io::Result<Point> {
let v = r.read_i16::<BigEndian>()?;
let h = r.read_i16::<BigEndian>()?;
Ok(Point { v, h })
}
pub fn write<W: WriteBytesExt>(&self, w: &mut W) -> io::Result<()> {
w.write_i16::<BigEndian>(self.v)?;
w.write_i16::<BigEndian>(self.h)?;
Ok(())
}
}
#[derive(Clone, Debug, Default)]
#[repr(C)]
pub struct Rect {
pub top: i16,
pub left: i16,
pub bottom: i16,
pub right: i16,
}
impl Rect {
pub fn read<R: Read>(r: &mut R) -> io::Result<Rect> {
let top = r.read_i16::<BigEndian>()?;
let left = r.read_i16::<BigEndian>()?;
let bottom = r.read_i16::<BigEndian>()?;
let right = r.read_i16::<BigEndian>()?;
Ok(Rect {
top,
left,
bottom,
right,
})
}
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
w.write_i16::<BigEndian>(self.top)?;
w.write_i16::<BigEndian>(self.left)?;
w.write_i16::<BigEndian>(self.bottom)?;
w.write_i16::<BigEndian>(self.right)?;
Ok(())
}
}
#[derive(Clone, Copy, Default, Eq, PartialEq)]
pub struct FinderFlags(u16);
impl FinderFlags {
pub fn color(&self) -> Option<LabelColor> {
LabelColor::from_u8((self.0 & constants::kColor) as u8)
}
pub fn set_color(&mut self, color: Option<LabelColor>) {
self.0 &= !constants::kColor;
self.0 |= u16::from(LabelColor::to_u8(color));
}
pub fn is_shared(&self) -> bool {
self.0 & constants::kIsShared != 0
}
pub fn has_no_inits(&self) -> bool {
self.0 & constants::kHasNoINITs != 0
}
pub fn has_been_inited(&self) -> bool {
self.0 & constants::kHasBeenInited != 0
}
pub fn has_custom_icon(&self) -> bool {
self.0 & constants::kHasCustomIcon != 0
}
pub fn set_has_custom_icon(&mut self, value: bool) {
if value {
self.0 |= constants::kHasCustomIcon;
} else {
self.0 &= !constants::kHasCustomIcon;
}
}
pub fn has_hidden_extension(&self) -> bool {
self.0 & constants::kHideExtension != 0
}
pub fn set_has_hidden_extension(&mut self, value: bool) {
if value {
self.0 |= constants::kHideExtension;
} else {
self.0 &= !constants::kHideExtension;
}
}
pub fn is_stationery(&self) -> bool {
self.0 & constants::kIsStationery != 0
}
pub fn name_locked(&self) -> bool {
self.0 & constants::kNameLocked != 0
}
pub fn has_bundle(&self) -> bool {
self.0 & constants::kHasBundle != 0
}
pub fn is_invisible(&self) -> bool {
self.0 & constants::kIsInvisible != 0
}
pub fn is_alias(&self) -> bool {
self.0 & constants::kIsAlias != 0
}
}
impl fmt::Debug for FinderFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut flags = vec![];
if let Some(color) = self.color() {
flags.push(format!("{:?}", color));
}
if self.is_shared() {
flags.push("kIsShared".to_string());
}
if self.has_no_inits() {
flags.push("kHasNoINITs".to_string());
}
if self.has_been_inited() {
flags.push("kHasBeenInited".to_string());
}
if self.has_custom_icon() {
flags.push("kHasCustomIcon".to_string());
}
if self.is_stationery() {
flags.push("kIsStationery".to_string());
}
if self.name_locked() {
flags.push("kNameLocked".to_string());
}
if self.has_bundle() {
flags.push("kHasBundle".to_string());
}
if self.is_invisible() {
flags.push("kIsInvisible".to_string());
}
if self.is_alias() {
flags.push("kIsAlias".to_string());
}
if self.has_hidden_extension() {
flags.push("kHideExtension".to_string());
}
f.debug_struct("FinderFlags")
.field("raw", &self.0)
.field("flags", &flags)
.finish()
}
}
impl From<u16> for FinderFlags {
fn from(s: u16) -> FinderFlags {
FinderFlags(s)
}
}
impl From<FinderFlags> for u16 {
fn from(f: FinderFlags) -> u16 {
f.0
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum LabelColor {
Gray,
Green,
Purple,
Blue,
Yellow,
Red,
Orange,
}
impl LabelColor {
pub fn from_u8(b: u8) -> Option<LabelColor> {
match b {
0x02 => Some(LabelColor::Gray),
0x04 => Some(LabelColor::Green),
0x06 => Some(LabelColor::Purple),
0x08 => Some(LabelColor::Blue),
0x0a => Some(LabelColor::Yellow),
0x0c => Some(LabelColor::Red),
0x0e => Some(LabelColor::Orange),
_ => None,
}
}
pub fn to_u8(c: Option<LabelColor>) -> u8 {
match c {
None => 0x00,
Some(LabelColor::Gray) => 0x02,
Some(LabelColor::Green) => 0x04,
Some(LabelColor::Purple) => 0x06,
Some(LabelColor::Blue) => 0x08,
Some(LabelColor::Yellow) => 0x0a,
Some(LabelColor::Red) => 0x0c,
Some(LabelColor::Orange) => 0x0e,
}
}
pub fn to_str(c: LabelColor) -> &'static str {
match c {
LabelColor::Gray => "Gray",
LabelColor::Green => "Green",
LabelColor::Purple => "Purple",
LabelColor::Blue => "Blue",
LabelColor::Yellow => "Yellow",
LabelColor::Red => "Red",
LabelColor::Orange => "Orange",
}
}
pub fn from_str(s: &str) -> Option<LabelColor> {
match s {
"Gray" => Some(LabelColor::Gray),
"Green" => Some(LabelColor::Green),
"Purple" => Some(LabelColor::Purple),
"Blue" => Some(LabelColor::Blue),
"Yellow" => Some(LabelColor::Yellow),
"Red" => Some(LabelColor::Red),
"Orange" => Some(LabelColor::Orange),
_ => None,
}
}
}
#[derive(Clone, Copy, Default, Eq, PartialEq)]
pub struct ExtendedFinderFlags(u16);
impl fmt::Debug for ExtendedFinderFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut flags = vec![];
if self.are_invalid() {
flags.push("kExtendedFlagsAreInvalid");
}
if self.has_custom_badge() {
flags.push("kExtendedFlagHasCustomBadge");
}
if self.has_routing_info() {
flags.push("kExtendedFlagHasCustomBadge");
}
f.debug_struct("ExtendedFinderFlags")
.field("raw", &self.0)
.field("flags", &flags)
.finish()
}
}
impl ExtendedFinderFlags {
pub fn are_invalid(&self) -> bool {
self.0 & constants::kExtendedFlagsAreInvalid != 0
}
pub fn has_custom_badge(&self) -> bool {
self.0 & constants::kExtendedFlagHasCustomBadge != 0
}
pub fn has_routing_info(&self) -> bool {
self.0 & constants::kExtendedFlagHasRoutingInfo != 0
}
}
impl From<u16> for ExtendedFinderFlags {
fn from(s: u16) -> ExtendedFinderFlags {
ExtendedFinderFlags(s)
}
}
impl From<ExtendedFinderFlags> for u16 {
fn from(f: ExtendedFinderFlags) -> u16 {
f.0
}
}
#[derive(Clone, Debug, Default)]
#[repr(C)]
pub struct FileInfo {
pub fileType: OSType,
pub fileCreator: OSType,
pub finderFlags: FinderFlags,
pub location: Point,
pub reservedField: u16,
}
impl FileInfo {
pub fn read<R: Read>(r: &mut R) -> io::Result<FileInfo> {
let mut fileType = [0u8; 4];
r.read(&mut fileType)?;
let mut fileCreator = [0u8; 4];
r.read(&mut fileCreator)?;
let finderFlags = r.read_u16::<BigEndian>()?.into();
let location = Point::read(r)?;
let reservedField = r.read_u16::<BigEndian>()?;
Ok(FileInfo {
fileType: OSType(fileType),
fileCreator: OSType(fileCreator),
finderFlags,
location,
reservedField,
})
}
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
w.write(&self.fileType.0)?;
w.write(&self.fileCreator.0)?;
w.write_u16::<BigEndian>(self.finderFlags.into())?;
self.location.write(w)?;
w.write_u16::<BigEndian>(self.reservedField)?;
Ok(())
}
}
#[derive(Clone, Default)]
#[repr(C)]
pub struct ExtendedFileInfo {
pub reserved1: [i16; 4],
pub extendedFinderFlags: ExtendedFinderFlags,
pub reserved2: i16,
pub putAwayFolderID: i32,
}
impl fmt::Debug for ExtendedFileInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.reserved1 == [0i16; 4] && self.reserved2 == 0 {
f.debug_struct("ExtendedFileInfo")
.field("extendedFinderFlags", &self.extendedFinderFlags)
.field("putAwayFolderID", &self.putAwayFolderID)
.finish()
} else {
f.debug_struct("ExtendedFileInfo")
.field("reserved1", &self.reserved1)
.field("extendedFinderFlags", &self.extendedFinderFlags)
.field("reserved2", &self.reserved2)
.field("putAwayFolderID", &self.putAwayFolderID)
.finish()
}
}
}
impl ExtendedFileInfo {
pub fn read<R: Read>(r: &mut R) -> io::Result<ExtendedFileInfo> {
let mut reserved1 = [0i16; 4];
r.read_i16_into::<BigEndian>(&mut reserved1)?;
let extendedFinderFlags = r.read_u16::<BigEndian>()?.into();
let reserved2 = r.read_i16::<BigEndian>()?;
let putAwayFolderID = r.read_i32::<BigEndian>()?;
Ok(ExtendedFileInfo {
reserved1,
extendedFinderFlags,
reserved2,
putAwayFolderID,
})
}
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
for r in &self.reserved1 {
w.write_i16::<BigEndian>(*r)?;
}
w.write_u16::<BigEndian>(self.extendedFinderFlags.into())?;
w.write_i16::<BigEndian>(self.reserved2)?;
w.write_i32::<BigEndian>(self.putAwayFolderID)?;
Ok(())
}
}
#[derive(Clone, Debug, Default)]
#[repr(C)]
pub struct FinderInfoFile {
pub file_info: FileInfo,
pub extended_file_info: ExtendedFileInfo,
}
impl FinderInfoFile {
pub fn read<R: Read>(r: &mut R) -> io::Result<FinderInfoFile> {
let file_info = FileInfo::read(r)?;
let extended_file_info = ExtendedFileInfo::read(r)?;
Ok(FinderInfoFile {
file_info,
extended_file_info,
})
}
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
self.file_info.write(w)?;
self.extended_file_info.write(w)?;
Ok(())
}
}
#[derive(Clone, Default)]
#[repr(C)]
pub struct FolderInfo {
pub windowBounds: Rect,
pub finderFlags: FinderFlags,
pub location: Point,
pub reservedField: u16,
}
impl fmt::Debug for FolderInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.reservedField == 0 {
f.debug_struct("FolderInfo")
.field("windowBounds", &self.windowBounds)
.field("finderFlags", &self.finderFlags)
.field("location", &self.location)
.finish()
} else {
f.debug_struct("FolderInfo")
.field("windowBounds", &self.windowBounds)
.field("finderFlags", &self.finderFlags)
.field("location", &self.location)
.field("reservedField", &self.reservedField)
.finish()
}
}
}
impl FolderInfo {
pub fn read<R: Read>(r: &mut R) -> io::Result<FolderInfo> {
let windowBounds = Rect::read(r)?;
let finderFlags = r.read_u16::<BigEndian>()?.into();
let location = Point::read(r)?;
let reservedField = r.read_u16::<BigEndian>()?;
Ok(FolderInfo {
windowBounds,
finderFlags,
location,
reservedField,
})
}
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
self.windowBounds.write(w)?;
w.write_u16::<BigEndian>(self.finderFlags.into())?;
self.location.write(w)?;
w.write_u16::<BigEndian>(self.reservedField)?;
Ok(())
}
}
#[derive(Clone, Default)]
#[repr(C)]
pub struct ExtendedFolderInfo {
pub scrollPosition: Point,
pub reserved1: i32,
pub extendedFinderFlags: ExtendedFinderFlags,
pub reserved2: i16,
pub putAwayFolderID: i32,
}
impl fmt::Debug for ExtendedFolderInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.reserved1 == 0 && self.reserved2 == 0 {
f.debug_struct("ExtendedFolderInfo")
.field("scrollPosition", &self.scrollPosition)
.field("extendedFinderFlags", &self.extendedFinderFlags)
.field("putAwayFolderID", &self.putAwayFolderID)
.finish()
} else {
f.debug_struct("ExtendedFolderInfo")
.field("scrollPosition", &self.scrollPosition)
.field("reserved1", &self.reserved1)
.field("extendedFinderFlags", &self.extendedFinderFlags)
.field("reserved2", &self.reserved2)
.field("putAwayFolderID", &self.putAwayFolderID)
.finish()
}
}
}
impl ExtendedFolderInfo {
pub fn read<R: Read>(r: &mut R) -> io::Result<ExtendedFolderInfo> {
let scrollPosition = Point::read(r)?;
let reserved1 = r.read_i32::<BigEndian>()?;
let extendedFinderFlags = r.read_u16::<BigEndian>()?.into();
let reserved2 = r.read_i16::<BigEndian>()?;
let putAwayFolderID = r.read_i32::<BigEndian>()?;
Ok(ExtendedFolderInfo {
scrollPosition,
reserved1,
extendedFinderFlags,
reserved2,
putAwayFolderID,
})
}
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
self.scrollPosition.write(w)?;
w.write_i32::<BigEndian>(self.reserved1)?;
w.write_u16::<BigEndian>(self.extendedFinderFlags.into())?;
w.write_i16::<BigEndian>(self.reserved2)?;
w.write_i32::<BigEndian>(self.putAwayFolderID)?;
Ok(())
}
}
#[derive(Clone, Debug, Default)]
#[repr(C)]
pub struct FinderInfoFolder {
pub folder_info: FolderInfo,
pub extended_folder_info: ExtendedFolderInfo,
}
impl FinderInfoFolder {
pub fn read<R: Read>(r: &mut R) -> io::Result<FinderInfoFolder> {
let folder_info = FolderInfo::read(r)?;
let extended_folder_info = ExtendedFolderInfo::read(r)?;
Ok(FinderInfoFolder {
folder_info,
extended_folder_info,
})
}
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
self.folder_info.write(w)?;
self.extended_folder_info.write(w)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
const DEFAULT_FINDERINFO_XATTR_VALUE: [u8; 32] = [0u8; 32];
const FINDERINFO_XATTR_VALUE_ON: [u8; 32] = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
];
const FINDERINFO_XATTR_RED_BLUE_FOO_ICON: [u8; 32] = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
];
const FINDERINFO_XATTR_FOO_BLUE_RED: [u8; 32] = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
];
const FINDERINFO_XATTR_FOO_BLUE_RED_ICON: [u8; 32] = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
];
#[test]
fn test_finderinfo_sizes() {
assert_eq!(::std::mem::size_of::<FileInfo>(), 16);
assert_eq!(::std::mem::size_of::<ExtendedFileInfo>(), 16);
assert_eq!(::std::mem::size_of::<FinderInfoFile>(), 32);
assert_eq!(::std::mem::size_of::<FolderInfo>(), 16);
assert_eq!(::std::mem::size_of::<ExtendedFolderInfo>(), 16);
assert_eq!(::std::mem::size_of::<FinderInfoFolder>(), 32);
}
#[test]
fn test_set_get_finderinfo_file() {
let mut finfo =
FinderInfoFile::read(&mut io::Cursor::new(DEFAULT_FINDERINFO_XATTR_VALUE)).unwrap();
assert!(!finfo.file_info.finderFlags.has_custom_icon());
assert_eq!(finfo.file_info.finderFlags.color(), None);
let mut cursor = io::Cursor::new(vec![]);
finfo.write(&mut cursor).unwrap();
let serialized = cursor.into_inner();
assert_eq!(DEFAULT_FINDERINFO_XATTR_VALUE.to_vec(), serialized);
finfo
.file_info
.finderFlags
.set_color(Some(LabelColor::Blue));
finfo.file_info.finderFlags.set_has_custom_icon(true);
let mut cursor = io::Cursor::new(vec![]);
finfo.write(&mut cursor).unwrap();
let serialized = cursor.into_inner();
assert_eq!(serialized.len(), 32);
assert_eq!(serialized, FINDERINFO_XATTR_RED_BLUE_FOO_ICON);
let finfo =
FinderInfoFile::read(&mut io::Cursor::new(FINDERINFO_XATTR_FOO_BLUE_RED_ICON)).unwrap();
assert!(finfo.file_info.finderFlags.has_custom_icon());
assert_eq!(finfo.file_info.finderFlags.color(), Some(LabelColor::Red));
}
#[test]
fn test_set_get_finderinfo_folder() {
let mut finfo =
FinderInfoFolder::read(&mut io::Cursor::new(DEFAULT_FINDERINFO_XATTR_VALUE)).unwrap();
assert!(!finfo.folder_info.finderFlags.has_custom_icon());
assert_eq!(finfo.folder_info.finderFlags.color(), None);
let mut cursor = io::Cursor::new(vec![]);
finfo.write(&mut cursor).unwrap();
let serialized = cursor.into_inner();
assert_eq!(DEFAULT_FINDERINFO_XATTR_VALUE.to_vec(), serialized);
finfo.folder_info.finderFlags.set_has_custom_icon(true);
let mut cursor = io::Cursor::new(vec![]);
finfo.write(&mut cursor).unwrap();
let serialized = cursor.into_inner();
assert_eq!(serialized.len(), 32);
assert_eq!(serialized, FINDERINFO_XATTR_VALUE_ON);
finfo
.folder_info
.finderFlags
.set_color(Some(LabelColor::Blue));
let mut cursor = io::Cursor::new(vec![]);
finfo.write(&mut cursor).unwrap();
let serialized = cursor.into_inner();
assert_eq!(serialized.len(), 32);
assert_eq!(serialized, FINDERINFO_XATTR_RED_BLUE_FOO_ICON);
let finfo =
FinderInfoFolder::read(&mut io::Cursor::new(FINDERINFO_XATTR_FOO_BLUE_RED)).unwrap();
assert!(!finfo.folder_info.finderFlags.has_custom_icon());
assert_eq!(finfo.folder_info.finderFlags.color(), Some(LabelColor::Red));
}
}