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
//! HTTP client wrapper for MEGA API requests.
use crate::error::{MegaError, Result};
use reqwest::Client;
use std::time::Duration;
/// HTTP client for making requests to MEGA servers.
#[derive(Debug)]
pub struct HttpClient {
client: Client,
}
impl HttpClient {
/// Create a new HTTP client.
pub fn new() -> Self {
Self {
client: Client::builder()
.build()
.expect("Failed to build reqwest client"),
}
}
/// Create a new HTTP client with a proxy.
///
pub fn with_proxy(proxy: &str) -> Result<Self> {
let proxy = reqwest::Proxy::all(proxy)
.map_err(|e| MegaError::CryptoError(format!("Invalid proxy: {}", e)))?;
let client = Client::builder()
.danger_accept_invalid_certs(true)
.danger_accept_invalid_hostnames(true)
.proxy(proxy)
.timeout(Duration::from_secs(60))
.redirect(reqwest::redirect::Policy::none())
.build()
.map_err(|e| MegaError::CryptoError(format!("Failed to build client: {}", e)))?;
Ok(Self { client })
}
/// Make a POST request with JSON body.
///
/// # Arguments
/// * `url` - URL to post to
/// * `body` - JSON body as string
///
/// # Returns
/// Response body as string
pub async fn post(&self, url: &str, body: &str) -> Result<String> {
let mut current = url.to_string();
for _ in 0..10 {
let response = self
.client
.post(¤t)
.header("Content-Type", "application/json")
.body(body.to_string())
.send()
.await?;
let status = response.status();
if status.is_redirection() {
if let Some(loc) = response.headers().get(reqwest::header::LOCATION) {
if let Ok(loc_str) = loc.to_str() {
let next =
if loc_str.starts_with("http://") || loc_str.starts_with("https://") {
loc_str.to_string()
} else {
let base = reqwest::Url::parse(¤t)
.map_err(|_| MegaError::HttpError(status.as_u16()))?;
base.join(loc_str)
.map_err(|_| MegaError::HttpError(status.as_u16()))?
.to_string()
};
current = next;
continue;
}
}
return Err(MegaError::HttpError(status.as_u16()));
}
if !status.is_success() {
return Err(MegaError::HttpError(status.as_u16()));
}
return Ok(response.text().await?);
}
Err(MegaError::Custom("Too many redirects".to_string()))
}
}
impl Default for HttpClient {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_client_creation() {
let _client = HttpClient::new();
let _default = HttpClient::default();
}
#[test]
fn test_proxy_creation() {
let client = HttpClient::with_proxy("http://127.0.0.1:8080");
assert!(client.is_ok());
}
#[test]
fn test_proxy_invalid() {
// reqwest::Proxy::all fails on empty or really bad URLs?
// Actually reqwest might be lenient on "all". Let's try something clearly invalid or empty.
// If "invalid-proxy" is accepted by reqwest but fails at connection time, this test might need adjustment.
// However, `reqwest::Proxy::all` usually parses the URI.
// "http" is not a valid proxy URL by itself (needs host)
let res = HttpClient::with_proxy(":::::::");
assert!(res.is_err());
}
}