pdf_perm/
lib.rs

1//! # `pdf-perm` library crate
2//!
3//! If you are reading this, you are reading the documentation for the `pdf-perm` library crate. For the cli, kindly refer to the README file.
4//!
5//! This library crate provides several traits related to [`Permissions`].
6
7#![deny(missing_docs)]
8#![warn(clippy::all, clippy::nursery, clippy::pedantic, clippy::cargo)]
9
10use bitflags::Flags;
11use log::{debug, error, warn};
12use lopdf::{
13    Document, EncryptionState, EncryptionVersion, Error, Permissions, Result as PdfResult,
14};
15
16/// Trait for non-encrypted PDF [`Document`]s, allowing for easy getting and setting of [`Permissions`].
17pub trait PdfPerm {
18    /// Returns the permissions of the PDF document.
19    fn permissions(&self) -> Permissions;
20    /// Sets the permissions of the PDF document.
21    ///
22    /// # Note
23    ///
24    /// You must save the document for the permissions to take effect. See [`Document::save`] and [`Document::save_to`].
25    ///
26    /// # Errors
27    ///
28    /// Returns [`Error::AlreadyEncrypted`] if the document is encrypted, or other variants if operation fails.
29    fn set_permissions(&mut self, permissions: Permissions) -> PdfResult<()>;
30}
31
32impl PdfPerm for Document {
33    fn permissions(&self) -> Permissions {
34        self.encryption_state
35            .as_ref()
36            .map(EncryptionState::permissions)
37            .unwrap_or_default()
38    }
39    fn set_permissions(&mut self, permissions: Permissions) -> PdfResult<()> {
40        if self.is_encrypted() {
41            error!("Does not support setting permissions on encrypted documents");
42            return Err(Error::AlreadyEncrypted);
43        }
44
45        if permissions == Permissions::default() {
46            debug!("Skipping encryption since permissions are default");
47            return Ok(());
48        }
49
50        let version = EncryptionVersion::V1 {
51            document: self,
52            owner_password: "",
53            user_password: "",
54            permissions,
55        };
56        let state: EncryptionState = version.try_into()?;
57        debug!("Encryption state: {state:?}");
58        self.encrypt(&state)?;
59
60        Ok(())
61    }
62}
63
64// TODO: Decoupling `ShortFlags` into a separate crate `short-flags`?
65/// Trait for [`Flags`] to provide short flag functionality.
66pub trait ShortFlags: Flags + Copy {
67    // Required constant
68    /// The set of defined short flags. Must be of the same length as [`Flags::FLAGS`].
69    const SHORT_FLAGS: &'static [char];
70
71    // Provided methods
72    /// Parses a character into a [`Flags`].
73    fn from_char(c: char) -> Option<Self> {
74        if c == '*' {
75            return Some(Self::all());
76        }
77        let index = Self::SHORT_FLAGS.iter().position(|&flag| flag == c)?;
78        Some(*Self::FLAGS.get(index)?.value())
79    }
80    /// Parses a string into self, with given short flags.
81    #[must_use]
82    fn from_str(s: &str) -> Self {
83        let mut flags = Self::empty();
84        for c in s.chars() {
85            if let Some(flag) = Self::from_char(c) {
86                flags.insert(flag);
87            } else {
88                warn!("Invalid short flag: {c}");
89            }
90        }
91        flags
92    }
93    /// Applies the given modification string.
94    fn apply_modification(&mut self, modification: &str) {
95        let (first, rest) = modification.split_at(1);
96        let flags_mod = Self::from_str(rest);
97        match first {
98            "+" => self.insert(flags_mod), // Set given flags
99            "-" => self.remove(flags_mod), // Unset given flags
100            "=" => *self = flags_mod,      // Set to given flags
101            _ => warn!("Invalid modification indicator: {modification}"),
102        }
103    }
104    /// Returns a concise summary of the flags, with short flag for set flags and `-` for unset flags.
105    fn summary(&self) -> String {
106        let mut summary = String::with_capacity(Self::SHORT_FLAGS.len());
107        for (short, flag) in Self::SHORT_FLAGS.iter().zip(Self::FLAGS) {
108            if self.contains(*flag.value()) {
109                summary.push(*short);
110            } else {
111                summary.push('-');
112            }
113        }
114        summary
115    }
116}
117
118impl ShortFlags for Permissions {
119    const SHORT_FLAGS: &'static [char] = &['p', 'm', 'c', 'a', 'f', 'x', 's', 'q'];
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use lopdf::Object;
126
127    // Test `PdfPerm` trait
128    fn create_test_document() -> Document {
129        // Cropped from https://github.com/J-F-Liu/lopdf/blob/bcb9244f4c862ca90dea3505339fb67185608175/src/creator.rs#L133-L191
130        let mut doc = Document::new();
131
132        doc.trailer.set(
133            "ID",
134            Object::Array(vec![
135                Object::string_literal(b"ABC"),
136                Object::string_literal(b"DEF"),
137            ]),
138        );
139
140        doc
141    }
142
143    #[test]
144    fn test_permissions() {
145        let mut doc = create_test_document();
146        assert_eq!(doc.permissions(), Permissions::default());
147
148        let pma_permissions = Permissions::from_str("pma");
149        doc.set_permissions(pma_permissions).unwrap();
150
151        let mut buffer = Vec::new();
152        doc.save_to(&mut buffer).unwrap();
153
154        let doc = Document::load_mem(&buffer).unwrap();
155        assert_eq!(doc.permissions(), pma_permissions);
156    }
157
158    // Test `ShortFlags` trait
159    #[test]
160    fn test_from_str_1() {
161        let permissions = Permissions::from_str("pmc");
162        let expected = Permissions::PRINTABLE | Permissions::MODIFIABLE | Permissions::COPYABLE;
163        assert_eq!(permissions, expected);
164    }
165
166    #[test]
167    fn test_from_str_2() {
168        let permissions = Permissions::from_str("*");
169        let expected = Permissions::all();
170        assert_eq!(permissions, expected);
171    }
172
173    #[test]
174    fn test_apply_modification() {
175        let mut permissions = Permissions::empty();
176        permissions.apply_modification("+p");
177        assert_eq!(permissions, Permissions::PRINTABLE);
178        permissions.apply_modification("-p");
179        assert_eq!(permissions, Permissions::empty());
180        permissions.apply_modification("=m");
181        assert_eq!(permissions, Permissions::MODIFIABLE);
182    }
183}