1use super::core::{self, FileInfo, Manifest, PullReport, SnapshotSource};
4use crate::config::Config;
5use anyhow::{Result, bail};
6use reqwest::header::AUTHORIZATION;
7
8struct HttpSource {
9 client: reqwest::Client,
10 base: String,
11 authorization: String,
12}
13
14impl HttpSource {
15 fn new(peer: &str, shared_key: &str) -> Self {
16 let base = if peer.starts_with("http") {
17 peer.trim_end_matches('/').to_owned()
18 } else {
19 format!("http://{}", peer.trim_end_matches('/'))
20 };
21 Self {
22 client: reqwest::Client::new(),
23 base,
24 authorization: format!("Bearer {shared_key}"),
25 }
26 }
27}
28
29impl SnapshotSource for HttpSource {
30 async fn manifest(&self) -> Result<Manifest> {
31 Ok(self
32 .client
33 .get(format!("{}/api/manifest", self.base))
34 .header(AUTHORIZATION, &self.authorization)
35 .send()
36 .await?
37 .error_for_status()?
38 .json()
39 .await?)
40 }
41
42 async fn read(&self, file: &FileInfo) -> Result<Vec<u8>> {
43 let url = format!(
44 "{}/api/file/{}",
45 self.base,
46 file.path
47 .split('/')
48 .map(url_encode)
49 .collect::<Vec<_>>()
50 .join("/")
51 );
52 let mut response = self
53 .client
54 .get(url)
55 .header(AUTHORIZATION, &self.authorization)
56 .send()
57 .await?
58 .error_for_status()?;
59 if response
60 .content_length()
61 .is_some_and(|size| size != file.size)
62 {
63 bail!("远端文件大小不符合清单:{}", file.path);
64 }
65 let mut bytes = Vec::with_capacity(file.size as usize);
66 while let Some(chunk) = response.chunk().await? {
67 if bytes.len().saturating_add(chunk.len()) > file.size as usize {
68 bail!("远端文件超过清单大小:{}", file.path);
69 }
70 bytes.extend_from_slice(&chunk);
71 }
72 Ok(bytes)
73 }
74}
75
76pub async fn pull(cfg: &Config, peer: &str, overwrite: bool) -> Result<PullReport> {
77 core::import_from(cfg, &HttpSource::new(peer, &cfg.shared_key), overwrite).await
78}
79
80fn url_encode(segment: &str) -> String {
81 segment
82 .bytes()
83 .map(|byte| {
84 if byte.is_ascii_alphanumeric() || b"-._~".contains(&byte) {
85 (byte as char).to_string()
86 } else {
87 format!("%{byte:02X}")
88 }
89 })
90 .collect()
91}