]> git.proxmox.com Git - proxmox-backup.git/blame - src/client/http_client.rs
Cargo.toml: set proxmox version 0.1.0 (latest packaged version)
[proxmox-backup.git] / src / client / http_client.rs
CommitLineData
c2b94534 1use std::io::Write;
db0cb9ce 2use std::task::{Context, Poll};
597641fd 3
cf9271e2 4use chrono::Utc;
7a57cb77 5use failure::*;
82ab7230 6use futures::*;
7a57cb77
WB
7use http::Uri;
8use http::header::HeaderValue;
9use http::{Request, Response};
10use hyper::Body;
1434f4f8 11use hyper::client::{Client, HttpConnector};
6d1f61b2 12use openssl::ssl::{SslConnector, SslMethod};
ba3a60b2 13use serde_json::{json, Value};
8a1028e0 14use percent_encoding::percent_encode;
7a57cb77 15use xdg::BaseDirectories;
1fdb4c6f 16
e18a6c9e 17use proxmox::tools::{
feaa1ad3 18 fs::{file_get_json, replace_file, CreateOptions},
e18a6c9e
DM
19};
20
7a57cb77 21use super::pipe_to_stream::PipeToSendStream;
1434f4f8 22use crate::tools::async_io::EitherStream;
8a1028e0 23use crate::tools::{self, tty, BroadcastFuture, DEFAULT_ENCODE_SET};
986bef16 24
5a2df000 25#[derive(Clone)]
e240d8be 26pub struct AuthInfo {
5a2df000
DM
27 username: String,
28 ticket: String,
29 token: String,
30}
56458d97 31
151c6ce2 32/// HTTP(S) API client
597641fd 33pub struct HttpClient {
1434f4f8 34 client: Client<HttpsConnector>,
597641fd 35 server: String,
5a2df000 36 auth: BroadcastFuture<AuthInfo>,
597641fd
DM
37}
38
e240d8be
DM
39/// Delete stored ticket data (logout)
40pub fn delete_ticket_info(server: &str, username: &str) -> Result<(), Error> {
41
42 let base = BaseDirectories::with_prefix("proxmox-backup")?;
43
44 // usually /run/user/<uid>/...
45 let path = base.place_runtime_file("tickets")?;
46
47 let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600);
48
49 let mut data = file_get_json(&path, Some(json!({})))?;
50
51 if let Some(map) = data[server].as_object_mut() {
52 map.remove(username);
53 }
54
feaa1ad3 55 replace_file(path, data.to_string().as_bytes(), CreateOptions::new().perm(mode))?;
e240d8be
DM
56
57 Ok(())
58}
59
ba3a60b2
DM
60fn store_ticket_info(server: &str, username: &str, ticket: &str, token: &str) -> Result<(), Error> {
61
62 let base = BaseDirectories::with_prefix("proxmox-backup")?;
63
64 // usually /run/user/<uid>/...
65 let path = base.place_runtime_file("tickets")?;
66
67 let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600);
68
e18a6c9e 69 let mut data = file_get_json(&path, Some(json!({})))?;
ba3a60b2
DM
70
71 let now = Utc::now().timestamp();
72
73 data[server][username] = json!({ "timestamp": now, "ticket": ticket, "token": token});
74
75 let mut new_data = json!({});
76
77 let ticket_lifetime = tools::ticket::TICKET_LIFETIME - 60;
78
79 let empty = serde_json::map::Map::new();
80 for (server, info) in data.as_object().unwrap_or(&empty) {
81 for (_user, uinfo) in info.as_object().unwrap_or(&empty) {
82 if let Some(timestamp) = uinfo["timestamp"].as_i64() {
83 let age = now - timestamp;
84 if age < ticket_lifetime {
85 new_data[server][username] = uinfo.clone();
86 }
87 }
88 }
89 }
90
feaa1ad3 91 replace_file(path, new_data.to_string().as_bytes(), CreateOptions::new().perm(mode))?;
ba3a60b2
DM
92
93 Ok(())
94}
95
96fn load_ticket_info(server: &str, username: &str) -> Option<(String, String)> {
66c8eb93 97 let base = BaseDirectories::with_prefix("proxmox-backup").ok()?;
ba3a60b2
DM
98
99 // usually /run/user/<uid>/...
66c8eb93
CE
100 let path = base.place_runtime_file("tickets").ok()?;
101 let data = file_get_json(&path, None).ok()?;
ba3a60b2 102 let now = Utc::now().timestamp();
ba3a60b2 103 let ticket_lifetime = tools::ticket::TICKET_LIFETIME - 60;
66c8eb93
CE
104 let uinfo = data[server][username].as_object()?;
105 let timestamp = uinfo["timestamp"].as_i64()?;
106 let age = now - timestamp;
107
108 if age < ticket_lifetime {
109 let ticket = uinfo["ticket"].as_str()?;
110 let token = uinfo["token"].as_str()?;
111 Some((ticket.to_owned(), token.to_owned()))
112 } else {
113 None
ba3a60b2 114 }
ba3a60b2
DM
115}
116
597641fd
DM
117impl HttpClient {
118
cc2ce4a9 119 pub fn new(server: &str, username: &str, password: Option<String>) -> Result<Self, Error> {
5a2df000 120 let client = Self::build_client();
5a2df000 121
cc2ce4a9
DM
122 let password = if let Some(password) = password {
123 password
124 } else if let Some((ticket, _token)) = load_ticket_info(server, username) {
45cdce06
DM
125 ticket
126 } else {
127 Self::get_password(&username)?
128 };
129
9d35dbbb 130 let login_future = Self::credentials(client.clone(), server.to_owned(), username.to_owned(), password);
45cdce06
DM
131
132 Ok(Self {
5a2df000 133 client,
597641fd 134 server: String::from(server),
96f5e80a 135 auth: BroadcastFuture::new(Box::new(login_future)),
45cdce06 136 })
597641fd
DM
137 }
138
1a7a0e74 139 /// Login
e240d8be
DM
140 ///
141 /// Login is done on demand, so this is onyl required if you need
142 /// access to authentication data in 'AuthInfo'.
96f5e80a
DM
143 pub async fn login(&self) -> Result<AuthInfo, Error> {
144 self.auth.listen().await
e240d8be
DM
145 }
146
5a2df000 147 fn get_password(_username: &str) -> Result<String, Error> {
56458d97
WB
148 use std::env::VarError::*;
149 match std::env::var("PBS_PASSWORD") {
150 Ok(p) => return Ok(p),
151 Err(NotUnicode(_)) => bail!("PBS_PASSWORD contains bad characters"),
152 Err(NotPresent) => {
153 // Try another method
154 }
155 }
156
157 // If we're on a TTY, query the user for a password
158 if tty::stdin_isatty() {
159 return Ok(String::from_utf8(tty::read_password("Password: ")?)?);
160 }
161
162 bail!("no password input mechanism available");
163 }
164
1434f4f8 165 fn build_client() -> Client<HttpsConnector> {
6d1f61b2
DM
166
167 let mut ssl_connector_builder = SslConnector::builder(SslMethod::tls()).unwrap();
168
169 ssl_connector_builder.set_verify(openssl::ssl::SslVerifyMode::NONE); // fixme!
170
1434f4f8 171 let mut httpc = hyper::client::HttpConnector::new();
fcf5dea5 172 httpc.set_nodelay(true); // important for h2 download performance!
99168f43 173 httpc.set_recv_buffer_size(Some(1024*1024)); //important for h2 download performance!
4a3f6517 174 httpc.enforce_http(false); // we want https...
6d1f61b2 175
1434f4f8 176 let https = HttpsConnector::with_connector(httpc, ssl_connector_builder.build());
6d1f61b2 177
adec8ea2
DM
178 Client::builder()
179 //.http2_initial_stream_window_size( (1 << 31) - 2)
180 //.http2_initial_connection_window_size( (1 << 31) - 2)
181 .build::<_, Body>(https)
a6b75513
DM
182 }
183
1a7a0e74 184 pub async fn request(&self, mut req: Request<Body>) -> Result<Value, Error> {
597641fd 185
5a2df000 186 let client = self.client.clone();
597641fd 187
1a7a0e74 188 let auth = self.login().await?;
597641fd 189
1a7a0e74
DM
190 let enc_ticket = format!("PBSAuthCookie={}", percent_encode(auth.ticket.as_bytes(), DEFAULT_ENCODE_SET));
191 req.headers_mut().insert("Cookie", HeaderValue::from_str(&enc_ticket).unwrap());
192 req.headers_mut().insert("CSRFPreventionToken", HeaderValue::from_str(&auth.token).unwrap());
597641fd 193
1a7a0e74 194 Self::api_request(client, req).await
1fdb4c6f
DM
195 }
196
1a7a0e74 197 pub async fn get(
a6782ca1
WB
198 &self,
199 path: &str,
200 data: Option<Value>,
1a7a0e74 201 ) -> Result<Value, Error> {
9e391bb7 202 let req = Self::request_builder(&self.server, "GET", path, data).unwrap();
1a7a0e74 203 self.request(req).await
a6b75513
DM
204 }
205
1a7a0e74 206 pub async fn delete(
a6782ca1
WB
207 &mut self,
208 path: &str,
209 data: Option<Value>,
1a7a0e74 210 ) -> Result<Value, Error> {
9e391bb7 211 let req = Self::request_builder(&self.server, "DELETE", path, data).unwrap();
1a7a0e74 212 self.request(req).await
a6b75513
DM
213 }
214
1a7a0e74 215 pub async fn post(
a6782ca1
WB
216 &mut self,
217 path: &str,
218 data: Option<Value>,
1a7a0e74 219 ) -> Result<Value, Error> {
5a2df000 220 let req = Self::request_builder(&self.server, "POST", path, data).unwrap();
1a7a0e74 221 self.request(req).await
024f11bb
DM
222 }
223
1a7a0e74 224 pub async fn download(
a6782ca1
WB
225 &mut self,
226 path: &str,
1a7a0e74
DM
227 output: &mut (dyn Write + Send),
228 ) -> Result<(), Error> {
5a2df000 229 let mut req = Self::request_builder(&self.server, "GET", path, None).unwrap();
024f11bb 230
5a2df000 231 let client = self.client.clone();
1fdb4c6f 232
1a7a0e74 233 let auth = self.login().await?;
81da38c1 234
1a7a0e74
DM
235 let enc_ticket = format!("PBSAuthCookie={}", percent_encode(auth.ticket.as_bytes(), DEFAULT_ENCODE_SET));
236 req.headers_mut().insert("Cookie", HeaderValue::from_str(&enc_ticket).unwrap());
6f62c924 237
1a7a0e74
DM
238 let resp = client.request(req).await?;
239 let status = resp.status();
240 if !status.is_success() {
241 HttpClient::api_response(resp)
242 .map(|_| Err(format_err!("unknown error")))
243 .await?
244 } else {
245 resp.into_body()
5a2df000 246 .map_err(Error::from)
1a7a0e74
DM
247 .try_fold(output, move |acc, chunk| async move {
248 acc.write_all(&chunk)?;
249 Ok::<_, Error>(acc)
5a2df000 250 })
1a7a0e74
DM
251 .await?;
252 }
253 Ok(())
6f62c924
DM
254 }
255
1a7a0e74 256 pub async fn upload(
04512d30
DM
257 &mut self,
258 content_type: &str,
259 body: Body,
260 path: &str,
261 data: Option<Value>,
1a7a0e74 262 ) -> Result<Value, Error> {
81da38c1
DM
263
264 let path = path.trim_matches('/');
04512d30
DM
265 let mut url = format!("https://{}:8007/{}", &self.server, path);
266
267 if let Some(data) = data {
268 let query = tools::json_object_to_query(data).unwrap();
269 url.push('?');
270 url.push_str(&query);
271 }
272
273 let url: Uri = url.parse().unwrap();
81da38c1 274
5a2df000 275 let req = Request::builder()
81da38c1
DM
276 .method("POST")
277 .uri(url)
278 .header("User-Agent", "proxmox-backup-client/1.0")
5a2df000
DM
279 .header("Content-Type", content_type)
280 .body(body).unwrap();
81da38c1 281
1a7a0e74 282 self.request(req).await
1fdb4c6f
DM
283 }
284
1a7a0e74 285 pub async fn start_h2_connection(
fb047083
DM
286 &self,
287 mut req: Request<Body>,
288 protocol_name: String,
dc089345 289 ) -> Result<(H2Client, futures::future::AbortHandle), Error> {
cf639a47 290
1a7a0e74 291 let auth = self.login().await?;
cf639a47
DM
292 let client = self.client.clone();
293
1a7a0e74
DM
294 let enc_ticket = format!("PBSAuthCookie={}", percent_encode(auth.ticket.as_bytes(), DEFAULT_ENCODE_SET));
295 req.headers_mut().insert("Cookie", HeaderValue::from_str(&enc_ticket).unwrap());
296 req.headers_mut().insert("UPGRADE", HeaderValue::from_str(&protocol_name).unwrap());
cf639a47 297
1a7a0e74
DM
298 let resp = client.request(req).await?;
299 let status = resp.status();
cf639a47 300
1a7a0e74 301 if status != http::StatusCode::SWITCHING_PROTOCOLS {
ca611955
DM
302 Self::api_response(resp).await?;
303 bail!("unknown error");
1a7a0e74
DM
304 }
305
306 let upgraded = resp
307 .into_body()
308 .on_upgrade()
309 .await?;
310
311 let max_window_size = (1 << 31) - 2;
312
313 let (h2, connection) = h2::client::Builder::new()
314 .initial_connection_window_size(max_window_size)
315 .initial_window_size(max_window_size)
316 .max_frame_size(4*1024*1024)
317 .handshake(upgraded)
318 .await?;
319
320 let connection = connection
321 .map_err(|_| panic!("HTTP/2.0 connection failed"));
322
dc089345 323 let (connection, abort) = futures::future::abortable(connection);
1a7a0e74
DM
324 // A cancellable future returns an Option which is None when cancelled and
325 // Some when it finished instead, since we don't care about the return type we
326 // need to map it away:
327 let connection = connection.map(|_| ());
328
329 // Spawn a new task to drive the connection state
db0cb9ce 330 tokio::spawn(connection);
1a7a0e74
DM
331
332 // Wait until the `SendRequest` handle has available capacity.
333 let c = h2.ready().await?;
dc089345 334 Ok((H2Client::new(c), abort))
cf639a47
DM
335 }
336
9d35dbbb 337 async fn credentials(
1434f4f8 338 client: Client<HttpsConnector>,
45cdce06
DM
339 server: String,
340 username: String,
341 password: String,
9d35dbbb
DM
342 ) -> Result<AuthInfo, Error> {
343 let data = json!({ "username": username, "password": password });
344 let req = Self::request_builder(&server, "POST", "/api2/json/access/ticket", Some(data)).unwrap();
345 let cred = Self::api_request(client, req).await?;
346 let auth = AuthInfo {
347 username: cred["data"]["username"].as_str().unwrap().to_owned(),
348 ticket: cred["data"]["ticket"].as_str().unwrap().to_owned(),
349 token: cred["data"]["CSRFPreventionToken"].as_str().unwrap().to_owned(),
350 };
351
352 let _ = store_ticket_info(&server, &auth.username, &auth.ticket, &auth.token);
353
354 Ok(auth)
ba3a60b2
DM
355 }
356
a6782ca1 357 async fn api_response(response: Response<Body>) -> Result<Value, Error> {
d2c48afc 358 let status = response.status();
db0cb9ce 359 let data = hyper::body::to_bytes(response.into_body()).await?;
a6782ca1
WB
360
361 let text = String::from_utf8(data.to_vec()).unwrap();
362 if status.is_success() {
11377a47
DM
363 if text.is_empty() {
364 Ok(Value::Null)
365 } else {
a6782ca1
WB
366 let value: Value = serde_json::from_str(&text)?;
367 Ok(value)
a6782ca1
WB
368 }
369 } else {
370 bail!("HTTP Error {}: {}", status, text);
371 }
d2c48afc
DM
372 }
373
1a7a0e74 374 async fn api_request(
1434f4f8 375 client: Client<HttpsConnector>,
5a2df000 376 req: Request<Body>
1a7a0e74 377 ) -> Result<Value, Error> {
ba3a60b2 378
5a2df000
DM
379 client.request(req)
380 .map_err(Error::from)
d2c48afc 381 .and_then(Self::api_response)
1a7a0e74 382 .await
0dffe3f9
DM
383 }
384
9e490a74
DM
385 // Read-only access to server property
386 pub fn server(&self) -> &str {
387 &self.server
388 }
389
5a2df000 390 pub fn request_builder(server: &str, method: &str, path: &str, data: Option<Value>) -> Result<Request<Body>, Error> {
591f570b 391 let path = path.trim_matches('/');
5a2df000
DM
392 let url: Uri = format!("https://{}:8007/{}", server, path).parse()?;
393
394 if let Some(data) = data {
395 if method == "POST" {
396 let request = Request::builder()
397 .method(method)
398 .uri(url)
399 .header("User-Agent", "proxmox-backup-client/1.0")
400 .header(hyper::header::CONTENT_TYPE, "application/json")
401 .body(Body::from(data.to_string()))?;
402 return Ok(request);
403 } else {
9e391bb7
DM
404 let query = tools::json_object_to_query(data)?;
405 let url: Uri = format!("https://{}:8007/{}?{}", server, path, query).parse()?;
406 let request = Request::builder()
407 .method(method)
408 .uri(url)
409 .header("User-Agent", "proxmox-backup-client/1.0")
410 .header(hyper::header::CONTENT_TYPE, "application/x-www-form-urlencoded")
411 .body(Body::empty())?;
412 return Ok(request);
5a2df000 413 }
5a2df000 414 }
0dffe3f9 415
1fdb4c6f 416 let request = Request::builder()
5a2df000 417 .method(method)
1fdb4c6f
DM
418 .uri(url)
419 .header("User-Agent", "proxmox-backup-client/1.0")
5a2df000
DM
420 .header(hyper::header::CONTENT_TYPE, "application/x-www-form-urlencoded")
421 .body(Body::empty())?;
1fdb4c6f 422
5a2df000 423 Ok(request)
597641fd
DM
424 }
425}
b57cb264 426
9af37c8f
DM
427
428#[derive(Clone)]
429pub struct H2Client {
430 h2: h2::client::SendRequest<bytes::Bytes>,
431}
432
433impl H2Client {
434
435 pub fn new(h2: h2::client::SendRequest<bytes::Bytes>) -> Self {
436 Self { h2 }
437 }
438
2a1e6d7d
DM
439 pub async fn get(
440 &self,
441 path: &str,
442 param: Option<Value>
443 ) -> Result<Value, Error> {
792a70b9 444 let req = Self::request_builder("localhost", "GET", path, param, None).unwrap();
2a1e6d7d 445 self.request(req).await
9af37c8f
DM
446 }
447
2a1e6d7d
DM
448 pub async fn put(
449 &self,
450 path: &str,
451 param: Option<Value>
452 ) -> Result<Value, Error> {
792a70b9 453 let req = Self::request_builder("localhost", "PUT", path, param, None).unwrap();
2a1e6d7d 454 self.request(req).await
9af37c8f
DM
455 }
456
2a1e6d7d
DM
457 pub async fn post(
458 &self,
459 path: &str,
460 param: Option<Value>
461 ) -> Result<Value, Error> {
792a70b9 462 let req = Self::request_builder("localhost", "POST", path, param, None).unwrap();
2a1e6d7d 463 self.request(req).await
9af37c8f
DM
464 }
465
d4a085e5 466 pub async fn download<W: Write + Send>(
a6782ca1
WB
467 &self,
468 path: &str,
469 param: Option<Value>,
2a1e6d7d 470 mut output: W,
d4a085e5 471 ) -> Result<W, Error> {
792a70b9 472 let request = Self::request_builder("localhost", "GET", path, param, None).unwrap();
dd066d28 473
2a1e6d7d 474 let response_future = self.send_request(request, None).await?;
984a7c35 475
2a1e6d7d
DM
476 let resp = response_future.await?;
477
478 let status = resp.status();
479 if !status.is_success() {
44f59dc7
DM
480 H2Client::h2api_response(resp).await?; // raise error
481 unreachable!();
2a1e6d7d
DM
482 }
483
484 let mut body = resp.into_body();
db0cb9ce
WB
485 while let Some(chunk) = body.data().await {
486 let chunk = chunk?;
487 body.flow_control().release_capacity(chunk.len())?;
2a1e6d7d
DM
488 output.write_all(&chunk)?;
489 }
490
491 Ok(output)
dd066d28
DM
492 }
493
2a1e6d7d 494 pub async fn upload(
a6782ca1 495 &self,
f011dba0 496 method: &str, // POST or PUT
a6782ca1
WB
497 path: &str,
498 param: Option<Value>,
792a70b9 499 content_type: &str,
a6782ca1 500 data: Vec<u8>,
2a1e6d7d 501 ) -> Result<Value, Error> {
f011dba0 502 let request = Self::request_builder("localhost", method, path, param, Some(content_type)).unwrap();
9af37c8f 503
2a1e6d7d
DM
504 let mut send_request = self.h2.clone().ready().await?;
505
506 let (response, stream) = send_request.send_request(request, false).unwrap();
2a05048b
DM
507
508 PipeToSendStream::new(bytes::Bytes::from(data), stream).await?;
509
510 response
511 .map_err(Error::from)
512 .and_then(Self::h2api_response)
2a1e6d7d 513 .await
9af37c8f 514 }
adec8ea2 515
2a1e6d7d 516 async fn request(
9af37c8f 517 &self,
b57cb264 518 request: Request<()>,
2a1e6d7d 519 ) -> Result<Value, Error> {
b57cb264 520
9af37c8f 521 self.send_request(request, None)
82ab7230
DM
522 .and_then(move |response| {
523 response
524 .map_err(Error::from)
525 .and_then(Self::h2api_response)
526 })
2a1e6d7d 527 .await
82ab7230
DM
528 }
529
cf9271e2 530 pub fn send_request(
9af37c8f 531 &self,
82ab7230
DM
532 request: Request<()>,
533 data: Option<bytes::Bytes>,
a6782ca1 534 ) -> impl Future<Output = Result<h2::client::ResponseFuture, Error>> {
82ab7230 535
9af37c8f 536 self.h2.clone()
10130cf4
DM
537 .ready()
538 .map_err(Error::from)
2a05048b 539 .and_then(move |mut send_request| async move {
82ab7230
DM
540 if let Some(data) = data {
541 let (response, stream) = send_request.send_request(request, false).unwrap();
2a05048b
DM
542 PipeToSendStream::new(data, stream).await?;
543 Ok(response)
82ab7230
DM
544 } else {
545 let (response, _stream) = send_request.send_request(request, true).unwrap();
2a05048b 546 Ok(response)
82ab7230 547 }
b57cb264
DM
548 })
549 }
550
f16aea68 551 pub async fn h2api_response(
a6782ca1 552 response: Response<h2::RecvStream>,
9edd3bf1 553 ) -> Result<Value, Error> {
b57cb264
DM
554 let status = response.status();
555
556 let (_head, mut body) = response.into_parts();
557
9edd3bf1 558 let mut data = Vec::new();
db0cb9ce
WB
559 while let Some(chunk) = body.data().await {
560 let chunk = chunk?;
561 // Whenever data is received, the caller is responsible for
562 // releasing capacity back to the server once it has freed
563 // the data from memory.
9edd3bf1 564 // Let the server send more data.
db0cb9ce 565 body.flow_control().release_capacity(chunk.len())?;
9edd3bf1
DM
566 data.extend(chunk);
567 }
568
569 let text = String::from_utf8(data.to_vec()).unwrap();
570 if status.is_success() {
11377a47
DM
571 if text.is_empty() {
572 Ok(Value::Null)
573 } else {
9edd3bf1
DM
574 let mut value: Value = serde_json::from_str(&text)?;
575 if let Some(map) = value.as_object_mut() {
576 if let Some(data) = map.remove("data") {
577 return Ok(data);
b57cb264 578 }
b57cb264 579 }
9edd3bf1 580 bail!("got result without data property");
9edd3bf1
DM
581 }
582 } else {
583 bail!("HTTP Error {}: {}", status, text);
584 }
b57cb264
DM
585 }
586
eb2bdd1b 587 // Note: We always encode parameters with the url
792a70b9
DM
588 pub fn request_builder(
589 server: &str,
590 method: &str,
591 path: &str,
592 param: Option<Value>,
593 content_type: Option<&str>,
594 ) -> Result<Request<()>, Error> {
b57cb264 595 let path = path.trim_matches('/');
b57cb264 596
792a70b9
DM
597 let content_type = content_type.unwrap_or("application/x-www-form-urlencoded");
598
a55b2975
DM
599 if let Some(param) = param {
600 let query = tools::json_object_to_query(param)?;
eb2bdd1b
DM
601 // We detected problem with hyper around 6000 characters - seo we try to keep on the safe side
602 if query.len() > 4096 { bail!("h2 query data too large ({} bytes) - please encode data inside body", query.len()); }
b57cb264 603 let url: Uri = format!("https://{}:8007/{}?{}", server, path, query).parse()?;
eb2bdd1b 604 let request = Request::builder()
b57cb264
DM
605 .method(method)
606 .uri(url)
607 .header("User-Agent", "proxmox-backup-client/1.0")
792a70b9 608 .header(hyper::header::CONTENT_TYPE, content_type)
b57cb264 609 .body(())?;
62ee2eb4 610 Ok(request)
eb2bdd1b
DM
611 } else {
612 let url: Uri = format!("https://{}:8007/{}", server, path).parse()?;
613 let request = Request::builder()
614 .method(method)
615 .uri(url)
616 .header("User-Agent", "proxmox-backup-client/1.0")
792a70b9 617 .header(hyper::header::CONTENT_TYPE, content_type)
eb2bdd1b 618 .body(())?;
b57cb264 619
eb2bdd1b
DM
620 Ok(request)
621 }
b57cb264
DM
622 }
623}
1434f4f8 624
db0cb9ce 625#[derive(Clone)]
1434f4f8
WB
626pub struct HttpsConnector {
627 http: HttpConnector,
db0cb9ce 628 ssl_connector: std::sync::Arc<SslConnector>,
1434f4f8
WB
629}
630
631impl HttpsConnector {
632 pub fn with_connector(mut http: HttpConnector, ssl_connector: SslConnector) -> Self {
633 http.enforce_http(false);
634
635 Self {
636 http,
db0cb9ce 637 ssl_connector: std::sync::Arc::new(ssl_connector),
1434f4f8
WB
638 }
639 }
640}
641
642type MaybeTlsStream = EitherStream<
643 tokio::net::TcpStream,
644 tokio_openssl::SslStream<tokio::net::TcpStream>,
645>;
646
db0cb9ce
WB
647impl hyper::service::Service<Uri> for HttpsConnector {
648 type Response = MaybeTlsStream;
1434f4f8 649 type Error = Error;
db0cb9ce
WB
650 type Future = std::pin::Pin<Box<
651 dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static
652 >>;
1434f4f8 653
db0cb9ce
WB
654 fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
655 // This connector is always ready, but others might not be.
656 Poll::Ready(Ok(()))
657 }
1434f4f8 658
db0cb9ce
WB
659 fn call(&mut self, dst: Uri) -> Self::Future {
660 let mut this = self.clone();
661 async move {
662 let is_https = dst
663 .scheme()
664 .ok_or_else(|| format_err!("missing URL scheme"))?
665 == "https";
666 let host = dst
667 .host()
668 .ok_or_else(|| format_err!("missing hostname in destination url?"))?
669 .to_string();
670
671 let config = this.ssl_connector.configure();
672 let conn = this.http.call(dst).await?;
1434f4f8
WB
673 if is_https {
674 let conn = tokio_openssl::connect(config?, &host, conn).await?;
db0cb9ce 675 Ok(MaybeTlsStream::Right(conn))
1434f4f8 676 } else {
db0cb9ce 677 Ok(MaybeTlsStream::Left(conn))
1434f4f8 678 }
db0cb9ce 679 }.boxed()
1434f4f8
WB
680 }
681}