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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
use crate::cfg::config::Config;
use tokio;
/*
use emissary_core::runtime::{
AsyncRead,
AsyncWrite,
};
*/
use std::{fs::File,
path::Path,
io::Write};
// use emissary_core::Profile;
// use emissary_core::i2np::Message;
use tokio::io::{AsyncReadExt,
AsyncWriteExt,
BufReader};
use yosemite::SessionOptions;
use yosemite::{Session, style::Stream};
use url;
pub struct I2PPackage {
config: Config,
}
impl I2PPackage {
/// Creates a new I2P object with the given configuration.
///
/// # Returns
///
/// A new I2P object with the given configuration. The session is initially set to None and the connected status is set to false.
pub fn new(config: Config) -> Self {
I2PPackage{
config: config,
}
}
/// Downloads the INDEX.tar.gz file from the configured repository
/// and stores it in the configured cache directory.
///
/// # Errors
///
/// Returns an error if the request fails, if the response status is not successful, or if there's an issue while reading or writing the file.
pub async fn fetch_index(&mut self) -> Result<bool, std::io::Error> {
let repo_url_str = &self.config.repo.repo_url;
let cache_dir = &self.config.paths.cache_dir;
let url = url::Url::parse(repo_url_str)
.map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid repo URL"))?;
let host = url.host_str()
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidInput, "No host in URL"))?;
let request_path = url.path();
let request_path = if request_path.ends_with(".tar.gz") {
request_path.to_string()
} else {
format!("{}/INDEX.tar.gz", request_path.trim_end_matches('/'))
};
let sam_host = "127.0.0.1";
let sam_port = 7656;
let session_options = SessionOptions::default();
let mut session = Session::new(session_options).await
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to create SAM session: {}", e)))?;
let mut stream = session.connect(host).await
.map_err(|e| std::io::Error::new(std::io::ErrorKind::ConnectionAborted, format!("Failed to connect: {}", e)))?;
let request = format!(
"GET {} HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n",
request_path, host
);
stream.write_all(request.as_bytes()).await
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to write request: {}", e)))?;
let mut reader = BufReader::new(stream);
let mut response_buffer = Vec::new();
reader.read_to_end(&mut response_buffer).await
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to read response: {}", e)))?;
let headers_end = response_buffer
.windows(4)
.position(|window| window == b"\r\n\r\n")
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid response: no headers end"))?;
let headers_str = std::str::from_utf8(&response_buffer[..headers_end])
.map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid header encoding"))?;
if !headers_str.starts_with("HTTP/1.1 200") && !headers_str.starts_with("HTTP/1.0 200") {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("HTTP Error: {}", headers_str.lines().next().unwrap_or("Unknown")),
));
}
let body_start = headers_end + 4;
let file_path = Path::new(cache_dir).join("INDEX.tar.gz");
let mut file = File::create(&file_path)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to create file: {}", e)))?;
file.write_all(&response_buffer[body_start..])
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to write file: {}", e)))?;
file.flush()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to flush file: {}", e)))?;
Ok(true)
}
}
|