1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/// Universal blob key validation.
///
/// Rejects keys that would be unsafe or **ambiguous** across any backend
/// (filesystem, S3, …):
///
/// | Rejected pattern | Why |
/// |------------------|-----|
/// | Empty string `""` | No backend (not even S3) can store an empty key |
/// | Starts with `"/"` | Resolves to an absolute path on FS; S3 allows it but the mapping is inconsistent |
/// | Contains `".."` as a path component | **Traversal** — `a/../../x` would escape the root directory |
/// | Contains `"."` as a path component | The current directory — `./foo` is equivalent to `foo` but ambiguous |
/// | Contains `"\"` (backslash) | On Windows this is a path separator; on Linux it's a legal filename — **ambiguous** across platforms |
/// | Contains empty component `"//"` | Ambiguous — maps to nested empty directory on FS, but is a flat key on S3 |
/// | Ends with `"/"` (trailing slash) | Ambiguous — FS creates an empty directory entry; S3 treats it as a flat key |
///
/// ## What S3 allows
///
/// S3 object keys *may* legally contain `..`, start with `/`, include
/// backslash, contain empty components, or end with `/`, but **this
/// library rejects them** to preserve a consistent, portable contract
/// across all backends:
///
/// > "Keys containing `/` are mapped to nested directories — identical
/// > behaviour to S3."
///
/// If a key passes `../etc/passwd` to the FS backend, it would escape the
/// root; if it passes the same key to S3, S3 would store it as a literal
/// string `../etc/passwd` — two completely different behaviours for the
/// same input. **This library refuses the ambiguity.**
///
/// ## Usage
///
/// ```rust
/// use xtax_blob_storage::validate_blob_key;
///
/// assert!(validate_blob_key("hello.txt").is_ok());
/// assert!(validate_blob_key("").is_err());
/// assert!(validate_blob_key("../outside.txt").is_err());
/// assert!(validate_blob_key("/absolute").is_err());
/// assert!(validate_blob_key("a/../../x").is_err());
/// assert!(validate_blob_key("./foo").is_err());
/// assert!(validate_blob_key("a\\b").is_err());
/// assert!(validate_blob_key("a//b").is_err());
/// assert!(validate_blob_key("foo/").is_err());
/// ```
///
/// All built-in [`BlobStore`](crate::BlobStore) implementations call
/// `validate_blob_key` on every `put`, `get`, `delete`, `exists`, and
/// `get_with_metadata` operation **before** touching any storage.
use crate;
use crateKEY_SEPARATOR;
/// Validate a blob key against the subset that all backends agree on.
///
/// Returns `Ok(())` if the key is valid, `Err(BlobStorageError(InvalidInput))`
/// if it would be rejected by any backend.
///
/// See the [module-level documentation](self) for details.