Magic bytes 🪄

Co-authored-by: Brendan Allan <brendonovich@outlook.com>
This commit is contained in:
Jamie Pine 2022-10-05 13:16:22 -07:00
parent d27ff3cd80
commit 5ab432ae46
No known key found for this signature in database
GPG key ID: D5AC85A0C2F646E9
7 changed files with 255 additions and 94 deletions

View file

@ -10,6 +10,7 @@ davidmytton
deel
elon
encryptor
Flac
haden
haoyuan
haris

View file

@ -51,6 +51,8 @@ impl Node {
fs::create_dir_all(&data_dir).await?;
// dbg!(get_object_kind_from_extension("png"));
tracing_subscriber::registry()
.with(
EnvFilter::from_default_env()

View file

@ -1,3 +1,8 @@
/// Object Kind
///
/// https://www.garykessler.net/library/file_sigs.html
/// https://github.com/bojand/infer/
///
use std::{
fmt::{Display, Formatter},
str::FromStr,
@ -8,6 +13,8 @@ use rspc::Type;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::prisma::file_path;
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Type, IntEnum)]
#[repr(u8)]
pub enum ObjectKind {
@ -47,19 +54,39 @@ pub enum ObjectKind {
Album = 16,
// Its like a folder, but appears like a stack of files, designed for burst photos / associated groups of files
Collection = 17,
// You know, text init
Font = 18,
}
/// Construct the extensions enum
macro_rules! extension_enum {
(
Extension {
$( $variant:ident($type:ident), )*
}
) => {
#[derive(Debug, Serialize, Deserialize)]
// construct enum
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub enum Extension {
$( $variant($type), )*
}
// convert extension to object kind
impl Extension {
pub fn from_str(s: &str) -> Option<ExtensionPossibility> {
let mut exts = [$(
$type::from_str(s).ok().map(Self::$variant)
),*]
.into_iter()
.filter_map(|s| s)
.collect::<Vec<_>>();
match exts {
_ if exts.len() == 0 => None,
_ if exts.len() == 1 => Some(ExtensionPossibility::Known(exts.swap_remove(0))),
_ => Some(ExtensionPossibility::Conflicts(exts))
}
}
}
// convert Extension to ObjectKind
impl From<Extension> for ObjectKind {
fn from(ext: Extension) -> Self {
match ext {
@ -67,6 +94,7 @@ macro_rules! extension_enum {
}
}
}
//
impl Display for Extension {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
@ -77,9 +105,67 @@ macro_rules! extension_enum {
};
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn extension_from_str() {
// single extension match
assert_eq!(
Extension::from_str("jpg"),
Some(ExtensionPossibility::Known(Extension::Image(
ImageExtension::Jpg
)))
);
// with conflicts
assert_eq!(
Extension::from_str("ts"),
Some(ExtensionPossibility::Conflicts(vec![
Extension::Video(VideoExtension::Ts),
Extension::Text(TextExtension::Ts)
]))
);
// invalid case
assert_eq!(Extension::from_str("jeff"), None);
}
}
impl file_path::Data {
// fn extension(&self, magic_bytes: Option<&[u8]>) -> Option<ExtensionPossibility>V {
// let ext = match self.extension {
// Some(ext) => ext.as_str(),
// None => return Ok(Extension::Unknown("".to_string())),
// };
// // if let Ok(ex) = VideoExtension::from_str(ext) {
// // // .ts files can be video or text
// // match ex {
// // VideoExtension::Ts => {
// // // double check if it is a video=
// // }
// // _ => Extension::Video(ex),
// // }
// // //
// // } else if let Ok(ex) = ImageExtension::from_str(ext) {
// // return Extension::Image(ex);
// // //
// // } else if let Ok(ex) = AudioExtension::from_str(ext) {
// // return Extension::Audio(ex);
// // //
// // } else {
// // return Extension::Unknown(ext);
// // }
// }
// fn object_kind(&self, magic_bytes: Option<&[u8]>) -> ObjectKind {
// let extension = self.extension(magic_bytes);
// extension.into()
// }
}
extension_enum! {
Extension {
Unknown(String),
Video(VideoExtension),
Image(ImageExtension),
Audio(AudioExtension),
@ -88,19 +174,50 @@ extension_enum! {
Text(TextExtension),
Encrypted(EncryptedExtension),
Key(KeyExtension),
Font(FontExtension),
}
}
#[derive(Debug, PartialEq)]
pub enum ExtensionPossibility {
Known(Extension),
Conflicts(Vec<Extension>),
}
pub trait MagicBytes: Sized {
fn from_magic_bytes_buf(buf: &[u8]) -> Option<Self>;
fn magic_bytes_len(&self) -> usize;
fn magic_bytes_offset(&self) -> usize;
}
macro_rules! magic_byte_value {
(_) => {
0 as u8
};
($val:literal) => {{
$val as u8
}};
}
macro_rules! magic_byte_offset {
() => {
0
};
($val:literal) => {
$val
};
}
/// Define a public enum with static array of all possible variants
/// including implementations to convert to/from string
macro_rules! enum_with_variants {
macro_rules! extension_category_enum {
(
$(#[$enum_attr:meta])*
$enum_name:ident $static_array_name:ident {
$($(#[$variant_attr:meta])* $variant:ident, )*
$($(#[$variant_attr:meta])* $variant:ident $(= [$($magic_bytes:tt),*] $(+ $offset:literal)?)? ,)*
}
) => {
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
#[serde(rename_all = "snake_case")]
$(#[$enum_attr])*
// construct enum
@ -111,6 +228,7 @@ macro_rules! enum_with_variants {
pub static $static_array_name: &[$enum_name] = &[
$( $enum_name::$variant, )*
];
extension_category_enum!(@magic_bytes; $enum_name ( $($(#[$variant_attr])* $variant $(= [$($magic_bytes),*] $(+ $offset)?)? ),* ));
// convert from string
impl FromStr for $enum_name {
type Err = serde_json::Error;
@ -124,115 +242,141 @@ macro_rules! enum_with_variants {
write!(f, "{}", serde_json::to_string(self).unwrap()) // SAFETY: This is safe
}
}
}
};
(@magic_bytes; $enum_name:ident ($($(#[$variant_attr:meta])* $variant:ident = [$($magic_bytes:tt),*] $(+ $offset:literal)? ),*)) => {
impl MagicBytes for $enum_name {
fn from_magic_bytes_buf(buf: &[u8]) -> Option<Self> {
match buf {
$( &[$($magic_bytes),*] => Some($enum_name::$variant),)*
_ => None
}
}
fn magic_bytes_len(&self) -> usize {
match self {
$( $enum_name::$variant => (&[$(magic_byte_value!($magic_bytes)),*] as &[u8]).len() ),*,
}
}
fn magic_bytes_offset(&self) -> usize {
match self {
$( $enum_name::$variant => magic_byte_offset!($($offset)?)),*
}
}
}
};
(@magic_bytes; $enum_name:ident ($($(#[$variant_attr:meta])* $variant:ident),*)) => {};
}
// video extensions
enum_with_variants! {
extension_category_enum! {
VideoExtension ALL_VIDEO_EXTENSIONS {
Avi,
Asf,
Mpeg,
Mts,
Mpg,
Mpe,
Qt,
Mov,
Swf,
Mjpeg,
Ts,
Mxf,
M2v,
M2ts,
Flv,
Wm,
Avi = [0x52, 0x49, 0x46, 0x46, _, _, _, _, 0x41, 0x56, 0x49, 0x20],
Mpeg = [0x47],
Mts = [0x47, 0x41, 0x39, 0x34],
Mpg = [],
Mpe = [],
Qt = [0x71, 0x74, 0x20, 0x20],
Mov = [0x66, 0x74, 0x79, 0x70, 0x71, 0x74, 0x20, 0x20] + 4,
Swf = [0x5A, 0x57, 0x53],
Mjpeg = [],
Ts = [0x47],
Mxf = [0x06, 0x0E, 0x2B, 0x34, 0x02, 0x05, 0x01, 0x01, 0x0D, 0x01, 0x02, 0x01, 0x01, 0x02],
M2v = [0x00, 0x00, 0x01, 0xBA],
M2ts = [],
Flv = [0x66, 0x74, 0x79, 0x70, 0x4D, 0x34, 0x56, 0x20] + 4,
Wm = [],
#[serde(rename = "3gp")]
_3gp,
M4v,
Wmv,
Mp4,
Webm,
_3gp = [],
M4v = [0x66, 0x74, 0x79, 0x70, 0x6D, 0x70, 0x34, 0x32] + 4,
Wmv = [0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C],
Asf = [0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C],
Wma = [0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C],
Mp4 = [],
Webm = [0x1A, 0x45, 0xDF, 0xA3],
Mkv = [0x1A, 0x45, 0xDF, 0xA3],
}
}
// image extensions
enum_with_variants! {
extension_category_enum! {
ImageExtension _ALL_IMAGE_EXTENSIONS {
Jpg,
Jpeg,
Png,
Gif,
Bmp,
Tiff,
Webp,
Svg,
Ico,
Heic,
Jpg = [0xFF, 0xD8],
Jpeg = [0xFF, 0xD8],
Png = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A],
Gif = [0x47, 0x49, 0x46, 0x38, _, 0x61],
Bmp = [0x42, 0x4D],
Tiff = [0x49, 0x49, 0x2A, 0x00],
Webp = [0x52, 0x49, 0x46, 0x46, _, _, _, _, 0x57, 0x45, 0x42, 0x50],
Svg = [0x3C, 0x73, 0x76, 0x67],
Ico = [0x00, 0x00, 0x01, 0x00],
Heic = [0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x68, 0x65, 0x69, 0x63],
}
}
// audio extensions
enum_with_variants! {
extension_category_enum! {
AudioExtension _ALL_AUDIO_EXTENSIONS {
Mp3,
M4a,
Wav,
Aiff,
Aif,
Flac,
Ogg,
Opus,
Mp3 = [0x49, 0x44, 0x33],
M4a = [0x66, 0x74, 0x79, 0x70, 0x4D, 0x34, 0x41, 0x20] + 4,
Wav = [0x52, 0x49, 0x46, 0x46, _, _, _, _, 0x57, 0x41, 0x56, 0x45],
Aiff = [0x46, 0x4F, 0x52, 0x4D, _, _, _, _, 0x41, 0x49, 0x46, 0x46],
Aif = [0x46, 0x4F, 0x52, 0x4D, _, _, _, _, 0x41, 0x49, 0x46, 0x46],
Flac = [0x66, 0x4C, 0x61, 0x43],
Ogg = [0x4F, 0x67, 0x67, 0x53],
Opus = [0x4F, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64],
}
}
// archive extensions
enum_with_variants! {
extension_category_enum! {
ArchiveExtension _ALL_ARCHIVE_EXTENSIONS {
Zip,
Rar,
Tar,
Gz,
Bz2,
SevenZip,
Zip = [0x50, 0x4B, 0x03, 0x04],
Rar = [0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00],
Tar = [0x75, 0x73, 0x74, 0x61, 0x72],
Gz = [0x1F, 0x8B, 0x08],
Bz2 = [0x42, 0x5A, 0x68],
#[serde(rename = "7z")]
_7z = [0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C],
Xz = [0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00],
}
}
// executable extensions
enum_with_variants! {
extension_category_enum! {
ExecutableExtension _ALL_EXECUTABLE_EXTENSIONS {
Exe,
App,
Apk,
Deb,
Dmg,
Pkg,
Rpm,
Msi,
Exe = [],
App = [],
Apk = [0x50, 0x4B, 0x03, 0x04],
Deb = [],
Dmg = [],
Pkg = [],
Rpm = [],
Msi = [],
}
}
// document extensions
enum_with_variants! {
extension_category_enum! {
DocumentExtension _ALL_DOCUMENT_EXTENSIONS {
Pdf,
Key,
Pages,
Numbers,
Doc,
Docx,
Xls,
Xlsx,
Ppt,
Pptx,
Odt,
Ods,
Odp,
Ics,
Pdf = [0x25, 0x50, 0x44, 0x46, 0x2D],
Key = [0x50, 0x4B, 0x03, 0x04],
Pages = [0x50, 0x4B, 0x03, 0x04],
Numbers = [0x50, 0x4B, 0x03, 0x04],
Doc = [0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1],
Docx = [0x50, 0x4B, 0x03, 0x04],
Xls = [0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1],
Xlsx = [0x50, 0x4B, 0x03, 0x04],
Ppt = [0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1],
Pptx = [0x50, 0x4B, 0x03, 0x04],
Odt = [0x50, 0x4B, 0x03, 0x04],
Ods = [0x50, 0x4B, 0x03, 0x04],
Odp = [0x50, 0x4B, 0x03, 0x04],
Ics = [0x42, 0x45, 0x47, 0x49, 0x4E, 0x3A, 0x56, 0x43, 0x41, 0x52, 0x44],
}
}
// text file extensions
enum_with_variants! {
extension_category_enum! {
TextExtension _ALL_TEXT_EXTENSIONS {
Txt,
Rtf,
@ -243,19 +387,23 @@ enum_with_variants! {
Yaml,
Xml,
Md,
Ts,
}
}
// encrypted file extensions
enum_with_variants! {
extension_category_enum! {
EncryptedExtension _ALL_ENCRYPTED_EXTENSIONS {
Bit, // Spacedrive encrypted file
Box, // Spacedrive container
Block,// Spacedrive block storage,
Bit,
Box,
Block,
}
}
// Spacedrive encrypted file
// Spacedrive container
// Spacedrive block storage,
// key extensions
enum_with_variants! {
extension_category_enum! {
KeyExtension _ALL_KEY_EXTENSIONS {
Pgp,
Pub,
@ -265,3 +413,11 @@ enum_with_variants! {
Keychain,
}
}
// font extensions
extension_category_enum! {
FontExtension _ALL_FONT_EXTENSIONS {
Ttf = [0x00, 0x01, 0x00, 0x00, 0x00],
Otf = [0x4F, 0x54, 0x54, 0x4F, 0x00],
}
}

View file

@ -45,7 +45,7 @@ export const Inspector = (props: Props) => {
<div className="">
{!!props.data && (
<>
<div className="flex items-center justify-center w-full overflow-hidden rounded-md ">
<div className="flex items-center justify-center w-full py-4 overflow-hidden rounded-md">
<FileThumb
iconClassNames="!my-10"
size={230}

View file

@ -21,7 +21,8 @@ export const SidebarLink = (props: NavLinkProps & { children: React.ReactNode })
className={clsx(
'max-w mb-[2px] text-gray-550 dark:text-gray-300 rounded px-2 py-1 flex flex-row flex-grow items-center font-medium text-sm',
{
'!bg-primary !text-white hover:bg-primary dark:hover:bg-primary': isActive
'!bg-gray-400 !bg-opacity-10 !text-white hover:bg-gray-400 dark:hover:bg-gray-400':
isActive
},
props.className
)}
@ -85,13 +86,14 @@ function LibraryScopedSection() {
className={clsx(
'max-w mb-[2px] text-gray-550 dark:text-gray-150 rounded px-2 py-1 gap-2 flex flex-row flex-grow items-center truncate text-sm',
{
'!bg-primary !text-white hover:bg-primary dark:hover:bg-primary': isActive
'!bg-gray-400 !bg-opacity-10 !text-white hover:bg-gray-400 dark:hover:bg-gray-400':
isActive
}
)}
>
<div className="-mt-0.5 flex-grow-0 flex-shrink-0">
<Folder size={18} className={clsx(!isActive && 'hidden')} white />
<Folder size={18} className={clsx(isActive && 'hidden')} />
{/* <Folder size={18} className={clsx(!isActive && 'hidden')} white /> */}
<Folder size={18} />
</div>
<span className="flex-grow flex-shrink-0">{location.name}</span>

View file

@ -102,7 +102,7 @@ const SearchBar = forwardRef<HTMLInputElement, DefaultProps>((props, forwardedRe
<div
className={clsx(
'space-x-1 absolute top-[2px] right-1 peer-focus:invisible pointer-events-none',
'space-x-1 absolute top-[1px] right-1 peer-focus:invisible pointer-events-none',
isDirty && 'hidden'
)}
>

View file

@ -12,10 +12,10 @@ export const Shortcut: React.FC<ShortcutProps> = (props) => {
return (
<kbd
className={clsx(
`px-1 py-0.5 border border-b-2`,
`px-1 border border-b-2`,
`rounded-lg text-xs font-bold`,
`text-gray-400 bg-gray-200 border-gray-300`,
`dark:text-gray-400 dark:bg-gray-600 dark:border-gray-500`,
`dark:text-gray-400 dark:bg-transparent dark:border-transparent`,
className
)}
{...rest}