2024-02-01 03:32:01 +00:00
|
|
|
use std::{ffi::OsStr, ops::Deref, path::Path, sync::Arc};
|
2023-12-23 17:58:20 +00:00
|
|
|
|
2021-09-14 01:22:42 +00:00
|
|
|
use crate::{
|
2024-02-01 03:32:01 +00:00
|
|
|
config::Media,
|
2023-09-02 01:50:10 +00:00
|
|
|
error_code::ErrorCode,
|
2023-07-13 03:12:21 +00:00
|
|
|
formats::ProcessableFormat,
|
2024-02-25 01:07:48 +00:00
|
|
|
process::{Process, ProcessError},
|
2024-02-04 00:18:13 +00:00
|
|
|
state::State,
|
2024-02-01 03:32:01 +00:00
|
|
|
tmp_file::{TmpDir, TmpFolder},
|
2021-09-14 01:22:42 +00:00
|
|
|
};
|
2023-09-24 16:54:16 +00:00
|
|
|
|
2023-11-10 00:20:59 +00:00
|
|
|
pub(crate) const MAGICK_TEMPORARY_PATH: &str = "MAGICK_TEMPORARY_PATH";
|
2024-02-01 03:32:01 +00:00
|
|
|
pub(crate) const MAGICK_CONFIGURE_PATH: &str = "MAGICK_CONFIGURE_PATH";
|
2023-11-10 00:20:59 +00:00
|
|
|
|
2023-07-10 20:29:41 +00:00
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
|
|
pub(crate) enum MagickError {
|
|
|
|
#[error("Error in imagemagick process")]
|
|
|
|
Process(#[source] ProcessError),
|
|
|
|
|
2023-12-23 18:48:32 +00:00
|
|
|
#[error("Invalid output format: {0}")]
|
|
|
|
Json(String, #[source] serde_json::Error),
|
2023-07-10 20:29:41 +00:00
|
|
|
|
2023-11-10 00:20:59 +00:00
|
|
|
#[error("Error creating temporary directory")]
|
|
|
|
CreateTemporaryDirectory(#[source] std::io::Error),
|
|
|
|
|
2023-07-14 00:21:57 +00:00
|
|
|
#[error("Error in metadata discovery")]
|
|
|
|
Discover(#[source] crate::discover::DiscoverError),
|
|
|
|
|
2023-07-17 18:30:08 +00:00
|
|
|
#[error("Invalid media file provided")]
|
|
|
|
CommandFailed(ProcessError),
|
|
|
|
|
2023-12-23 17:58:20 +00:00
|
|
|
#[error("Error cleaning up after command")]
|
|
|
|
Cleanup(#[source] std::io::Error),
|
|
|
|
|
2023-07-14 00:21:57 +00:00
|
|
|
#[error("Command output is empty")]
|
|
|
|
Empty,
|
2023-07-10 20:29:41 +00:00
|
|
|
}
|
|
|
|
|
2023-07-17 18:30:08 +00:00
|
|
|
impl From<ProcessError> for MagickError {
|
|
|
|
fn from(value: ProcessError) -> Self {
|
|
|
|
match value {
|
|
|
|
e @ ProcessError::Status(_, _) => Self::CommandFailed(e),
|
|
|
|
otherwise => Self::Process(otherwise),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-10 20:29:41 +00:00
|
|
|
impl MagickError {
|
2023-09-02 01:50:10 +00:00
|
|
|
pub(crate) const fn error_code(&self) -> ErrorCode {
|
|
|
|
match self {
|
|
|
|
Self::CommandFailed(_) => ErrorCode::COMMAND_FAILURE,
|
|
|
|
Self::Process(e) => e.error_code(),
|
2023-12-23 18:48:32 +00:00
|
|
|
Self::Json(_, _)
|
2023-11-10 00:20:59 +00:00
|
|
|
| Self::CreateTemporaryDirectory(_)
|
2023-09-02 01:50:10 +00:00
|
|
|
| Self::Discover(_)
|
2023-12-23 17:58:20 +00:00
|
|
|
| Self::Cleanup(_)
|
2023-11-10 00:20:59 +00:00
|
|
|
| Self::Empty => ErrorCode::COMMAND_ERROR,
|
2023-09-02 01:50:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-10 20:29:41 +00:00
|
|
|
pub(crate) fn is_client_error(&self) -> bool {
|
|
|
|
// Failing validation or imagemagick bailing probably means bad input
|
2023-08-05 21:18:06 +00:00
|
|
|
matches!(
|
|
|
|
self,
|
|
|
|
Self::CommandFailed(_) | Self::Process(ProcessError::Timeout(_))
|
|
|
|
)
|
2023-07-10 20:29:41 +00:00
|
|
|
}
|
|
|
|
}
|
2021-08-28 22:15:14 +00:00
|
|
|
|
2024-02-25 01:07:48 +00:00
|
|
|
pub(crate) async fn process_image_command<S>(
|
2024-02-03 19:31:54 +00:00
|
|
|
state: &State<S>,
|
2023-07-13 03:12:21 +00:00
|
|
|
process_args: Vec<String>,
|
2023-07-14 00:21:57 +00:00
|
|
|
input_format: ProcessableFormat,
|
2023-07-13 03:12:21 +00:00
|
|
|
format: ProcessableFormat,
|
2023-07-18 21:18:01 +00:00
|
|
|
quality: Option<u8>,
|
2024-02-25 01:07:48 +00:00
|
|
|
) -> Result<Process, MagickError> {
|
2024-02-03 19:31:54 +00:00
|
|
|
let temporary_path = state
|
|
|
|
.tmp_dir
|
2023-11-10 00:20:59 +00:00
|
|
|
.tmp_folder()
|
|
|
|
.await
|
|
|
|
.map_err(MagickError::CreateTemporaryDirectory)?;
|
|
|
|
|
2024-02-25 01:07:48 +00:00
|
|
|
let input_arg = format!("{}:-", input_format.magick_format());
|
2023-07-14 00:21:57 +00:00
|
|
|
let output_arg = format!("{}:-", format.magick_format());
|
2023-07-18 21:18:01 +00:00
|
|
|
let quality = quality.map(|q| q.to_string());
|
2021-09-14 01:22:42 +00:00
|
|
|
|
2023-09-24 17:42:23 +00:00
|
|
|
let len = 3
|
2023-09-24 18:20:33 +00:00
|
|
|
+ if input_format.coalesce() { 1 } else { 0 }
|
2023-09-24 17:42:23 +00:00
|
|
|
+ if quality.is_some() { 1 } else { 0 }
|
2023-09-24 16:54:16 +00:00
|
|
|
+ process_args.len();
|
2023-07-13 03:12:21 +00:00
|
|
|
|
2023-11-10 00:20:59 +00:00
|
|
|
let mut args: Vec<&OsStr> = Vec::with_capacity(len);
|
|
|
|
args.push("convert".as_ref());
|
2024-02-25 01:07:48 +00:00
|
|
|
args.push(input_arg.as_ref());
|
2023-09-24 18:20:33 +00:00
|
|
|
if input_format.coalesce() {
|
2023-11-10 00:20:59 +00:00
|
|
|
args.push("-coalesce".as_ref());
|
2023-07-13 03:12:21 +00:00
|
|
|
}
|
2023-11-10 00:26:57 +00:00
|
|
|
args.extend(process_args.iter().map(AsRef::<OsStr>::as_ref));
|
2023-07-18 21:18:01 +00:00
|
|
|
if let Some(quality) = &quality {
|
2023-11-10 00:20:59 +00:00
|
|
|
args.extend(["-quality".as_ref(), quality.as_ref()] as [&OsStr; 2]);
|
2023-07-18 21:18:01 +00:00
|
|
|
}
|
2023-11-10 00:20:59 +00:00
|
|
|
args.push(output_arg.as_ref());
|
|
|
|
|
2024-02-01 03:32:01 +00:00
|
|
|
let envs = [
|
|
|
|
(MAGICK_TEMPORARY_PATH, temporary_path.as_os_str()),
|
2024-02-03 19:31:54 +00:00
|
|
|
(MAGICK_CONFIGURE_PATH, state.policy_dir.as_os_str()),
|
2024-02-01 03:32:01 +00:00
|
|
|
];
|
2023-07-14 00:21:57 +00:00
|
|
|
|
2024-03-09 21:19:13 +00:00
|
|
|
let process = Process::run("magick", &args, &envs, state.config.media.process_timeout)
|
|
|
|
.await?
|
2023-12-23 02:52:58 +00:00
|
|
|
.add_extras(temporary_path);
|
2023-07-14 00:21:57 +00:00
|
|
|
|
2024-02-25 01:07:48 +00:00
|
|
|
Ok(process)
|
2023-12-23 02:52:58 +00:00
|
|
|
}
|
2024-02-01 03:32:01 +00:00
|
|
|
|
|
|
|
pub(crate) type ArcPolicyDir = Arc<PolicyDir>;
|
|
|
|
|
|
|
|
pub(crate) struct PolicyDir {
|
|
|
|
folder: TmpFolder,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PolicyDir {
|
|
|
|
pub(crate) async fn cleanup(self: Arc<Self>) -> std::io::Result<()> {
|
|
|
|
if let Some(this) = Arc::into_inner(self) {
|
|
|
|
this.folder.cleanup().await?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl AsRef<Path> for PolicyDir {
|
|
|
|
fn as_ref(&self) -> &Path {
|
|
|
|
&self.folder
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Deref for PolicyDir {
|
|
|
|
type Target = Path;
|
|
|
|
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
&self.folder
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(super) async fn write_magick_policy(
|
|
|
|
media: &Media,
|
|
|
|
tmp_dir: &TmpDir,
|
|
|
|
) -> std::io::Result<ArcPolicyDir> {
|
|
|
|
let folder = tmp_dir.tmp_folder().await?;
|
|
|
|
let file = folder.join("policy.xml");
|
|
|
|
|
|
|
|
let res = tokio::fs::write(&file, generate_policy(media)).await;
|
|
|
|
|
|
|
|
if let Err(e) = res {
|
|
|
|
folder.cleanup().await?;
|
|
|
|
return Err(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Arc::new(PolicyDir { folder }))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn generate_policy(media: &Media) -> String {
|
|
|
|
let width = media.magick.max_width;
|
|
|
|
let height = media.magick.max_height;
|
|
|
|
let area = media.magick.max_area;
|
2024-02-05 20:30:01 +00:00
|
|
|
let memory = media.magick.memory;
|
|
|
|
let map = media.magick.map;
|
|
|
|
let disk = media.magick.disk;
|
|
|
|
let frames = media.animation.max_frame_count;
|
2024-02-01 03:32:01 +00:00
|
|
|
let timeout = media.process_timeout;
|
|
|
|
|
|
|
|
format!(
|
|
|
|
r#"<policymap>
|
2024-02-05 20:30:01 +00:00
|
|
|
<policy domain="resource" name="width" value="{width}P" />
|
|
|
|
<policy domain="resource" name="height" value="{height}P" />
|
|
|
|
<policy domain="resource" name="area" value="{area}P" />
|
|
|
|
<policy domain="resource" name="list-length" value="{frames}" />
|
2024-02-01 03:32:01 +00:00
|
|
|
<policy domain="resource" name="time" value="{timeout}" />
|
2024-02-05 20:30:01 +00:00
|
|
|
<policy domain="resource" name="memory" value="{memory}MiB" />
|
|
|
|
<policy domain="resource" name="map" value="{map}MiB" />
|
|
|
|
<policy domain="resource" name="disk" value="{disk}MiB" />
|
2024-02-01 03:32:01 +00:00
|
|
|
<policy domain="resource" name="file" value="768" />
|
|
|
|
<policy domain="resource" name="thread" value="2" />
|
2024-02-05 20:30:01 +00:00
|
|
|
<policy domain="cache" name="memory-map" value="anonymous"/>
|
|
|
|
<policy domain="cache" name="synchronize" value="true"/>
|
2024-02-01 03:32:01 +00:00
|
|
|
<policy domain="path" rights="none" pattern="@*" />
|
|
|
|
<policy domain="coder" rights="none" pattern="*" />
|
2024-02-24 04:12:19 +00:00
|
|
|
<policy domain="coder" rights="read | write" pattern="{{APNG,AVIF,GIF,HEIC,JPEG,JSON,JXL,PNG,RGB,RGBA,WEBP,MP4,WEBM,TMP,PAM}}" />
|
2024-02-01 03:32:01 +00:00
|
|
|
<policy domain="delegate" rights="none" pattern="*" />
|
2024-02-25 17:40:03 +00:00
|
|
|
<policy domain="delegate" rights="execute" pattern="ffmpeg" />
|
2024-02-01 03:32:01 +00:00
|
|
|
<policy domain="filter" rights="none" pattern="*" />
|
|
|
|
<policy domain="module" rights="none" pattern="*" />
|
2024-02-24 04:12:19 +00:00
|
|
|
<policy domain="module" rights="read | write" pattern="{{APNG,AVIF,GIF,HEIC,JPEG,JSON,JXL,PNG,RGB,RGBA,WEBP,TMP,PAM,PNM,VIDEO}}" />
|
2024-02-01 03:32:01 +00:00
|
|
|
<!-- indirect reads not permitted -->
|
2024-02-05 20:30:01 +00:00
|
|
|
<policy domain="system" name="max-memory-request" value="256MiB"/>
|
|
|
|
<policy domain="system" name="memory-map" value="anonymous"/>
|
2024-02-01 03:32:01 +00:00
|
|
|
<policy domain="system" name="precision" value="6" />
|
|
|
|
</policymap>"#
|
|
|
|
)
|
|
|
|
}
|