summaryrefslogtreecommitdiff
path: root/src/net/i2p_package.rs
blob: d149454831adb395b675bf1f0bbe81a44ca4be7e (plain)
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
use crate::cfg::config::Config;

use tokio;
/*
use emissary_core::runtime::{
    AsyncRead,
    AsyncWrite,
};
*/

use std::{fs::File, io::Write, path::Path};
// use emissary_core::Profile;
// use emissary_core::i2np::Message;
use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader};

use url;
use yosemite::Session;
use yosemite::SessionOptions;

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 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)
    }
}