]> git.proxmox.com Git - proxmox-backup.git/blobdiff - src/api2/reader.rs
extract create_download_response API helper
[proxmox-backup.git] / src / api2 / reader.rs
index efd04bdff16b473ec0ae91a01f7b812441fee5e0..4c14c97ca56de3bf56927305f56113a84f028a0d 100644 (file)
@@ -1,5 +1,5 @@
 //use chrono::{Local, TimeZone};
-use failure::*;
+use anyhow::{bail, format_err, Error};
 use futures::*;
 use hyper::header::{self, HeaderValue, UPGRADE};
 use hyper::http::request::Parts;
@@ -7,14 +7,17 @@ use hyper::{Body, Response, StatusCode};
 use serde_json::Value;
 
 use proxmox::{sortable, identity};
-use proxmox::api::http_err;
-use proxmox::api::{ApiFuture, ApiHandler, ApiMethod, Router, RpcEnvironment};
+use proxmox::api::{ApiResponseFuture, ApiHandler, ApiMethod, Router, RpcEnvironment, Permission};
 use proxmox::api::schema::*;
+use proxmox::http_err;
 
 use crate::api2::types::*;
 use crate::backup::*;
 use crate::server::{WorkerTask, H2Service};
 use crate::tools;
+use crate::config::acl::PRIV_DATASTORE_READ;
+use crate::config::cached_user_info::CachedUserInfo;
+use crate::api2::helpers;
 
 mod environment;
 use environment::*;
@@ -24,23 +27,21 @@ pub const ROUTER: Router = Router::new()
 
 #[sortable]
 pub const API_METHOD_UPGRADE_BACKUP: ApiMethod = ApiMethod::new(
-    &ApiHandler::Async(&upgrade_to_backup_reader_protocol),
+    &ApiHandler::AsyncHttp(&upgrade_to_backup_reader_protocol),
     &ObjectSchema::new(
         concat!("Upgraded to backup protocol ('", PROXMOX_BACKUP_READER_PROTOCOL_ID_V1!(), "')."),
         &sorted!([
-            ("store", false, &StringSchema::new("Datastore name.").schema()),
-            ("backup-type", false, &StringSchema::new("Backup type.")
-             .format(&ApiStringFormat::Enum(&["vm", "ct", "host"]))
-             .schema()
-            ),
-            ("backup-id", false, &StringSchema::new("Backup ID.").schema()),
-            ("backup-time", false, &IntegerSchema::new("Backup time (Unix epoch.)")
-             .minimum(1_547_797_308)
-             .schema()
-            ),
+            ("store", false, &DATASTORE_SCHEMA),
+            ("backup-type", false, &BACKUP_TYPE_SCHEMA),
+            ("backup-id", false, &BACKUP_ID_SCHEMA),
+            ("backup-time", false, &BACKUP_TIME_SCHEMA),
             ("debug", true, &BooleanSchema::new("Enable verbose debug logging.").schema()),
         ]),
     )
+).access(
+    // Note: parameter 'store' is no uri parameter, so we need to test inside function body
+    Some("The user needs Datastore.Read privilege on /datastore/{store}."),
+    &Permission::Anybody
 );
 
 fn upgrade_to_backup_reader_protocol(
@@ -49,12 +50,17 @@ fn upgrade_to_backup_reader_protocol(
     param: Value,
     _info: &ApiMethod,
     rpcenv: Box<dyn RpcEnvironment>,
-) -> ApiFuture {
+) -> ApiResponseFuture {
 
     async move {
         let debug = param["debug"].as_bool().unwrap_or(false);
 
+        let username = rpcenv.get_user().unwrap();
         let store = tools::required_string_param(&param, "store")?.to_owned();
+
+        let user_info = CachedUserInfo::new()?;
+        user_info.check_privs(&username, &["datastore", &store], PRIV_DATASTORE_READ, false)?;
+
         let datastore = DataStore::lookup_datastore(&store)?;
 
         let backup_type = tools::required_string_param(&param, "backup-type")?;
@@ -75,7 +81,6 @@ fn upgrade_to_backup_reader_protocol(
             bail!("unexpected http version '{:?}' (expected version < 2)", parts.version);
         }
 
-        let username = rpcenv.get_user().unwrap();
         let env_type = rpcenv.env_type();
 
         let backup_dir = BackupDir::new(backup_type, backup_id, backup_time);
@@ -127,7 +132,7 @@ fn upgrade_to_backup_reader_protocol(
                     Either::Right((Ok(res), _)) => Ok(res),
                     Either::Right((Err(err), _)) => Err(err),
                 })
-                .map_ok(move |_| env.log("reader finished sucessfully"))
+                .map_ok(move |_| env.log("reader finished successfully"))
         })?;
 
         let response = Response::builder()
