lockbook_shared/
validate.rs1use crate::access_info::UserAccessMode;
2use crate::file_like::FileLike;
3use crate::file_metadata::{Diff, FileDiff, FileType, Owner};
4use crate::filename::MAX_ENCRYPTED_FILENAME_LENGTH;
5use crate::lazy::LazyTree;
6use crate::staged::StagedTreeLike;
7use crate::tree_like::TreeLike;
8use crate::{SharedErrorKind, SharedResult, ValidationFailure};
9use std::collections::{HashMap, HashSet};
10
11pub fn file_name(name: &str) -> SharedResult<()> {
12 if name.is_empty() {
13 Err(SharedErrorKind::FileNameEmpty)?;
14 }
15 if name.contains('/') {
16 Err(SharedErrorKind::FileNameContainsSlash)?;
17 }
18 Ok(())
19}
20
21pub fn not_root<F: FileLike>(file: &F) -> SharedResult<()> {
22 if file.is_root() {
23 Err(SharedErrorKind::RootModificationInvalid.into())
24 } else {
25 Ok(())
26 }
27}
28
29pub fn is_folder<F: FileLike>(file: &F) -> SharedResult<()> {
30 if file.is_folder() {
31 Ok(())
32 } else {
33 Err(SharedErrorKind::FileNotFolder.into())
34 }
35}
36
37pub fn is_document<F: FileLike>(file: &F) -> SharedResult<()> {
38 if file.is_document() {
39 Ok(())
40 } else {
41 Err(SharedErrorKind::FileNotDocument.into())
42 }
43}
44
45pub fn path(path: &str) -> SharedResult<()> {
46 if path.contains("//") || path.is_empty() {
47 Err(SharedErrorKind::PathContainsEmptyFileName)?;
48 }
49
50 Ok(())
51}
52
53impl<T, Base, Local> LazyTree<T>
54where
55 T: StagedTreeLike<Base = Base, Staged = Local>,
56 Base: TreeLike<F = T::F>,
57 Local: TreeLike<F = T::F>,
58{
59 pub fn validate(&mut self, owner: Owner) -> SharedResult<()> {
60 self.assert_no_root_changes()?;
62 self.assert_no_changes_to_deleted_files()?;
63 self.assert_all_filenames_size_limit()?;
64 self.assert_all_files_decryptable(owner)?;
65 self.assert_only_folders_have_children()?;
66 self.assert_all_files_same_owner_as_parent()?;
67
68 self.assert_no_cycles()?;
70 self.assert_no_path_conflicts()?;
71 self.assert_no_shared_links()?;
72 self.assert_no_duplicate_links()?;
73 self.assert_no_broken_links()?;
74 self.assert_no_owned_links()?;
75
76 self.assert_changes_authorized(owner)?;
78
79 Ok(())
80 }
81
82 pub fn assert_all_files_decryptable(&mut self, owner: Owner) -> SharedResult<()> {
84 for file in self.ids().into_iter().filter_map(|id| self.maybe_find(id)) {
85 if self.maybe_find_parent(file).is_none()
86 && !file
87 .user_access_keys()
88 .iter()
89 .any(|k| k.encrypted_for == owner.0)
90 {
91 Err(SharedErrorKind::ValidationFailure(ValidationFailure::Orphan(*file.id())))?;
92 }
93 }
94 Ok(())
95 }
96
97 pub fn assert_all_filenames_size_limit(&self) -> SharedResult<()> {
98 for file in self.all_files()? {
99 if file.secret_name().encrypted_value.value.len() > MAX_ENCRYPTED_FILENAME_LENGTH {
100 return Err(SharedErrorKind::ValidationFailure(
101 ValidationFailure::FileNameTooLong(*file.id()),
102 ))?;
103 }
104 }
105 Ok(())
106 }
107
108 pub fn assert_only_folders_have_children(&self) -> SharedResult<()> {
109 for file in self.all_files()? {
110 if let Some(parent) = self.maybe_find(file.parent()) {
111 if !parent.is_folder() {
112 Err(SharedErrorKind::ValidationFailure(
113 ValidationFailure::NonFolderWithChildren(*parent.id()),
114 ))?;
115 }
116 }
117 }
118 Ok(())
119 }
120
121 pub fn assert_all_files_same_owner_as_parent(&mut self) -> SharedResult<()> {
124 for id in self.owned_ids() {
125 if self.calculate_deleted(&id)? {
126 continue;
127 }
128 let file = self.find(&id)?;
129 if let Some(parent) = self.maybe_find(file.parent()) {
130 if parent.owner() != file.owner() {
131 Err(SharedErrorKind::ValidationFailure(
132 ValidationFailure::FileWithDifferentOwnerParent(*file.id()),
133 ))?;
134 }
135 }
136 }
137 Ok(())
138 }
139
140 pub fn assert_no_cycles(&mut self) -> SharedResult<()> {
142 let mut owners_with_found_roots = HashSet::new();
143 let mut no_cycles_in_ancestors = HashSet::new();
144 for id in self.owned_ids() {
145 let mut ancestors = HashSet::new();
146 let mut current_file = self.find(&id)?;
147 loop {
148 if no_cycles_in_ancestors.contains(current_file.id()) {
149 break;
150 } else if current_file.is_root() {
151 if owners_with_found_roots.insert(current_file.owner()) {
152 ancestors.insert(*current_file.id());
153 break;
154 } else {
155 Err(SharedErrorKind::ValidationFailure(ValidationFailure::Cycle(
156 HashSet::from([id]),
157 )))?;
158 }
159 } else if ancestors.contains(current_file.parent()) {
160 Err(SharedErrorKind::ValidationFailure(ValidationFailure::Cycle(
161 self.ancestors(current_file.id())?,
162 )))?;
163 }
164 ancestors.insert(*current_file.id());
165 current_file = match self.maybe_find_parent(current_file) {
166 Some(file) => file,
167 None => {
168 if !current_file.user_access_keys().is_empty() {
169 break;
170 } else {
171 return Err(SharedErrorKind::FileParentNonexistent.into());
172 }
173 }
174 }
175 }
176 no_cycles_in_ancestors.extend(ancestors);
177 }
178 Ok(())
179 }
180
181 pub fn assert_no_path_conflicts(&mut self) -> SharedResult<()> {
182 let mut id_by_name = HashMap::new();
183 for id in self.owned_ids() {
184 if !self.calculate_deleted(&id)? {
185 let file = self.find(&id)?;
186 if file.is_root() || self.maybe_find(file.parent()).is_none() {
187 continue;
188 }
189 if let Some(conflicting) = id_by_name.remove(file.secret_name()) {
190 Err(SharedErrorKind::ValidationFailure(ValidationFailure::PathConflict(
191 HashSet::from([conflicting, *file.id()]),
192 )))?;
193 }
194 id_by_name.insert(file.secret_name().clone(), *file.id());
195 }
196 }
197 Ok(())
198 }
199
200 pub fn assert_no_shared_links(&self) -> SharedResult<()> {
201 for link in self.owned_ids() {
202 let meta = self.find(&link)?;
203 if let FileType::Link { target: _ } = meta.file_type() {
204 if meta.is_shared() {
205 Err(SharedErrorKind::ValidationFailure(ValidationFailure::SharedLink {
206 link,
207 shared_ancestor: link,
208 }))?;
209 }
210 for ancestor in self.ancestors(&link)? {
211 if self.find(&ancestor)?.is_shared() {
212 Err(SharedErrorKind::ValidationFailure(ValidationFailure::SharedLink {
213 link,
214 shared_ancestor: ancestor,
215 }))?;
216 }
217 }
218 }
219 }
220 Ok(())
221 }
222
223 pub fn assert_no_duplicate_links(&mut self) -> SharedResult<()> {
224 let mut linked_targets = HashSet::new();
225 for link in self.owned_ids() {
226 if self.calculate_deleted(&link)? {
227 continue;
228 }
229 if let FileType::Link { target } = self.find(&link)?.file_type() {
230 if !linked_targets.insert(target) {
231 Err(SharedErrorKind::ValidationFailure(ValidationFailure::DuplicateLink {
232 target,
233 }))?;
234 }
235 }
236 }
237 Ok(())
238 }
239
240 pub fn assert_no_broken_links(&mut self) -> SharedResult<()> {
246 for link in self.owned_ids() {
247 if let FileType::Link { target } = self.find(&link)?.file_type() {
248 if !self.calculate_deleted(&link)? && self.maybe_find(&target).is_none() {
249 Err(SharedErrorKind::ValidationFailure(ValidationFailure::BrokenLink(link)))?;
250 }
251 }
252 }
253 Ok(())
254 }
255
256 pub fn assert_no_owned_links(&self) -> SharedResult<()> {
257 for link in self.owned_ids() {
258 if let FileType::Link { target } = self.find(&link)?.file_type() {
259 if let Some(target_owner) = self.maybe_find(&target).map(|f| f.owner()) {
260 if self.find(&link)?.owner() == target_owner {
261 Err(SharedErrorKind::ValidationFailure(ValidationFailure::OwnedLink(
262 link,
263 )))?;
264 }
265 }
266 }
267 }
268 Ok(())
269 }
270
271 pub fn assert_no_root_changes(&mut self) -> SharedResult<()> {
272 for id in self.tree.staged().owned_ids() {
273 if let Some(base) = self.tree.base().maybe_find(&id) {
275 if base.is_root() {
276 Err(SharedErrorKind::RootModificationInvalid)?;
277 }
278 }
279 if self.find(&id)?.is_root() {
281 Err(SharedErrorKind::ValidationFailure(ValidationFailure::Cycle(
282 vec![id].into_iter().collect(),
283 )))?;
284 }
285 }
286 Ok(())
287 }
288
289 pub fn assert_no_changes_to_deleted_files(&mut self) -> SharedResult<()> {
290 for id in self.tree.staged().owned_ids() {
291 let mut base = self.tree.base().to_lazy();
293 if base.maybe_find(&id).is_some() && base.calculate_deleted(&id)? {
294 Err(SharedErrorKind::DeletedFileUpdated(id))?;
295 }
296 if self.calculate_deleted(&id)? {
298 if let Some(base) = self.tree.base().maybe_find(&id) {
299 if FileDiff::edit(&base, &self.find(&id)?)
300 .diff()
301 .iter()
302 .any(|d| d != &Diff::Deleted)
303 {
304 Err(SharedErrorKind::DeletedFileUpdated(id))?;
305 }
306 }
307 }
308 }
309 Ok(())
310 }
311
312 pub fn assert_changes_authorized(&mut self, owner: Owner) -> SharedResult<()> {
313 let new_files = {
332 let mut new_files = HashSet::new();
333 for id in self.tree.staged().owned_ids() {
334 if self.tree.base().maybe_find(&id).is_none() {
335 new_files.insert(id);
336 }
337 }
338 new_files
339 };
340
341 for file_diff in self.diffs()? {
342 for field_diff in file_diff.diff() {
343 match field_diff {
344 Diff::New | Diff::Name | Diff::Deleted => {
345 let file =
347 if let Some(ref old) = file_diff.old { old } else { &file_diff.new };
348 if !new_files.contains(file.parent()) {
350 if let Some(parent) = self.maybe_find(file.parent()) {
352 if self.access_mode(owner, parent.id())?
353 < Some(UserAccessMode::Write)
354 {
355 Err(SharedErrorKind::InsufficientPermission)?;
357 }
358 } else {
359 Err(SharedErrorKind::InsufficientPermission)?;
361 }
362 }
363 }
364 Diff::Parent | Diff::Owner => {
365 {
367 let parent = if let Some(ref old) = file_diff.old {
368 old.parent()
369 } else {
370 return Err(SharedErrorKind::Unexpected(
371 "Non-New FileDiff with no old",
372 )
373 .into());
374 };
375
376 if let Some(parent) = self.maybe_find(parent) {
378 if self.access_mode(owner, parent.id())?
379 < Some(UserAccessMode::Write)
380 {
381 Err(SharedErrorKind::InsufficientPermission)?;
383 }
384 } else {
385 Err(SharedErrorKind::InsufficientPermission)?;
387 }
388 }
389 {
391 let parent = file_diff.new.parent();
392
393 if !new_files.contains(parent) {
395 if let Some(parent) = self.maybe_find(parent) {
397 if self.access_mode(owner, parent.id())?
398 < Some(UserAccessMode::Write)
399 {
400 Err(SharedErrorKind::InsufficientPermission)?;
402 }
403 } else {
404 Err(SharedErrorKind::InsufficientPermission)?;
406 }
407 }
408 }
409 }
410 Diff::Hmac => {
411 if self.access_mode(owner, file_diff.id())? < Some(UserAccessMode::Write) {
413 Err(SharedErrorKind::InsufficientPermission)?;
414 }
415 }
416 Diff::UserKeys => {
417 let base_keys = {
419 if let Some(ref old) = file_diff.old {
420 let mut base_keys = HashMap::new();
421 for key in old.user_access_keys() {
422 base_keys.insert(
423 (Owner(key.encrypted_by), Owner(key.encrypted_for)),
424 (key.mode, key.deleted),
425 );
426 }
427 base_keys
428 } else {
429 return Err(SharedErrorKind::Unexpected(
430 "Non-New FileDiff with no old",
431 )
432 .into());
433 }
434 };
435 for key in file_diff.new.user_access_keys() {
436 if let Some((base_mode, base_deleted)) =
437 base_keys.get(&(Owner(key.encrypted_by), Owner(key.encrypted_for)))
438 {
439 let (staged_mode, staged_deleted) = (&key.mode, &key.deleted);
442 if *staged_deleted
444 && !*base_deleted
445 && self.access_mode(owner, file_diff.id())?
446 < Some(UserAccessMode::Write)
447 && owner.0 != key.encrypted_for
448 {
449 Err(SharedErrorKind::InsufficientPermission)?;
450 }
451 if staged_mode != base_mode
453 && self.access_mode(owner, file_diff.id())?
454 < Some(UserAccessMode::Write)
455 {
456 Err(SharedErrorKind::InsufficientPermission)?;
457 }
458 } else {
459 if self.access_mode(owner, file_diff.id())? < Some(key.mode) {
463 Err(SharedErrorKind::InsufficientPermission)?;
464 }
465 }
466 }
467 }
468 }
469 }
470 }
471
472 Ok(())
473 }
474
475 fn diffs(&self) -> SharedResult<Vec<FileDiff<Base::F>>> {
476 let mut result = Vec::new();
477 for id in self.tree.staged().owned_ids() {
478 let staged = self.tree.staged().find(&id)?;
479 if let Some(base) = self.tree.base().maybe_find(&id) {
480 result.push(FileDiff::edit(base, staged));
481 } else {
482 result.push(FileDiff::new(staged));
483 }
484 }
485 Ok(result)
486 }
487}