diff --git a/Cargo.toml b/Cargo.toml index de6b070..27decf1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,8 @@ version = "0.1.0" crate-type = ["cdylib"] [dependencies] -napi = "2" +anyhow = "1" +napi = { version = "2", features = ["anyhow", "napi6"] } napi-derive = "2" tar = "0.4" diff --git a/index.d.ts b/index.d.ts index db36fe3..59cef05 100644 --- a/index.d.ts +++ b/index.d.ts @@ -3,11 +3,349 @@ /* auto-generated by NAPI-RS */ +/** + * Indicate for the type of file described by a header. + * + * Each `Header` has an `entry_type` method returning an instance of this type + * which can be used to inspect what the header is describing. + * A non-exhaustive enum representing the possible entry types + */ +export const enum EntryType { + /** Regular file */ + Regular = 0, + /** Hard link */ + Link = 1, + /** Symbolic link */ + Symlink = 2, + /** Character device */ + Char = 3, + /** Block device */ + Block = 4, + /** Directory */ + Directory = 5, + /** Named pipe (fifo) */ + Fifo = 6, + /** Implementation-defined 'high-performance' type, treated as regular file */ + Continuous = 7, + /** GNU extension - long file name */ + GNULongName = 8, + /** GNU extension - long link name (link target) */ + GNULongLink = 9, + /** GNU extension - sparse file */ + GNUSparse = 10, + /** Global extended header */ + XGlobalHeader = 11, + /** Extended Header */ + XHeader = 12, +} export class Entries { [Symbol.iterator](): Iterator } export class Entry { + /** + * Returns the path name for this entry. + * + * This method may fail if the pathname is not valid Unicode and this is + * called on a Windows platform. + * + * Note that this function will convert any `\` characters to directory + * separators, and it will not always return the same value as + * `self.header().path()` as some archive formats have support for longer + * path names described in separate entries. + * + * It is recommended to use this method instead of inspecting the `header` + * directly to ensure that various archive formats are handled correctly. + */ path(): string | null + header(): ReadonlyHeader +} +export class Header { + /** Returns a view into this header as a byte array. */ + asBytes(): Buffer + /** + * Returns the size of entry's data this header represents. + * + * This is different from `Header::size` for sparse files, which have + * some longer `size()` but shorter `entry_size()`. The `entry_size()` + * listed here should be the number of bytes in the archive this header + * describes. + * + * May return an error if the field is corrupted. + */ + entrySize(): bigint + /** + * Returns the file size this header represents. + * + * May return an error if the field is corrupted. + */ + size(): bigint + /** Encodes the `size` argument into the size field of this header. */ + setSize(size: number | bigint): void + /** + * Returns the raw path name stored in this header. + * + * This method may fail if the pathname is not valid Unicode and this is + * called on a Windows platform. + * + * Note that this function will convert any `\` characters to directory + * separators. + */ + path(): string + /** + * Sets the path name for this header. + * + * This function will set the pathname listed in this header, encoding it + * in the appropriate format. May fail if the path is too long or if the + * path specified is not Unicode and this is a Windows platform. Will + * strip out any "." path component, which signifies the current directory. + * + * Note: This function does not support names over 100 bytes, or paths + * over 255 bytes, even for formats that support longer names. Instead, + * use `Builder` methods to insert a long-name extension at the same time + * as the file content. + */ + setPath(path: string): void + /** + * Returns the link name stored in this header as a byte array, if any. + * + * This function is guaranteed to succeed, but you may wish to call the + * `link_name` method to convert to a `Path`. + * + * Note that this function will convert any `\` characters to directory + * separators. + */ + linkName(): string | null + /** + * Sets the link name for this header. + * + * This function will set the linkname listed in this header, encoding it + * in the appropriate format. May fail if the link name is too long or if + * the path specified is not Unicode and this is a Windows platform. Will + * strip out any "." path component, which signifies the current directory. + * + * To use GNU long link names, prefer instead [`crate::Builder::append_link`]. + */ + setLinkName(linkName: string): void + /** + * Sets the link name for this header without any transformation. + * + * This function is like [`Self::set_link_name`] but accepts an arbitrary byte array. + * Hence it will not perform any canonicalization, such as replacing duplicate `//` with `/`. + */ + setLinkNameLiteral(linkName: string): void + /** + * Returns the mode bits for this file + * + * May return an error if the field is corrupted. + */ + mode(): number + /** Encodes the `mode` provided into this header. */ + setMode(mode: number): void + /** + * Returns the value of the owner's user ID field + * + * May return an error if the field is corrupted. + */ + uid(): bigint + /** Encodes the `uid` provided into this header. */ + setUid(uid: bigint): void + /** Returns the value of the group's user ID field */ + gid(): bigint + /** Encodes the `gid` provided into this header. */ + setGid(gid: bigint): void + /** Returns the last modification time in Unix time format */ + mtime(): bigint + /** + * Encodes the `mtime` provided into this header. + * + * Note that this time is typically a number of seconds passed since + * January 1, 1970. + */ + setTime(mtime: bigint): void + /** + * Return the user name of the owner of this file. + * + * A return value of `Ok(Some(..))` indicates that the user name was + * present and was valid utf-8, `Ok(None)` indicates that the user name is + * not present in this archive format, and `Err` indicates that the user + * name was present but was not valid utf-8. + */ + username(): string | null + /** + * Sets the username inside this header. + * + * This function will return an error if this header format cannot encode a + * user name or the name is too long. + */ + setUsername(username: string): void + /** + * Return the group name of the owner of this file. + * + * A return value of `Ok(Some(..))` indicates that the group name was + * present and was valid utf-8, `Ok(None)` indicates that the group name is + * not present in this archive format, and `Err` indicates that the group + * name was present but was not valid utf-8. + */ + groupname(): string | null + /** + * Sets the group name inside this header. + * + * This function will return an error if this header format cannot encode a + * group name or the name is too long. + */ + setGroupname(groupname: string): void + /** + * Returns the device major number, if present. + * + * This field may not be present in all archives, and it may not be + * correctly formed in all archives. `Ok(Some(..))` means it was present + * and correctly decoded, `Ok(None)` indicates that this header format does + * not include the device major number, and `Err` indicates that it was + * present and failed to decode. + */ + deviceMajor(): number | null + /** + * Encodes the value `major` into the dev_major field of this header. + * + * This function will return an error if this header format cannot encode a + * major device number. + */ + setDeviceMajor(deviceMajor: number): void + /** + * Returns the device minor number, if present. + * + * This field may not be present in all archives, and it may not be + * correctly formed in all archives. `Ok(Some(..))` means it was present + * and correctly decoded, `Ok(None)` indicates that this header format does + * not include the device minor number, and `Err` indicates that it was + * present and failed to decode. + */ + deviceMinor(): number | null + /** + * Encodes the value `minor` into the dev_minor field of this header. + * + * This function will return an error if this header format cannot encode a + * minor device number. + */ + setDeviceMinor(deviceMinor: number): void + /** Returns the type of file described by this header. */ + entryType(): EntryType + /** Sets the type of file that will be described by this header. */ + setEntryType(entryType: EntryType): void + /** + * Returns the checksum field of this header. + * + * May return an error if the field is corrupted. + */ + cksum(): number + /** + * Sets the checksum field of this header based on the current fields in + * this header. + */ + setCksum(): void +} +export class ReadonlyHeader { + /** Returns a view into this header as a byte array. */ + asBytes(): Buffer + /** + * Returns the size of entry's data this header represents. + * + * This is different from `Header::size` for sparse files, which have + * some longer `size()` but shorter `entry_size()`. The `entry_size()` + * listed here should be the number of bytes in the archive this header + * describes. + * + * May return an error if the field is corrupted. + */ + entrySize(): bigint + /** + * Returns the file size this header represents. + * + * May return an error if the field is corrupted. + */ + size(): bigint + /** + * Returns the raw path name stored in this header. + * + * This method may fail if the pathname is not valid Unicode and this is + * called on a Windows platform. + * + * Note that this function will convert any `\` characters to directory + * separators. + */ + path(): string + /** + * Returns the link name stored in this header as a byte array, if any. + * + * This function is guaranteed to succeed, but you may wish to call the + * `link_name` method to convert to a `Path`. + * + * Note that this function will convert any `\` characters to directory + * separators. + */ + linkName(): string | null + /** + * Returns the mode bits for this file + * + * May return an error if the field is corrupted. + */ + mode(): number + /** + * Returns the value of the owner's user ID field + * + * May return an error if the field is corrupted. + */ + uid(): bigint + /** Returns the value of the group's user ID field */ + gid(): bigint + /** Returns the last modification time in Unix time format */ + mtime(): bigint + /** + * Return the user name of the owner of this file. + * + * A return value of `Ok(Some(..))` indicates that the user name was + * present and was valid utf-8, `Ok(None)` indicates that the user name is + * not present in this archive format, and `Err` indicates that the user + * name was present but was not valid utf-8. + */ + username(): string | null + /** + * Return the group name of the owner of this file. + * + * A return value of `Ok(Some(..))` indicates that the group name was + * present and was valid utf-8, `Ok(None)` indicates that the group name is + * not present in this archive format, and `Err` indicates that the group + * name was present but was not valid utf-8. + */ + groupname(): string | null + /** + * Returns the device major number, if present. + * + * This field may not be present in all archives, and it may not be + * correctly formed in all archives. `Ok(Some(..))` means it was present + * and correctly decoded, `Ok(None)` indicates that this header format does + * not include the device major number, and `Err` indicates that it was + * present and failed to decode. + */ + deviceMajor(): number | null + /** + * Returns the device minor number, if present. + * + * This field may not be present in all archives, and it may not be + * correctly formed in all archives. `Ok(Some(..))` means it was present + * and correctly decoded, `Ok(None)` indicates that this header format does + * not include the device minor number, and `Err` indicates that it was + * present and failed to decode. + */ + deviceMinor(): number | null + /** Returns the type of file described by this header. */ + entryType(): EntryType + /** + * Returns the checksum field of this header. + * + * May return an error if the field is corrupted. + */ + cksum(): number } export class Archive { /** Create a new archive with the underlying path. */ @@ -25,4 +363,59 @@ export class Archive { * a '..' in their path are skipped during the unpacking process. */ unpack(to: string): void + /** + * Set the mask of the permission bits when unpacking this entry. + * + * The mask will be inverted when applying against a mode, similar to how + * `umask` works on Unix. In logical notation it looks like: + * + * ```text + * new_mode = old_mode & (~mask) + * ``` + * + * The mask is 0 by default and is currently only implemented on Unix. + */ + setMask(mask: number): void + /** + * Indicate whether extended file attributes (xattrs on Unix) are preserved + * when unpacking this archive. + * + * This flag is disabled by default and is currently only implemented on + * Unix using xattr support. This may eventually be implemented for + * Windows, however, if other archive implementations are found which do + * this as well. + */ + setUnpackXattrs(unpackXattrs: boolean): void + /** + * Indicate whether extended permissions (like suid on Unix) are preserved + * when unpacking this entry. + * + * This flag is disabled by default and is currently only implemented on + * Unix. + */ + setPreservePermissions(preservePermissions: boolean): void + /** + * Indicate whether numeric ownership ids (like uid and gid on Unix) + * are preserved when unpacking this entry. + * + * This flag is disabled by default and is currently only implemented on + * Unix. + */ + setPreserveOwnerships(preserveOwnerships: boolean): void + /** Indicate whether files and symlinks should be overwritten on extraction. */ + setOverwrite(overwrite: boolean): void + /** + * Indicate whether access time information is preserved when unpacking + * this entry. + * + * This flag is enabled by default. + */ + setPreserveMtime(preserveMtime: boolean): void + /** + * Ignore zeroed headers, which would otherwise indicate to the archive that it has no more + * entries. + * + * This can be used in case multiple tar archives have been concatenated together. + */ + setIgnoreZeros(ignoreZeros: boolean): void } diff --git a/index.js b/index.js index d4bd263..c7faf61 100644 --- a/index.js +++ b/index.js @@ -234,8 +234,11 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { Entries, Entry, Archive } = nativeBinding +const { Entries, Entry, EntryType, Header, ReadonlyHeader, Archive } = nativeBinding module.exports.Entries = Entries module.exports.Entry = Entry +module.exports.EntryType = EntryType +module.exports.Header = Header +module.exports.ReadonlyHeader = ReadonlyHeader module.exports.Archive = Archive diff --git a/src/entry.rs b/src/entry.rs index 2943807..4016fb4 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -1,8 +1,11 @@ use std::fs::File; +use napi::bindgen_prelude::Reference; +use napi::Env; use napi::{bindgen_prelude::SharedReference, iterator::Generator}; use napi_derive::napi; +use crate::header::ReadonlyHeader; use crate::Archive; #[napi(iterator)] @@ -17,11 +20,7 @@ impl Generator for Entries { type Return = (); fn next(&mut self, _next: Option<()>) -> Option { - let entry = self - .inner - .next()? - .map(crate::entry::Entry::new) - .ok()?; + let entry = self.inner.next()?.map(crate::entry::Entry::new).ok()?; Some(entry) } } @@ -38,7 +37,26 @@ impl Entry { } #[napi] + /// Returns the path name for this entry. + /// + /// This method may fail if the pathname is not valid Unicode and this is + /// called on a Windows platform. + /// + /// Note that this function will convert any `\` characters to directory + /// separators, and it will not always return the same value as + /// `self.header().path()` as some archive formats have support for longer + /// path names described in separate entries. + /// + /// It is recommended to use this method instead of inspecting the `header` + /// directly to ensure that various archive formats are handled correctly. pub fn path(&self) -> napi::Result> { Ok(self.inner.path()?.to_str().map(|s| s.to_owned())) } + + #[napi] + pub fn header(&self, this: Reference, env: Env) -> napi::Result { + Ok(ReadonlyHeader::new( + this.share_with(env, |e| Ok(e.inner.header()))?, + )) + } } diff --git a/src/header.rs b/src/header.rs new file mode 100644 index 0000000..a1afdb6 --- /dev/null +++ b/src/header.rs @@ -0,0 +1,555 @@ +use napi::{ + bindgen_prelude::{BigInt, Buffer, SharedReference}, + Either, Result, +}; +use napi_derive::napi; + +use crate::entry::Entry; + +// See https://en.wikipedia.org/wiki/Tar_%28computing%29#UStar_format +/// Indicate for the type of file described by a header. +/// +/// Each `Header` has an `entry_type` method returning an instance of this type +/// which can be used to inspect what the header is describing. + +/// A non-exhaustive enum representing the possible entry types +#[napi] +pub enum EntryType { + /// Regular file + Regular, + /// Hard link + Link, + /// Symbolic link + Symlink, + /// Character device + Char, + /// Block device + Block, + /// Directory + Directory, + /// Named pipe (fifo) + Fifo, + /// Implementation-defined 'high-performance' type, treated as regular file + Continuous, + /// GNU extension - long file name + GNULongName, + /// GNU extension - long link name (link target) + GNULongLink, + /// GNU extension - sparse file + GNUSparse, + /// Global extended header + XGlobalHeader, + /// Extended Header + XHeader, +} + +impl From for EntryType { + fn from(value: tar::EntryType) -> Self { + match value { + tar::EntryType::Regular => Self::Regular, + tar::EntryType::Link => Self::Link, + tar::EntryType::Symlink => Self::Symlink, + tar::EntryType::Char => Self::Char, + tar::EntryType::Block => Self::Block, + tar::EntryType::Directory => Self::Directory, + tar::EntryType::Fifo => Self::Fifo, + tar::EntryType::Continuous => Self::Continuous, + tar::EntryType::GNULongName => Self::GNULongName, + tar::EntryType::GNULongLink => Self::GNULongLink, + tar::EntryType::GNUSparse => Self::GNUSparse, + tar::EntryType::XGlobalHeader => Self::XGlobalHeader, + tar::EntryType::XHeader => Self::XHeader, + _ => unreachable!(), + } + } +} + +impl From for tar::EntryType { + fn from(value: EntryType) -> Self { + match value { + EntryType::Regular => Self::Regular, + EntryType::Link => Self::Link, + EntryType::Symlink => Self::Symlink, + EntryType::Char => Self::Char, + EntryType::Block => Self::Block, + EntryType::Directory => Self::Directory, + EntryType::Fifo => Self::Fifo, + EntryType::Continuous => Self::Continuous, + EntryType::GNULongName => Self::GNULongName, + EntryType::GNULongLink => Self::GNULongLink, + EntryType::GNUSparse => Self::GNUSparse, + EntryType::XGlobalHeader => Self::XGlobalHeader, + EntryType::XHeader => Self::XHeader, + } + } +} + +#[napi] +pub struct Header { + inner: SharedReference, +} + +#[napi] +impl Header { + pub fn new(inner: SharedReference) -> Self { + Self { inner } + } + + #[napi] + /// Returns a view into this header as a byte array. + pub fn as_bytes(&self) -> Buffer { + self.inner.as_bytes().to_vec().into() + } + + #[napi] + /// Returns the size of entry's data this header represents. + /// + /// This is different from `Header::size` for sparse files, which have + /// some longer `size()` but shorter `entry_size()`. The `entry_size()` + /// listed here should be the number of bytes in the archive this header + /// describes. + /// + /// May return an error if the field is corrupted. + pub fn entry_size(&self) -> Result { + Ok(self.inner.entry_size()?) + } + + #[napi] + /// Returns the file size this header represents. + /// + /// May return an error if the field is corrupted. + pub fn size(&self) -> Result { + Ok(self.inner.size()?) + } + + #[napi] + /// Encodes the `size` argument into the size field of this header. + pub fn set_size(&mut self, size: Either) { + let size = match size { + Either::A(size) => size as u64, + Either::B(size) => size.get_u64().1, + }; + self.inner.set_size(size); + } + + #[napi] + /// Returns the raw path name stored in this header. + /// + /// This method may fail if the pathname is not valid Unicode and this is + /// called on a Windows platform. + /// + /// Note that this function will convert any `\` characters to directory + /// separators. + pub fn path(&self) -> Result { + Ok(self.inner.path()?.to_string_lossy().to_string()) + } + + #[napi] + /// Sets the path name for this header. + /// + /// This function will set the pathname listed in this header, encoding it + /// in the appropriate format. May fail if the path is too long or if the + /// path specified is not Unicode and this is a Windows platform. Will + /// strip out any "." path component, which signifies the current directory. + /// + /// Note: This function does not support names over 100 bytes, or paths + /// over 255 bytes, even for formats that support longer names. Instead, + /// use `Builder` methods to insert a long-name extension at the same time + /// as the file content. + pub fn set_path(&mut self, path: String) -> Result<()> { + self.inner.set_path(path)?; + Ok(()) + } + + #[napi] + /// Returns the link name stored in this header as a byte array, if any. + /// + /// This function is guaranteed to succeed, but you may wish to call the + /// `link_name` method to convert to a `Path`. + /// + /// Note that this function will convert any `\` characters to directory + /// separators. + pub fn link_name(&self) -> Result> { + Ok( + self + .inner + .link_name()? + .map(|l| l.to_string_lossy().to_string()), + ) + } + + #[napi] + /// Sets the link name for this header. + /// + /// This function will set the linkname listed in this header, encoding it + /// in the appropriate format. May fail if the link name is too long or if + /// the path specified is not Unicode and this is a Windows platform. Will + /// strip out any "." path component, which signifies the current directory. + /// + /// To use GNU long link names, prefer instead [`crate::Builder::append_link`]. + pub fn set_link_name(&mut self, link_name: String) -> Result<()> { + self.inner.set_link_name(link_name)?; + Ok(()) + } + + #[napi] + /// Sets the link name for this header without any transformation. + /// + /// This function is like [`Self::set_link_name`] but accepts an arbitrary byte array. + /// Hence it will not perform any canonicalization, such as replacing duplicate `//` with `/`. + pub fn set_link_name_literal(&mut self, link_name: String) -> Result<()> { + self.inner.set_link_name_literal(link_name)?; + Ok(()) + } + + #[napi] + /// Returns the mode bits for this file + /// + /// May return an error if the field is corrupted. + pub fn mode(&self) -> Result { + Ok(self.inner.mode()?) + } + + #[napi] + /// Encodes the `mode` provided into this header. + pub fn set_mode(&mut self, mode: u32) { + self.inner.set_mode(mode); + } + + #[napi] + /// Returns the value of the owner's user ID field + /// + /// May return an error if the field is corrupted. + pub fn uid(&self) -> Result { + Ok(self.inner.uid()?) + } + + #[napi] + /// Encodes the `uid` provided into this header. + pub fn set_uid(&mut self, uid: BigInt) { + let (_, uid, _) = uid.get_u64(); + self.inner.set_uid(uid); + } + + #[napi] + /// Returns the value of the group's user ID field + pub fn gid(&self) -> Result { + Ok(self.inner.gid()?) + } + + #[napi] + /// Encodes the `gid` provided into this header. + pub fn set_gid(&mut self, gid: BigInt) { + let (_, gid, _) = gid.get_u64(); + self.inner.set_gid(gid); + } + + #[napi] + /// Returns the last modification time in Unix time format + pub fn mtime(&self) -> Result { + Ok(self.inner.mtime()?) + } + + #[napi] + /// Encodes the `mtime` provided into this header. + /// + /// Note that this time is typically a number of seconds passed since + /// January 1, 1970. + pub fn set_time(&mut self, mtime: BigInt) { + let (_, mtime, _) = mtime.get_u64(); + self.inner.set_mtime(mtime); + } + + #[napi] + /// Return the user name of the owner of this file. + /// + /// A return value of `Ok(Some(..))` indicates that the user name was + /// present and was valid utf-8, `Ok(None)` indicates that the user name is + /// not present in this archive format, and `Err` indicates that the user + /// name was present but was not valid utf-8. + pub fn username(&self) -> Result> { + Ok( + self + .inner + .username() + .map_err(anyhow::Error::from)? + .map(|u| u.to_string()), + ) + } + + #[napi] + /// Sets the username inside this header. + /// + /// This function will return an error if this header format cannot encode a + /// user name or the name is too long. + pub fn set_username(&mut self, username: String) -> Result<()> { + self.inner.set_username(&username)?; + Ok(()) + } + + #[napi] + /// Return the group name of the owner of this file. + /// + /// A return value of `Ok(Some(..))` indicates that the group name was + /// present and was valid utf-8, `Ok(None)` indicates that the group name is + /// not present in this archive format, and `Err` indicates that the group + /// name was present but was not valid utf-8. + pub fn groupname(&self) -> Result> { + Ok( + self + .inner + .groupname() + .map_err(anyhow::Error::from)? + .map(|g| g.to_string()), + ) + } + + #[napi] + /// Sets the group name inside this header. + /// + /// This function will return an error if this header format cannot encode a + /// group name or the name is too long. + pub fn set_groupname(&mut self, groupname: String) -> Result<()> { + self.inner.set_groupname(&groupname)?; + Ok(()) + } + + #[napi] + /// Returns the device major number, if present. + /// + /// This field may not be present in all archives, and it may not be + /// correctly formed in all archives. `Ok(Some(..))` means it was present + /// and correctly decoded, `Ok(None)` indicates that this header format does + /// not include the device major number, and `Err` indicates that it was + /// present and failed to decode. + pub fn device_major(&self) -> Result> { + Ok(self.inner.device_major()?) + } + + #[napi] + /// Encodes the value `major` into the dev_major field of this header. + /// + /// This function will return an error if this header format cannot encode a + /// major device number. + pub fn set_device_major(&mut self, device_major: u32) -> Result<()> { + self.inner.set_device_major(device_major)?; + Ok(()) + } + + #[napi] + /// Returns the device minor number, if present. + /// + /// This field may not be present in all archives, and it may not be + /// correctly formed in all archives. `Ok(Some(..))` means it was present + /// and correctly decoded, `Ok(None)` indicates that this header format does + /// not include the device minor number, and `Err` indicates that it was + /// present and failed to decode. + pub fn device_minor(&self) -> Result> { + Ok(self.inner.device_minor()?) + } + + #[napi] + /// Encodes the value `minor` into the dev_minor field of this header. + /// + /// This function will return an error if this header format cannot encode a + /// minor device number. + pub fn set_device_minor(&mut self, device_minor: u32) -> Result<()> { + self.inner.set_device_minor(device_minor)?; + Ok(()) + } + + #[napi] + /// Returns the type of file described by this header. + pub fn entry_type(&self) -> EntryType { + self.inner.entry_type().into() + } + + #[napi] + /// Sets the type of file that will be described by this header. + pub fn set_entry_type(&mut self, entry_type: EntryType) { + self.inner.set_entry_type(entry_type.into()); + } + + #[napi] + /// Returns the checksum field of this header. + /// + /// May return an error if the field is corrupted. + pub fn cksum(&self) -> Result { + Ok(self.inner.cksum()?) + } + + #[napi] + /// Sets the checksum field of this header based on the current fields in + /// this header. + pub fn set_cksum(&mut self) { + self.inner.set_cksum(); + } +} + +#[napi] +pub struct ReadonlyHeader { + inner: SharedReference, +} + +#[napi] +impl ReadonlyHeader { + pub fn new(inner: SharedReference) -> Self { + Self { inner } + } + + #[napi] + /// Returns a view into this header as a byte array. + pub fn as_bytes(&self) -> Buffer { + self.inner.as_bytes().to_vec().into() + } + + #[napi] + /// Returns the size of entry's data this header represents. + /// + /// This is different from `Header::size` for sparse files, which have + /// some longer `size()` but shorter `entry_size()`. The `entry_size()` + /// listed here should be the number of bytes in the archive this header + /// describes. + /// + /// May return an error if the field is corrupted. + pub fn entry_size(&self) -> Result { + Ok(self.inner.entry_size()?) + } + + #[napi] + /// Returns the file size this header represents. + /// + /// May return an error if the field is corrupted. + pub fn size(&self) -> Result { + Ok(self.inner.size()?) + } + + #[napi] + /// Returns the raw path name stored in this header. + /// + /// This method may fail if the pathname is not valid Unicode and this is + /// called on a Windows platform. + /// + /// Note that this function will convert any `\` characters to directory + /// separators. + pub fn path(&self) -> Result { + Ok(self.inner.path()?.to_string_lossy().to_string()) + } + + #[napi] + /// Returns the link name stored in this header as a byte array, if any. + /// + /// This function is guaranteed to succeed, but you may wish to call the + /// `link_name` method to convert to a `Path`. + /// + /// Note that this function will convert any `\` characters to directory + /// separators. + pub fn link_name(&self) -> Result> { + Ok( + self + .inner + .link_name()? + .map(|l| l.to_string_lossy().to_string()), + ) + } + + #[napi] + /// Returns the mode bits for this file + /// + /// May return an error if the field is corrupted. + pub fn mode(&self) -> Result { + Ok(self.inner.mode()?) + } + + #[napi] + /// Returns the value of the owner's user ID field + /// + /// May return an error if the field is corrupted. + pub fn uid(&self) -> Result { + Ok(self.inner.uid()?) + } + + #[napi] + /// Returns the value of the group's user ID field + pub fn gid(&self) -> Result { + Ok(self.inner.gid()?) + } + + #[napi] + /// Returns the last modification time in Unix time format + pub fn mtime(&self) -> Result { + Ok(self.inner.mtime()?) + } + + #[napi] + /// Return the user name of the owner of this file. + /// + /// A return value of `Ok(Some(..))` indicates that the user name was + /// present and was valid utf-8, `Ok(None)` indicates that the user name is + /// not present in this archive format, and `Err` indicates that the user + /// name was present but was not valid utf-8. + pub fn username(&self) -> Result> { + Ok( + self + .inner + .username() + .map_err(anyhow::Error::from)? + .map(|u| u.to_string()), + ) + } + + #[napi] + /// Return the group name of the owner of this file. + /// + /// A return value of `Ok(Some(..))` indicates that the group name was + /// present and was valid utf-8, `Ok(None)` indicates that the group name is + /// not present in this archive format, and `Err` indicates that the group + /// name was present but was not valid utf-8. + pub fn groupname(&self) -> Result> { + Ok( + self + .inner + .groupname() + .map_err(anyhow::Error::from)? + .map(|g| g.to_string()), + ) + } + + #[napi] + /// Returns the device major number, if present. + /// + /// This field may not be present in all archives, and it may not be + /// correctly formed in all archives. `Ok(Some(..))` means it was present + /// and correctly decoded, `Ok(None)` indicates that this header format does + /// not include the device major number, and `Err` indicates that it was + /// present and failed to decode. + pub fn device_major(&self) -> Result> { + Ok(self.inner.device_major()?) + } + + #[napi] + /// Returns the device minor number, if present. + /// + /// This field may not be present in all archives, and it may not be + /// correctly formed in all archives. `Ok(Some(..))` means it was present + /// and correctly decoded, `Ok(None)` indicates that this header format does + /// not include the device minor number, and `Err` indicates that it was + /// present and failed to decode. + pub fn device_minor(&self) -> Result> { + Ok(self.inner.device_minor()?) + } + + #[napi] + /// Returns the type of file described by this header. + pub fn entry_type(&self) -> EntryType { + self.inner.entry_type().into() + } + + #[napi] + /// Returns the checksum field of this header. + /// + /// May return an error if the field is corrupted. + pub fn cksum(&self) -> Result { + Ok(self.inner.cksum()?) + } +} diff --git a/src/lib.rs b/src/lib.rs index 946fee7..2976df2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ use napi::bindgen_prelude::{Env, Reference}; use napi_derive::napi; mod entry; +mod header; #[napi] pub struct Archive { @@ -47,4 +48,75 @@ impl Archive { self.inner.unpack(to)?; Ok(()) } + + #[napi] + /// Set the mask of the permission bits when unpacking this entry. + /// + /// The mask will be inverted when applying against a mode, similar to how + /// `umask` works on Unix. In logical notation it looks like: + /// + /// ```text + /// new_mode = old_mode & (~mask) + /// ``` + /// + /// The mask is 0 by default and is currently only implemented on Unix. + pub fn set_mask(&mut self, mask: u32) { + self.inner.set_mask(mask); + } + + #[napi] + /// Indicate whether extended file attributes (xattrs on Unix) are preserved + /// when unpacking this archive. + /// + /// This flag is disabled by default and is currently only implemented on + /// Unix using xattr support. This may eventually be implemented for + /// Windows, however, if other archive implementations are found which do + /// this as well. + pub fn set_unpack_xattrs(&mut self, unpack_xattrs: bool) { + self.inner.set_unpack_xattrs(unpack_xattrs); + } + + #[napi] + /// Indicate whether extended permissions (like suid on Unix) are preserved + /// when unpacking this entry. + /// + /// This flag is disabled by default and is currently only implemented on + /// Unix. + pub fn set_preserve_permissions(&mut self, preserve_permissions: bool) { + self.inner.set_preserve_permissions(preserve_permissions); + } + + #[napi] + /// Indicate whether numeric ownership ids (like uid and gid on Unix) + /// are preserved when unpacking this entry. + /// + /// This flag is disabled by default and is currently only implemented on + /// Unix. + pub fn set_preserve_ownerships(&mut self, preserve_ownerships: bool) { + self.inner.set_preserve_ownerships(preserve_ownerships); + } + + #[napi] + /// Indicate whether files and symlinks should be overwritten on extraction. + pub fn set_overwrite(&mut self, overwrite: bool) { + self.inner.set_overwrite(overwrite); + } + + #[napi] + /// Indicate whether access time information is preserved when unpacking + /// this entry. + /// + /// This flag is enabled by default. + pub fn set_preserve_mtime(&mut self, preserve_mtime: bool) { + self.inner.set_preserve_mtime(preserve_mtime); + } + + #[napi] + /// Ignore zeroed headers, which would otherwise indicate to the archive that it has no more + /// entries. + /// + /// This can be used in case multiple tar archives have been concatenated together. + pub fn set_ignore_zeros(&mut self, ignore_zeros: bool) { + self.inner.set_ignore_zeros(ignore_zeros); + } }