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