]>
Commit | Line | Data |
---|---|---|
d15009c0 | 1 | use crate::tools; |
ef2f2efb | 2 | use crate::api_schema::*; |
dc9a007b DM |
3 | use crate::api_schema::router::*; |
4 | use crate::api_schema::config::*; | |
b9903d63 | 5 | use crate::auth_helpers::*; |
0f253593 | 6 | use super::environment::RestEnvironment; |
1571873d | 7 | use super::formatter::*; |
16b48b81 | 8 | |
826bb982 | 9 | use std::path::{Path, PathBuf}; |
9bc17e8d | 10 | use std::sync::Arc; |
e7ea17de | 11 | use std::collections::HashMap; |
9bc17e8d DM |
12 | |
13 | use failure::*; | |
f4c514c1 | 14 | use serde_json::{json, Value}; |
e7ea17de | 15 | use url::form_urlencoded; |
9bc17e8d | 16 | |
9bc17e8d DM |
17 | use futures::future::{self, Either}; |
18 | //use tokio::prelude::*; | |
19 | //use tokio::timer::Delay; | |
20 | use tokio::fs::File; | |
9bc17e8d DM |
21 | //use bytes::{BytesMut, BufMut}; |
22 | ||
23 | //use hyper::body::Payload; | |
24 | use hyper::http::request::Parts; | |
10be1d29 | 25 | use hyper::{Body, Request, Response, StatusCode}; |
9bc17e8d DM |
26 | use hyper::service::{Service, NewService}; |
27 | use hyper::rt::{Future, Stream}; | |
28 | use hyper::header; | |
29 | ||
4b2cdeb9 DM |
30 | extern "C" { fn tzset(); } |
31 | ||
f0b10921 DM |
32 | pub struct RestServer { |
33 | pub api_config: Arc<ApiConfig>, | |
34 | } | |
35 | ||
36 | impl RestServer { | |
37 | ||
38 | pub fn new(api_config: ApiConfig) -> Self { | |
39 | Self { api_config: Arc::new(api_config) } | |
40 | } | |
41 | } | |
42 | ||
43 | impl NewService for RestServer | |
44 | { | |
45 | type ReqBody = Body; | |
46 | type ResBody = Body; | |
47 | type Error = hyper::Error; | |
48 | type InitError = hyper::Error; | |
49 | type Service = ApiService; | |
50 | type Future = Box<Future<Item = Self::Service, Error = Self::InitError> + Send>; | |
51 | fn new_service(&self) -> Self::Future { | |
52 | Box::new(future::ok(ApiService { api_config: self.api_config.clone() })) | |
53 | } | |
54 | } | |
55 | ||
56 | pub struct ApiService { | |
57 | pub api_config: Arc<ApiConfig>, | |
58 | } | |
59 | ||
d4736445 | 60 | fn log_response(method: hyper::Method, path: &str, resp: &Response<Body>) { |
7e03988c | 61 | |
d4736445 | 62 | if resp.extensions().get::<NoLogExtension>().is_some() { return; }; |
7e03988c | 63 | |
d4736445 | 64 | let status = resp.status(); |
7e03988c | 65 | |
d4736445 DM |
66 | if !status.is_success() { |
67 | let reason = status.canonical_reason().unwrap_or("unknown reason"); | |
68 | let client = "unknown"; // fixme: howto get peer_addr ? | |
44c00c0d | 69 | |
d4736445 DM |
70 | let mut message = "request failed"; |
71 | if let Some(data) = resp.extensions().get::<ErrorMessageExtension>() { | |
72 | message = &data.0; | |
78a1fa67 | 73 | } |
d4736445 DM |
74 | |
75 | log::error!("{} {}: {} {}: [client {}] {}", method.as_str(), path, status.as_str(), reason, client, message); | |
78a1fa67 DM |
76 | } |
77 | } | |
f0b10921 DM |
78 | |
79 | impl Service for ApiService { | |
80 | type ReqBody = Body; | |
81 | type ResBody = Body; | |
82 | type Error = hyper::Error; | |
83 | type Future = Box<Future<Item = Response<Body>, Error = Self::Error> + Send>; | |
84 | ||
85 | fn call(&mut self, req: Request<Self::ReqBody>) -> Self::Future { | |
78a1fa67 | 86 | let path = req.uri().path().to_owned(); |
d4736445 DM |
87 | let method = req.method().clone(); |
88 | ||
78a1fa67 | 89 | Box::new(handle_request(self.api_config.clone(), req).then(move |result| { |
f0b10921 | 90 | match result { |
78a1fa67 | 91 | Ok(res) => { |
d4736445 | 92 | log_response(method, &path, &res); |
78a1fa67 DM |
93 | Ok::<_, hyper::Error>(res) |
94 | } | |
f0b10921 | 95 | Err(err) => { |
0dffe3f9 | 96 | if let Some(apierr) = err.downcast_ref::<HttpError>() { |
f0b10921 DM |
97 | let mut resp = Response::new(Body::from(apierr.message.clone())); |
98 | *resp.status_mut() = apierr.code; | |
d4736445 | 99 | log_response(method, &path, &resp); |
f0b10921 DM |
100 | Ok(resp) |
101 | } else { | |
102 | let mut resp = Response::new(Body::from(err.to_string())); | |
103 | *resp.status_mut() = StatusCode::BAD_REQUEST; | |
d4736445 | 104 | log_response(method, &path, &resp); |
f0b10921 DM |
105 | Ok(resp) |
106 | } | |
107 | } | |
108 | } | |
109 | })) | |
110 | } | |
111 | } | |
112 | ||
279ecfdf DM |
113 | fn get_request_parameters_async( |
114 | info: &'static ApiMethod, | |
9bc17e8d DM |
115 | parts: Parts, |
116 | req_body: Body, | |
e7ea17de | 117 | uri_param: HashMap<String, String>, |
279ecfdf | 118 | ) -> Box<Future<Item = Value, Error = failure::Error> + Send> |
9bc17e8d DM |
119 | { |
120 | let resp = req_body | |
4dcf43d2 | 121 | .map_err(|err| http_err!(BAD_REQUEST, format!("Promlems reading request body: {}", err))) |
9bc17e8d DM |
122 | .fold(Vec::new(), |mut acc, chunk| { |
123 | if acc.len() + chunk.len() < 64*1024 { //fimxe: max request body size? | |
124 | acc.extend_from_slice(&*chunk); | |
125 | Ok(acc) | |
126 | } | |
4dcf43d2 | 127 | else { Err(http_err!(BAD_REQUEST, format!("Request body too large"))) } |
9bc17e8d DM |
128 | }) |
129 | .and_then(move |body| { | |
130 | ||
1ed86a0b | 131 | let utf8 = std::str::from_utf8(&body)?; |
9bc17e8d | 132 | |
e7ea17de | 133 | let mut param_list: Vec<(String, String)> = vec![]; |
9bc17e8d | 134 | |
1ed86a0b WB |
135 | if utf8.len() > 0 { |
136 | for (k, v) in form_urlencoded::parse(utf8.as_bytes()).into_owned() { | |
e7ea17de DM |
137 | param_list.push((k, v)); |
138 | } | |
139 | ||
9bc17e8d DM |
140 | } |
141 | ||
142 | if let Some(query_str) = parts.uri.query() { | |
e7ea17de | 143 | for (k, v) in form_urlencoded::parse(query_str.as_bytes()).into_owned() { |
1571873d | 144 | if k == "_dc" { continue; } // skip extjs "disable cache" parameter |
e7ea17de | 145 | param_list.push((k, v)); |
9bc17e8d DM |
146 | } |
147 | } | |
148 | ||
e7ea17de DM |
149 | for (k, v) in uri_param { |
150 | param_list.push((k.clone(), v.clone())); | |
151 | } | |
152 | ||
153 | let params = parse_parameter_strings(¶m_list, &info.parameters, true)?; | |
154 | ||
9bc17e8d DM |
155 | Ok(params) |
156 | }); | |
157 | ||
158 | Box::new(resp) | |
159 | } | |
160 | ||
7171b3e0 DM |
161 | struct NoLogExtension(); |
162 | ||
c8f3f9b1 | 163 | fn proxy_protected_request( |
4b2cdeb9 | 164 | info: &'static ApiMethod, |
a3da38dd | 165 | mut parts: Parts, |
f1204833 | 166 | req_body: Body, |
f1204833 DM |
167 | ) -> BoxFut |
168 | { | |
169 | ||
a3da38dd DM |
170 | let mut uri_parts = parts.uri.clone().into_parts(); |
171 | ||
172 | uri_parts.scheme = Some(http::uri::Scheme::HTTP); | |
173 | uri_parts.authority = Some(http::uri::Authority::from_static("127.0.0.1:82")); | |
174 | let new_uri = http::Uri::from_parts(uri_parts).unwrap(); | |
175 | ||
176 | parts.uri = new_uri; | |
177 | ||
178 | let request = Request::from_parts(parts, req_body); | |
179 | ||
180 | let resp = hyper::client::Client::new() | |
181 | .request(request) | |
7e03988c DM |
182 | .map_err(|e| Error::from(e)) |
183 | .map(|mut resp| { | |
7171b3e0 | 184 | resp.extensions_mut().insert(NoLogExtension); |
7e03988c DM |
185 | resp |
186 | }); | |
a3da38dd | 187 | |
4b2cdeb9 DM |
188 | let resp = if info.reload_timezone { |
189 | Either::A(resp.then(|resp| {unsafe { tzset() }; resp })) | |
190 | } else { | |
191 | Either::B(resp) | |
192 | }; | |
193 | ||
a3da38dd | 194 | return Box::new(resp); |
f1204833 DM |
195 | } |
196 | ||
279ecfdf | 197 | fn handle_sync_api_request( |
e82dad97 | 198 | mut rpcenv: RestEnvironment, |
279ecfdf | 199 | info: &'static ApiMethod, |
1571873d | 200 | formatter: &'static OutputFormatter, |
9bc17e8d DM |
201 | parts: Parts, |
202 | req_body: Body, | |
e7ea17de | 203 | uri_param: HashMap<String, String>, |
279ecfdf | 204 | ) -> BoxFut |
9bc17e8d | 205 | { |
e7ea17de | 206 | let params = get_request_parameters_async(info, parts, req_body, uri_param); |
9bc17e8d | 207 | |
a154a8e8 DM |
208 | let delay_unauth_time = std::time::Instant::now() + std::time::Duration::from_millis(3000); |
209 | ||
9bc17e8d DM |
210 | let resp = params |
211 | .and_then(move |params| { | |
a154a8e8 | 212 | let mut delay = false; |
6049b71f DM |
213 | let resp = match (info.handler)(params, info, &mut rpcenv) { |
214 | Ok(data) => (formatter.format_result)(data, &rpcenv), | |
a154a8e8 DM |
215 | Err(err) => { |
216 | if let Some(httperr) = err.downcast_ref::<HttpError>() { | |
217 | if httperr.code == StatusCode::UNAUTHORIZED { delay = true; } | |
218 | } | |
219 | (formatter.format_error)(err) | |
220 | } | |
6049b71f | 221 | }; |
a154a8e8 | 222 | |
4b2cdeb9 DM |
223 | if info.reload_timezone { |
224 | unsafe { tzset() }; | |
225 | } | |
226 | ||
a154a8e8 DM |
227 | if delay { |
228 | let delayed_response = tokio::timer::Delay::new(delay_unauth_time) | |
229 | .map_err(|err| http_err!(INTERNAL_SERVER_ERROR, format!("tokio timer delay error: {}", err))) | |
230 | .and_then(|_| Ok(resp)); | |
231 | ||
232 | Either::A(delayed_response) | |
233 | } else { | |
234 | Either::B(future::ok(resp)) | |
235 | } | |
9bc17e8d DM |
236 | }); |
237 | ||
238 | Box::new(resp) | |
239 | } | |
240 | ||
50cfb695 | 241 | fn handle_async_api_request( |
e82dad97 | 242 | mut rpcenv: RestEnvironment, |
50cfb695 | 243 | info: &'static ApiAsyncMethod, |
cf16af2a DM |
244 | formatter: &'static OutputFormatter, |
245 | parts: Parts, | |
7e21da6e DM |
246 | req_body: Body, |
247 | uri_param: HashMap<String, String>, | |
248 | ) -> BoxFut | |
249 | { | |
250 | // fixme: convert parameters to Json | |
251 | let mut param_list: Vec<(String, String)> = vec![]; | |
252 | ||
cf16af2a DM |
253 | if let Some(query_str) = parts.uri.query() { |
254 | for (k, v) in form_urlencoded::parse(query_str.as_bytes()).into_owned() { | |
255 | if k == "_dc" { continue; } // skip extjs "disable cache" parameter | |
256 | param_list.push((k, v)); | |
257 | } | |
258 | } | |
259 | ||
7e21da6e DM |
260 | for (k, v) in uri_param { |
261 | param_list.push((k.clone(), v.clone())); | |
262 | } | |
263 | ||
264 | let params = match parse_parameter_strings(¶m_list, &info.parameters, true) { | |
265 | Ok(v) => v, | |
266 | Err(err) => { | |
6049b71f | 267 | let resp = (formatter.format_error)(Error::from(err)); |
cf16af2a | 268 | return Box::new(future::ok(resp)); |
7e21da6e DM |
269 | } |
270 | }; | |
271 | ||
e82dad97 | 272 | match (info.handler)(parts, req_body, params, info, &mut rpcenv) { |
0ee0ad5b DM |
273 | Ok(future) => future, |
274 | Err(err) => { | |
6049b71f | 275 | let resp = (formatter.format_error)(Error::from(err)); |
0ee0ad5b DM |
276 | Box::new(future::ok(resp)) |
277 | } | |
278 | } | |
7e21da6e DM |
279 | } |
280 | ||
f4c514c1 DM |
281 | fn get_index() -> BoxFut { |
282 | ||
d15009c0 | 283 | let nodename = tools::nodename(); |
34f956bc DM |
284 | let username = ""; // fixme: implement real auth |
285 | let token = ""; | |
f4c514c1 DM |
286 | |
287 | let setup = json!({ | |
288 | "Setup": { "auth_cookie_name": "PBSAuthCookie" }, | |
289 | "NodeName": nodename, | |
290 | "UserName": username, | |
d15009c0 | 291 | "CSRFPreventionToken": token, |
f4c514c1 DM |
292 | }); |
293 | ||
294 | let index = format!(r###" | |
295 | <!DOCTYPE html> | |
296 | <html> | |
297 | <head> | |
298 | <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> | |
299 | <meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
300 | <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> | |
301 | <title>Proxmox Backup Server</title> | |
8adbdb0a | 302 | <link rel="icon" sizes="128x128" href="/images/logo-128.png" /> |
f4c514c1 | 303 | <link rel="apple-touch-icon" sizes="128x128" href="/pve2/images/logo-128.png" /> |
8adbdb0a DM |
304 | <link rel="stylesheet" type="text/css" href="/extjs/theme-crisp/resources/theme-crisp-all.css" /> |
305 | <link rel="stylesheet" type="text/css" href="/extjs/crisp/resources/charts-all.css" /> | |
f4c514c1 DM |
306 | <link rel="stylesheet" type="text/css" href="/fontawesome/css/font-awesome.css" /> |
307 | <script type='text/javascript'> function gettext(buf) {{ return buf; }} </script> | |
8adbdb0a DM |
308 | <script type="text/javascript" src="/extjs/ext-all-debug.js"></script> |
309 | <script type="text/javascript" src="/extjs/charts-debug.js"></script> | |
f4c514c1 DM |
310 | <script type="text/javascript"> |
311 | Proxmox = {}; | |
312 | </script> | |
8adbdb0a DM |
313 | <script type="text/javascript" src="/widgettoolkit/proxmoxlib.js"></script> |
314 | <script type="text/javascript" src="/extjs/locale/locale-en.js"></script> | |
f4c514c1 DM |
315 | <script type="text/javascript"> |
316 | Ext.History.fieldid = 'x-history-field'; | |
317 | </script> | |
5c7a1b15 | 318 | <script type="text/javascript" src="/js/proxmox-backup-gui.js"></script> |
f4c514c1 DM |
319 | </head> |
320 | <body> | |
321 | <!-- Fields required for history management --> | |
322 | <form id="history-form" class="x-hidden"> | |
323 | <input type="hidden" id="x-history-field"/> | |
324 | </form> | |
325 | </body> | |
326 | </html> | |
327 | "###, setup.to_string()); | |
328 | ||
d15009c0 DM |
329 | let resp = Response::builder() |
330 | .status(StatusCode::OK) | |
331 | .header(header::CONTENT_TYPE, "text/html") | |
d15009c0 DM |
332 | .body(index.into()) |
333 | .unwrap(); | |
334 | ||
335 | Box::new(future::ok(resp)) | |
f4c514c1 DM |
336 | } |
337 | ||
826bb982 DM |
338 | fn extension_to_content_type(filename: &Path) -> (&'static str, bool) { |
339 | ||
340 | if let Some(ext) = filename.extension().and_then(|osstr| osstr.to_str()) { | |
341 | return match ext { | |
342 | "css" => ("text/css", false), | |
343 | "html" => ("text/html", false), | |
344 | "js" => ("application/javascript", false), | |
345 | "json" => ("application/json", false), | |
346 | "map" => ("application/json", false), | |
347 | "png" => ("image/png", true), | |
348 | "ico" => ("image/x-icon", true), | |
349 | "gif" => ("image/gif", true), | |
350 | "svg" => ("image/svg+xml", false), | |
351 | "jar" => ("application/java-archive", true), | |
352 | "woff" => ("application/font-woff", true), | |
353 | "woff2" => ("application/font-woff2", true), | |
354 | "ttf" => ("application/font-snft", true), | |
355 | "pdf" => ("application/pdf", true), | |
356 | "epub" => ("application/epub+zip", true), | |
357 | "mp3" => ("audio/mpeg", true), | |
358 | "oga" => ("audio/ogg", true), | |
359 | "tgz" => ("application/x-compressed-tar", true), | |
360 | _ => ("application/octet-stream", false), | |
361 | }; | |
362 | } | |
363 | ||
364 | ("application/octet-stream", false) | |
365 | } | |
366 | ||
9bc17e8d DM |
367 | fn simple_static_file_download(filename: PathBuf) -> BoxFut { |
368 | ||
826bb982 DM |
369 | let (content_type, _nocomp) = extension_to_content_type(&filename); |
370 | ||
9bc17e8d | 371 | Box::new(File::open(filename) |
4dcf43d2 | 372 | .map_err(|err| http_err!(BAD_REQUEST, format!("File open failed: {}", err))) |
826bb982 | 373 | .and_then(move |file| { |
9bc17e8d DM |
374 | let buf: Vec<u8> = Vec::new(); |
375 | tokio::io::read_to_end(file, buf) | |
4dcf43d2 | 376 | .map_err(|err| http_err!(BAD_REQUEST, format!("File read failed: {}", err))) |
826bb982 DM |
377 | .and_then(move |data| { |
378 | let mut response = Response::new(data.1.into()); | |
379 | response.headers_mut().insert( | |
380 | header::CONTENT_TYPE, | |
381 | header::HeaderValue::from_static(content_type)); | |
382 | Ok(response) | |
383 | }) | |
9bc17e8d DM |
384 | })) |
385 | } | |
386 | ||
387 | fn chuncked_static_file_download(filename: PathBuf) -> BoxFut { | |
388 | ||
826bb982 DM |
389 | let (content_type, _nocomp) = extension_to_content_type(&filename); |
390 | ||
9bc17e8d | 391 | Box::new(File::open(filename) |
4dcf43d2 | 392 | .map_err(|err| http_err!(BAD_REQUEST, format!("File open failed: {}", err))) |
826bb982 | 393 | .and_then(move |file| { |
059ca7c3 | 394 | let payload = tokio::codec::FramedRead::new(file, tokio::codec::BytesCodec::new()). |
9bc17e8d DM |
395 | map(|bytes| { |
396 | //sigh - howto avoid copy here? or the whole map() ?? | |
397 | hyper::Chunk::from(bytes.to_vec()) | |
398 | }); | |
399 | let body = Body::wrap_stream(payload); | |
826bb982 DM |
400 | |
401 | // fixme: set other headers ? | |
9bc17e8d DM |
402 | Ok(Response::builder() |
403 | .status(StatusCode::OK) | |
826bb982 | 404 | .header(header::CONTENT_TYPE, content_type) |
9bc17e8d DM |
405 | .body(body) |
406 | .unwrap()) | |
407 | })) | |
408 | } | |
409 | ||
410 | fn handle_static_file_download(filename: PathBuf) -> BoxFut { | |
411 | ||
412 | let response = tokio::fs::metadata(filename.clone()) | |
4dcf43d2 | 413 | .map_err(|err| http_err!(BAD_REQUEST, format!("File access problems: {}", err))) |
9bc17e8d DM |
414 | .and_then(|metadata| { |
415 | if metadata.len() < 1024*32 { | |
416 | Either::A(simple_static_file_download(filename)) | |
417 | } else { | |
418 | Either::B(chuncked_static_file_download(filename)) | |
419 | } | |
420 | }); | |
421 | ||
422 | return Box::new(response); | |
423 | } | |
424 | ||
5ddf8cb1 DM |
425 | fn extract_auth_data(headers: &http::HeaderMap) -> (Option<String>, Option<String>) { |
426 | ||
427 | let mut ticket = None; | |
428 | if let Some(raw_cookie) = headers.get("COOKIE") { | |
429 | if let Ok(cookie) = raw_cookie.to_str() { | |
430 | ticket = tools::extract_auth_cookie(cookie, "PBSAuthCookie"); | |
431 | } | |
432 | } | |
433 | ||
434 | let token = match headers.get("CSRFPreventionToken").map(|v| v.to_str()) { | |
435 | Some(Ok(v)) => Some(v.to_owned()), | |
436 | _ => None, | |
437 | }; | |
438 | ||
439 | (ticket, token) | |
440 | } | |
441 | ||
442 | fn check_auth(method: &hyper::Method, ticket: Option<String>, token: Option<String>) -> Result<String, Error> { | |
443 | ||
444 | let ticket_lifetime = 3600*2; // 2 hours | |
445 | ||
446 | let username = match ticket { | |
447 | Some(ticket) => match tools::ticket::verify_rsa_ticket(public_auth_key(), "PBS", &ticket, None, -300, ticket_lifetime) { | |
448 | Ok((_age, Some(username))) => username.to_owned(), | |
449 | Ok((_, None)) => bail!("ticket without username."), | |
450 | Err(err) => return Err(err), | |
451 | } | |
452 | None => bail!("missing ticket"), | |
453 | }; | |
454 | ||
455 | if method != hyper::Method::GET { | |
456 | if let Some(token) = token { | |
8225aa2f | 457 | println!("CSRF prevention token: {:?}", token); |
5ddf8cb1 DM |
458 | verify_csrf_prevention_token(csrf_secret(), &username, &token, -300, ticket_lifetime)?; |
459 | } else { | |
8225aa2f | 460 | bail!("missing CSRF prevention token"); |
5ddf8cb1 DM |
461 | } |
462 | } | |
463 | ||
464 | Ok(username) | |
465 | } | |
466 | ||
9bc17e8d DM |
467 | pub fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> BoxFut { |
468 | ||
469 | let (parts, body) = req.into_parts(); | |
470 | ||
471 | let method = parts.method.clone(); | |
472 | let path = parts.uri.path(); | |
473 | ||
474 | // normalize path | |
475 | // do not allow ".", "..", or hidden files ".XXXX" | |
476 | // also remove empty path components | |
477 | ||
478 | let items = path.split('/'); | |
479 | let mut path = String::new(); | |
480 | let mut components = vec![]; | |
481 | ||
482 | for name in items { | |
483 | if name.is_empty() { continue; } | |
484 | if name.starts_with(".") { | |
10be1d29 | 485 | return Box::new(future::err(http_err!(BAD_REQUEST, "Path contains illegal components.".to_string()))); |
9bc17e8d DM |
486 | } |
487 | path.push('/'); | |
488 | path.push_str(name); | |
489 | components.push(name); | |
490 | } | |
491 | ||
492 | let comp_len = components.len(); | |
493 | ||
494 | println!("REQUEST {} {}", method, path); | |
495 | println!("COMPO {:?}", components); | |
496 | ||
f1204833 DM |
497 | let env_type = api.env_type(); |
498 | let mut rpcenv = RestEnvironment::new(env_type); | |
e82dad97 | 499 | |
b9903d63 DM |
500 | let delay_unauth_time = std::time::Instant::now() + std::time::Duration::from_millis(3000); |
501 | ||
576e3bf2 | 502 | if comp_len >= 1 && components[0] == "api2" { |
9bc17e8d | 503 | println!("GOT API REQUEST"); |
5ddf8cb1 | 504 | |
9bc17e8d DM |
505 | if comp_len >= 2 { |
506 | let format = components[1]; | |
1571873d DM |
507 | let formatter = match format { |
508 | "json" => &JSON_FORMATTER, | |
509 | "extjs" => &EXTJS_FORMATTER, | |
510 | _ => { | |
511 | return Box::new(future::err(http_err!(BAD_REQUEST, format!("Unsupported output format '{}'.", format)))); | |
512 | } | |
513 | }; | |
9bc17e8d | 514 | |
e7ea17de DM |
515 | let mut uri_param = HashMap::new(); |
516 | ||
b9903d63 DM |
517 | if comp_len == 4 && components[2] == "access" && components[3] == "ticket" { |
518 | // explicitly allow those calls without auth | |
519 | } else { | |
5ddf8cb1 DM |
520 | let (ticket, token) = extract_auth_data(&parts.headers); |
521 | match check_auth(&method, ticket, token) { | |
522 | Ok(username) => { | |
523 | ||
524 | // fixme: check permissions | |
525 | ||
526 | rpcenv.set_user(Some(username)); | |
527 | } | |
528 | Err(err) => { | |
529 | // always delay unauthorized calls by 3 seconds (from start of request) | |
530 | let err = http_err!(UNAUTHORIZED, format!("permission check failed - {}", err)); | |
531 | let resp = (formatter.format_error)(err); | |
532 | let delayed_response = tokio::timer::Delay::new(delay_unauth_time) | |
533 | .map_err(|err| http_err!(INTERNAL_SERVER_ERROR, format!("tokio timer delay error: {}", err))) | |
534 | .and_then(|_| Ok(resp)); | |
535 | ||
536 | return Box::new(delayed_response); | |
537 | } | |
b9903d63 DM |
538 | } |
539 | } | |
d7d23785 | 540 | |
7e21da6e DM |
541 | match api.find_method(&components[2..], method, &mut uri_param) { |
542 | MethodDefinition::None => {} | |
543 | MethodDefinition::Simple(api_method) => { | |
f1204833 | 544 | if api_method.protected && env_type == RpcEnvironmentType::PUBLIC { |
4b2cdeb9 | 545 | return proxy_protected_request(api_method, parts, body); |
f1204833 DM |
546 | } else { |
547 | return handle_sync_api_request(rpcenv, api_method, formatter, parts, body, uri_param); | |
548 | } | |
7e21da6e | 549 | } |
50cfb695 | 550 | MethodDefinition::Async(async_method) => { |
e82dad97 | 551 | return handle_async_api_request(rpcenv, async_method, formatter, parts, body, uri_param); |
7e21da6e | 552 | } |
9bc17e8d DM |
553 | } |
554 | } | |
555 | } else { | |
556 | // not Auth for accessing files! | |
557 | ||
f4c514c1 DM |
558 | if comp_len == 0 { |
559 | return get_index(); | |
560 | } else { | |
561 | let filename = api.find_alias(&components); | |
562 | return handle_static_file_download(filename); | |
563 | } | |
9bc17e8d DM |
564 | } |
565 | ||
10be1d29 | 566 | Box::new(future::err(http_err!(NOT_FOUND, "Path not found.".to_string()))) |
9bc17e8d | 567 | } |