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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
use crate::cfg::config::Config;
use tokio;
use emissary_core::runtime::{
AsyncRead,
AsyncWrite,
};
use std::{io,
fs::File,
path::Path,
io::Write};
// use emissary_core::Profile;
// use emissary_core::i2np::Message;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use yosemite::SessionOptions;
use yosemite::{Session, style::Stream};
/*
use i2p_client::ClientType;
use i2p_client::I2PClient;
use i2p_client::SessionStyle::Stream;
use i2p_client::Session;
struct I2PStatus {
Connected: bool,
}
impl I2PStatus {
pub fn connect(&self) -> Result<bool, std::io::Error> {
let config: Config = Config::parse().unwrap();
let client= I2PClient::new(true, "MeskPKG-manager".to_string(), "2.0", "2.58.0", 10);
// let destination = Session::r#gen(&mut self, SigType::EdDsaSha512Ed25519)
let session = Session::create(config.repo.repo_url,
&config.repo.destination.0,
"MeskPKG-manager",
Stream,
"2.0",
"2.58");
Ok(true)
}
}
*/
struct I2P<S> {
session: Option<Session<S>>,
connected: bool,
config: Config,
}
impl<S> I2P<S> {
/// 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 {
I2P {
session: None,
connected: false,
config: config,
}
}
/// Fetches the list of packages from the repository specified in the configuration.
///
/// This function connects to the repository specified in the configuration, sends a GET request for the repository list and returns true if the request is successful, false otherwise.
///
/// # Errors
///
/// Returns an error if the repository URL is invalid or if the request fails.
///
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 repo_pos = repo_url_str.find("/repo").ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidData, "URL does not contain '/repo'")
})?;
// BaseURL
let base_url = &repo_url_str[..repo_pos];
// Url after /repo
let path_suffix = &repo_url_str[repo_pos + "/repo".len()..];
// HTTP path
let request_path = format!("/repo{}{}", path_suffix, if path_suffix.ends_with(".tar.gz") { "" } else { "/INDEX.tar.gz" });
let mut opts = SessionOptions::default();
// FIXME: Make sure, opts setted to I2P
// opts.set_host("127.0.0.1");
// opts.set_port(7656);
let mut session = Session::<Stream>::new(opts).await.map_err(|e| {
io::Error::new(io::ErrorKind::Other, format!("Failed to create session: {}", e))
})?;
let mut stream = session.connect(base_url).await.map_err(|e| {
io::Error::new(io::ErrorKind::ConnectionAborted, format!("Failed to connect: {}", e))
})?;
let request = format!("GET {} HTTP/1.1\r\nHost: {}\r\n\r\n", request_path, base_url);
stream.write_all(request.as_bytes()).await.map_err(|e| {
io::Error::new(io::ErrorKind::Other, format!("Failed to write request: {}", e))
})?;
let mut response_buffer = Vec::new();
let mut chunk = [0u8; 1024];
loop {
// FIXME: Check docs and make suro stream allows to AsyncReadExt
let bytes_read = stream.read(&mut chunk).await.map_err(|e| {
io::Error::new(io::ErrorKind::Other, format!("Failed to read response: {}", e))
})?;
if bytes_read == 0 {
break;
}
response_buffer.extend_from_slice(&chunk[..bytes_read]);
if let Some(headers_end) = Self::find_double_crlf(&response_buffer) {
let body_start = headers_end + 4;
let headers_str = std::str::from_utf8(&response_buffer[..headers_end])
.map_err(|_| io::Error::new(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(io::Error::new(io::ErrorKind::Other, format!("HTTP Error: {}", headers_str.lines().next().unwrap_or("Unknown"))));
}
let file_path = Path::new(cache_dir).join("INDEX.tar.gz");
let mut file = File::create(&file_path)?;
file.write_all(&response_buffer[body_start..])?;
while let Ok(bytes_read) = stream.read(&mut chunk).await {
if bytes_read == 0 {
break;
}
file.write_all(&chunk[..bytes_read])?;
}
file.flush()?;
break;
}
}
Ok(true)
}
fn find_double_crlf(buf: &[u8]) -> Option<usize> {
for i in 0..buf.len().saturating_sub(3) {
if buf[i] == b'\r' && buf[i+1] == b'\n' && buf[i+2] == b'\r' && buf[i+3] == b'\n' {
return Some(i);
}
}
None
}
}
|