solid_pod_rs/wac/
resolver.rs1use async_trait::async_trait;
8
9use crate::error::PodError;
10#[cfg(feature = "tokio-runtime")]
15use crate::storage::Storage;
16use crate::wac::document::AclDocument;
17#[cfg(feature = "tokio-runtime")]
18use crate::wac::parse_jsonld_acl;
19#[cfg(feature = "tokio-runtime")]
20use crate::wac::parser::parse_turtle_acl;
21
22#[async_trait]
29pub trait AclResolver: Send + Sync {
30 async fn find_effective_acl(
32 &self,
33 resource_path: &str,
34 ) -> Result<Option<AclDocument>, PodError>;
35}
36
37#[cfg(feature = "tokio-runtime")]
39pub struct StorageAclResolver<S: Storage> {
40 storage: std::sync::Arc<S>,
41}
42
43#[cfg(feature = "tokio-runtime")]
44impl<S: Storage> StorageAclResolver<S> {
45 pub fn new(storage: std::sync::Arc<S>) -> Self {
47 Self { storage }
48 }
49}
50
51#[cfg(feature = "tokio-runtime")]
52#[async_trait]
53impl<S: Storage> AclResolver for StorageAclResolver<S> {
54 async fn find_effective_acl(
56 &self,
57 resource_path: &str,
58 ) -> Result<Option<AclDocument>, PodError> {
59 let mut path = resource_path.to_string();
60 loop {
61 let acl_key = if path == "/" {
62 "/.acl".to_string()
63 } else {
64 format!("{}.acl", path.trim_end_matches('/'))
65 };
66 if let Ok((body, meta)) = self.storage.get(&acl_key).await {
67 match parse_jsonld_acl(&body) {
72 Ok(doc) => return Ok(Some(doc)),
73 Err(PodError::BadRequest(_)) => {
74 return Err(PodError::BadRequest(
75 "ACL document exceeds bounds".into(),
76 ));
77 }
78 Err(PodError::PayloadTooLarge(msg)) => {
79 return Err(PodError::PayloadTooLarge(msg));
80 }
81 Err(_) => {}
82 }
83 let ct = meta.content_type.to_ascii_lowercase();
84 let looks_turtle = ct.starts_with("text/turtle")
85 || ct.starts_with("application/turtle")
86 || ct.starts_with("application/x-turtle");
87 let text = std::str::from_utf8(&body).unwrap_or("");
88 if looks_turtle || text.contains("@prefix") || text.contains("acl:Authorization") {
89 if let Ok(doc) = parse_turtle_acl(text) {
90 return Ok(Some(doc));
91 }
92 }
93 }
94 if path == "/" || path.is_empty() {
95 break;
96 }
97 let trimmed = path.trim_end_matches('/');
98 path = match trimmed.rfind('/') {
99 Some(0) => "/".to_string(),
100 Some(pos) => trimmed[..pos].to_string(),
101 None => "/".to_string(),
102 };
103 }
104 Ok(None)
105 }
106}