]>
Commit | Line | Data |
---|---|---|
597641fd DM |
1 | use failure::*; |
2 | ||
3 | use http::Uri; | |
4 | use hyper::Body; | |
5 | use hyper::client::Client; | |
6 | use hyper::rt::{self, Future}; | |
7 | ||
1fdb4c6f DM |
8 | use http::Request; |
9 | use futures::stream::Stream; | |
10 | ||
11 | use serde_json::{Value}; | |
0dffe3f9 | 12 | use url::percent_encoding::{percent_encode, DEFAULT_ENCODE_SET}; |
1fdb4c6f | 13 | |
597641fd | 14 | pub struct HttpClient { |
0dffe3f9 | 15 | username: String, |
597641fd DM |
16 | server: String, |
17 | } | |
18 | ||
19 | impl HttpClient { | |
20 | ||
0dffe3f9 | 21 | pub fn new(server: &str, username: &str) -> Self { |
597641fd DM |
22 | Self { |
23 | server: String::from(server), | |
0dffe3f9 | 24 | username: String::from(username), |
597641fd DM |
25 | } |
26 | } | |
27 | ||
4a3f6517 WB |
28 | fn run_request( |
29 | request: Request<Body>, | |
30 | ) -> Result<Value, Error> { | |
31 | let mut builder = native_tls::TlsConnector::builder(); | |
32 | // FIXME: We need a CLI option for this! | |
33 | builder.danger_accept_invalid_certs(true); | |
34 | let tlsconnector = builder.build()?; | |
35 | let mut httpc = hyper::client::HttpConnector::new(1); | |
36 | httpc.enforce_http(false); // we want https... | |
37 | let mut https = hyper_tls::HttpsConnector::from((httpc, tlsconnector)); | |
38 | https.https_only(true); // force it! | |
39 | let client = Client::builder().build::<_, Body>(https); | |
597641fd | 40 | |
1adb353d | 41 | let (tx, rx) = std::sync::mpsc::channel(); |
597641fd DM |
42 | |
43 | let future = client | |
44 | .request(request) | |
45 | .map_err(|e| Error::from(e)) | |
46 | .and_then(|resp| { | |
47 | ||
48 | let status = resp.status(); | |
49 | ||
50 | resp.into_body().concat2().map_err(|e| Error::from(e)) | |
51 | .and_then(move |data| { | |
52 | ||
53 | let text = String::from_utf8(data.to_vec()).unwrap(); | |
54 | if status.is_success() { | |
1fdb4c6f DM |
55 | if text.len() > 0 { |
56 | let value: Value = serde_json::from_str(&text)?; | |
57 | Ok(value) | |
58 | } else { | |
59 | Ok(Value::Null) | |
60 | } | |
597641fd | 61 | } else { |
1fdb4c6f | 62 | bail!("HTTP Error {}: {}", status, text); |
597641fd | 63 | } |
597641fd DM |
64 | }) |
65 | }) | |
1adb353d DM |
66 | .then(move |res| { |
67 | tx.send(res).unwrap(); | |
68 | Ok(()) | |
597641fd DM |
69 | }); |
70 | ||
71 | // drop client, else client keeps connectioon open (keep-alive feature) | |
72 | drop(client); | |
73 | ||
74 | rt::run(future); | |
75 | ||
1adb353d | 76 | rx.recv().unwrap() |
1fdb4c6f DM |
77 | } |
78 | ||
79 | pub fn get(&self, path: &str) -> Result<Value, Error> { | |
80 | ||
4a3f6517 | 81 | let url: Uri = format!("https://{}:8007/{}", self.server, path).parse()?; |
1fdb4c6f | 82 | |
0dffe3f9 DM |
83 | let ticket = self.login()?; |
84 | ||
85 | let enc_ticket = percent_encode(ticket.as_bytes(), DEFAULT_ENCODE_SET).to_string(); | |
86 | ||
1fdb4c6f DM |
87 | let request = Request::builder() |
88 | .method("GET") | |
89 | .uri(url) | |
90 | .header("User-Agent", "proxmox-backup-client/1.0") | |
0dffe3f9 | 91 | .header("Cookie", format!("PBSAuthCookie={}", enc_ticket)) |
1fdb4c6f DM |
92 | .body(Body::empty())?; |
93 | ||
94 | Self::run_request(request) | |
95 | } | |
96 | ||
0dffe3f9 DM |
97 | fn login(&self) -> Result<String, Error> { |
98 | ||
99 | let url: Uri = format!("https://{}:8007/{}", self.server, "/api2/json/access/ticket").parse()?; | |
100 | ||
101 | let password = match std::env::var("PBS_PASSWORD") { | |
102 | Ok(p) => p, | |
103 | Err(err) => bail!("missing passphrase - {}", err), | |
104 | }; | |
105 | ||
106 | let query = url::form_urlencoded::Serializer::new(String::new()) | |
107 | .append_pair("username", &self.username) | |
108 | .append_pair("password", &password) | |
109 | .finish(); | |
110 | ||
111 | let request = Request::builder() | |
112 | .method("POST") | |
113 | .uri(url) | |
114 | .header("User-Agent", "proxmox-backup-client/1.0") | |
115 | .header("Content-Type", "application/x-www-form-urlencoded") | |
116 | .body(Body::from(query))?; | |
117 | ||
118 | let auth_res = Self::run_request(request)?; | |
119 | ||
120 | let ticket = match auth_res["data"]["ticket"].as_str() { | |
121 | Some(t) => t, | |
122 | None => bail!("got unexpected respose for login request."), | |
123 | }; | |
124 | ||
125 | Ok(ticket.to_owned()) | |
126 | } | |
127 | ||
1fdb4c6f DM |
128 | pub fn upload(&self, content_type: &str, body: Body, path: &str) -> Result<Value, Error> { |
129 | ||
4a3f6517 | 130 | let url: Uri = format!("https://{}:8007/{}", self.server, path).parse()?; |
1fdb4c6f | 131 | |
0dffe3f9 DM |
132 | let ticket = self.login()?; |
133 | ||
134 | let enc_ticket = percent_encode(ticket.as_bytes(), DEFAULT_ENCODE_SET).to_string(); | |
135 | ||
1fdb4c6f DM |
136 | let request = Request::builder() |
137 | .method("POST") | |
138 | .uri(url) | |
139 | .header("User-Agent", "proxmox-backup-client/1.0") | |
0dffe3f9 | 140 | .header("Cookie", format!("PBSAuthCookie={}", enc_ticket)) |
1fdb4c6f DM |
141 | .header("Content-Type", content_type) |
142 | .body(body)?; | |
143 | ||
144 | Self::run_request(request) | |
597641fd DM |
145 | } |
146 | } |