1#![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
16pub trait PdfPerm {
18 fn permissions(&self) -> Permissions;
20 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 let version = EncryptionVersion::V1 {
45 document: self,
46 owner_password: "",
47 user_password: "",
48 permissions,
49 };
50 let state: EncryptionState = version.try_into()?;
51 debug!("Encryption state: {state:?}");
52 self.encrypt(&state)?;
53
54 Ok(())
55 }
56}
57
58pub trait ShortFlags: Flags + Copy {
61 const SHORT_FLAGS: &'static [char];
64
65 fn from_char(c: char) -> Option<Self> {
68 if c == '*' {
69 return Some(Self::all());
70 }
71 let index = Self::SHORT_FLAGS.iter().position(|&flag| flag == c)?;
72 Some(*Self::FLAGS.get(index)?.value())
73 }
74 #[must_use]
76 fn from_str(s: &str) -> Self {
77 let mut flags = Self::empty();
78 for c in s.chars() {
79 if let Some(flag) = Self::from_char(c) {
80 flags.insert(flag);
81 } else {
82 warn!("Invalid permission character: {c}");
83 }
84 }
85 flags
86 }
87 fn apply_modification(&mut self, modification: &str) {
89 let (first, rest) = modification.split_at(1);
90 let flags_mod = Self::from_str(rest);
91 match first {
92 "+" => self.insert(flags_mod), "-" => self.remove(flags_mod), "=" => *self = flags_mod, _ => warn!("Invalid modification: {modification}"),
96 }
97 }
98 fn summary(&self) -> String {
100 let mut summary = String::with_capacity(Self::SHORT_FLAGS.len());
101 for (short, flag) in Self::SHORT_FLAGS.iter().zip(Self::FLAGS) {
102 if self.contains(*flag.value()) {
103 summary.push(*short);
104 } else {
105 summary.push('-');
106 }
107 }
108 summary
109 }
110}
111
112impl ShortFlags for Permissions {
113 const SHORT_FLAGS: &'static [char] = &['p', 'm', 'c', 'a', 'f', 'x', 's', 'q'];
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use lopdf::Object;
120
121 fn create_test_document() -> Document {
123 let mut doc = Document::new();
125
126 doc.trailer.set(
127 "ID",
128 Object::Array(vec![
129 Object::string_literal(b"ABC"),
130 Object::string_literal(b"DEF"),
131 ]),
132 );
133
134 doc
135 }
136
137 #[test]
138 fn test_permissions() {
139 let mut doc = create_test_document();
140 assert_eq!(doc.permissions(), Permissions::default());
141
142 let pma_permissions = Permissions::from_str("pma");
143 doc.set_permissions(pma_permissions).unwrap();
144
145 let mut buffer = Vec::new();
146 doc.save_to(&mut buffer).unwrap();
147
148 let doc = Document::load_mem(&buffer).unwrap();
149 assert_eq!(doc.permissions(), pma_permissions);
150 }
151
152 #[test]
154 fn test_from_str_1() {
155 let permissions = Permissions::from_str("pmc");
156 let expected = Permissions::PRINTABLE | Permissions::MODIFIABLE | Permissions::COPYABLE;
157 assert_eq!(permissions, expected);
158 }
159
160 #[test]
161 fn test_from_str_2() {
162 let permissions = Permissions::from_str("*");
163 let expected = Permissions::all();
164 assert_eq!(permissions, expected);
165 }
166
167 #[test]
168 fn test_apply_modification() {
169 let mut permissions = Permissions::empty();
170 permissions.apply_modification("+p");
171 assert_eq!(permissions, Permissions::PRINTABLE);
172 permissions.apply_modification("-p");
173 assert_eq!(permissions, Permissions::empty());
174 permissions.apply_modification("=m");
175 assert_eq!(permissions, Permissions::MODIFIABLE);
176 }
177}