1mod reader;
28mod storage;
29mod writer;
30
31pub use reader::CdxReader;
32pub use storage::{ArchiveStorage, MemoryStorage};
33pub use writer::{CdxWriter, CompressionMethod};
34
35pub const MANIFEST_PATH: &str = "manifest.json";
37
38pub const CONTENT_PATH: &str = "content/document.json";
40
41pub const DUBLIN_CORE_PATH: &str = "metadata/dublin-core.json";
43
44pub const SIGNATURES_PATH: &str = "security/signatures.json";
46
47pub const ENCRYPTION_PATH: &str = "security/encryption.json";
49
50pub const PHANTOMS_PATH: &str = "phantoms/clusters.json";
52
53pub const ACADEMIC_NUMBERING_PATH: &str = "academic/numbering.json";
55
56pub const COMMENTS_PATH: &str = "collaboration/comments.json";
58
59pub const FORMS_DATA_PATH: &str = "forms/data.json";
61
62pub const BIBLIOGRAPHY_PATH: &str = "semantic/bibliography.json";
64
65pub const JSONLD_PATH: &str = "metadata/jsonld.json";
67
68pub const ZIP_COMMENT: &str = "Codex Document Format v0.1";
70
71#[must_use]
80pub fn is_url_safe_path(path: &str) -> bool {
81 path.bytes()
82 .all(|b| b.is_ascii_alphanumeric() || matches!(b, b'.' | b'-' | b'_' | b'/'))
83}
84
85pub(crate) fn validate_path(path: &str) -> crate::Result<()> {
91 if path.contains("..") {
93 return Err(crate::Error::PathTraversal {
94 path: path.to_string(),
95 });
96 }
97
98 if path.starts_with('/') {
100 return Err(crate::Error::PathTraversal {
101 path: path.to_string(),
102 });
103 }
104
105 if path.contains('\\') {
107 return Err(crate::Error::PathTraversal {
108 path: path.to_string(),
109 });
110 }
111
112 Ok(())
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn test_validate_path_safe() {
121 assert!(validate_path("manifest.json").is_ok());
122 assert!(validate_path("content/document.json").is_ok());
123 assert!(validate_path("assets/images/photo.png").is_ok());
124 }
125
126 #[test]
127 fn test_validate_path_traversal() {
128 assert!(validate_path("../secret").is_err());
129 assert!(validate_path("foo/../bar").is_err());
130 assert!(validate_path("foo/..").is_err());
131 }
132
133 #[test]
134 fn test_validate_path_absolute() {
135 assert!(validate_path("/etc/passwd").is_err());
136 assert!(validate_path("/manifest.json").is_err());
137 }
138
139 #[test]
140 fn test_validate_path_backslash() {
141 assert!(validate_path("foo\\bar").is_err());
142 assert!(validate_path("..\\secret").is_err());
143 }
144
145 #[test]
146 fn test_url_safe_path_valid() {
147 assert!(is_url_safe_path("assets/image-01.png"));
148 assert!(is_url_safe_path("assets/photo_2024.jpg"));
149 assert!(is_url_safe_path("content/document.json"));
150 assert!(is_url_safe_path("a-z_0-9/file.ext"));
151 }
152
153 #[test]
154 fn test_url_safe_path_invalid() {
155 assert!(!is_url_safe_path("assets/file name.png"));
157 assert!(!is_url_safe_path("assets/file%20name.png"));
159 assert!(!is_url_safe_path("assets/文档.txt"));
161 assert!(!is_url_safe_path("assets/file@2x.png"));
163 assert!(!is_url_safe_path("assets/file#1.png"));
164 assert!(!is_url_safe_path("assets/file(1).png"));
165 }
166}