diff --git a/server/Cargo.lock b/server/Cargo.lock index 4597966d1..8cdacc650 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -82,6 +82,12 @@ dependencies = [ "password-hash", ] +[[package]] +name = "array-init" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" + [[package]] name = "arrayref" version = "0.3.9" @@ -1638,6 +1644,48 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jxl" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ad19139224d7643adda1d4b9792caea2346ce7a079ef00ae653b1d7a938dce7" +dependencies = [ + "array-init", + "byteorder", + "jxl_macros", + "jxl_simd", + "jxl_transforms", + "num-derive", + "num-traits", + "thiserror", +] + +[[package]] +name = "jxl_macros" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "341267f3e48d02b0fc44aa3102e0ac46204e726bc63a1caa161431f309e9de42" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jxl_simd" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b772b4e3b3cf3af88c7dde9fc2d6b63cbb89f29eb5cf30cb132ec7adfed533e" + +[[package]] +name = "jxl_transforms" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2dae97098351cbffdbca17907de1055bf60be86e692f9d657f5b565e336b43b" +dependencies = [ + "jxl_simd", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -2098,6 +2146,7 @@ dependencies = [ "hmac", "image", "infer", + "jxl", "lettre", "md5", "mimalloc", diff --git a/server/Cargo.toml b/server/Cargo.toml index 4a1bc1710..6333fc9e0 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -23,6 +23,7 @@ hex = "0.4.3" hmac = "0.13.0" image = { version = "0.25.5", default-features = false, features = ["bmp", "gif", "jpeg", "png", "webp", "rayon"] } infer = "0.22.0" +jxl = "0.5.1" lettre = { version = "0.11.12", features = ["serde"] } md5 = "0.7.0" mime = "0.3.17" diff --git a/server/src/api/error.rs b/server/src/api/error.rs index e9adb8ac9..90d00a924 100644 --- a/server/src/api/error.rs +++ b/server/src/api/error.rs @@ -62,6 +62,7 @@ pub enum ApiError { Image(#[from] image::ImageError), JsonRejection(#[from] axum::extract::rejection::JsonRejection), JsonSerialization(#[from] serde_json::Error), + JxlDecoding(#[from] jxl::error::Error), #[error("Missing {0} content")] MissingContent(ResourceType), #[error("Failed to infer content type")] @@ -141,6 +142,7 @@ impl ApiError { | Self::InvalidTime(_) | Self::InvalidUploadToken | Self::InvalidUserRank + | Self::JxlDecoding(_) | Self::NoEmail | Self::NoNamesGiven(_) | Self::NotAnInteger(_) @@ -197,6 +199,7 @@ impl ApiError { Self::Image(_) => "Image Error", Self::JsonRejection(_) => "JSON Rejection", Self::JsonSerialization(_) => "JSON Serialization Error", + Self::JxlDecoding(_) => "JPEG XL Decoding Error", Self::MissingContent(_) => "Missing Content", Self::MissingContentType => "Missing Content Type", Self::MissingFormData => "Missing Form Data", diff --git a/server/src/content/decode.rs b/server/src/content/decode.rs index 4a099fbbb..f24866b6c 100644 --- a/server/src/content/decode.rs +++ b/server/src/content/decode.rs @@ -1,6 +1,6 @@ use crate::api::error::{ApiError, ApiResult}; use crate::config::Config; -use crate::content::{self, flash}; +use crate::content::{self, flash, jxl}; use crate::model::enums::{MimeType, PostType}; use ffmpeg_sidecar::child::FfmpegChild; use ffmpeg_sidecar::command::FfmpegCommand; @@ -30,6 +30,7 @@ pub fn representative_image(config: &Config, file_path: &Path, mime_type: MimeTy MimeType::Jpeg => image(config, file_path, ImageFormat::Jpeg), MimeType::Png => image(config, file_path, ImageFormat::Png), MimeType::Webp => image(config, file_path, ImageFormat::WebP), + MimeType::Jxl => jxl::image(file_path), MimeType::Swf => flash_image(config, file_path).and_then(|frame| frame.ok_or(ApiError::EmptySwf)), MimeType::Avif => ffmpeg_frame(config, file_path, PostType::Image) .and_then(|frame| frame.ok_or(ApiError::FfmpegError("Unable to decode AVIF image with FFmpeg".into()))), @@ -90,6 +91,7 @@ pub fn detect_post_type(config: &Config, file_path: &Path, mime_type: MimeType) match mime_type { MimeType::Avif => avif_is_animated(config, file_path).map(image_type), MimeType::Gif => gif_is_animated(config, file_path).map(image_type), + MimeType::Jxl => jxl::is_animated(file_path).map(image_type), MimeType::Webp => webp_is_animated(config, file_path).map(image_type), MimeType::Bmp | MimeType::Jpeg | MimeType::Png => Ok(PostType::Image), MimeType::Mp4 | MimeType::Mov | MimeType::Webm => Ok(PostType::Video), diff --git a/server/src/content/jxl.rs b/server/src/content/jxl.rs new file mode 100644 index 000000000..b4ec4041d --- /dev/null +++ b/server/src/content/jxl.rs @@ -0,0 +1,97 @@ +use crate::api::error::{ApiError, ApiResult}; +use crate::content; +use image::{DynamicImage, GrayAlphaImage, GrayImage, RgbImage, RgbaImage}; +use jxl::api::states::WithImageInfo; +use jxl::api::{ + JxlBitstreamInput, JxlColorType, JxlDataFormat, JxlDecoder, JxlDecoderOptions, JxlOutputBuffer, JxlPixelFormat, + ProcessingResult, +}; +use jxl::error::Error; +use jxl::headers::extra_channels::ExtraChannel; +use std::fs::File; +use std::io::BufReader; +use std::path::Path; + +/// Decodes the first visible frame of the JPEG XL file at the given `file_path`. +pub fn image(file_path: &Path) -> ApiResult { + let file = content::map_read_result(File::open(file_path))?; + let mut input = BufReader::new(file); + + let mut decoder = jxl_read_info(&mut input)?; + let info = decoder.basic_info(); + + let (width, height) = info.size; + let has_alpha = info + .extra_channels + .iter() + .any(|channel| channel.ec_type == ExtraChannel::Alpha); + let grayscale = decoder.current_pixel_format().color_type.is_grayscale(); + + let color_type = match (grayscale, has_alpha) { + (false, false) => JxlColorType::Rgb, + (false, true) => JxlColorType::Rgba, + (true, false) => JxlColorType::Grayscale, + (true, true) => JxlColorType::GrayscaleAlpha, + }; + let samples_per_pixel = color_type.samples_per_pixel(); + decoder.set_pixel_format(JxlPixelFormat { + color_type, + // u8 output keeps buffer rows free of alignment requirements + color_data_format: Some(JxlDataFormat::U8 { bit_depth: 8 }), + // None ignores non-color extra channels (depth, spot colors, ...) + extra_channel_format: vec![None; info.extra_channels.len()], + }); + + // Advance to the first frame + let decoder = match decoder.process(&mut input)? { + ProcessingResult::Complete { result } => result, + ProcessingResult::NeedsMoreInput { size_hint, .. } => { + return Err(ApiError::JxlDecoding(Error::OutOfBounds(size_hint))); + } + }; + + let bytes_per_row = width * samples_per_pixel; + let mut pixel_data = vec![0; bytes_per_row * height]; + // One buffer for the interleaved color channels; ignored extra channels need none + let mut buffers = [JxlOutputBuffer::new(&mut pixel_data, height, bytes_per_row)]; + // Decode the frame's pixels; for animations this stops after the first frame + match decoder.process(&mut input, &mut buffers)? { + ProcessingResult::Complete { .. } => {} + ProcessingResult::NeedsMoreInput { size_hint, .. } => { + return Err(ApiError::JxlDecoding(Error::OutOfBounds(size_hint))); + } + } + + let buffer_len = pixel_data.len(); + let (width, height) = (u32::try_from(width).expect(CAST_MESSAGE), u32::try_from(height).expect(CAST_MESSAGE)); + match color_type { + JxlColorType::Rgb => RgbImage::from_raw(width, height, pixel_data).map(DynamicImage::ImageRgb8), + JxlColorType::Rgba => RgbaImage::from_raw(width, height, pixel_data).map(DynamicImage::ImageRgba8), + JxlColorType::Grayscale => GrayImage::from_raw(width, height, pixel_data).map(DynamicImage::ImageLuma8), + JxlColorType::GrayscaleAlpha => { + GrayAlphaImage::from_raw(width, height, pixel_data).map(DynamicImage::ImageLumaA8) + } + JxlColorType::Bgr | JxlColorType::Bgra => unreachable!("Unrequested JPEG XL color type"), + } + .ok_or(ApiError::FrameBufferMismatch(width, height, buffer_len)) +} + +/// Returns whether the JPEG XL file declares an animation, based on the image header. +pub fn is_animated(path: &Path) -> ApiResult { + let file = content::map_read_result(File::open(path))?; + let mut input = BufReader::new(file); + jxl_read_info(&mut input).map(|decoder| decoder.basic_info().animation.is_some()) +} + +const CAST_MESSAGE: &str = "JPEG XL level 10 caps dimensions at 2^30, so u32 cannot overflow"; + +/// Parses input up to the image header, returning a decoder ready to report +/// image info. Fails if the input ends before the header is complete. +fn jxl_read_info(input: &mut In) -> ApiResult> { + let decoder = JxlDecoder::new(JxlDecoderOptions::default()); + match decoder.process(input)? { + ProcessingResult::Complete { result } => Ok(result), + // The full file is available, so needing more input means it's truncated + ProcessingResult::NeedsMoreInput { size_hint, .. } => Err(ApiError::JxlDecoding(Error::OutOfBounds(size_hint))), + } +} diff --git a/server/src/content/mod.rs b/server/src/content/mod.rs index 2c6ed271e..2a989650b 100644 --- a/server/src/content/mod.rs +++ b/server/src/content/mod.rs @@ -13,6 +13,7 @@ pub mod decode; pub mod download; mod flash; pub mod hash; +mod jxl; pub mod signature; pub mod thumbnail; pub mod upload; diff --git a/server/src/error.rs b/server/src/error.rs index 1d383b123..ece1df223 100644 --- a/server/src/error.rs +++ b/server/src/error.rs @@ -5,9 +5,14 @@ use utoipa::ToSchema; pub enum ErrorName { AddressInUse, AddressNotAvailable, + AlphabetTooLargeHuff, AlreadyInTransaction, + AnsChecksumMismatch, ArgumentListTooLong, + ArithmeticOverflow, BadConnection, + BaseColorCorrelationOutOfRange, + BlockContextMapSizeTooBig, BrokenPipe, BrokenTransactionManager, BytesRejection, @@ -17,6 +22,7 @@ pub enum ErrorName { ConnectionAborted, ConnectionRefused, ConnectionReset, + CopyOfDifferentSize, CrossesDevices, CryptoError, CyclicDependency, @@ -25,6 +31,7 @@ pub enum ErrorName { DeserializationError, DimensionLimitsExceeded, DimensionMismatch, + DimShiftTooLarge, DirectoryNotEmpty, DisabledToken, DownloadTooLarge, @@ -45,6 +52,7 @@ pub enum ErrorName { EmptySwf, EmptyValue, EmptyVideo, + EndOfBlockResidualNonZeros, ExecutableFileBusy, ExpiredToken, ExpressionFailsRegex, @@ -59,49 +67,114 @@ pub enum ErrorName { FileAlreadyExists, FileNotFound, FileTooLarge, + FloatNaNOrInf, ForeignKeyViolation, FrameBufferMismatch, FromStrError, GenericImageError, HeaderDeserialization, + HFBlockOutOfBounds, + HfQuantFactorTooSmall, HostUnreachable, + IccEndOfStream, + IccInvalidTagString, + IccInvalidWhitePoint, + IccInvalidWhitePointY, + IccMlucTextNotAscii, + IccTableSizeExceeded, + IccTooLarge, + IccUnsupportedTransferFunction, + IccValueOutOfRangeS15Fixed16, + IccWriteOutOfBounds, + ImageDimensionTooLarge, + ImageOutOfMemory, + ImageSizeTooLarge, InsufficientMemory, InsufficientPrivileges, + IntegerTooLarge, Interrupted, + InvalidAFVBands, + InvalidAnsHistogram, InvalidAuthType, + InvalidBitsPerSample, + InvalidBlendingAlphaChannel, + InvalidBlockSizeForChromaSubsampling, InvalidBoundary, + InvalidBox, InvalidByte, + InvalidChannelRange, InvalidCharacter, + InvalidColorEncoding, + InvalidColorSpace, InvalidConnectionUrl, + InvalidContextMap, + InvalidContextMapHole, InvalidCString, InvalidData, InvalidDigit, + InvalidDistanceBand, + InvalidEcUpsampling, InvalidEncoding, + InvalidEnum, + InvalidEpfValue, + InvalidExponent, InvalidFilename, InvalidFormat, + InvalidGamma, InvalidHeader, + InvalidHistogramIndex, + InvalidHuffman, + InvalidIccStream, + InvalidImageSize, InvalidInput, + InvalidIntensityTarget, InvalidLastSymbol, InvalidLength, + InvalidLfLevel, + InvalidLinearBelow, + InvalidMantissa, InvalidMime, + InvalidMinNits, + InvalidNumNonZeros, + InvalidOutputBufferSize, InvalidPadding, InvalidPassword, + InvalidPermutationLehmerCode, + InvalidPermutationSize, InvalidPhcStringField, + InvalidPredictor, + InvalidProperty, + InvalidQuantEncoding, + InvalidQuantEncodingMode, + InvalidQuantizationTableWeight, + InvalidRawQuantTable, + InvalidRCT, + InvalidRenderingIntent, + InvalidSignature, InvalidSort, + InvalidTransformId, + InvalidUintConfig, InvalidUploadToken, InvalidUserRank, InvalidUtf8InPathParam, + InvalidVarDCTTransform, + InvalidVarDCTTransformMap, InvalidVersion, IsADirectory, + JpegXlOutOfMemory, JsonDataError, JsonInvalidData, JsonInvalidSyntax, JsonIoError, JsonSyntaxError, JsonUnexpectedEOF, + LfQuantFactorTooSmall, + Lz77Disallowed, MalformedCredentials, MalformedToken, MalformedValue, + MatrixInversionFailed, + MetaSqueezeRequiresInPlace, MissingContent, MissingContentType, MissingFormData, @@ -109,32 +182,57 @@ pub enum ErrorName { MissingMetadata, MissingPathParams, MissingSmtpInfo, + MixingDifferentChannels, MultipartError, NegativeOverflow, NetworkDown, NetworkUnreachable, NoEmail, + NoGlobalTree, + NoLfFrame, NoMoreData, + Non444ChromaSubsampling, NoNamesGiven, + NonPatchReferenceWithCrop, + NonZeroPadding, NotADirectory, NotConnected, + NotGrayscale, NotInTransaction, NotLoggedIn, NotNullViolation, NotSeekable, + NumPassesTooLarge, OtherIoError, OtherPathError, + OutOfBounds, OutOfMemory, OutOfRange, ParamNameDuplicated, ParamNameInvalid, ParamsMaxExceeded, + PassesDownsampleNonDecreasing, + PassesLastPassNonIncreasing, + PassesLastPassTooLarge, + PatchesInvalidAlphaChannel, + PatchesInvalidBlendMode, + PatchesInvalidDelta, + PatchesInvalidPosition, + PatchesInvalidReference, + PatchesOutOfBounds, + PatchesPostColorTransform, + PatchesRefTooLarge, + PatchesTooMany, + PatchesUnsupportedMixedUpsampling, PathDeserializeError, PathParseError, PathParseErrorAtIndex, PathParseErrorAtKey, PermissionDenied, PhcStringTrailingData, + PipelineChannelTypeMismatch, + PipelineInvalidStageAfterExtend, + PointListEmpty, PoolCategoryNameAlreadyExists, PoolCategoryNotFound, PoolNameAlreadyExists, @@ -154,14 +252,24 @@ pub enum ErrorName { ResourceModified, RollbackTransaction, RowNotFound, + SaveDifferentDownsample, + SectionTooShort, SelfMerge, SerializationError, SerializationFailure, + SizeOverflow, + SplineAdjacentCoincidingControlPoints, + SplinesAreaTooLarge, + SplinesCoordinatesLimit, + SplinesDeltaLimit, + SplinesDistanceTooLarge, + SplinesPointOutOfRange, + SplinesTooMany, + SplinesTooManyControlPoints, StaleNetworkFileHandle, StorageFull, SwfAvm1ParseError, SwfInvalidData, - SwfIoError, SwfParseError, SwfUnsupported, TagCategoryNameAlreadyExists, @@ -172,10 +280,20 @@ pub enum ErrorName { TaskPanicked, TimedOut, TooManyArgs, + TooManyBlockContexts, + TooManyExtraChannels, TooManyLinks, + TooManySqueezes, + TransferFunctionUnknown, + TreeMultiplierBitsTooLarge, + TreeMultiplierTooLarge, + TreeSplitOnEmptyRange, + TreeTooLarge, + TreeTooTall, UnableToSendCommand, UnauthorizedPasswordReset, UnexpectedEof, + UnexpectedLz77Repeat, UnexpectedOutputSize, UniqueViolation, UnknownArgonError, @@ -188,6 +306,7 @@ pub enum ErrorName { UnknownImageUnsupportedError, UnknownIntParseError, UnknownIoError, + UnknownJpegXlError, UnknownJsonRejectionError, UnknownMultipartRejectionError, UnknownPathDeserializeError, @@ -196,8 +315,8 @@ pub enum ErrorName { UnknownQueryRejectionError, Unsupported, UnsupportedAlgorithm, - UnsupportedContentType, UnsupportedColor, + UnsupportedContentType, UnsupportedExtension, UnsupportedFeature, UnsupportedFormat, @@ -215,6 +334,7 @@ pub enum ErrorName { ValueTooShort, WouldBlock, WriteZero, + WrongBufferCount, WrongNumberOfPathParameters, ZeroNotAllowed, } @@ -486,6 +606,135 @@ impl ErrorKind for image::ImageError { } } +impl ErrorKind for jxl::error::Error { + fn kind(&self) -> ErrorName { + match self { + Self::InvalidRawQuantTable => ErrorName::InvalidRawQuantTable, + Self::InvalidDistanceBand(..) => ErrorName::InvalidDistanceBand, + Self::InvalidAFVBands => ErrorName::InvalidAFVBands, + Self::InvalidQuantizationTableWeight(_) => ErrorName::InvalidQuantizationTableWeight, + Self::OutOfBounds(_) => ErrorName::OutOfBounds, + Self::SectionTooShort => ErrorName::SectionTooShort, + Self::NonZeroPadding => ErrorName::NonZeroPadding, + Self::InvalidSignature => ErrorName::InvalidSignature, + Self::InvalidExponent(_) => ErrorName::InvalidExponent, + Self::InvalidMantissa(_) => ErrorName::InvalidMantissa, + Self::InvalidBitsPerSample(_) => ErrorName::InvalidBitsPerSample, + Self::InvalidEnum(..) => ErrorName::InvalidEnum, + Self::DimShiftTooLarge(_) => ErrorName::DimShiftTooLarge, + Self::FloatNaNOrInf => ErrorName::FloatNaNOrInf, + Self::InvalidGamma(_) => ErrorName::InvalidGamma, + Self::InvalidColorEncoding => ErrorName::InvalidColorEncoding, + Self::InvalidColorSpace => ErrorName::InvalidColorSpace, + Self::InvalidRenderingIntent => ErrorName::InvalidRenderingIntent, + Self::InvalidIntensityTarget(_) => ErrorName::InvalidIntensityTarget, + Self::InvalidMinNits(_) => ErrorName::InvalidMinNits, + Self::InvalidLinearBelow(..) => ErrorName::InvalidLinearBelow, + Self::SizeOverflow => ErrorName::SizeOverflow, + Self::InvalidBox => ErrorName::InvalidBox, + Self::IccTooLarge => ErrorName::IccTooLarge, + Self::IccEndOfStream => ErrorName::IccEndOfStream, + Self::InvalidIccStream => ErrorName::InvalidIccStream, + Self::InvalidUintConfig(..) => ErrorName::InvalidUintConfig, + Self::Lz77Disallowed => ErrorName::Lz77Disallowed, + Self::UnexpectedLz77Repeat => ErrorName::UnexpectedLz77Repeat, + Self::AlphabetTooLargeHuff(_) => ErrorName::AlphabetTooLargeHuff, + Self::InvalidHuffman => ErrorName::InvalidHuffman, + Self::InvalidAnsHistogram => ErrorName::InvalidAnsHistogram, + Self::AnsChecksumMismatch => ErrorName::AnsChecksumMismatch, + Self::IntegerTooLarge(_) => ErrorName::IntegerTooLarge, + Self::InvalidContextMap(_) => ErrorName::InvalidContextMap, + Self::InvalidContextMapHole(..) => ErrorName::InvalidContextMapHole, + Self::InvalidPermutationSize { .. } => ErrorName::InvalidPermutationSize, + Self::InvalidPermutationLehmerCode { .. } => ErrorName::InvalidPermutationLehmerCode, + Self::InvalidQuantEncodingMode => ErrorName::InvalidQuantEncodingMode, + Self::InvalidQuantEncoding { .. } => ErrorName::InvalidQuantEncoding, + Self::InvalidEcUpsampling(..) => ErrorName::InvalidEcUpsampling, + Self::InvalidLfLevel(_) => ErrorName::InvalidLfLevel, + Self::NumPassesTooLarge(..) => ErrorName::NumPassesTooLarge, + Self::PassesDownsampleNonDecreasing => ErrorName::PassesDownsampleNonDecreasing, + Self::PassesLastPassNonIncreasing => ErrorName::PassesLastPassNonIncreasing, + Self::PassesLastPassTooLarge => ErrorName::PassesLastPassTooLarge, + Self::NonPatchReferenceWithCrop => ErrorName::NonPatchReferenceWithCrop, + Self::Non444ChromaSubsampling => ErrorName::Non444ChromaSubsampling, + Self::InvalidBlockSizeForChromaSubsampling => ErrorName::InvalidBlockSizeForChromaSubsampling, + Self::OutOfMemory(_) => ErrorName::JpegXlOutOfMemory, + Self::ImageOutOfMemory(..) => ErrorName::ImageOutOfMemory, + Self::ImageSizeTooLarge(..) => ErrorName::ImageSizeTooLarge, + Self::ImageDimensionTooLarge(_) => ErrorName::ImageDimensionTooLarge, + Self::InvalidImageSize(..) => ErrorName::InvalidImageSize, + Self::ArithmeticOverflow => ErrorName::ArithmeticOverflow, + Self::PipelineChannelTypeMismatch(..) => ErrorName::PipelineChannelTypeMismatch, + Self::PipelineInvalidStageAfterExtend(_) => ErrorName::PipelineInvalidStageAfterExtend, + Self::CopyOfDifferentSize(..) => ErrorName::CopyOfDifferentSize, + Self::LfQuantFactorTooSmall(_) => ErrorName::LfQuantFactorTooSmall, + Self::HfQuantFactorTooSmall(_) => ErrorName::HfQuantFactorTooSmall, + Self::InvalidPredictor(_) => ErrorName::InvalidPredictor, + Self::InvalidProperty(_) => ErrorName::InvalidProperty, + Self::InvalidBlendingAlphaChannel(..) => ErrorName::InvalidBlendingAlphaChannel, + Self::PatchesInvalidAlphaChannel(..) => ErrorName::PatchesInvalidAlphaChannel, + Self::PatchesInvalidBlendMode(..) => ErrorName::PatchesInvalidBlendMode, + Self::PatchesInvalidDelta(..) => ErrorName::PatchesInvalidDelta, + Self::PatchesInvalidPosition(..) => ErrorName::PatchesInvalidPosition, + Self::PatchesInvalidReference(_) => ErrorName::PatchesInvalidReference, + Self::PatchesOutOfBounds(..) => ErrorName::PatchesOutOfBounds, + Self::PatchesPostColorTransform() => ErrorName::PatchesPostColorTransform, + Self::PatchesUnsupportedMixedUpsampling(..) => ErrorName::PatchesUnsupportedMixedUpsampling, + Self::PatchesTooMany(..) => ErrorName::PatchesTooMany, + Self::PatchesRefTooLarge(..) => ErrorName::PatchesRefTooLarge, + Self::PointListEmpty => ErrorName::PointListEmpty, + Self::SplinesAreaTooLarge(..) => ErrorName::SplinesAreaTooLarge, + Self::SplinesDistanceTooLarge(..) => ErrorName::SplinesDistanceTooLarge, + Self::SplinesTooMany(..) => ErrorName::SplinesTooMany, + Self::SplineAdjacentCoincidingControlPoints(..) => ErrorName::SplineAdjacentCoincidingControlPoints, + Self::SplinesTooManyControlPoints(..) => ErrorName::SplinesTooManyControlPoints, + Self::SplinesPointOutOfRange(..) => ErrorName::SplinesPointOutOfRange, + Self::SplinesCoordinatesLimit(..) => ErrorName::SplinesCoordinatesLimit, + Self::SplinesDeltaLimit(..) => ErrorName::SplinesDeltaLimit, + Self::TreeTooLarge(..) => ErrorName::TreeTooLarge, + Self::TreeTooTall(..) => ErrorName::TreeTooTall, + Self::TreeMultiplierTooLarge(..) => ErrorName::TreeMultiplierTooLarge, + Self::TreeMultiplierBitsTooLarge(..) => ErrorName::TreeMultiplierBitsTooLarge, + Self::TreeSplitOnEmptyRange(..) => ErrorName::TreeSplitOnEmptyRange, + Self::NoGlobalTree => ErrorName::NoGlobalTree, + Self::InvalidTransformId => ErrorName::InvalidTransformId, + Self::InvalidRCT(_) => ErrorName::InvalidRCT, + Self::InvalidChannelRange(..) => ErrorName::InvalidChannelRange, + Self::MixingDifferentChannels => ErrorName::MixingDifferentChannels, + Self::MetaSqueezeRequiresInPlace => ErrorName::MetaSqueezeRequiresInPlace, + Self::TooManySqueezes => ErrorName::TooManySqueezes, + Self::BlockContextMapSizeTooBig(..) => ErrorName::BlockContextMapSizeTooBig, + Self::TooManyBlockContexts => ErrorName::TooManyBlockContexts, + Self::BaseColorCorrelationOutOfRange => ErrorName::BaseColorCorrelationOutOfRange, + Self::InvalidEpfValue(_) => ErrorName::InvalidEpfValue, + Self::InvalidVarDCTTransform(_) => ErrorName::InvalidVarDCTTransform, + Self::InvalidVarDCTTransformMap => ErrorName::InvalidVarDCTTransformMap, + Self::HFBlockOutOfBounds => ErrorName::HFBlockOutOfBounds, + Self::InvalidNumNonZeros(..) => ErrorName::InvalidNumNonZeros, + Self::InvalidHistogramIndex(..) => ErrorName::InvalidHistogramIndex, + Self::EndOfBlockResidualNonZeros(_) => ErrorName::EndOfBlockResidualNonZeros, + Self::TransferFunctionUnknown => ErrorName::TransferFunctionUnknown, + Self::IccWriteOutOfBounds => ErrorName::IccWriteOutOfBounds, + Self::IccInvalidTagString(_) => ErrorName::IccInvalidTagString, + Self::IccMlucTextNotAscii(_) => ErrorName::IccMlucTextNotAscii, + Self::IccValueOutOfRangeS15Fixed16(_) => ErrorName::IccValueOutOfRangeS15Fixed16, + Self::IccInvalidWhitePointY(_) => ErrorName::IccInvalidWhitePointY, + Self::IccInvalidWhitePoint(..) => ErrorName::IccInvalidWhitePoint, + Self::MatrixInversionFailed(_) => ErrorName::MatrixInversionFailed, + Self::IccUnsupportedTransferFunction => ErrorName::IccUnsupportedTransferFunction, + Self::IccTableSizeExceeded(_) => ErrorName::IccTableSizeExceeded, + Self::IOError(err) => err.kind().kind(), + Self::WrongBufferCount(..) => ErrorName::WrongBufferCount, + Self::NotGrayscale => ErrorName::NotGrayscale, + Self::InvalidOutputBufferSize(..) => ErrorName::InvalidOutputBufferSize, + Self::SaveDifferentDownsample(..) => ErrorName::SaveDifferentDownsample, + Self::TooManyExtraChannels(_) => ErrorName::TooManyExtraChannels, + Self::NoLfFrame(_) => ErrorName::NoLfFrame, + _ => ErrorName::UnknownJpegXlError, + } + } +} + impl ErrorKind for lettre::address::AddressError { fn kind(&self) -> ErrorName { match self { @@ -592,7 +841,7 @@ impl ErrorKind for swf::error::Error { Self::Avm1ParseError { .. } => ErrorName::SwfAvm1ParseError, Self::InvalidData(_) => ErrorName::SwfInvalidData, Self::SwfParseError { .. } => ErrorName::SwfParseError, - Self::IoError(_) => ErrorName::SwfIoError, + Self::IoError(err) => err.kind().kind(), Self::Unsupported(_) => ErrorName::SwfUnsupported, } } @@ -640,6 +889,7 @@ impl ErrorKind for crate::api::error::ApiError { Self::Image(err) => err.kind(), Self::JsonRejection(err) => err.kind(), Self::JsonSerialization(err) => err.classify().kind(), + Self::JxlDecoding(err) => err.kind(), Self::NoEmail => ErrorName::NoEmail, Self::MissingContent(_) => ErrorName::MissingContent, Self::MissingContentType => ErrorName::MissingContentType, diff --git a/server/src/model/enums.rs b/server/src/model/enums.rs index 5d0b7fa3d..4dd8ed16a 100644 --- a/server/src/model/enums.rs +++ b/server/src/model/enums.rs @@ -105,6 +105,8 @@ pub enum MimeType { Swf, #[serde(rename = "image/avif")] Avif, + #[serde(rename = "image/jxl")] + Jxl, } impl MimeType { @@ -116,6 +118,7 @@ impl MimeType { "bmp" | "dib" => Ok(Self::Bmp), "gif" => Ok(Self::Gif), "jpg" | "jpeg" | "jpe" | "jif" | "jfif" | "jfi" => Ok(Self::Jpeg), + "jxl" => Ok(Self::Jxl), "png" => Ok(Self::Png), "mp4" | "m4v" => Ok(Self::Mp4), "mov" | "movie" | "qt" => Ok(Self::Mov), @@ -140,6 +143,7 @@ impl MimeType { Self::Bmp => "bmp", Self::Gif => "gif", Self::Jpeg => "jpg", + Self::Jxl => "jxl", Self::Png => "png", Self::Webp => "webp", Self::Mp4 => "mp4", @@ -164,6 +168,7 @@ impl FromStr for MimeType { "image/bmp" => Ok(MimeType::Bmp), "image/gif" => Ok(MimeType::Gif), "image/jpeg" => Ok(MimeType::Jpeg), + "image/jxl" => Ok(MimeType::Jxl), "image/png" => Ok(MimeType::Png), "image/webp" => Ok(MimeType::Webp), "video/mp4" | "video/x-m4v" => Ok(MimeType::Mp4),