Require FIPS for peer playback and stream owned media with bounded reads

This commit is contained in:
archipelago
2026-10-05 23:52:44 -04:00
parent 9af49291e9
commit bf7fb425eb
5 changed files with 291 additions and 55 deletions
+81
View File
@@ -184,6 +184,45 @@ pub async fn is_owned(data_dir: &Path, onion: &str, content_id: &str) -> bool {
}
/// Read a purchased item's bytes + mime type from the local cache, if present.
pub async fn open_owned(
data_dir: &Path,
onion: &str,
content_id: &str,
) -> Result<Option<(String, fs::File)>> {
let index = load_index_checked(data_dir).await?;
let Some(item) = index
.items
.iter()
.find(|item| item.onion == onion && item.content_id == content_id)
else {
return Ok(None);
};
// Reject path components even when called outside the HTTP route.
anyhow::ensure!(
!onion.is_empty()
&& !content_id.is_empty()
&& onion != "."
&& content_id != "."
&& !onion.contains("..")
&& !content_id.contains("..")
&& sanitize(onion) == onion
&& sanitize(content_id) == content_id,
"Invalid purchase path"
);
let file = fs::OpenOptions::new()
.read(true)
.custom_flags(libc::O_NOFOLLOW)
.open(bytes_path(data_dir, onion, content_id))
.await
.context("Purchased bytes unavailable")?;
let metadata = file.metadata().await?;
anyhow::ensure!(
metadata.is_file() && metadata.len() == item.size_bytes,
"Purchased bytes incomplete"
);
Ok(Some((item.mime_type.clone(), file)))
}
pub async fn read_owned(
data_dir: &Path,
onion: &str,
@@ -205,6 +244,48 @@ pub async fn read_owned(
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn owned_stream_preserves_ownership_on_missing_corrupt_or_symlinked_bytes() {
let dir = tempfile::tempdir().unwrap();
record_purchase(
dir.path(),
"seller.onion",
"video",
"video",
"video/mp4",
b"video",
1,
"cashu",
"now",
)
.await
.unwrap();
assert!(open_owned(dir.path(), "seller.onion", "video")
.await
.unwrap()
.is_some());
let path = bytes_path(dir.path(), "seller.onion", "video");
fs::write(&path, b"bad").await.unwrap();
assert!(open_owned(dir.path(), "seller.onion", "video")
.await
.is_err());
fs::remove_file(&path).await.unwrap();
assert!(open_owned(dir.path(), "seller.onion", "video")
.await
.is_err());
let private = dir.path().join("private");
fs::write(&private, b"other").await.unwrap();
std::os::unix::fs::symlink(&private, &path).unwrap();
assert!(open_owned(dir.path(), "seller.onion", "video")
.await
.is_err());
assert_eq!(list_owned_checked(dir.path()).await.unwrap().len(), 1);
assert!(open_owned(dir.path(), "seller.onion", "not-bought")
.await
.unwrap()
.is_none());
}
#[tokio::test]
async fn concurrent_purchases_preserve_every_item_and_exact_bytes() {
let dir = tempfile::tempdir().unwrap();