//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;
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::*;
#[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(
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(¶m, "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(¶m, "backup-type")?;
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);
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()
#[sortable]
pub const API_METHOD_DOWNLOAD_FILE: ApiMethod = ApiMethod::new(
- &ApiHandler::Async(&download_file),
+ &ApiHandler::AsyncHttp(&download_file),
&ObjectSchema::new(
"Download specified file.",
&sorted!([
param: Value,
_info: &ApiMethod,
rpcenv: Box<dyn RpcEnvironment>,
-) -> ApiFuture {
+) -> ApiResponseFuture {
async move {
let env: &ReaderEnvironment = rpcenv.as_ref();
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!([
param: Value,
_info: &ApiMethod,
rpcenv: Box<dyn RpcEnvironment>,
-) -> ApiFuture {
+) -> ApiResponseFuture {
async move {
let env: &ReaderEnvironment = rpcenv.as_ref();
param: Value,
_info: &ApiMethod,
rpcenv: Box<dyn RpcEnvironment>,
-) -> Result<ApiFuture, Error> {
+) -> Result<ApiResponseFuture, Error> {
let env: &ReaderEnvironment = rpcenv.as_ref();
let env2 = env.clone();
.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);
*/
pub const API_METHOD_SPEEDTEST: ApiMethod = ApiMethod::new(
- &ApiHandler::Async(&speedtest),
+ &ApiHandler::AsyncHttp(&speedtest),
&ObjectSchema::new("Test 4M block download speed.", &[])
);
_param: Value,
_info: &ApiMethod,
_rpcenv: Box<dyn RpcEnvironment>,
-) -> ApiFuture {
+) -> ApiResponseFuture {
let buffer = vec![65u8; 1024*1024]; // nonsense [A,A,A...]