@@ -157,7 +162,7 @@ pub const READER_API_ROUTER: Router = Router::new()
 
 #[sortable]
 pub const API_METHOD_DOWNLOAD_FILE: ApiMethod = ApiMethod::new(
-    &ApiHandler::Async(&download_file),
+    &ApiHandler::AsyncHttp(&download_file),
     &ObjectSchema::new(
         "Download specified file.",
         &sorted!([
@@ -172,7 +177,7 @@ fn download_file(
     param: Value,
     _info: &ApiMethod,
     rpcenv: Box<dyn RpcEnvironment>,
-) -> ApiFuture {
+) -> ApiResponseFuture {
 
     async move {
         let env: &ReaderEnvironment = rpcenv.as_ref();
@@ -183,32 +188,15 @@ fn download_file(
         path.push(env.backup_dir.relative_path());
         path.push(&file_name);
 
-        let path2 = path.clone();
-        let path3 = path.clone();
-
-        let file = tokio::fs::File::open(path)
-            .map_err(move |err| http_err!(BAD_REQUEST, format!("open file {:?} failed: {}", path2, err)))
-            .await?;
-
-        env.log(format!("download {:?}", path3));
+        env.log(format!("download {:?}", path.clone()));
 
-        let payload = tokio::codec::FramedRead::new(file, tokio::codec::BytesCodec::new())
-            .map_ok(|bytes| hyper::Chunk::from(bytes.freeze()));
-
-        let body = Body::wrap_stream(payload);
-
-        // fixme: set other headers ?
-        Ok(Response::builder()
-           .status(StatusCode::OK)
-           .header(header::CONTENT_TYPE, "application/octet-stream")
-           .body(body)
-           .unwrap())
+        helpers::create_download_response(path).await
     }.boxed()
 }
 
 #[sortable]
 pub const API_METHOD_DOWNLOAD_CHUNK: ApiMethod = ApiMethod::new(
-    &ApiHandler::Async(&download_chunk),
+    &ApiHandler::AsyncHttp(&download_chunk),
     &ObjectSchema::new(
         "Download specified chunk.",
         &sorted!([
@@ -223,7 +211,7 @@ fn download_chunk(
     param: Value,
     _info: &ApiMethod,
     rpcenv: Box<dyn RpcEnvironment>,
-) -> ApiFuture {
+) -> ApiResponseFuture {
 
     async move {
         let env: &ReaderEnvironment = rpcenv.as_ref();
@@ -258,7 +246,7 @@ fn download_chunk_old(
     param: Value,
     _info: &ApiMethod,
     rpcenv: Box<dyn RpcEnvironment>,
-) -> Result<ApiFuture, Error> {
+) -> Result<ApiResponseFuture, Error> {
 
     let env: &ReaderEnvironment = rpcenv.as_ref();
     let env2 = env.clone();
@@ -275,8 +263,8 @@ fn download_chunk_old(
         .map_err(move |err| http_err!(BAD_REQUEST, format!("open file {:?} failed: {}", path2, err)))
         .and_then(move |file| {
             env2.debug(format!("download chunk {:?}", path3));
-            let payload = tokio::codec::FramedRead::new(file, tokio::codec::BytesCodec::new())
-                .map_ok(|bytes| hyper::Chunk::from(bytes.freeze()));
+            let payload = tokio_util::codec::FramedRead::new(file, tokio_util::codec::BytesCodec::new())
+                .map_ok(|bytes| hyper::body::Bytes::from(bytes.freeze()));
 
             let body = Body::wrap_stream(payload);
 
@@ -293,7 +281,7 @@ fn download_chunk_old(
 */
 
 pub const API_METHOD_SPEEDTEST: ApiMethod = ApiMethod::new(
-    &ApiHandler::Async(&speedtest),
+    &ApiHandler::AsyncHttp(&speedtest),
     &ObjectSchema::new("Test 4M block download speed.", &[])
 );
 
@@ -303,7 +291,7 @@ fn speedtest(
     _param: Value,
     _info: &ApiMethod,
     _rpcenv: Box<dyn RpcEnvironment>,
-) -> ApiFuture {
+) -> ApiResponseFuture {
 
     let buffer = vec![65u8; 1024*1024]; // nonsense [A,A,A...]