mirror of
https://git.asonix.dog/asonix/pict-rs.git
synced 2024-11-28 12:31:00 +00:00
Use rexiv2 for metadata removal
This commit is contained in:
parent
eaeb12ed60
commit
154914e61a
11 changed files with 436 additions and 170 deletions
28
Cargo.lock
generated
28
Cargo.lock
generated
|
@ -920,6 +920,16 @@ dependencies = [
|
||||||
"wasi",
|
"wasi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gexiv2-sys"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "edc8f7e79962171a99792ff6895fac7abe89380c02f9abf9dc73c88f2e56697c"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gif"
|
name = "gif"
|
||||||
version = "0.10.3"
|
version = "0.10.3"
|
||||||
|
@ -1378,6 +1388,7 @@ dependencies = [
|
||||||
"mime",
|
"mime",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rand",
|
"rand",
|
||||||
|
"rexiv2",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
@ -1422,6 +1433,12 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkg-config"
|
||||||
|
version = "0.3.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "png"
|
name = "png"
|
||||||
version = "0.16.5"
|
version = "0.16.5"
|
||||||
|
@ -1611,6 +1628,17 @@ dependencies = [
|
||||||
"quick-error",
|
"quick-error",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rexiv2"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "982534bb5ab05ec02973487cb3338392dc68fdc8e189fdcf268ef8c95a78cfa8"
|
||||||
|
dependencies = [
|
||||||
|
"gexiv2-sys",
|
||||||
|
"libc",
|
||||||
|
"num-rational",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.16.14"
|
version = "0.16.14"
|
||||||
|
|
|
@ -24,6 +24,7 @@ image = "0.23.4"
|
||||||
mime = "0.3.1"
|
mime = "0.3.1"
|
||||||
once_cell = "1.4.0"
|
once_cell = "1.4.0"
|
||||||
rand = "0.7.3"
|
rand = "0.7.3"
|
||||||
|
rexiv2 = "0.9.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
sha2 = "0.9.0"
|
sha2 = "0.9.0"
|
||||||
|
|
|
@ -1,30 +1,106 @@
|
||||||
# Build
|
FROM rustembedded/cross:x86_64-unknown-linux-gnu AS x86_64-builder
|
||||||
FROM ekidd/rust-musl-builder:1.44.0 as rust
|
|
||||||
|
ARG UID=1000
|
||||||
|
ARG GID=1000
|
||||||
|
|
||||||
|
ENV TOOLCHAIN=stable
|
||||||
|
ENV TARGET=x86_64-unknown-linux-gnu
|
||||||
|
ENV TOOL=x86_64-linux-gnu
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get upgrade -y
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
addgroup --gid "${GID}" build && \
|
||||||
|
adduser \
|
||||||
|
--disabled-password \
|
||||||
|
--gecos "" \
|
||||||
|
--ingroup build \
|
||||||
|
--uid "${UID}" \
|
||||||
|
--home /opt/build \
|
||||||
|
build
|
||||||
|
|
||||||
|
ADD https://sh.rustup.rs /opt/build/rustup.sh
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
chown -R build:build /opt/build
|
||||||
|
|
||||||
|
USER build
|
||||||
|
WORKDIR /opt/build
|
||||||
|
|
||||||
|
ENV PATH=$PATH:/opt/build/.cargo/bin
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
chmod +x rustup.sh && \
|
||||||
|
./rustup.sh --default-toolchain $TOOLCHAIN --profile minimal -y && \
|
||||||
|
rustup target add $TARGET
|
||||||
|
|
||||||
|
FROM x86_64-builder as builder
|
||||||
|
|
||||||
|
USER root
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
dpkg --add-architecture amd64 && \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get -y install libgexiv2-dev:amd64
|
||||||
|
|
||||||
|
USER build
|
||||||
|
ENV USER=build
|
||||||
|
|
||||||
# Cache deps
|
# Cache deps
|
||||||
WORKDIR /app
|
RUN \
|
||||||
RUN sudo chown -R rust:rust .
|
cargo new repo
|
||||||
RUN USER=root cargo new server
|
|
||||||
WORKDIR /app/server
|
WORKDIR /opt/build/repo
|
||||||
|
|
||||||
COPY Cargo.toml Cargo.lock ./
|
COPY Cargo.toml Cargo.lock ./
|
||||||
RUN sudo chown -R rust:rust .
|
|
||||||
RUN mkdir -p ./src/bin \
|
USER root
|
||||||
&& echo 'fn main() { println!("Dummy") }' > ./src/bin/main.rs
|
RUN \
|
||||||
RUN cargo build --release
|
chown -R build:build ./
|
||||||
RUN rm -f ./target/x86_64-unknown-linux-musl/release/deps/pict_rs*
|
|
||||||
|
USER build
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
mkdir -p ./src && \
|
||||||
|
echo 'fn main() { println!("Dummy") }' > ./src/main.rs && \
|
||||||
|
cargo build --release && \
|
||||||
|
rm -rf ./src
|
||||||
|
|
||||||
COPY src ./src/
|
COPY src ./src/
|
||||||
|
|
||||||
|
USER root
|
||||||
|
RUN \
|
||||||
|
chown -R build:build ./src && \
|
||||||
|
rm -r ./target/release/deps/pict_rs-*
|
||||||
|
|
||||||
|
USER build
|
||||||
|
|
||||||
# Build for release
|
# Build for release
|
||||||
RUN cargo build --frozen --release
|
RUN cargo build --frozen --release
|
||||||
|
|
||||||
FROM alpine:3.11
|
FROM ubuntu:20.04
|
||||||
|
|
||||||
|
ARG UID=1000
|
||||||
|
ARG GID=1000
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y libgexiv2-2
|
||||||
|
|
||||||
# Copy resources
|
# Copy resources
|
||||||
COPY --from=rust /app/server/target/x86_64-unknown-linux-musl/release/pict-rs /app/pict-rs
|
COPY --from=builder /opt/build/repo/target/release/pict-rs /usr/bin/pict-rs
|
||||||
|
|
||||||
RUN addgroup -g 1000 pictrs
|
RUN \
|
||||||
RUN adduser -D -s /bin/sh -u 1000 -G pictrs pictrs
|
addgroup -gid "${GID}" pictrs && \
|
||||||
RUN chown pictrs:pictrs /app/pict-rs
|
adduser \
|
||||||
|
--disabled-password \
|
||||||
|
--gecos "" \
|
||||||
|
--ingroup pictrs \
|
||||||
|
--uid "${UID}" \
|
||||||
|
--home /opt/pictrs \
|
||||||
|
pictrs
|
||||||
|
WORKDIR /opt/pictrs
|
||||||
USER pictrs
|
USER pictrs
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
CMD ["/app/pict-rs"]
|
CMD ["/usr/bin/pict-rs"]
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
FROM rustembedded/cross:x86_64-unknown-linux-musl AS x86_64-builder
|
FROM rustembedded/cross:x86_64-unknown-linux-gnu AS x86_64-builder
|
||||||
|
|
||||||
ARG UID=991
|
ARG UID=991
|
||||||
ARG GID=991
|
ARG GID=991
|
||||||
|
|
||||||
ENV TOOLCHAIN=stable
|
ENV TOOLCHAIN=stable
|
||||||
ENV TARGET=x86_64-unknown-linux-musl
|
ENV TARGET=x86_64-unknown-linux-gnu
|
||||||
ENV TOOL=x86_64-linux-musl
|
ENV TOOL=x86_64-linux-gnu
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
|
@ -29,7 +29,7 @@ RUN \
|
||||||
USER build
|
USER build
|
||||||
WORKDIR /opt/build
|
WORKDIR /opt/build
|
||||||
|
|
||||||
ENV PATH=/opt/build/.cargo/bin:/usr/local/musl/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
ENV PATH=$PATH:/opt/build/.cargo/bin
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
chmod +x rustup.sh && \
|
chmod +x rustup.sh && \
|
||||||
|
@ -38,10 +38,22 @@ RUN \
|
||||||
|
|
||||||
FROM x86_64-builder as builder
|
FROM x86_64-builder as builder
|
||||||
|
|
||||||
|
USER root
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
dpkg --add-architecture amd64 && \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get -y install libgexiv2-dev:amd64
|
||||||
|
|
||||||
|
USER build
|
||||||
|
|
||||||
ARG TAG=master
|
ARG TAG=master
|
||||||
ARG REPOSITORY=https://git.asonix.dog/asonix/pict-rs
|
ARG REPOSITORY=https://git.asonix.dog/asonix/pict-rs
|
||||||
ARG BINARY=pict-rs
|
ARG BINARY=pict-rs
|
||||||
|
|
||||||
|
ENV PKG_CONFIG_ALLOW_CROSS=1
|
||||||
|
ENV PKG_CONFIG_PATH=/usr/lib/$TOOL/pkgconfig:/usr/lib/$PKGCONFIG
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
git clone -b $TAG $REPOSITORY repo
|
git clone -b $TAG $REPOSITORY repo
|
||||||
|
|
||||||
|
@ -51,18 +63,26 @@ RUN \
|
||||||
cargo build --release --target $TARGET && \
|
cargo build --release --target $TARGET && \
|
||||||
$TOOL-strip target/$TARGET/release/$BINARY
|
$TOOL-strip target/$TARGET/release/$BINARY
|
||||||
|
|
||||||
FROM amd64/alpine:3.11
|
FROM amd64/ubuntu:20.04
|
||||||
|
|
||||||
ARG UID=991
|
ARG UID=991
|
||||||
ARG GID=991
|
ARG GID=991
|
||||||
ARG BINARY=pict-rs
|
ARG BINARY=pict-rs
|
||||||
|
|
||||||
COPY --from=builder /opt/build/repo/target/x86_64-unknown-linux-musl/release/$BINARY /usr/bin/$BINARY
|
COPY --from=builder /opt/build/repo/target/x86_64-unknown-linux-gnu/release/$BINARY /usr/bin/$BINARY
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
apk add tini && \
|
apt-get update && \
|
||||||
addgroup -g $GID pictrs && \
|
apt-get -y upgrade && \
|
||||||
adduser -D -G pictrs -u $UID -g "" -h /opt/pictrs pictrs
|
apt-get -y install tini libgexiv2-2 && \
|
||||||
|
addgroup --gid $GID pictrs && \
|
||||||
|
adduser \
|
||||||
|
--disabled-password \
|
||||||
|
--gecos "" \
|
||||||
|
--ingroup pictrs \
|
||||||
|
--uid $UID \
|
||||||
|
--home /opt/pictrs \
|
||||||
|
pictrs
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
chown -R pictrs:pictrs /mnt
|
chown -R pictrs:pictrs /mnt
|
||||||
|
@ -70,5 +90,5 @@ RUN \
|
||||||
VOLUME /mnt
|
VOLUME /mnt
|
||||||
WORKDIR /opt/pictrs
|
WORKDIR /opt/pictrs
|
||||||
USER pictrs
|
USER pictrs
|
||||||
ENTRYPOINT ["/sbin/tini", "--"]
|
ENTRYPOINT ["/usr/bin/tini", "--"]
|
||||||
CMD ["/usr/bin/pict-rs", "-p", "/mnt", "-a", "0.0.0.0:8080", "-w", "thumbnail"]
|
CMD ["/usr/bin/pict-rs", "-p", "/mnt", "-a", "0.0.0.0:8080", "-w", "thumbnail"]
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
FROM rustembedded/cross:arm-unknown-linux-musleabihf AS arm32v7-builder
|
FROM rustembedded/cross:arm-unknown-linux-gnueabihf AS arm32v7-builder
|
||||||
|
|
||||||
ARG UID=991
|
ARG UID=991
|
||||||
ARG GID=991
|
ARG GID=991
|
||||||
|
|
||||||
ENV TOOLCHAIN=stable
|
ENV TOOLCHAIN=stable
|
||||||
ENV TARGET=arm-unknown-linux-musleabihf
|
ENV TARGET=arm-unknown-linux-gnueabihf
|
||||||
ENV TOOL=arm-linux-musleabihf
|
ENV TOOL=arm-linux-gnueabihf
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
|
@ -29,7 +29,7 @@ RUN \
|
||||||
USER build
|
USER build
|
||||||
WORKDIR /opt/build
|
WORKDIR /opt/build
|
||||||
|
|
||||||
ENV PATH=/opt/build/.cargo/bin:/usr/local/musl/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
ENV PATH=$PATH:/opt/build/.cargo/bin
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
chmod +x rustup.sh && \
|
chmod +x rustup.sh && \
|
||||||
|
@ -38,10 +38,22 @@ RUN \
|
||||||
|
|
||||||
FROM arm32v7-builder as builder
|
FROM arm32v7-builder as builder
|
||||||
|
|
||||||
|
USER root
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
dpkg --add-architecture armhf && \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get -y install libgexiv2-dev:armhf
|
||||||
|
|
||||||
|
USER build
|
||||||
|
|
||||||
ARG TAG=master
|
ARG TAG=master
|
||||||
ARG REPOSITORY=https://git.asonix.dog/asonix/pict-rs
|
ARG REPOSITORY=https://git.asonix.dog/asonix/pict-rs
|
||||||
ARG BINARY=pict-rs
|
ARG BINARY=pict-rs
|
||||||
|
|
||||||
|
ENV PKG_CONFIG_ALLOW_CROSS=1
|
||||||
|
ENV PKG_CONFIG_PATH=/usr/lib/$TOOL/pkgconfig:/usr/lib/$PKGCONFIG
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
git clone -b $TAG $REPOSITORY repo
|
git clone -b $TAG $REPOSITORY repo
|
||||||
|
|
||||||
|
@ -51,18 +63,26 @@ RUN \
|
||||||
cargo build --release --target $TARGET && \
|
cargo build --release --target $TARGET && \
|
||||||
$TOOL-strip target/$TARGET/release/$BINARY
|
$TOOL-strip target/$TARGET/release/$BINARY
|
||||||
|
|
||||||
FROM arm32v7/alpine:3.11
|
FROM arm32v7/ubuntu:20.04
|
||||||
|
|
||||||
ARG UID=991
|
ARG UID=991
|
||||||
ARG GID=991
|
ARG GID=991
|
||||||
ARG BINARY=pict-rs
|
ARG BINARY=pict-rs
|
||||||
|
|
||||||
COPY --from=builder /opt/build/repo/target/arm-unknown-linux-musleabihf/release/$BINARY /usr/bin/$BINARY
|
COPY --from=builder /opt/build/repo/target/arm-unknown-linux-gnueabihf/release/$BINARY /usr/bin/$BINARY
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
apk add tini && \
|
apt-get update && \
|
||||||
addgroup -g $GID pictrs && \
|
apt-get -y upgrade && \
|
||||||
adduser -D -G pictrs -u $UID -g "" -h /opt/pictrs pictrs
|
apt-get -y install tini libgexiv2-2 && \
|
||||||
|
addgroup --gid $GID pictrs && \
|
||||||
|
adduser \
|
||||||
|
--disabled-password \
|
||||||
|
--gecos "" \
|
||||||
|
--ingroup pictrs \
|
||||||
|
--uid $UID \
|
||||||
|
--home /opt/pictrs \
|
||||||
|
pictrs
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
chown -R pictrs:pictrs /mnt
|
chown -R pictrs:pictrs /mnt
|
||||||
|
@ -70,5 +90,5 @@ RUN \
|
||||||
VOLUME /mnt
|
VOLUME /mnt
|
||||||
WORKDIR /opt/pictrs
|
WORKDIR /opt/pictrs
|
||||||
USER pictrs
|
USER pictrs
|
||||||
ENTRYPOINT ["/sbin/tini", "--"]
|
ENTRYPOINT ["/usr/bin/tini", "--"]
|
||||||
CMD ["/usr/bin/pict-rs", "-p", "/mnt", "-a", "0.0.0.0:8080", "-w", "thumbnail"]
|
CMD ["/usr/bin/pict-rs", "-p", "/mnt", "-a", "0.0.0.0:8080", "-w", "thumbnail"]
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
FROM rustembedded/cross:aarch64-unknown-linux-musl AS aarch64-builder
|
FROM rustembedded/cross:aarch64-unknown-linux-gnu AS aarch64-builder
|
||||||
|
|
||||||
ARG UID=991
|
ARG UID=991
|
||||||
ARG GID=991
|
ARG GID=991
|
||||||
|
|
||||||
ENV TOOLCHAIN=stable
|
ENV TOOLCHAIN=stable
|
||||||
ENV TARGET=aarch64-unknown-linux-musl
|
ENV TARGET=aarch64-unknown-linux-gnu
|
||||||
ENV TOOL=aarch64-linux-musl
|
ENV TOOL=aarch64-linux-gnu
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
|
@ -29,7 +29,7 @@ RUN \
|
||||||
USER build
|
USER build
|
||||||
WORKDIR /opt/build
|
WORKDIR /opt/build
|
||||||
|
|
||||||
ENV PATH=/opt/build/.cargo/bin:/usr/local/musl/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
ENV PATH=$PATH:/opt/build/.cargo/bin
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
chmod +x rustup.sh && \
|
chmod +x rustup.sh && \
|
||||||
|
@ -38,10 +38,22 @@ RUN \
|
||||||
|
|
||||||
FROM aarch64-builder as builder
|
FROM aarch64-builder as builder
|
||||||
|
|
||||||
|
USER root
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
dpkg --add-architecture arm64 && \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get -y install libgexiv2-dev:arm64
|
||||||
|
|
||||||
|
USER build
|
||||||
|
|
||||||
ARG TAG=master
|
ARG TAG=master
|
||||||
ARG REPOSITORY=https://git.asonix.dog/asonix/pict-rs
|
ARG REPOSITORY=https://git.asonix.dog/asonix/pict-rs
|
||||||
ARG BINARY=pict-rs
|
ARG BINARY=pict-rs
|
||||||
|
|
||||||
|
ENV PKG_CONFIG_ALLOW_CROSS=1
|
||||||
|
ENV PKG_CONFIG_PATH=/usr/lib/$TOOL/pkgconfig:/usr/lib/$PKGCONFIG
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
git clone -b $TAG $REPOSITORY repo
|
git clone -b $TAG $REPOSITORY repo
|
||||||
|
|
||||||
|
@ -51,18 +63,26 @@ RUN \
|
||||||
cargo build --release --target $TARGET && \
|
cargo build --release --target $TARGET && \
|
||||||
$TOOL-strip target/$TARGET/release/$BINARY
|
$TOOL-strip target/$TARGET/release/$BINARY
|
||||||
|
|
||||||
FROM arm64v8/alpine:3.11
|
FROM arm64v8/ubuntu:20.04
|
||||||
|
|
||||||
ARG UID=991
|
ARG UID=991
|
||||||
ARG GID=991
|
ARG GID=991
|
||||||
ARG BINARY=pict-rs
|
ARG BINARY=pict-rs
|
||||||
|
|
||||||
COPY --from=builder /opt/build/repo/target/aarch64-unknown-linux-musl/release/$BINARY /usr/bin/$BINARY
|
COPY --from=builder /opt/build/repo/target/aarch64-unknown-linux-gnu/release/$BINARY /usr/bin/$BINARY
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
apk add tini && \
|
apt-get update && \
|
||||||
addgroup -g $GID pictrs && \
|
apt-get -y upgrade && \
|
||||||
adduser -D -G pictrs -u $UID -g "" -h /opt/pictrs pictrs
|
apt-get -y install tini libgexiv2-2 && \
|
||||||
|
addgroup --gid $GID pictrs && \
|
||||||
|
adduser \
|
||||||
|
--disabled-password \
|
||||||
|
--gecos "" \
|
||||||
|
--ingroup pictrs \
|
||||||
|
--uid $UID \
|
||||||
|
--home /opt/pictrs \
|
||||||
|
pictrs
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
chown -R pictrs:pictrs /mnt
|
chown -R pictrs:pictrs /mnt
|
||||||
|
@ -70,5 +90,5 @@ RUN \
|
||||||
VOLUME /mnt
|
VOLUME /mnt
|
||||||
WORKDIR /opt/pictrs
|
WORKDIR /opt/pictrs
|
||||||
USER pictrs
|
USER pictrs
|
||||||
ENTRYPOINT ["/sbin/tini", "--"]
|
ENTRYPOINT ["/usr/bin/tini", "--"]
|
||||||
CMD ["/usr/bin/pict-rs", "-p", "/mnt", "-a", "0.0.0.0:8080", "-w", "thumbnail"]
|
CMD ["/usr/bin/pict-rs", "-p", "/mnt", "-a", "0.0.0.0:8080", "-w", "thumbnail"]
|
||||||
|
|
|
@ -90,22 +90,6 @@ pub(crate) enum Format {
|
||||||
Png,
|
Png,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Format {
|
|
||||||
pub(crate) fn to_image_format(&self) -> image::ImageFormat {
|
|
||||||
match self {
|
|
||||||
Format::Jpeg => image::ImageFormat::Jpeg,
|
|
||||||
Format::Png => image::ImageFormat::Png,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn to_mime(&self) -> mime::Mime {
|
|
||||||
match self {
|
|
||||||
Format::Jpeg => mime::IMAGE_JPEG,
|
|
||||||
Format::Png => mime::IMAGE_PNG,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::str::FromStr for Format {
|
impl std::str::FromStr for Format {
|
||||||
type Err = FormatError;
|
type Err = FormatError;
|
||||||
|
|
||||||
|
|
|
@ -39,9 +39,6 @@ pub(crate) enum UploadError {
|
||||||
#[error("Provided token did not match expected token")]
|
#[error("Provided token did not match expected token")]
|
||||||
InvalidToken,
|
InvalidToken,
|
||||||
|
|
||||||
#[error("Uploaded content could not be validated as an image")]
|
|
||||||
InvalidImage(image::error::ImageError),
|
|
||||||
|
|
||||||
#[error("Unsupported image format")]
|
#[error("Unsupported image format")]
|
||||||
UnsupportedFormat,
|
UnsupportedFormat,
|
||||||
|
|
||||||
|
@ -65,6 +62,12 @@ pub(crate) enum UploadError {
|
||||||
|
|
||||||
#[error("Error validating Gif file, {0}")]
|
#[error("Error validating Gif file, {0}")]
|
||||||
Gif(#[from] GifError),
|
Gif(#[from] GifError),
|
||||||
|
|
||||||
|
#[error("Tried to create file, but file already exists")]
|
||||||
|
FileExists,
|
||||||
|
|
||||||
|
#[error("File metadata could not be parsed, {0}")]
|
||||||
|
Validate(#[from] rexiv2::Rexiv2Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<actix_web::client::SendRequestError> for UploadError {
|
impl From<actix_web::client::SendRequestError> for UploadError {
|
||||||
|
|
16
src/main.rs
16
src/main.rs
|
@ -7,6 +7,7 @@ use actix_web::{
|
||||||
web, App, HttpResponse, HttpServer,
|
web, App, HttpResponse, HttpServer,
|
||||||
};
|
};
|
||||||
use futures::stream::{Stream, TryStreamExt};
|
use futures::stream::{Stream, TryStreamExt};
|
||||||
|
use image::{ImageFormat, ImageOutputFormat};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::{collections::HashSet, path::PathBuf};
|
use std::{collections::HashSet, path::PathBuf};
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
@ -150,6 +151,7 @@ async fn download(
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Delete aliases and files
|
||||||
#[instrument(skip(manager))]
|
#[instrument(skip(manager))]
|
||||||
async fn delete(
|
async fn delete(
|
||||||
manager: web::Data<UploadManager>,
|
manager: web::Data<UploadManager>,
|
||||||
|
@ -162,6 +164,16 @@ async fn delete(
|
||||||
Ok(HttpResponse::NoContent().finish())
|
Ok(HttpResponse::NoContent().finish())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn convert_format(format: ImageFormat) -> Result<ImageOutputFormat, UploadError> {
|
||||||
|
match format {
|
||||||
|
ImageFormat::Jpeg => Ok(ImageOutputFormat::Jpeg(100)),
|
||||||
|
ImageFormat::Png => Ok(ImageOutputFormat::Png),
|
||||||
|
ImageFormat::Gif => Ok(ImageOutputFormat::Gif),
|
||||||
|
ImageFormat::Bmp => Ok(ImageOutputFormat::Bmp),
|
||||||
|
_ => Err(UploadError::UnsupportedFormat),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Serve files
|
/// Serve files
|
||||||
#[instrument(skip(manager, whitelist))]
|
#[instrument(skip(manager, whitelist))]
|
||||||
async fn serve(
|
async fn serve(
|
||||||
|
@ -220,8 +232,8 @@ async fn serve(
|
||||||
debug!("Exporting image");
|
debug!("Exporting image");
|
||||||
let img_bytes: bytes::Bytes = web::block(move || {
|
let img_bytes: bytes::Bytes = web::block(move || {
|
||||||
let mut bytes = std::io::Cursor::new(vec![]);
|
let mut bytes = std::io::Cursor::new(vec![]);
|
||||||
img.write_to(&mut bytes, format)?;
|
img.write_to(&mut bytes, convert_format(format)?)?;
|
||||||
Ok(bytes::Bytes::from(bytes.into_inner())) as Result<_, image::error::ImageError>
|
Ok(bytes::Bytes::from(bytes.into_inner())) as Result<_, UploadError>
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{config::Format, error::UploadError, safe_save_file, to_ext, validate::validate_image};
|
use crate::{config::Format, error::UploadError, to_ext, validate::validate_image};
|
||||||
use actix_web::web;
|
use actix_web::web;
|
||||||
use futures::stream::{Stream, StreamExt};
|
use futures::stream::{Stream, StreamExt, TryStreamExt};
|
||||||
use sha2::Digest;
|
use sha2::Digest;
|
||||||
use std::{path::PathBuf, pin::Pin, sync::Arc};
|
use std::{path::PathBuf, pin::Pin, sync::Arc};
|
||||||
use tracing::{debug, error, info, instrument, warn, Span};
|
use tracing::{debug, error, info, instrument, warn, Span};
|
||||||
|
@ -272,29 +272,32 @@ impl UploadManager {
|
||||||
) -> Result<String, UploadError>
|
) -> Result<String, UploadError>
|
||||||
where
|
where
|
||||||
UploadError: From<E>,
|
UploadError: From<E>,
|
||||||
|
E: Unpin,
|
||||||
{
|
{
|
||||||
|
// -- READ IN BYTES FROM CLIENT --
|
||||||
debug!("Reading stream");
|
debug!("Reading stream");
|
||||||
let bytes = read_stream(stream).await?;
|
let tmpfile = tmp_file();
|
||||||
|
safe_save_stream(tmpfile.clone(), stream).await?;
|
||||||
|
|
||||||
let (bytes, content_type) = if validate {
|
let content_type = if validate {
|
||||||
debug!("Validating image");
|
debug!("Validating image");
|
||||||
let format = self.inner.format.clone();
|
let format = self.inner.format.clone();
|
||||||
validate_image(bytes, format).await?
|
validate_image(tmpfile.clone(), format).await?
|
||||||
} else {
|
} else {
|
||||||
(bytes, content_type)
|
content_type
|
||||||
};
|
};
|
||||||
|
|
||||||
// -- DUPLICATE CHECKS --
|
// -- DUPLICATE CHECKS --
|
||||||
|
|
||||||
// Cloning bytes is fine because it's actually a pointer
|
// Cloning bytes is fine because it's actually a pointer
|
||||||
debug!("Hashing bytes");
|
debug!("Hashing bytes");
|
||||||
let hash = self.hash(bytes.clone()).await?;
|
let hash = self.hash(tmpfile.clone()).await?;
|
||||||
|
|
||||||
debug!("Storing alias");
|
debug!("Storing alias");
|
||||||
self.add_existing_alias(&hash, &alias).await?;
|
self.add_existing_alias(&hash, &alias).await?;
|
||||||
|
|
||||||
debug!("Saving file");
|
debug!("Saving file");
|
||||||
self.save_upload(bytes, hash, content_type).await?;
|
self.save_upload(tmpfile, hash, content_type).await?;
|
||||||
|
|
||||||
// Return alias to file
|
// Return alias to file
|
||||||
Ok(alias)
|
Ok(alias)
|
||||||
|
@ -305,27 +308,29 @@ impl UploadManager {
|
||||||
pub(crate) async fn upload<E>(&self, stream: UploadStream<E>) -> Result<String, UploadError>
|
pub(crate) async fn upload<E>(&self, stream: UploadStream<E>) -> Result<String, UploadError>
|
||||||
where
|
where
|
||||||
UploadError: From<E>,
|
UploadError: From<E>,
|
||||||
|
E: Unpin,
|
||||||
{
|
{
|
||||||
// -- READ IN BYTES FROM CLIENT --
|
// -- READ IN BYTES FROM CLIENT --
|
||||||
debug!("Reading stream");
|
debug!("Reading stream");
|
||||||
let bytes = read_stream(stream).await?;
|
let tmpfile = tmp_file();
|
||||||
|
safe_save_stream(tmpfile.clone(), stream).await?;
|
||||||
|
|
||||||
// -- VALIDATE IMAGE --
|
// -- VALIDATE IMAGE --
|
||||||
debug!("Validating image");
|
debug!("Validating image");
|
||||||
let format = self.inner.format.clone();
|
let format = self.inner.format.clone();
|
||||||
let (bytes, content_type) = validate_image(bytes, format).await?;
|
let content_type = validate_image(tmpfile.clone(), format).await?;
|
||||||
|
|
||||||
// -- DUPLICATE CHECKS --
|
// -- DUPLICATE CHECKS --
|
||||||
|
|
||||||
// Cloning bytes is fine because it's actually a pointer
|
// Cloning bytes is fine because it's actually a pointer
|
||||||
debug!("Hashing bytes");
|
debug!("Hashing bytes");
|
||||||
let hash = self.hash(bytes.clone()).await?;
|
let hash = self.hash(tmpfile.clone()).await?;
|
||||||
|
|
||||||
debug!("Adding alias");
|
debug!("Adding alias");
|
||||||
let alias = self.add_alias(&hash, content_type.clone()).await?;
|
let alias = self.add_alias(&hash, content_type.clone()).await?;
|
||||||
|
|
||||||
debug!("Saving file");
|
debug!("Saving file");
|
||||||
self.save_upload(bytes, hash, content_type).await?;
|
self.save_upload(tmpfile, hash, content_type).await?;
|
||||||
|
|
||||||
// Return alias to file
|
// Return alias to file
|
||||||
Ok(alias)
|
Ok(alias)
|
||||||
|
@ -405,7 +410,7 @@ impl UploadManager {
|
||||||
// check duplicates & store image if new
|
// check duplicates & store image if new
|
||||||
async fn save_upload(
|
async fn save_upload(
|
||||||
&self,
|
&self,
|
||||||
bytes: bytes::Bytes,
|
tmpfile: PathBuf,
|
||||||
hash: Hash,
|
hash: Hash,
|
||||||
content_type: mime::Mime,
|
content_type: mime::Mime,
|
||||||
) -> Result<(), UploadError> {
|
) -> Result<(), UploadError> {
|
||||||
|
@ -421,19 +426,29 @@ impl UploadManager {
|
||||||
let mut real_path = self.image_dir();
|
let mut real_path = self.image_dir();
|
||||||
real_path.push(name);
|
real_path.push(name);
|
||||||
|
|
||||||
safe_save_file(real_path, bytes).await?;
|
safe_move_file(tmpfile, real_path).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// produce a sh256sum of the uploaded file
|
// produce a sh256sum of the uploaded file
|
||||||
async fn hash(&self, bytes: bytes::Bytes) -> Result<Hash, UploadError> {
|
async fn hash(&self, tmpfile: PathBuf) -> Result<Hash, UploadError> {
|
||||||
let mut hasher = self.inner.hasher.clone();
|
let mut hasher = self.inner.hasher.clone();
|
||||||
let hash = web::block(move || {
|
|
||||||
hasher.update(&bytes);
|
let mut stream = actix_fs::read_to_stream(tmpfile).await?;
|
||||||
Ok(hasher.finalize_reset().to_vec()) as Result<_, UploadError>
|
|
||||||
})
|
while let Some(res) = stream.next().await {
|
||||||
.await?;
|
let bytes = res?;
|
||||||
|
hasher = web::block(move || {
|
||||||
|
hasher.update(&bytes);
|
||||||
|
Ok(hasher) as Result<_, UploadError>
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let hash =
|
||||||
|
web::block(move || Ok(hasher.finalize_reset().to_vec()) as Result<_, UploadError>)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(Hash::new(hash))
|
Ok(Hash::new(hash))
|
||||||
}
|
}
|
||||||
|
@ -620,20 +635,68 @@ impl UploadManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(stream))]
|
pub(crate) fn tmp_file() -> PathBuf {
|
||||||
async fn read_stream<E>(mut stream: UploadStream<E>) -> Result<bytes::Bytes, UploadError>
|
use rand::distributions::{Alphanumeric, Distribution};
|
||||||
where
|
let limit: usize = 10;
|
||||||
UploadError: From<E>,
|
let rng = rand::thread_rng();
|
||||||
{
|
|
||||||
let mut bytes = bytes::BytesMut::new();
|
|
||||||
|
|
||||||
while let Some(res) = stream.next().await {
|
let s: String = Alphanumeric.sample_iter(rng).take(limit).collect();
|
||||||
let new = res?;
|
|
||||||
debug!("Extending with {} bytes", new.len());
|
let name = format!("{}.tmp", s);
|
||||||
bytes.extend(new);
|
|
||||||
|
let mut path = std::env::temp_dir();
|
||||||
|
path.push("pict-rs");
|
||||||
|
path.push(&name);
|
||||||
|
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument]
|
||||||
|
async fn safe_move_file(from: PathBuf, to: PathBuf) -> Result<(), UploadError> {
|
||||||
|
if let Some(path) = to.parent() {
|
||||||
|
debug!("Creating directory {:?}", path);
|
||||||
|
actix_fs::create_dir_all(path.to_owned()).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(bytes.freeze())
|
debug!("Checking if {:?} already exists", to);
|
||||||
|
if let Err(e) = actix_fs::metadata(to.clone()).await {
|
||||||
|
if e.kind() != Some(std::io::ErrorKind::NotFound) {
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(UploadError::FileExists);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("Moving {:?} to {:?}", from, to);
|
||||||
|
actix_fs::rename(from, to).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(stream))]
|
||||||
|
async fn safe_save_stream<E>(to: PathBuf, stream: UploadStream<E>) -> Result<(), UploadError>
|
||||||
|
where
|
||||||
|
UploadError: From<E>,
|
||||||
|
E: Unpin,
|
||||||
|
{
|
||||||
|
if let Some(path) = to.parent() {
|
||||||
|
debug!("Creating directory {:?}", path);
|
||||||
|
actix_fs::create_dir_all(path.to_owned()).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("Checking if {:?} alreayd exists", to);
|
||||||
|
if let Err(e) = actix_fs::metadata(to.clone()).await {
|
||||||
|
if e.kind() != Some(std::io::ErrorKind::NotFound) {
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(UploadError::FileExists);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("Writing stream to {:?}", to);
|
||||||
|
let stream = stream.err_into::<UploadError>();
|
||||||
|
actix_fs::write_stream(to, stream).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn remove_path(path: sled::IVec) -> Result<(), UploadError> {
|
async fn remove_path(path: sled::IVec) -> Result<(), UploadError> {
|
||||||
|
|
181
src/validate.rs
181
src/validate.rs
|
@ -1,9 +1,13 @@
|
||||||
use crate::{config::Format, error::UploadError};
|
use crate::{config::Format, error::UploadError, upload_manager::tmp_file};
|
||||||
use actix_web::web;
|
use actix_web::web;
|
||||||
use bytes::Bytes;
|
use image::{io::Reader, ImageDecoder, ImageEncoder, ImageFormat};
|
||||||
use image::{ImageDecoder, ImageEncoder, ImageFormat};
|
use rexiv2::{MediaType, Metadata};
|
||||||
use std::io::Cursor;
|
use std::{
|
||||||
use tracing::{debug, instrument, Span};
|
fs::File,
|
||||||
|
io::{BufReader, BufWriter, Write},
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
use tracing::{debug, instrument, trace, Span};
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub(crate) enum GifError {
|
pub(crate) enum GifError {
|
||||||
|
@ -15,86 +19,119 @@ pub(crate) enum GifError {
|
||||||
}
|
}
|
||||||
|
|
||||||
// import & export image using the image crate
|
// import & export image using the image crate
|
||||||
#[instrument(skip(bytes, prescribed_format))]
|
#[instrument]
|
||||||
pub(crate) async fn validate_image(
|
pub(crate) async fn validate_image(
|
||||||
bytes: Bytes,
|
tmpfile: PathBuf,
|
||||||
prescribed_format: Option<Format>,
|
prescribed_format: Option<Format>,
|
||||||
) -> Result<(Bytes, mime::Mime), UploadError> {
|
) -> Result<mime::Mime, UploadError> {
|
||||||
let span = Span::current();
|
let span = Span::current();
|
||||||
|
|
||||||
let tup = web::block(move || {
|
let content_type = web::block(move || {
|
||||||
let entered = span.enter();
|
let entered = span.enter();
|
||||||
if let Some(prescribed) = prescribed_format {
|
|
||||||
debug!("Load from memory");
|
|
||||||
let img = image::load_from_memory(&bytes).map_err(UploadError::InvalidImage)?;
|
|
||||||
debug!("Loaded");
|
|
||||||
|
|
||||||
let mime = prescribed.to_mime();
|
let meta = Metadata::new_from_path(&tmpfile)?;
|
||||||
|
|
||||||
debug!("Writing");
|
let content_type = match (prescribed_format, meta.get_media_type()?) {
|
||||||
let mut bytes = Cursor::new(vec![]);
|
(_, MediaType::Gif) => {
|
||||||
img.write_to(&mut bytes, prescribed.to_image_format())?;
|
let newfile = tmp_file();
|
||||||
debug!("Written");
|
validate_gif(&tmpfile, &newfile)?;
|
||||||
return Ok((Bytes::from(bytes.into_inner()), mime));
|
|
||||||
}
|
|
||||||
|
|
||||||
let format = image::guess_format(&bytes).map_err(UploadError::InvalidImage)?;
|
mime::IMAGE_GIF
|
||||||
|
}
|
||||||
|
(Some(Format::Jpeg), MediaType::Jpeg) | (None, MediaType::Jpeg) => {
|
||||||
|
validate(&tmpfile, ImageFormat::Jpeg)?;
|
||||||
|
|
||||||
debug!("Validating {:?}", format);
|
meta.clear();
|
||||||
let res = match format {
|
meta.save_to_file(&tmpfile)?;
|
||||||
ImageFormat::Png => Ok((validate_png(bytes)?, mime::IMAGE_PNG)),
|
|
||||||
ImageFormat::Jpeg => Ok((validate_jpg(bytes)?, mime::IMAGE_JPEG)),
|
mime::IMAGE_JPEG
|
||||||
ImageFormat::Bmp => Ok((validate_bmp(bytes)?, mime::IMAGE_BMP)),
|
}
|
||||||
ImageFormat::Gif => Ok((validate_gif(bytes)?, mime::IMAGE_GIF)),
|
(Some(Format::Png), MediaType::Png) | (None, MediaType::Png) => {
|
||||||
_ => Err(UploadError::UnsupportedFormat),
|
validate(&tmpfile, ImageFormat::Png)?;
|
||||||
|
|
||||||
|
meta.clear();
|
||||||
|
meta.save_to_file(&tmpfile)?;
|
||||||
|
|
||||||
|
mime::IMAGE_PNG
|
||||||
|
}
|
||||||
|
(Some(Format::Jpeg), _) => {
|
||||||
|
let newfile = tmp_file();
|
||||||
|
convert(&tmpfile, &newfile, ImageFormat::Jpeg)?;
|
||||||
|
|
||||||
|
mime::IMAGE_JPEG
|
||||||
|
}
|
||||||
|
(Some(Format::Png), _) => {
|
||||||
|
let newfile = tmp_file();
|
||||||
|
convert(&tmpfile, &newfile, ImageFormat::Png)?;
|
||||||
|
|
||||||
|
mime::IMAGE_PNG
|
||||||
|
}
|
||||||
|
(_, MediaType::Bmp) => {
|
||||||
|
let newfile = tmp_file();
|
||||||
|
validate_bmp(&tmpfile, &newfile)?;
|
||||||
|
|
||||||
|
mime::IMAGE_BMP
|
||||||
|
}
|
||||||
|
_ => return Err(UploadError::UnsupportedFormat),
|
||||||
};
|
};
|
||||||
debug!("Validated");
|
|
||||||
drop(entered);
|
drop(entered);
|
||||||
res
|
Ok(content_type) as Result<mime::Mime, UploadError>
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(tup)
|
Ok(content_type)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(bytes))]
|
#[instrument]
|
||||||
fn validate_png(bytes: Bytes) -> Result<Bytes, UploadError> {
|
fn convert(from: &PathBuf, to: &PathBuf, format: ImageFormat) -> Result<(), UploadError> {
|
||||||
let decoder = image::png::PngDecoder::new(Cursor::new(&bytes))?;
|
debug!("Converting");
|
||||||
|
let reader = Reader::new(BufReader::new(File::open(from)?)).with_guessed_format()?;
|
||||||
|
|
||||||
let mut bytes = Cursor::new(vec![]);
|
if reader.format() != Some(format) {
|
||||||
let encoder = image::png::PNGEncoder::new(&mut bytes);
|
return Err(UploadError::UnsupportedFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
let img = reader.decode()?;
|
||||||
|
|
||||||
|
img.save_with_format(to, format)?;
|
||||||
|
std::fs::rename(to, from)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument]
|
||||||
|
fn validate(path: &PathBuf, format: ImageFormat) -> Result<(), UploadError> {
|
||||||
|
debug!("Validating");
|
||||||
|
let reader = Reader::new(BufReader::new(File::open(path)?)).with_guessed_format()?;
|
||||||
|
|
||||||
|
if reader.format() != Some(format) {
|
||||||
|
return Err(UploadError::UnsupportedFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.decode()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument]
|
||||||
|
fn validate_bmp(from: &PathBuf, to: &PathBuf) -> Result<(), UploadError> {
|
||||||
|
debug!("Transmuting BMP");
|
||||||
|
let decoder = image::bmp::BmpDecoder::new(BufReader::new(File::open(from)?))?;
|
||||||
|
|
||||||
|
let mut writer = BufWriter::new(File::create(to)?);
|
||||||
|
let encoder = image::bmp::BMPEncoder::new(&mut writer);
|
||||||
validate_still_image(decoder, encoder)?;
|
validate_still_image(decoder, encoder)?;
|
||||||
|
|
||||||
Ok(Bytes::from(bytes.into_inner()))
|
writer.flush()?;
|
||||||
|
std::fs::rename(to, from)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(bytes))]
|
#[instrument]
|
||||||
fn validate_jpg(bytes: Bytes) -> Result<Bytes, UploadError> {
|
fn validate_gif(from: &PathBuf, to: &PathBuf) -> Result<(), GifError> {
|
||||||
let decoder = image::jpeg::JpegDecoder::new(Cursor::new(&bytes))?;
|
debug!("Transmuting GIF");
|
||||||
|
|
||||||
let mut bytes = Cursor::new(vec![]);
|
|
||||||
let encoder = image::jpeg::JPEGEncoder::new(&mut bytes);
|
|
||||||
validate_still_image(decoder, encoder)?;
|
|
||||||
|
|
||||||
Ok(Bytes::from(bytes.into_inner()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(skip(bytes))]
|
|
||||||
fn validate_bmp(bytes: Bytes) -> Result<Bytes, UploadError> {
|
|
||||||
let decoder = image::bmp::BmpDecoder::new(Cursor::new(&bytes))?;
|
|
||||||
|
|
||||||
let mut bytes = Cursor::new(vec![]);
|
|
||||||
let encoder = image::bmp::BMPEncoder::new(&mut bytes);
|
|
||||||
validate_still_image(decoder, encoder)?;
|
|
||||||
|
|
||||||
Ok(Bytes::from(bytes.into_inner()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(skip(bytes))]
|
|
||||||
fn validate_gif(bytes: Bytes) -> Result<Bytes, GifError> {
|
|
||||||
use gif::{Parameter, SetParameter};
|
use gif::{Parameter, SetParameter};
|
||||||
|
|
||||||
let mut decoder = gif::Decoder::new(Cursor::new(&bytes));
|
let mut decoder = gif::Decoder::new(BufReader::new(File::open(from)?));
|
||||||
|
|
||||||
decoder.set(gif::ColorOutput::Indexed);
|
decoder.set(gif::ColorOutput::Indexed);
|
||||||
|
|
||||||
|
@ -104,19 +141,21 @@ fn validate_gif(bytes: Bytes) -> Result<Bytes, GifError> {
|
||||||
let height = reader.height();
|
let height = reader.height();
|
||||||
let global_palette = reader.global_palette().unwrap_or(&[]);
|
let global_palette = reader.global_palette().unwrap_or(&[]);
|
||||||
|
|
||||||
let mut bytes = Cursor::new(vec![]);
|
let mut writer = BufWriter::new(File::create(to)?);
|
||||||
{
|
let mut encoder = gif::Encoder::new(&mut writer, width, height, global_palette)?;
|
||||||
let mut encoder = gif::Encoder::new(&mut bytes, width, height, global_palette)?;
|
|
||||||
|
|
||||||
gif::Repeat::Infinite.set_param(&mut encoder)?;
|
gif::Repeat::Infinite.set_param(&mut encoder)?;
|
||||||
|
|
||||||
while let Some(frame) = reader.read_next_frame()? {
|
while let Some(frame) = reader.read_next_frame()? {
|
||||||
debug!("Writing frame");
|
trace!("Writing frame");
|
||||||
encoder.write_frame(frame)?;
|
encoder.write_frame(frame)?;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Bytes::from(bytes.into_inner()))
|
drop(encoder);
|
||||||
|
writer.flush()?;
|
||||||
|
|
||||||
|
std::fs::rename(to, from)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_still_image<'a, D, E>(decoder: D, encoder: E) -> Result<(), UploadError>
|
fn validate_still_image<'a, D, E>(decoder: D, encoder: E) -> Result<(), UploadError>
|
||||||
|
|
Loading…
Reference in a new issue