diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 32d5d55..1134010 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,74 +1,44 @@ -ARG HTTPD_VERSION=2.4.49 +ARG HTTPD_VERSION=2.4.62 -FROM httpd:${HTTPD_VERSION} as build-mod-dims - -ARG DIMS_VERSION=3.3.26 -ARG IMAGEMAGICK_VERSION=6.9.12-34 -ARG WEBP_VERSION=1.2.1 -ARG TIFF_VERSION=4.3.0 -ARG PREFIX=/usr/local/imagemagick +FROM httpd:${HTTPD_VERSION}-alpine ENV DIMS_DOWNLOAD_TIMEOUT=60000 ENV DIMS_IMAGEMAGICK_TIMEOUT=20000 -ENV DIMS_CLIENT=development -ENV DIMS_NO_IMAGE_URL="http://placehold.it/350x150" -ENV DIMS_DEFAULT_IMAGE_URL="http://placehold.it/350x150" ENV DIMS_CACHE_CONTROL_MAX_AGE=604800 ENV DIMS_EDGE_CONTROL_DOWNSTREAM_TTL=604800 ENV DIMS_TRUST_SOURCE=true -ENV DIMS_SOURCE_CACHE=604800 +ENV DIMS_MIN_SOURCE_CACHE=0 ENV DIMS_MAX_SOURCE_CACHE=604800 -ENV DIMS_SECRET="" ENV DIMS_CACHE_EXPIRE=604800 -ENV DIMS_NO_IMAGE_CACHE_EXPIRE=60 -ENV DIMS_WHITELIST="*.com *.org *.net *.io" +ENV DIMS_ERROR_IMAGE_CACHE_EXPIRE=60 +ENV DIMS_ERROR_IMAGE_BACKGROUND="#5ADAFD" +ENV PREFIX=/usr/local/apache2 +ENV PATH=${PREFIX}/bin:${PATH} -RUN apt-get -y update && \ - apt-get install -y --no-install-recommends \ - automake libtool autoconf build-essential \ - git ca-certificates \ - libapr1-dev libaprutil1-dev \ - curl \ - libcurl4-openssl-dev libfreetype6-dev libopenexr-dev libxml2-dev \ - libgif-dev libjpeg62-turbo-dev libpng-dev \ - liblcms2-dev pkg-config libssl-dev libpangocairo-1.0-0 wget +# Imagemagick configuration -# WEBP Library -RUN wget https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-${WEBP_VERSION}.tar.gz && \ - tar xzvf libwebp-${WEBP_VERSION}.tar.gz && \ - cd libwebp-${WEBP_VERSION} && \ - ./configure --prefix=/usr/local/imagemagick && \ - make -j4 && make install +# 2GB disk limit +ENV MAGICK_DISK_LIMIT=2147483648 -# TIFF Library -RUN wget http://download.osgeo.org/libtiff/tiff-${TIFF_VERSION}.tar.gz && \ - tar xzvf tiff-${TIFF_VERSION}.tar.gz && \ - cd tiff-${TIFF_VERSION} && \ - ./configure --prefix=$PREFIX --with-webp-include-dir=$PREFIX/include --with-webp-lib-dir=$PREFIX/lib && \ - make -j4 && make install +# 512MB memory limit +ENV MAGICK_MEMORY_LIMIT=536870912 -# Imagemagick -RUN wget https://download.imagemagick.org/ImageMagick/download/releases/ImageMagick-${IMAGEMAGICK_VERSION}.tar.xz && \ - export PKG_CONFIG_PATH=$PREFIX/lib/pkgconfig && \ - tar xvf ImageMagick-${IMAGEMAGICK_VERSION}.tar.xz && \ - cd ImageMagick-${IMAGEMAGICK_VERSION} && \ - ./configure --without-x --with-quantum-depth=8 --prefix=$PREFIX && \ - make -j4 && make install +# 512MB map limit +ENV MAGICK_MAP_LIMIT=536870912 -RUN apt-get --no-install-recommends install -y apt-transport-https apt-utils \ - automake build-essential ccache cmake ca-certificates curl git \ - gcc g++ libc-ares-dev libc-ares2 libcurl4-openssl-dev libre2-dev \ - libssl-dev m4 make pkg-config tar wget zlib1g-dev +# 128MB area limit +ENV MAGICK_AREA_LIMIT=134217728 -WORKDIR /build +RUN apk update && \ + apk add vim gdb zig apr-dev apr-util-dev imagemagick-dev curl-dev \ + imagemagick-jpeg imagemagick-webp imagemagick-tiff && \ + zig build --verbose || true; \ + ln -sf /workspaces/mod_dims/zig-out/lib/libmod_dims.so.4.0.0 ${PREFIX}/modules/libmod_dims.so -RUN apt-get install -y vim && \ - chown -R www-data:www-data /usr/local/apache2 && \ - sed "s|Listen 80|Listen 8000|" /usr/local/apache2/conf/httpd.conf -i && \ - sed "s|^#LoadModule authz_core_module|LoadModule authz_core_module|" /usr/local/apache2/conf/httpd.conf -i && \ - sed "s|^LogLevel warn|LogLevel debug|" /usr/local/apache2/conf/httpd.conf -i && \ - echo "Include conf/extra/dims.conf" >> /usr/local/apache2/conf/httpd.conf +COPY dims.conf /usr/local/apache2/conf/httpd.conf -COPY docker/dims.conf /usr/local/apache2/conf/extra/dims.conf - -ENV LC_ALL="C" \ No newline at end of file +ENV DIMS_CLIENT=development +ENV DIMS_SECRET=devmode +ENV DIMS_WHITELIST="*.com *.net *.org *.io" +ENV DIMS_NO_IMAGE_URL="http://192.168.65.1:8081/mod-dims_00001_.jpg" +ENV DIMS_DEFAULT_IMAGE_URL="http://192.168.65.1:8081/mod-dims_00001_.jpg" \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d793e84..0a12013 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -8,14 +8,31 @@ "containerUser": "root", "remoteUser": "root", - // Set *default* container specific settings.json values on container create. - "settings": {}, - - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "ms-vscode.cpptools" - ], + "customizations": { + "vscode": { + "settings": { + "C_Cpp.default.compilerPath": "/usr/bin/zig", + "C_Cpp.default.includePath": [ + "${default}", + "/usr/local/apache2/include", + "/usr/lib/zig/libc/include", + "/usr/lib/zig/libcxx/include", + "/usr/lib/zig/libc/include/generic-musl", + "/usr/include", + "/usr/include/apr-1/", + "/usr/include/ImageMagick-7/", + "/usr/lib/zig/libc/include/aarch64-linux-musl" + ] + }, + "extensions": [ + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "vscodevim.vim", + "GitHub.copilot" + ] + }, + }, // https://stackoverflow.com/questions/35860527/warning-error-disabling-address-space-randomization-operation-not-permitted "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ], diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..55f1256 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.zig-cache +zig-out +zig-cache diff --git a/.env b/.env new file mode 100644 index 0000000..9c0152a --- /dev/null +++ b/.env @@ -0,0 +1,15 @@ +export DIMS_DOWNLOAD_TIMEOUT=60000 +export DIMS_IMAGEMAGICK_TIMEOUT=20000 +export DIMS_CLIENT=development +export DIMS_NO_IMAGE_URL="http://placehold.it/350x150" +export DIMS_DEFAULT_IMAGE_URL="http://placehold.it/350x150" +export DIMS_CACHE_CONTROL_MAX_AGE=604800 +export DIMS_EDGE_CONTROL_DOWNSTREAM_TTL=604800 +export DIMS_TRUST_SOURCE=true +export DIMS_SOURCE_CACHE=604800 +export DIMS_MIN_SOURCE_CACHE=0 +export DIMS_MAX_SOURCE_CACHE=604800 +export DIMS_SECRET="devmode" +export DIMS_CACHE_EXPIRE=604800 +export DIMS_NO_IMAGE_CACHE_EXPIRE=60 +export DIMS_WHITELIST="*.com *.net *.org *.io 127.0.0.1 localhost" diff --git a/.gitignore b/.gitignore index 4e6da25..cd68f67 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.idea +.DS_Store m4/ Makefile Makefile.in @@ -20,3 +22,20 @@ install-sh missing INSTALL stamp-h1 + +*.lo +*.la +*.lai +*.o +*.Plo +*.Tpo +*.a +*~ + +*.dylib +*.so + +.zig-out +.zig-cache +zig-out +zig-cache \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..790f2a9 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,30 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "mod-dims - debug", + "type": "cppdbg", + "request": "launch", + "program": "/usr/local/apache2/bin/httpd", + "args": ["-X"], + "stopAtEntry": false, + "cwd": ".", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set Disassembly Flavor to Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ], + "preLaunchTask": "zig build" + }, + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4270e1b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "files.associations": { + "*.templ": "templ", + "strings.h": "c" + } +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..f6c5b06 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,15 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "zig build", + "type": "shell", + "command": "zig build --verbose", + "problemMatcher": [], + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} \ No newline at end of file diff --git a/AUTHORS b/AUTHORS deleted file mode 100644 index e69de29..0000000 diff --git a/ChangeLog b/ChangeLog deleted file mode 100644 index 700c6cd..0000000 --- a/ChangeLog +++ /dev/null @@ -1,13 +0,0 @@ - -2012-11-14 Jeremy Collins - - - Added new image manipulation commands: brightness, flip/flop, sepia, - grayscale, rotate and invert. - - - Improved autorun.sh to work better on OS X with brew. - -2012-11-08 Jeremy Collins - - - Convert images with a CMYK colorspace to RGB. - - diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1110752 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,52 @@ +ARG HTTPD_VERSION=2.4.62 + +FROM httpd:${HTTPD_VERSION}-alpine AS builder + +ENV PREFIX=/usr/local/apache2 +ENV PATH=${PREFIX}/bin:${PATH} + +COPY . /build/mod-dims +WORKDIR /build/mod-dims + +RUN apk update && \ + apk add vim gdb zig apr-dev apr-util-dev imagemagick-dev curl-dev \ + imagemagick-jpeg imagemagick-webp imagemagick-tiff && \ + zig build || true; \ + ln -sf /build/mod-dims/zig-out/lib/libmod_dims.so.4.0.0 ${PREFIX}/modules/libmod_dims.so || true + +FROM httpd:${HTTPD_VERSION}-alpine AS final + +ENV DIMS_DOWNLOAD_TIMEOUT=60000 +ENV DIMS_IMAGEMAGICK_TIMEOUT=20000 +ENV DIMS_CACHE_CONTROL_MAX_AGE=604800 +ENV DIMS_EDGE_CONTROL_DOWNSTREAM_TTL=604800 +ENV DIMS_TRUST_SOURCE=true +ENV DIMS_MIN_SOURCE_CACHE=0 +ENV DIMS_MAX_SOURCE_CACHE=604800 +ENV DIMS_CACHE_EXPIRE=604800 +ENV DIMS_ERROR_IMAGE_CACHE_EXPIRE=60 +ENV DIMS_ERROR_IMAGE_BACKGROUND="#5ADAFD" +ENV PREFIX=/usr/local/apache2 +ENV PATH=${PREFIX}/bin:${PATH} + +# Imagemagick configuration + +# 2GB disk limit +ENV MAGICK_DISK_LIMIT=2147483648 + +# 512MB memory limit +ENV MAGICK_MEMORY_LIMIT=536870912 + +# 512MB map limit +ENV MAGICK_MAP_LIMIT=536870912 + +# 128MB area limit +ENV MAGICK_AREA_LIMIT=134217728 + +RUN apk update && \ + apk add imagemagick imagemagick-jpeg imagemagick-webp imagemagick-tiff curl + +COPY --from=builder /build/mod-dims/zig-out/lib/libmod_dims.so.4.0.0 ${PREFIX}/modules/libmod_dims.so +COPY dims.conf /usr/local/apache2/conf/httpd.conf + +EXPOSE 8000 \ No newline at end of file diff --git a/COPYING b/LICENSE similarity index 94% rename from COPYING rename to LICENSE index 277ca3d..691c04d 100644 --- a/COPYING +++ b/LICENSE @@ -1,4 +1,5 @@ Copyright 2009 AOL LLC +Copyright 2024 Jeremy Collins Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of diff --git a/Makefile.am b/Makefile.am deleted file mode 100644 index 0a7b929..0000000 --- a/Makefile.am +++ /dev/null @@ -1,2 +0,0 @@ -ACLOCAL_AMFLAGS = -I m4 -SUBDIRS = src diff --git a/NEWS b/NEWS deleted file mode 100644 index e69de29..0000000 diff --git a/README b/README deleted file mode 100644 index e69de29..0000000 diff --git a/README.markdown b/README.md similarity index 87% rename from README.markdown rename to README.md index 5485143..453a16f 100644 --- a/README.markdown +++ b/README.md @@ -1,42 +1,52 @@ -Dependencies -============ - -* Apache 2.2.x -* Imagemagick 6.6+ -* libcurl 7.18.0+ +mod-dims +======== -Compiling -========= +mod-dims is an HTTP microservice for dynamic image manipulation. It run as an Apache httpd module. -./autorun.sh --with-imagemagick=/path/to/imagemagick --with-apache=/path/to/apache +Dependencies +------------ -The paths provided above are prefix paths used to install those dependencies. If you installed -Imagemagick and Apache (including APR) in /usr/local you would run: +* Apache 2.4.x +* Imagemagick 7.1.x +* libcurl 7.x -./autorun.sh --with-imagemagick=/usr/local --with-apache=/usr/local +Compiling +--------- + +```bash +$ zig build +find zig-out/ +zig-out/ +zig-out/lib +zig-out/lib/libmod_dims.so.4 +zig-out/lib/libmod_dims.so.4.0.0 +zig-out/lib/libmod_dims.so +``` Installation -============ +------------ Add the following to the Apache configuration: +``` LoadModule dims_module modules/mod_dims.so AddHandler dims-local .gif .jpg - - SetHandler dims + + SetHandler dims3 - + SetHandler dims3 SetHandler dims-status +``` This assumes mod_dims.so has been installed in $HTTP_ROOT/modules. diff --git a/autorun.sh b/autorun.sh deleted file mode 100755 index b834187..0000000 --- a/autorun.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/sh -# -# setup/re-init the autoconf files on a raw checkout -# minimalistic version -# - -fail() { - echo "FAIL: $@" >&2 - exit 1 -} - -# check location - -if [ ! -f ./configure.ac ]; then - fail "$0 needs to run fromt the top level directory where configure.ac resides." -fi - -# check tools we need - -AUTOCONF="${AUTOCONF:-autoconf}" -AUTORECONF="${AUTORECONF:-autoreconf}" -AUTOMAKE="${AUTOMAKE:-automake}" - -for tool in "$AUTOCONF" "$AUTORECONF" "$AUTOMAKE"; do - type "$tool" 2>&1 >/dev/null - if test $? -ne 0; then - fail "need ${tool} installed." - fi -done - -for i in .configured .deps compile aclocal.m4 autom4te.cache \ - autoscan.log config.guess config.status config.sub \ - config.h config.h.in config.h.in~ configure configure.scan \ - depcomp install-sh libtool ltmain.sh missing stamp-h1 \ - Makefile.in Makefile \ - src/Makefile.in src/Makefile \ - test/Makefile.in test/Makefile \ - ; do - test -z "$i" || rm -rf "$i" -done - -"$AUTORECONF" -i || exit $? -"$AUTOMAKE" || exit $? -"$AUTOCONF" || exit $?01 - -./configure $@ \ No newline at end of file diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..f787f21 --- /dev/null +++ b/build.zig @@ -0,0 +1,45 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const libmod_dims = b.addSharedLibrary(.{ + .name = "mod_dims", + .target = target, + .optimize = optimize, + .version = .{ .major = 4, .minor = 0, .patch = 0 }, + }); + + libmod_dims.linker_allow_shlib_undefined = true; + libmod_dims.linkSystemLibrary("apr-1"); + libmod_dims.linkSystemLibrary("apr-util-1"); + libmod_dims.linkSystemLibrary("curl"); + libmod_dims.linkSystemLibrary("openssl"); + libmod_dims.linkSystemLibrary("MagickWand"); + libmod_dims.addCSourceFiles(.{ + .files = &.{ + "src/curl.c", + "src/configuration.c", + "src/encryption.c", + "src/handler.c", + "src/initialize.c", + "src/mod_dims_ops.c", + "src/mod_dims.c", + "src/module.c", + "src/status.c", + }, + .flags = &.{ + "-std=c17", + "-I/usr/local/apache2/include/", + "-I/opt/homebrew/include/httpd", + "-Wall", + "-W", + "-Wstrict-prototypes", + "-Wwrite-strings", + "-Wno-missing-field-initializers", + }, + }); + + b.installArtifact(libmod_dims); +} diff --git a/configure.ac b/configure.ac deleted file mode 100644 index 2616604..0000000 --- a/configure.ac +++ /dev/null @@ -1,48 +0,0 @@ -AC_PREREQ(2.60) -AC_INIT(mod_dims, 3.3.30, [jeremy.collins@beetlebug.org]) -AM_INIT_AUTOMAKE([no-define]) -AC_CONFIG_SRCDIR([src/mod_dims.c]) - -# Checks for programs. -AC_PROG_CC -AC_PROG_INSTALL -AC_PROG_LIBTOOL - -# Check for ImageMagick -PKG_CHECK_MODULES(MagickCore, MagickCore) -PKG_CHECK_MODULES(MagickWand, MagickWand) -PKG_CHECK_MODULES(libcurl, libcurl) - -AC_CONFIG_MACRO_DIRS([m4]) -AC_CONFIG_HEADERS([src/config.h]) - -AP_VERSION=2.4.0 -AP_CHECK_APACHE([$AP_VERSION], [ - LIBTOOL="`$APR_CONFIG --apr-libtool`" - AC_SUBST([LIBTOOL]) - - MODULE_CFLAGS="$AP_CFLAGS" - AC_SUBST([MODULE_CFLAGS]) - - MODULE_LDFLAGS="`$APR_CONFIG --link-libtool` `$APU_CONFIG --link-libtool`" - AC_SUBST([MODULE_LDFLAGS]) - - BIN_LDFLAGS="`$APR_CONFIG --link-libtool` `$APU_CONFIG --link-libtool` `$APR_CONFIG --ldflags --libs` `$APU_CONFIG --ldflags --libs`" - AC_SUBST([BIN_LDFLAGS]) - - prefix="$AP_PREFIX" -], AC_MSG_ERROR([*** Apache version $AP_VERSION not found!])) - -# Checks for header files. -AC_CHECK_HEADERS([stdlib.h sys/time.h]) - -# Checks for typedefs, structures, and compiler characteristics. -AC_TYPE_SIZE_T - -# Checks for library functions. -AC_FUNC_REALLOC -AC_CHECK_FUNCS([memset sqrt strstr]) - -AC_CONFIG_FILES([Makefile src/Makefile]) -AC_OUTPUT - diff --git a/dims.conf b/dims.conf new file mode 100644 index 0000000..f73b2d7 --- /dev/null +++ b/dims.conf @@ -0,0 +1,56 @@ +ServerRoot "/usr/local/apache2" +Listen 8000 +DocumentRoot "/usr/local/apache2/htdocs" + +LoadModule dims_module modules/libmod_dims.so +LoadModule mpm_event_module modules/mod_mpm_event.so +LoadModule unixd_module modules/mod_unixd.so +LoadModule log_config_module modules/mod_log_config.so +LoadModule logio_module modules/mod_logio.so +LoadModule authn_core_module modules/mod_authn_core.so +LoadModule authz_core_module modules/mod_authz_core.so + +LogLevel debug + +User www-data +Group www-data + +LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %{DIMS_DL_TIME}n %{DIMS_IM_TIME}n %{DIMS_ORIG_BYTES}n %O" common + +CustomLog /proc/self/fd/1 common +ErrorLog /proc/self/fd/2 + +ServerLimit 16 +StartServers 2 +MaxRequestWorkers 150 +MinSpareThreads 25 +MaxSpareThreads 75 +ThreadsPerChild 25 + +# DimsClient [ ] +DimsAddClient ${DIMS_CLIENT} ${DIMS_ERROR_IMAGE_BACKGROUND} ${DIMS_CACHE_CONTROL_MAX_AGE} ${DIMS_EDGE_CONTROL_DOWNSTREAM_TTL} ${DIMS_TRUST_SOURCE} ${DIMS_MIN_SOURCE_CACHE} ${DIMS_MAX_SOURCE_CACHE} ${DIMS_SECRET} + +DimsDefaultImageBackground ${DIMS_ERROR_IMAGE_BACKGROUND} +DimsCacheExpire ${DIMS_CACHE_EXPIRE} +DimsErrorImageCacheExpire ${DIMS_ERROR_IMAGE_CACHE_EXPIRE} +DimsDownloadTimeout ${DIMS_DOWNLOAD_TIMEOUT} +DimsImagemagickTimeout ${DIMS_IMAGEMAGICK_TIMEOUT} +DimsAddWhitelist ${DIMS_WHITELIST} + + + SetHandler dims3 + AuthType None + Require all granted + + + + SetHandler dims4 + AuthType None + Require all granted + + + + SetHandler dims-status + AuthType None + Require all granted + diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index de2577f..0000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,105 +0,0 @@ -ARG HTTPD_VERSION=2.4.52 - -FROM httpd:${HTTPD_VERSION} as build-mod-dims - -ARG DIMS_VERSION=3.3.26 -ARG IMAGEMAGICK_VERSION=6.9.12-34 -ARG WEBP_VERSION=1.2.1 -ARG TIFF_VERSION=4.3.0 -ARG PREFIX=/usr/local/imagemagick - -WORKDIR /build - -RUN apt-get -y update && \ - apt-get install -y --no-install-recommends \ - automake libtool autoconf build-essential \ - git ca-certificates \ - libapr1-dev libaprutil1-dev \ - curl \ - libcurl4-openssl-dev libfreetype6-dev libopenexr-dev libxml2-dev \ - libgif-dev libjpeg62-turbo-dev libpng-dev \ - liblcms2-dev pkg-config libssl-dev libpangocairo-1.0-0 wget - -# WEBP Library -RUN wget https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-${WEBP_VERSION}.tar.gz && \ - tar xzvf libwebp-${WEBP_VERSION}.tar.gz && \ - cd libwebp-${WEBP_VERSION} && \ - ./configure --prefix=/usr/local/imagemagick && \ - make -j4 && make install - -# TIFF Library -RUN wget http://download.osgeo.org/libtiff/tiff-${TIFF_VERSION}.tar.gz && \ - tar xzvf tiff-${TIFF_VERSION}.tar.gz && \ - cd tiff-${TIFF_VERSION} && \ - ./configure --prefix=$PREFIX --with-webp-include-dir=$PREFIX/include --with-webp-lib-dir=$PREFIX/lib && \ - make -j4 && make install - -# Imagemagick -RUN wget https://download.imagemagick.org/ImageMagick/download/releases/ImageMagick-${IMAGEMAGICK_VERSION}.tar.xz && \ - export PKG_CONFIG_PATH=$PREFIX/lib/pkgconfig && \ - tar xvf ImageMagick-${IMAGEMAGICK_VERSION}.tar.xz && \ - cd ImageMagick-${IMAGEMAGICK_VERSION} && \ - ./configure --without-x --with-quantum-depth=8 --prefix=$PREFIX && \ - make -j4 && make install - -# libcurl -RUN wget https://curl.se/download/curl-7.87.0.tar.xz && \ - export PKG_CONFIG_PATH=$PREFIX/lib/pkgconfig && \ - tar xvf curl-7.87.0.tar.xz && \ - cd curl-7.87.0 && \ - ./configure --with-openssl --prefix=$PREFIX && \ - make -j4 && make install - -RUN apt-get --no-install-recommends install -y apt-transport-https apt-utils \ - automake autoconf build-essential ccache cmake ca-certificates git \ - gcc g++ libc-ares-dev libc-ares2 libre2-dev \ - libssl-dev m4 make pkg-config tar wget zlib1g-dev - -ENV PKG_CONFIG_PATH=/usr/local/imagemagick/pkgconfig - -COPY . /build/mod_dims -WORKDIR /build/mod_dims -RUN export PKG_CONFIG_PATH=$PREFIX/lib/pkgconfig && \ - ./autorun.sh && \ - ./configure && \ - make && \ - make install - -FROM httpd:${HTTPD_VERSION} - -ENV DIMS_DOWNLOAD_TIMEOUT=60000 -ENV DIMS_IMAGEMAGICK_TIMEOUT=20000 -ENV DIMS_CLIENT=development -ENV DIMS_NO_IMAGE_URL="http://placehold.it/350x150" -ENV DIMS_DEFAULT_IMAGE_URL="http://placehold.it/350x150" -ENV DIMS_CACHE_CONTROL_MAX_AGE=604800 -ENV DIMS_EDGE_CONTROL_DOWNSTREAM_TTL=604800 -ENV DIMS_TRUST_SOURCE=true -ENV DIMS_MIN_SOURCE_CACHE=604800 -ENV DIMS_MAX_SOURCE_CACHE=604800 -ENV DIMS_SECRET="" -ENV DIMS_CACHE_EXPIRE=604800 -ENV DIMS_NO_IMAGE_CACHE_EXPIRE=60 -ENV LC_ALL="C" - -USER root - -COPY --from=build-mod-dims /usr/local/apache2/modules/libmod_dims.so /usr/local/apache2/modules/ -COPY --from=build-mod-dims /usr/local/imagemagick /usr/local/imagemagick -COPY docker/dims.conf /usr/local/apache2/conf/extra/dims.conf - -RUN apt-get update && \ - apt-get -y install \ - libpangocairo-1.0-0 libgif7 libjpeg62-turbo libpng16-16 libgomp1 libjbig0 liblcms2-2 \ - libbz2-1.0 libfftw3-double3 libfontconfig1 libfreetype6 libheif1 \ - liblqr-1-0 libltdl7 liblzma5 libopenjp2-7 libopenexr25 ca-certificates && \ - rm -rf /usr/local/apache2/build \ - /usr/local/apache2/cgi-bin \ - /usr/local/apache2/include \ - /usr/local/apache2/htdocs/index.html && \ - chown -R www-data:www-data /usr/local/apache2 && \ - sed "s|Listen 80|Listen 8000|" /usr/local/apache2/conf/httpd.conf -i && \ - sed "s|^#LoadModule authz_core_module|LoadModule authz_core_module|" /usr/local/apache2/conf/httpd.conf -i && \ - echo "Include conf/extra/dims.conf" >> /usr/local/apache2/conf/httpd.conf - -USER 33 diff --git a/docker/README.md b/docker/README.md deleted file mode 100644 index 56249f1..0000000 --- a/docker/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# mod-dims Docker Container - -# How to build this image - -``` -$ docker build -t mod-dims:latest -f Dockerfile .. -``` - -# How to use this image - -## Set DIMS_SECRET to use /dims4/ based signed URLs - -```shell -$ docker run -e DIMS_SECRET=mysecret mod-dims:latest -``` - -## Set DIMS_WHITELIST to use /dims3/ based URLs - -```shell -$ docker run -e DIMS_WHITELIST="images.pexels.com" mod-dims:latest -``` - -# Configuration - -| Environment Variables | Description | Default | -|-----------------------|-------------|---------| -| `DIMS_CLIENT` | Name of client | development | -| `DIMS_SECRET` | Shared secret for /dims4/ signatures | "" | -| `DIMS_DOWNLOAD_TIMEOUT` | Max time allowed for downloading source images, in milliseconds. | 60000 | -| `DIMS_IMAGEMAGICK_TIMEOUT` | Max time allowed for Imagemagick processing, in milliseconds. | 20000 | -| `DIMS_NO_IMAGE_URL` | URL (http(s):// or file:///) to an image displayed for errors | "http://placehold.it/350x150" | -| `DIMS_CACHE_CONTROL_MAX_AGE` | Cache control max age header setting, in seconds | 604800 | -| `DIMS_EDGE_CONTROL_DOWNSTREAM_TTL` | Edge control downstream TTL | 604800 | -| `DIMS_TRUST_SOURCE` | Whether or not to trust origin cache headers | true | -| `DIMS_MIN_SOURCE_CACHE` | Min max-age to accept from image origin, in seconds | 604800 | -| `DIMS_MAX_SOURCE_CACHE` | Max max-age to accept from image origin, in seconds | 604800 | -| `DIMS_CACHE_EXPIRE` | Default expire time when no cache headers are present on origin image, in seconds | 604800 | -| `DIMS_NO_IMAGE_CACHE_EXPIRE` | Time to cache "no image" (i.e. dims failures), in seconds | 60 | \ No newline at end of file diff --git a/docker/dims.conf b/docker/dims.conf deleted file mode 100644 index 7189891..0000000 --- a/docker/dims.conf +++ /dev/null @@ -1,29 +0,0 @@ - - LoadModule dims_module modules/libmod_dims.so - - -DimsDownloadTimeout ${DIMS_DOWNLOAD_TIMEOUT} -DimsImagemagickTimeout ${DIMS_IMAGEMAGICK_TIMEOUT} - -# DimsClient [ ] -DimsAddClient ${DIMS_CLIENT} ${DIMS_NO_IMAGE_URL} ${DIMS_CACHE_CONTROL_MAX_AGE} ${DIMS_EDGE_CONTROL_DOWNSTREAM_TTL} ${DIMS_TRUST_SOURCE} ${DIMS_MIN_SOURCE_CACHE} ${DIMS_MAX_SOURCE_CACHE} ${DIMS_SECRET} - -DimsDefaultImageURL ${DIMS_DEFAULT_IMAGE_URL} -DimsCacheExpire ${DIMS_CACHE_EXPIRE} -DimsNoImageCacheExpire ${DIMS_NO_IMAGE_CACHE_EXPIRE} -DimsUserAgentEnabled true - -## Handler definitions. ## - -DimsAddWhitelist ${DIMS_WHITELIST} - - SetHandler dims3 - - - - SetHandler dims4 - - - - SetHandler dims-status - \ No newline at end of file diff --git a/examples/dims.conf b/examples/dims.conf deleted file mode 100644 index 3731937..0000000 --- a/examples/dims.conf +++ /dev/null @@ -1,37 +0,0 @@ - - - LoadModule dims_module mod_dims/modules/mod_dims.so - - -DimsDownloadTimeout 60000 -DimsImagemagickTimeout 20000 - -# DimsClient [ ] -DimsAddClient TEST http://placehold.it/350x150 604800 604800 trust 604800 604800 t3st - -DimsDefaultImageURL http://placehold.it/350x150 -DimsCacheExpire 604800 -DimsNoImageCacheExpire 60 - -DimsAddWhitelist *.beetlebug.org *.aolcdn.com -DimsAddWhitelist www.google.com - -## Handler definitions. ## - -AddHandler dims-local .gif .jpg .png - - - SetHandler dims - - - - SetHandler dims3 - - - - SetHandler dims4 - - - - SetHandler dims-status - diff --git a/m4/apache.m4 b/m4/apache.m4 deleted file mode 100644 index de066f5..0000000 --- a/m4/apache.m4 +++ /dev/null @@ -1,199 +0,0 @@ -dnl -------------------------------------------------------- -*- autoconf -*- -dnl Licensed to the Apache Software Foundation (ASF) under one or more -dnl contributor license agreements. See the NOTICE file distributed with -dnl this work for additional information regarding copyright ownership. -dnl The ASF licenses this file to You under the Apache License, Version 2.0 -dnl (the "License"); you may not use this file except in compliance with -dnl the License. You may obtain a copy of the License at -dnl -dnl http://www.apache.org/licenses/LICENSE-2.0 -dnl -dnl Unless required by applicable law or agreed to in writing, software -dnl distributed under the License is distributed on an "AS IS" BASIS, -dnl WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -dnl See the License for the specific language governing permissions and -dnl limitations under the License. - -dnl -dnl AP_TEST_APACHE_VERSION([MINIMUM-VERSION [, ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]]) -dnl - -AC_DEFUN([AP_TEST_APACHE_VERSION], [ - min_apache_version="$1" - no_apache="" - ac_save_CFLAGS="$CFLAGS" - CFLAGS="$CFLAGS $AP_CFLAGS $APR_INCLUDES $APU_INCLUDES" - AC_TRY_RUN([ -#include -#include -#include -#include "httpd.h" - -#ifndef AP_SERVER_BASEREVISION - #define AP_SERVER_BASEREVISION SERVER_BASEREVISION -#endif - -char* my_strdup (char *str) -{ - char *new_str; - - if (str) { - new_str = (char *)malloc ((strlen (str) + 1) * sizeof(char)); - strcpy (new_str, str); - } else - new_str = NULL; - - return new_str; -} - -int main (int argc, char *argv[]) -{ - int major1, minor1, micro1; - int major2, minor2, micro2; - char *tmp_version; - - { FILE *fp = fopen("conf.apachetest", "a"); if ( fp ) fclose(fp); } - - // TODO: Support abbreviated versions, e.g. 2.2 instead of 2.2.0 - tmp_version = my_strdup("$min_apache_version"); - if (sscanf(tmp_version, "%d.%d.%d", &major1, &minor1, µ1) != 3) { - printf("%s, bad version string\n", "$min_apache_version"); - exit(1); - } - tmp_version = my_strdup(AP_SERVER_BASEREVISION); - if (sscanf(tmp_version, "%d.%d.%d", &major2, &minor2, µ2) != 3) { - printf("%s, bad version string\n", AP_SERVER_BASEREVISION); - exit(1); - } - - if (major2 == major1 && - (minor2 > minor1 || - (minor2 == minor1 && micro2 >= micro1))) { - exit(0); - } else - exit(1); -} - ], [], [no_apache=yes], [echo $ac_n "cross compiling; assumed OK... $ac_c"]) - CFLAGS="$ac_save_CFLAGS" - - if test "x$no_apache" = x ; then - ifelse([$2], [], [:], [$2]) - else - if test -f conf.apachetest; then - : - else - AC_MSG_WARN([*** Could not run Apache test program, checking why...]) - CFLAGS="$CFLAGS $AP_CFLAGS $APR_INCLUDES $APU_INCLUDES" - AC_TRY_LINK([ -#include -#include "httpd.h" - -int main(int argc, char *argv[]) -{ return 0; } -#undef main -#define main K_and_R_C_main - ], [ return 0; ], - [AC_MSG_ERROR([*** The test program compiled, but failed to run. Check config.log])], - [AC_MSG_ERROR([*** The test program failed to compile or link. Check config.log])]) - CFLAGS="$ac_save_CFLAGS" - fi - ifelse([$3], [], :, [$3]) - fi - rm -f conf.apachetest -]) - -dnl -dnl AP_CHECK_APACHE([MINIMUM-VERSION [, ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]]) -dnl - -AC_DEFUN([AP_CHECK_APACHE], [ - AC_ARG_WITH([apxs], - [AC_HELP_STRING([--with-apxs=PATH], [Path to apxs])], - [apxs_prefix="$withval"], - [apxs_prefix="/usr"]) - - AC_ARG_ENABLE([apachetest], - [AC_HELP_STRING([--disable-apachetest], [Do not try to compile and run Apache version test program])], - [], - [enable_apachetest=yes]) - - # Find apxs - if test -x $apxs_prefix -a ! -d $apxs_prefix; then - APXS_BIN=$apxs_prefix - else - test_paths="/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/usr/local/apache2/bin" - - if test -d $apxs_prefix; then - test_paths="$apxs_prefix:$apxs_prefix/bin:$apxs_prefix/sbin:$test_paths" - apxs_prefix="apxs" - fi - - AC_PATH_PROG([APXS_BIN], [$apxs_prefix], [no], [$test_paths]) - fi - - if test "$APXS_BIN" = "no"; then - AC_MSG_ERROR([*** The apxs binary installed by Apache could not be found!]) - AC_MSG_ERROR([*** Use the --with-apxs option with the full path to apxs]) - else - # Set AP_ variables from apxs - - AP_INCLUDEDIR="`$APXS_BIN -q INCLUDEDIR 2>/dev/null`" - AP_INCLUDES="-I$AP_INCLUDEDIR" - - AP_PREFIX="`$APXS_BIN -q prefix 2>/dev/null`" - - AP_BINDIR="`$APXS_BIN -q bindir 2>/dev/null`" - AP_SBINDIR="`$APXS_BIN -q sbindir 2>/dev/null`" - - APXS_CFLAGS="" - for flag in CFLAGS EXTRA_CFLAGS NOTEST_CFLAGS; do - APXS_CFLAGS="$APXS_CFLAGS `$APXS_BIN -q $flag 2>/dev/null`" - done - - AP_CFLAGS="$APXS_CFLAGS $AP_INCLUDES" - - AP_LIBEXECDIR=`$APXS_BIN -q LIBEXECDIR 2>/dev/null` - - # Set APR_ variables from apr-config - APR_CONFIG="`$APXS_BIN -q APR_BINDIR 2>/dev/null`/apr-1-config" - if test ! -x $APR_CONFIG; then - APR_CONFIG="`$APXS_BIN -q APR_BINDIR 2>/dev/null`/apr-config" - fi - APR_INCLUDES=`$APR_CONFIG --includes 2>/dev/null` - APR_VERSION=`$APR_CONFIG --version 2>/dev/null` - - # Set APU_ variables from apu-config - APU_CONFIG="`$APXS_BIN -q APU_BINDIR 2>/dev/null`/apu-1-config" - if test ! -x $APU_CONFIG; then - APU_CONFIG="`$APXS_BIN -q APU_BINDIR 2>/dev/null`/apu-config" - fi - APU_INCLUDES=`$APU_CONFIG --includes 2>/dev/null` - APU_VERSION=`$APU_CONFIG --version 2>/dev/null` - - min_apache_version=ifelse([$1], [], [no], [$1]) - if test "x$enable_apachetest" = "xyes" -a "$min_apache_version" != "no"; then - AC_MSG_CHECKING([for Apache 2.0 version >= $min_apache_version]) - AP_TEST_APACHE_VERSION([$min_apache_version], - AC_MSG_RESULT([yes]) - AP_CFLAGS="$AP_CFLAGS $APU_INCLUDES $APR_INCLUDES" - AP_CPPFLAGS="$AP_CPPFLAGS $APU_INCLUDES $APR_INCLUDES" - ifelse([$2], [], [], [$2]), - AC_MSG_RESULT([no]) - ifelse([$3], [], [], [$3]) - ) - fi - AC_SUBST(AP_INCLUDEDIR) - AC_SUBST(AP_INCLUDES) - AC_SUBST(AP_PREFIX) - AC_SUBST(AP_BINDIR) - AC_SUBST(AP_SBINDIR) - AC_SUBST(AP_CFLAGS) - AC_SUBST(AP_LIBEXECDIR) - AC_SUBST(APR_CONFIG) - AC_SUBST(APR_INCLUDES) - AC_SUBST(APU_CONFIG) - AC_SUBST(APU_INCLUDES) - AC_SUBST(APXS_BIN) - AC_SUBST(APXS_CFLAGS) - fi -]) diff --git a/src/Makefile.am b/src/Makefile.am deleted file mode 100644 index ce68aaa..0000000 --- a/src/Makefile.am +++ /dev/null @@ -1,9 +0,0 @@ -moddir = ${AP_LIBEXECDIR} -lib_LTLIBRARIES = libmod_dims.la - -libmod_dims_la_SOURCES = mod_dims.c mod_dims_ops.c mod_dims.h -libmod_dims_la_LDFLAGS = -module -avoid-version $(MagickCore_LIBS) $(MagickWand_LIBS) $(libcurl_LIBS) ${MODULE_LDFLAGS} -libmod_dims_la_CFLAGS = -std=c99 -D_LARGEFILE64_SOURCE $(MagickCore_CFLAGS) $(MagickWand_CFLAGS) $(libcurl_CFLAGS) ${MODULE_CFLAGS} - -install: libmod_dims.la - $(APXS_BIN) -i -a -n dims libmod_dims.la diff --git a/src/configuration.c b/src/configuration.c new file mode 100644 index 0000000..3846bfd --- /dev/null +++ b/src/configuration.c @@ -0,0 +1,295 @@ + +#include "mod_dims.h" +#include "module.h" + +#include +#include + +void * +dims_create_config(apr_pool_t *p, server_rec *s) +{ + dims_config_rec *config; + + config = (dims_config_rec *) apr_pcalloc(p, sizeof(dims_config_rec)); + config->whitelist = apr_table_make(p, 5); + config->clients = apr_hash_make(p); + config->ignore_default_output_format = apr_table_make(p, 3); + + config->download_timeout = 3000; + config->imagemagick_timeout = 3000; + + config->error_image_background = "#5ADAFD"; + config->error_image_expire = 60; + config->default_image_prefix = NULL; + + config->default_expire = 86400; + + config->strip_metadata = 1; + config->default_output_format = NULL; + + config->curl_queue_size = 10; + config->cache_dir = NULL; + config->secret_key = apr_pstrdup(p,"m0d1ms"); + config->encryption_algorithm = "aes/ecb/pkcs5padding"; + config->max_expiry_period= 0; // never expire + + return (void *) config; +} + +const char * +dims_config_set_whitelist(cmd_parms *cmd, void *d, int argc, char *const argv[]) +{ + dims_config_rec *config = (dims_config_rec *) ap_get_module_config( + cmd->server->module_config, + &dims_module); + int i; + + for(i = 0; i < argc; i++) { + char *hostname = argv[i]; + + /* remove glob character and '.' if they're on the string and set + * the value in the hash to glob. + */ + if(hostname[0] == '*') { + if(*++hostname == '.') { + hostname++; + } + + apr_table_setn(config->whitelist, hostname, "glob"); + } else { + apr_table_setn(config->whitelist, argv[i], "exact"); + } + } + + return NULL; +} + +const char * +dims_config_set_ignore_default_output_format(cmd_parms *cmd, void *d, int argc, char *const argv[]) +{ + dims_config_rec *config = (dims_config_rec *) ap_get_module_config( + cmd->server->module_config, + &dims_module); + int i; + + for(i = 0; i < argc; i++) { + char *format = argv[i]; + char *s = format; + while (*s) { *s = toupper(*s); s++; } + + apr_table_setn(config->ignore_default_output_format, format, "1"); + } + return NULL; +} + +const char * +dims_config_set_default_expire(cmd_parms *cmd, void *dummy, const char *arg) +{ + dims_config_rec *config = (dims_config_rec *) ap_get_module_config( + cmd->server->module_config, &dims_module); + config->default_expire = atol(arg); + return NULL; +} + +const char * +dims_config_set_error_image_expire(cmd_parms *cmd, void *dummy, const char *arg) +{ + dims_config_rec *config = (dims_config_rec *) ap_get_module_config( + cmd->server->module_config, &dims_module); + config->error_image_expire = atol(arg); + return NULL; +} + +const char * +dims_config_set_download_timeout(cmd_parms *cmd, void *dummy, const char *arg) +{ + dims_config_rec *config = (dims_config_rec *) ap_get_module_config( + cmd->server->module_config, &dims_module); + config->download_timeout = atol(arg); + return NULL; +} + +const char * +dims_config_set_imagemagick_timeout(cmd_parms *cmd, void *dummy, const char *arg) +{ + dims_config_rec *config = (dims_config_rec *) ap_get_module_config( + cmd->server->module_config, &dims_module); + config->imagemagick_timeout = atol(arg); + return NULL; +} + +const char * +dims_config_set_strip_metadata(cmd_parms *cmd, void *dummy, const char *arg) +{ + dims_config_rec *config = (dims_config_rec *) ap_get_module_config( + cmd->server->module_config, &dims_module); + // the default is 1, so anything other than "false" will use the default + if(strcmp(arg, "false") == 0) { + config->strip_metadata = 0; + } + else { + config->strip_metadata = 1; + } + return NULL; +} + +const char * +dims_config_set_include_disposition(cmd_parms *cmd, void *dummy, const char *arg) +{ + dims_config_rec *config = (dims_config_rec *) ap_get_module_config( + cmd->server->module_config, &dims_module); + if(strcmp(arg, "true") == 0) { + config->include_disposition = 1; + } + else { + config->include_disposition = 0; + } + return NULL; +} + +const char * +dims_config_set_encryption_algorithm(cmd_parms *cmd, void *dummy, const char *arg) +{ + dims_config_rec *config = (dims_config_rec *) ap_get_module_config( + cmd->server->module_config, &dims_module); + config->encryption_algorithm = (char *) arg; + return NULL; +} + +const char * +dims_config_set_default_output_format(cmd_parms *cmd, void *dummy, const char *arg) +{ + dims_config_rec *config = (dims_config_rec *) ap_get_module_config( + cmd->server->module_config, &dims_module); + char *output_format = (char *) arg; + char *s = output_format; + while (*s) { *s = toupper(*s); s++; } + config->default_output_format = output_format; + return NULL; +} + +const char * +dims_config_set_user_agent_override(cmd_parms *cmd, void *dummy, const char *arg) +{ + dims_config_rec *config = (dims_config_rec *) ap_get_module_config( + cmd->server->module_config, &dims_module); + char *user_agent = (char *) arg; + config->user_agent_override = user_agent; + return NULL; +} + +const char * +dims_config_set_client(cmd_parms *cmd, void *d, int argc, char *const argv[]) +{ + dims_config_rec *config = (dims_config_rec *) ap_get_module_config( + cmd->server->module_config, &dims_module); + + dims_client_config_rec *client_config = NULL; + + if(argc == 0) { + return NULL; + } + + if(argc >= 1) { + client_config = (dims_client_config_rec *) + apr_pcalloc(cmd->pool, + sizeof(dims_client_config_rec)); + + client_config->error_image_background = config->error_image_background; + client_config->cache_control_max_age = config->default_expire; + client_config->edge_control_downstream_ttl = -1; + client_config->trust_src = 0; + client_config->min_src_cache_control = -1; + client_config->max_src_cache_control = -1; + + switch(argc) { + case 8: + if(strcmp(argv[7], "-") != 0) { + client_config->secret_key = argv[7]; + } else { + client_config->secret_key = NULL; + } + case 7: + if(strcmp(argv[6], "-") != 0) { + if(atoi(argv[6]) <= 0 && strcmp(argv[6], "0") != 0) { + // erroneous value + client_config->max_src_cache_control = -2; + } + else { + client_config->max_src_cache_control = atoi(argv[6]); + } + } + case 6: + if(strcmp(argv[5], "-") != 0) { + if(atoi(argv[5]) <= 0 && strcmp(argv[5], "0") != 0) { + // erroneous value + client_config->min_src_cache_control = -2; + } + else { + client_config->min_src_cache_control = atoi(argv[5]); + } + } + case 5: + if(strcmp(argv[4], "trust") == 0) { + client_config->trust_src = 1; + } + case 4: + if(strcmp(argv[3], "-") != 0) { + client_config->edge_control_downstream_ttl = atoi(argv[3]); + } + case 3: + if(strcmp(argv[2], "-") != 0) { + client_config->cache_control_max_age = atoi(argv[2]); + } + case 2: + if(strcmp(argv[1], "-") != 0) { + client_config->error_image_background = argv[1]; + } + case 1: + client_config->id = argv[0]; + } + + // The min and max values for 'max-age' must be set in order to trust this client. + if(client_config->min_src_cache_control == -1 && client_config->max_src_cache_control == -1) { + client_config->trust_src = 0; + } + + } + + apr_hash_set(config->clients, argv[0], APR_HASH_KEY_STRING, client_config); + + return NULL; +} + +const char * +dims_config_set_error_image_background(cmd_parms *cmd, void *dummy, const char *arg) +{ + dims_config_rec *config = (dims_config_rec *) ap_get_module_config( + cmd->server->module_config, &dims_module); + config->error_image_background = (char *) arg; + return NULL; +} + +const char * +dims_config_set_image_prefix(cmd_parms *cmd, void *dummy, const char *arg) +{ + dims_config_rec *config = (dims_config_rec *) ap_get_module_config( + cmd->server->module_config, &dims_module); + config->default_image_prefix = (char *) arg; + + if (strncmp(config->default_image_prefix, "https://", 8) != 0 && + strncmp(config->default_image_prefix, "http://", 7) != 0) { + return "dimsdefaultimageprefix must start with 'https://' or 'http://'"; + } + + return NULL; +} + +const char * +dims_config_set_secretkey_expiry_period(cmd_parms *cmd, void *dummy, const char *arg) +{ + dims_config_rec *config = (dims_config_rec *) ap_get_module_config( + cmd->server->module_config, &dims_module); + config->max_expiry_period = atol(arg); + return NULL; +} diff --git a/src/configuration.h b/src/configuration.h new file mode 100644 index 0000000..b083b13 --- /dev/null +++ b/src/configuration.h @@ -0,0 +1,63 @@ + +#ifndef _CONFIGURATION_H_ +#define _CONFIGURATION_H_ + +#include +#include +#include + +typedef struct dims_config_rec dims_config_rec; +typedef struct dims_client_config_rec dims_client_config_rec; + +void *dims_create_config(apr_pool_t *p, server_rec *s); +const char *dims_config_set_whitelist(cmd_parms *cmd, void *d, int argc, char *const argv[]); +const char *dims_config_set_ignore_default_output_format(cmd_parms *cmd, void *d, int argc, char *const argv[]); +const char *dims_config_set_default_expire(cmd_parms *cmd, void *dummy, const char *arg); +const char *dims_config_set_download_timeout(cmd_parms *cmd, void *dummy, const char *arg); +const char *dims_config_set_imagemagick_timeout(cmd_parms *cmd, void *dummy, const char *arg); +const char *dims_config_set_strip_metadata(cmd_parms *cmd, void *dummy, const char *arg); +const char *dims_config_set_include_disposition(cmd_parms *cmd, void *dummy, const char *arg); +const char *dims_config_set_encryption_algorithm(cmd_parms *cmd, void *dummy, const char *arg); +const char *dims_config_set_default_output_format(cmd_parms *cmd, void *dummy, const char *arg); +const char *dims_config_set_user_agent_override(cmd_parms *cmd, void *dummy, const char *arg); +const char *dims_config_set_client(cmd_parms *cmd, void *d, int argc, char *const argv[]); +const char *dims_config_set_error_image_background(cmd_parms *cmd, void *dummy, const char *arg); +const char *dims_config_set_error_image_expire(cmd_parms *cmd, void *dummy, const char *arg); +const char *dims_config_set_image_prefix(cmd_parms *cmd, void *dummy, const char *arg); +const char *dims_config_set_secretkey_expiry_period(cmd_parms *cmd, void *dummy, const char *arg); + +struct dims_config_rec { + int download_timeout; + int imagemagick_timeout; + + apr_table_t *whitelist; + apr_hash_t *clients; + apr_table_t *ignore_default_output_format; + + char *error_image_background; + long error_image_expire; + long default_expire; + int strip_metadata; + int include_disposition; + char *default_output_format; + int curl_queue_size; + char *secret_key; + char *encryption_algorithm; + long max_expiry_period; + char *cache_dir; + char *default_image_prefix; + char *user_agent_override; +}; + +struct dims_client_config_rec { + char *id; + char *error_image_background; + int cache_control_max_age; + int edge_control_downstream_ttl; + int trust_src; + int min_src_cache_control; + int max_src_cache_control; + char *secret_key; +}; + +#endif \ No newline at end of file diff --git a/src/curl.c b/src/curl.c new file mode 100644 index 0000000..5d3f307 --- /dev/null +++ b/src/curl.c @@ -0,0 +1,263 @@ + +#include +#include + +#include "request.h" +#include "mod_dims.h" +#include "curl.h" + +/* Converts a hex character to its integer value */ +static char from_hex(char ch) { + return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10; +} + +/* Converts an integer value to its hex character*/ +static char to_hex(char code) { + static char hex[] = "0123456789abcdef"; + return hex[code & 15]; +} + +void +lock_share(CURL *handle, + curl_lock_data data, + curl_lock_access access, + void *userptr) +{ + dims_curl_rec *locks = (dims_curl_rec *) userptr; + + switch(data) { + case CURL_LOCK_DATA_DNS: + apr_thread_mutex_lock(locks->dns_mutex); + break; + default: + apr_thread_mutex_lock(locks->share_mutex); + } +} + +void +unlock_share(CURL *handle, + curl_lock_data data, + void *userptr) +{ + dims_curl_rec *locks = (dims_curl_rec *) userptr; + + switch(data) { + case CURL_LOCK_DATA_DNS: + apr_thread_mutex_unlock(locks->dns_mutex); + break; + default: + apr_thread_mutex_unlock(locks->share_mutex); + } +} + +static int +dims_curl_debug_cb( + CURL *handle, + curl_infotype type, + char *data, + size_t size, + void *clientp) +{ + dims_request_rec *d = (dims_request_rec *) clientp; + switch(type) { + case CURLINFO_HEADER_OUT: + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, d->r, "Curl request header data: %s ", data); + break; + default: + break; + } + + return 0; +} + +/** + * dims_write_image_cb - Appends image data chunk to an existing buffer. + * + * @chunk: Pointer to the incoming data chunk. + * @size: The number of elements in the data chunk. + * @bytes_size: Size of each element in bytes. + * @data: Pointer to the image data structure (dims_image_data_t). + * + * This function writes a given image data chunk into an existing dynamically + * allocated buffer, reallocating memory as needed. + * + * Returns: The number of bytes successfully appended to the buffer, or 0 on failure. + */ +size_t +dims_write_image_cb(void *chunk, size_t size, size_t bytes_size, void *data) +{ + ap_assert(data != NULL); + ap_assert(chunk != NULL); + + // Prevent overflow ('size' is always 1 but just in case) + if (size != 1 && bytes_size > SIZE_MAX / size) { + return 0; + } + + dims_image_data_t *image = (dims_image_data_t *) data; + size_t chunk_size = size * bytes_size; + + // Allocate more memory if needed. + if(image->used + chunk_size > image->size) { + size_t new_size = (image->used + chunk_size) * 2; + char *new_data = (char *)realloc(image->data, new_size); + if (new_data == NULL) { + return 0; + } + + image->data = new_data; + image->size = new_size; + } + + memcpy(&(image->data[image->used]), chunk, chunk_size); + image->used += chunk_size; + + return chunk_size; +} + +CURLcode +dims_curl(dims_request_rec *d, const char *url, dims_image_data_t *source_image) +{ + CURL *curl_handle; + CURLcode code; + + dims_image_data_t image_data; + image_data.data = NULL; + image_data.size = 0; + image_data.used = 0; + + curl_handle = curl_easy_init(); + curl_easy_setopt(curl_handle, CURLOPT_URL, url); + curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, dims_write_image_cb); + curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) &image_data); + curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT_MS, d->config->download_timeout); + curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(curl_handle, CURLOPT_DEBUGFUNCTION, dims_curl_debug_cb); + curl_easy_setopt(curl_handle, CURLOPT_DEBUGDATA, d); + + /* Set the user agent to dims/ */ + if (d->config->user_agent_override != NULL) { + curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, d->config->user_agent_override); + } else { + char *dims_useragent = apr_psprintf(d->r->pool, "mod_dims/%s", MODULE_VERSION); + curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, dims_useragent); + } + + /* The curl shared handle allows this process to share DNS cache + * and prevents the DNS cache from going away after every request. + */ + void *shared_locks = NULL; + apr_pool_userdata_get((void *) &shared_locks, DIMS_CURL_SHARED_KEY, d->r->server->process->pool); + if (shared_locks) { + dims_curl_rec *locks = (dims_curl_rec *) shared_locks; + curl_easy_setopt(curl_handle, CURLOPT_SHARE, locks->share); + } + + code = curl_easy_perform(curl_handle); + + // Grab cache headers using curl_easy_headers() + struct curl_header *header = NULL; + CURLHcode header_code = curl_easy_header(curl_handle, "cache-control", 0, CURLH_HEADER, -1, &header); + if (CURLHE_OK == header_code) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, "Cache-Control: %s", header->value); + source_image->cache_control = apr_pstrdup(d->pool, header->value); + + // Parse the Cache-Control header to extract "max-age" if present + char *cache_control = source_image->cache_control; + char *saveptr; + char *directive = strtok_r(cache_control, ",", &saveptr); + + while (directive != NULL) { + // Trim leading spaces + while (*directive == ' ') { + directive++; + } + + // Check if the directive is "max-age" + if (strncmp(directive, "max-age=", 8) == 0) { + d->source_image->max_age = atol(directive + 8); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, "Max-Age: %d", d->source_image->max_age); + break; // Exit loop once max-age is found + } + + // Get the next directive + directive = strtok_r(NULL, ",", &saveptr); + } + } + + header_code = curl_easy_header(curl_handle, "edge-control", 0, CURLH_HEADER, -1, &header); + if (CURLHE_OK == header_code) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, "Edge-Control: %s", header->value); + source_image->edge_control = apr_pstrdup(d->pool, header->value); + } + + header_code = curl_easy_header(curl_handle, "last-modified", 0, CURLH_HEADER, -1, &header); + if (CURLHE_OK == header_code) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, "Last-Modified: %s", header->value); + source_image->last_modified = apr_pstrdup(d->pool, header->value); + } + + header_code = curl_easy_header(curl_handle, "etag", 0, CURLH_HEADER, -1, &header); + if (CURLHE_OK == header_code) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, "ETag: %s", header->value); + source_image->etag = apr_pstrdup(d->pool, header->value); + } + + curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &source_image->response_code); + curl_easy_cleanup(curl_handle); + + if (source_image->response_code == 200) { + source_image->data = apr_pmemdup(d->pool, image_data.data, image_data.used); + source_image->size = image_data.used; + source_image->used = image_data.used; + } + + free(image_data.data); + + return code; +} + +static apr_status_t +dims_curl_cleanup(void *data) { + dims_curl_rec *locks = (dims_curl_rec *) data; + + curl_share_cleanup(locks->share); + curl_global_cleanup(); + + apr_thread_mutex_destroy(locks->share_mutex); + apr_thread_mutex_destroy(locks->dns_mutex); + + apr_pool_userdata_set(NULL, DIMS_CURL_SHARED_KEY, NULL, locks->s->process->pool); +} + +void +dims_curl_init(apr_pool_t *p, server_rec *s) { + curl_global_init(CURL_GLOBAL_ALL); + + dims_curl_rec *locks = (dims_curl_rec *) apr_pcalloc(p, sizeof(dims_curl_rec)); + + locks->s = s; + locks->share = curl_share_init(); + + apr_thread_mutex_create(&locks->share_mutex, APR_THREAD_MUTEX_DEFAULT, p); + apr_thread_mutex_create(&locks->dns_mutex, APR_THREAD_MUTEX_DEFAULT, p); + + curl_share_setopt(locks->share, CURLSHOPT_LOCKFUNC, lock_share); + curl_share_setopt(locks->share, CURLSHOPT_UNLOCKFUNC, unlock_share); + curl_share_setopt(locks->share, CURLSHOPT_USERDATA, (void *) locks); + curl_share_setopt(locks->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); + + /* We have to associate our handle/locks with the process->pool otherwise + * we won't be able to get at it from the remote_fetch_image function. This + * pool doesn't seem to go away when the child process goes away so we + * have to register the clean up method below. + */ + apr_pool_userdata_set(locks, DIMS_CURL_SHARED_KEY, NULL, s->process->pool); + + /* Register cleanup with the 'p' pool so we can clean up the locks and + * shared curl handle when this process dies. + */ + apr_pool_cleanup_register(p, locks, dims_curl_cleanup, dims_curl_cleanup); +} \ No newline at end of file diff --git a/src/curl.h b/src/curl.h new file mode 100644 index 0000000..ab5fddb --- /dev/null +++ b/src/curl.h @@ -0,0 +1,24 @@ +#ifndef _CURL_H_ +#define _CURL_H_ + +#include +#include + +#include "request.h" + +#define DIMS_CURL_SHARED_KEY "dims_curl_shared" + +typedef struct { + CURLSH *share; + + server_rec *s; + + apr_thread_mutex_t *share_mutex; + apr_thread_mutex_t *dns_mutex; +} dims_curl_rec; + +CURLcode dims_curl(dims_request_rec *d, const char *url, dims_image_data_t *data); + +void dims_curl_init(apr_pool_t *p, server_rec *s); + +#endif \ No newline at end of file diff --git a/src/directives.h b/src/directives.h new file mode 100644 index 0000000..253ef04 --- /dev/null +++ b/src/directives.h @@ -0,0 +1,69 @@ + +#ifndef _DIRECTIVES_H_ +#define _DIRECTIVES_H_ + +#include +#include + +#include "configuration.h" + +static const command_rec dims_directives[] = +{ + AP_INIT_TAKE_ARGV("DimsAddWhitelist", + dims_config_set_whitelist, NULL, RSRC_CONF, + "Add whitelist hostname for DIMS URL requests."), + AP_INIT_TAKE_ARGV("DimsAddClient", + dims_config_set_client, NULL, RSRC_CONF, + "Add a client with optional no image url, max-age and downstream-ttl settings."), + AP_INIT_TAKE_ARGV("DimsIgnoreDefaultOutputFormat", + dims_config_set_ignore_default_output_format, NULL, RSRC_CONF, + "Add input formats that shouldn't be converted to the default output format."), + AP_INIT_TAKE1("DimsDefaultImageBackground", + dims_config_set_error_image_background, NULL, RSRC_CONF, + "Default image background color if processing fails."), + AP_INIT_TAKE1("DimsDefaultImagePrefix", + dims_config_set_image_prefix, NULL, RSRC_CONF, + "Default image prefix if URL is relative."), + AP_INIT_TAKE1("DimsCacheExpire", + dims_config_set_default_expire, NULL, RSRC_CONF, + "Default cache-control expire time headers, in seconds." + "The default is 86400"), + AP_INIT_TAKE1("DimsErrorImageCacheExpire", + dims_config_set_error_image_expire, NULL, RSRC_CONF, + "Default cache-control expire time for error image, in seconds." + "The default is 60"), + AP_INIT_TAKE1("DimsDownloadTimeout", + dims_config_set_download_timeout, NULL, RSRC_CONF, + "Timeout for downloading remote images." + "The default is 3000."), + AP_INIT_TAKE1("DimsImagemagickTimeout", + dims_config_set_imagemagick_timeout, NULL, RSRC_CONF, + "Timeout for processing images." + "The default is 3000."), + AP_INIT_TAKE1("DimsSecretMaxExpiryPeriod", + dims_config_set_secretkey_expiry_period, NULL, RSRC_CONF, + "How long in the future (in seconds) can the expiry date on the URL be requesting. 0 = forever" + "The default is 0."), + AP_INIT_TAKE1("DimsStripMetadata", + dims_config_set_strip_metadata, NULL, RSRC_CONF, + "Should DIMS strip the metadata from the image, true OR false." + "The default is true."), + AP_INIT_TAKE1("DimsIncludeDisposition", + dims_config_set_include_disposition, NULL, RSRC_CONF, + "Should DIMS include Content-Disposition header, true OR false." + "The default is false."), + AP_INIT_TAKE1("DimsEncryptionAlgorithm", + dims_config_set_encryption_algorithm, NULL, RSRC_CONF, + "What algorithm should DIMS user to decrypt the 'eurl' parameter." + "The default is AES/ECB/PKCS5Padding."), + AP_INIT_TAKE1("DimsDefaultOutputFormat", + dims_config_set_default_output_format, NULL, RSRC_CONF, + "Default output format if 'format' command is not present in the request."), + AP_INIT_TAKE1("DimsUserAgentOverride", + dims_config_set_user_agent_override, NULL, RSRC_CONF, + "Override DIMS User-Agent header" + "The default is 'dims/."), + {NULL} +}; + +#endif \ No newline at end of file diff --git a/src/encryption.c b/src/encryption.c new file mode 100644 index 0000000..d15066a --- /dev/null +++ b/src/encryption.c @@ -0,0 +1,107 @@ + +#include +#include +#include +#include +#include +#include +#include + +int +aes_errors(const char *message, size_t length, void *u) +{ + request_rec *r = (request_rec *) u; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "%s", message); + return 0; +} + +char * +aes_128_gcm_decrypt(request_rec *r, unsigned char *key, unsigned char *base64_encrypted_text) { + EVP_CIPHER_CTX *ctx; + int ret; + int plaintext_length = 0; + int out_length; + char *plaintext; + + // Decode the Base64 input + int encrypted_length = apr_base64_decode_len((const char *)base64_encrypted_text); + unsigned char *encrypted_data = apr_palloc(r->pool, encrypted_length); + int decoded_length = apr_base64_decode((char *)encrypted_data, (const char *)base64_encrypted_text); + + // Extract IV (12 bytes), ciphertext, and tag (16 bytes) + unsigned char *iv = encrypted_data; + unsigned char *encrypted_text = encrypted_data + 12; // 12-byte IV + int ciphertext_length = decoded_length - 12 - 16; // 16-byte tag at the end + unsigned char *tag = encrypted_text + ciphertext_length; // 16-byte tag + + // Initialize the context + if (!(ctx = EVP_CIPHER_CTX_new())) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Decrypting: Failed to create new EVP_CIPHER_CTX"); + ERR_print_errors_cb(aes_errors, r); + return NULL; + } + + // Initialize the decryption operation + if (!EVP_DecryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, NULL, NULL)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Decrypting: EVP_DecryptInit_ex failed (1)"); + ERR_print_errors_cb(aes_errors, r); + EVP_CIPHER_CTX_free(ctx); + return NULL; + } + + // Set the IV length, if necessary + if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 12, NULL)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Decrypting: EVP_CIPHER_CTX_ctrl failed to set IV length"); + ERR_print_errors_cb(aes_errors, r); + EVP_CIPHER_CTX_free(ctx); + return NULL; + } + + // Set the key and IV + if (!EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Decrypting: EVP_DecryptInit_ex failed (2)"); + ERR_print_errors_cb(aes_errors, r); + EVP_CIPHER_CTX_free(ctx); + return NULL; + } + + plaintext = apr_palloc(r->pool, ciphertext_length + 1); // +1 for null terminator + if (!plaintext) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Decrypting: Memory allocation failed"); + EVP_CIPHER_CTX_free(ctx); + return NULL; + } + + // Provide the message to be decrypted and obtain the plaintext output + if (!EVP_DecryptUpdate(ctx, (unsigned char *)plaintext, &out_length, encrypted_text, ciphertext_length)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Decrypting: EVP_DecryptUpdate failed"); + ERR_print_errors_cb(aes_errors, r); + EVP_CIPHER_CTX_free(ctx); + return NULL; + } + + plaintext_length = out_length; + + // Set expected tag value (must be done after EVP_DecryptUpdate) + if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, tag)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Decrypting: EVP_CIPHER_CTX_ctrl failed to set tag"); + ERR_print_errors_cb(aes_errors, r); + EVP_CIPHER_CTX_free(ctx); + return NULL; + } + + // Finalize the decryption + ret = EVP_DecryptFinal_ex(ctx, (unsigned char *)plaintext + plaintext_length, &out_length); + + EVP_CIPHER_CTX_free(ctx); + + if (ret > 0) { + plaintext_length += out_length; + plaintext[plaintext_length] = '\0'; // Explicitly add the null terminator + return plaintext; + } else { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Decrypting: EVP_DecryptFinal_ex failed"); + ERR_print_errors_cb(aes_errors, r); + return NULL; + } +} \ No newline at end of file diff --git a/src/encryption.h b/src/encryption.h new file mode 100644 index 0000000..5bb511d --- /dev/null +++ b/src/encryption.h @@ -0,0 +1,8 @@ +#ifndef _ENCRYPTION_H_ +#define _ENCRYPTION_H_ + +int aes_errors(const char *message, size_t length, void *u); +char *aes_128_decrypt(request_rec *r, unsigned char *key, unsigned char *encrypted_text, int encrypted_length); +char *aes_128_gcm_decrypt(request_rec *r, unsigned char *key, unsigned char *base64_encrypted_text); + +#endif diff --git a/src/handler.c b/src/handler.c new file mode 100644 index 0000000..56f39a6 --- /dev/null +++ b/src/handler.c @@ -0,0 +1,37 @@ + +#include +#include +#include +#include +#include + +#include "mod_dims.h" +#include "handler.h" +#include "configuration.h" +#include "request.h" +#include "module.h" +#include "encryption.h" +#include "status.h" + +// Called by Apache httpd per request. +apr_status_t +dims_handler(request_rec *r) +{ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "URI: %s ARGS: %s", r->uri, r->args); + + if(strcmp(r->handler, "dims-status") == 0) { + return status_handler(r); + } + + if (!(strcmp(r->handler, "dims3") == 0 || strcmp(r->handler, "dims4") == 0)) { + return DECLINED; + } + + if ((strcmp(r->handler, "dims3") == 0)) { + return dims_handle_dims3(r); + } else if (strcmp(r->handler, "dims4") == 0) { + return dims_handle_dims4(r); + } + + return DECLINED; +} diff --git a/src/handler.h b/src/handler.h new file mode 100644 index 0000000..2db3f3e --- /dev/null +++ b/src/handler.h @@ -0,0 +1,6 @@ +#ifndef _HANDLER_H_ +#define _HANDLER_H_ + +apr_status_t dims_handler(request_rec *r); + +#endif \ No newline at end of file diff --git a/src/initialize.c b/src/initialize.c new file mode 100644 index 0000000..6ca5fb6 --- /dev/null +++ b/src/initialize.c @@ -0,0 +1,64 @@ + +#include +#include + +#include "mod_dims.h" +#include "mod_dims_ops.h" +#include "curl.h" +#include "module.h" + +typedef struct operations { + char *name; + dims_operation_func *func; +} operations; + +static operations ops[] = { + {"strip", dims_strip_operation}, + {"resize", dims_resize_operation}, + {"crop", dims_crop_operation}, + {"thumbnail", dims_thumbnail_operation}, + {"legacy_thumbnail", dims_legacy_thumbnail_operation}, + {"legacy_crop", dims_legacy_crop_operation}, + {"quality", dims_quality_operation}, + {"sharpen", dims_sharpen_operation}, + {"format", dims_format_operation}, + {"brightness", dims_brightness_operation}, + {"flipflop", dims_flipflop_operation}, + {"sepia", dims_sepia_operation}, + {"grayscale", dims_grayscale_operation}, + {"autolevel", dims_autolevel_operation}, + {"rotate", dims_rotate_operation}, + {"invert", dims_invert_operation}, + {"watermark", dims_watermark_operation}, + NULL +}; + +int +dims_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t* ptemp, server_rec *s) +{ + dims_config_rec *config = (dims_config_rec *) ap_get_module_config(s->module_config, &dims_module); + apr_size_t retsize; + + ap_add_version_component(p, "mod_dims/" MODULE_VERSION); + + MagickWandGenesis(); + + return OK; +} + +static apr_status_t +dims_child_cleanup(void *data) +{ + MagickWandTerminus(); + + return APR_SUCCESS; +} + +void +dims_child_init(apr_pool_t *p, server_rec *s) +{ + dims_curl_init(p, s); + + MagickWandGenesis(); + apr_pool_cleanup_register(p, NULL, dims_child_cleanup, dims_child_cleanup); +} \ No newline at end of file diff --git a/src/initialize.h b/src/initialize.h new file mode 100644 index 0000000..9df0551 --- /dev/null +++ b/src/initialize.h @@ -0,0 +1,10 @@ +#ifndef _INITIALIZE_H_ +#define _INITIALIZE_H_ + +#include + +int dims_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s); +void dims_child_init(apr_pool_t *p, server_rec *s); +void dims_register_hooks(apr_pool_t *p); + +#endif \ No newline at end of file diff --git a/src/mod_dims.c b/src/mod_dims.c index 17bbad5..c9b8af4 100755 --- a/src/mod_dims.c +++ b/src/mod_dims.c @@ -1,24 +1,8 @@ /** * mod_dims - Dynamic Image Manipulation Service * - * This module provides a webservice for dynamically manipulating - * images. Currently cropping, resizing, reformatting and - * thumbnail creation are supported. - * - * Code Flow Logic: - * - * dims_handler - called by apache, determines if request should be processed - * \ and does initial request setup. - * dims_handle_request - validates against whitelist, client list and loads image. - * \ - * dims_process_image - parses operations (resize, etc) and executes them - * \ using imagemagick api. - * dims_send_image - sends image to connection w/appropriate headers - * - * Any errors during processing will call 'dims_cleanup' which will free - * any memory and return the 'no image' image to the connection. - * * Copyright 2009 AOL LLC + * Copyright 2024 Jeremy Collins * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -33,498 +17,49 @@ * the License. */ -#define MODULE_RELEASE "$Revision: $" -#define MODULE_VERSION "3.3.30" - #include "mod_dims.h" -#include "util_md5.h" +#include "mod_dims_ops.h" +#include "configuration.h" +#include "encryption.h" +#include "curl.h" +#include "request.h" +#include "module.h" #include "cmyk_icc.h" + +#include #include #include #include #include -#include -#include #include -#include - -#include - -module dims_module; - -#define DIMS_CURL_SHARED_KEY "dims_curl_shared" #define MAGICK_CHECK(func, d) \ do {\ if(func == MagickFalse) \ - return dims_cleanup(d, NULL, DIMS_FAILURE); \ + return DIMS_FAILURE; \ if(d->status == DIMS_IMAGEMAGICK_TIMEOUT) \ - return dims_cleanup(d, NULL, d->status); \ + return d->status; \ } while(0); -typedef struct { - CURLSH *share; - - server_rec *s; +#define MAGICK_WAND_CLEANUP(wand_ptr, error_code) \ + do { \ + if ((wand_ptr) != NULL) { \ + DestroyMagickWand(wand_ptr); \ + (wand_ptr) = NULL; \ + } \ + return (error_code); \ + } while (0) - apr_thread_mutex_t *share_mutex; - apr_thread_mutex_t *dns_mutex; -} dims_curl_rec; - -typedef struct { +typedef struct dims_progress_rec { dims_request_rec *d; apr_time_t start_time; } dims_progress_rec; -typedef struct { - apr_uint32_t success_count; - apr_uint32_t failure_count; - apr_uint32_t download_timeout_count; - apr_uint32_t imagemagick_timeout_count; -} dims_stats_rec; - -dims_stats_rec *stats; -apr_shm_t *shm; -apr_hash_t *ops; - -static void * -dims_create_config(apr_pool_t *p, server_rec *s) -{ - dims_config_rec *config; - - config = (dims_config_rec *) apr_pcalloc(p, sizeof(dims_config_rec)); - config->whitelist = apr_table_make(p, 5); - config->clients = apr_hash_make(p); - config->ignore_default_output_format = apr_table_make(p, 3); - - config->download_timeout = 3000; - config->imagemagick_timeout = 3000; - - config->no_image_url = NULL; - config->no_image_expire = 60; - config->default_image_prefix = NULL; - - config->default_expire = 86400; - - config->strip_metadata = 1; - config->optimize_resize = 0; - config->disable_encoded_fetch = 0; - config->default_output_format = NULL; - - config->area_size = 128 * 1024 * 1024; // 128mb max. - config->memory_size = 512 * 1024 * 1024; // 512mb max. - config->map_size = 1024 * 1024 * 1024; // 1024mb max. - config->disk_size = 2048UL * 1024UL * 1024UL; // 2048mb max. - - config->curl_queue_size = 10; - config->cache_dir = NULL; - config->secret_key = apr_pstrdup(p,"m0d1ms"); - config->encryption_algorithm = "AES/ECB/PKCS5Padding"; - config->max_expiry_period= 0; // never expire - - return (void *) config; -} - -static const char * -dims_config_set_whitelist(cmd_parms *cmd, void *d, int argc, char *const argv[]) -{ - dims_config_rec *config = (dims_config_rec *) ap_get_module_config( - cmd->server->module_config, - &dims_module); - int i; - - for(i = 0; i < argc; i++) { - char *hostname = argv[i]; - - /* Remove glob character and '.' if they're on the string and set - * the value in the hash to glob. - */ - if(hostname[0] == '*') { - if(*++hostname == '.') { - hostname++; - } - - apr_table_setn(config->whitelist, hostname, "glob"); - } else { - apr_table_setn(config->whitelist, argv[i], "exact"); - } - } - - return NULL; -} - -static const char * -dims_config_set_ignore_default_output_format(cmd_parms *cmd, void *d, int argc, char *const argv[]) -{ - dims_config_rec *config = (dims_config_rec *) ap_get_module_config( - cmd->server->module_config, - &dims_module); - int i; - - for(i = 0; i < argc; i++) { - char *format = argv[i]; - char *s = format; - while (*s) { *s = toupper(*s); s++; } - - apr_table_setn(config->ignore_default_output_format, format, "1"); - } - return NULL; -} - -static const char * -dims_config_set_default_expire(cmd_parms *cmd, void *dummy, const char *arg) -{ - dims_config_rec *config = (dims_config_rec *) ap_get_module_config( - cmd->server->module_config, &dims_module); - config->default_expire = atol(arg); - return NULL; -} - -static const char * -dims_config_set_no_image_expire(cmd_parms *cmd, void *dummy, const char *arg) -{ - dims_config_rec *config = (dims_config_rec *) ap_get_module_config( - cmd->server->module_config, &dims_module); - config->no_image_expire = atol(arg); - return NULL; -} - -static const char * -dims_config_set_download_timeout(cmd_parms *cmd, void *dummy, const char *arg) -{ - dims_config_rec *config = (dims_config_rec *) ap_get_module_config( - cmd->server->module_config, &dims_module); - config->download_timeout = atol(arg); - return NULL; -} - -static const char * -dims_config_set_imagemagick_timeout(cmd_parms *cmd, void *dummy, const char *arg) -{ - dims_config_rec *config = (dims_config_rec *) ap_get_module_config( - cmd->server->module_config, &dims_module); - config->imagemagick_timeout = atol(arg); - return NULL; -} - -static const char * -dims_config_set_strip_metadata(cmd_parms *cmd, void *dummy, const char *arg) -{ - dims_config_rec *config = (dims_config_rec *) ap_get_module_config( - cmd->server->module_config, &dims_module); - // The default is 1, so anything other than "false" will use the default - if(strcmp(arg, "false") == 0) { - config->strip_metadata = 0; - } - else { - config->strip_metadata = 1; - } - return NULL; -} - -static const char * -dims_config_set_include_disposition(cmd_parms *cmd, void *dummy, const char *arg) -{ - dims_config_rec *config = (dims_config_rec *) ap_get_module_config( - cmd->server->module_config, &dims_module); - if(strcmp(arg, "true") == 0) { - config->include_disposition = 1; - } - else { - config->include_disposition = 0; - } - return NULL; -} - -static const char * -dims_config_set_optimize_resize(cmd_parms *cmd, void *dummy, const char *arg) -{ - dims_config_rec *config = (dims_config_rec *) ap_get_module_config( - cmd->server->module_config, &dims_module); - config->optimize_resize = atof(arg); - return NULL; -} - -static const char * -dims_config_set_encoded_fetch(cmd_parms *cmd, void *dummy, const char *arg) -{ - dims_config_rec *config = (dims_config_rec *) ap_get_module_config( - cmd->server->module_config, &dims_module); - config->disable_encoded_fetch = atoi(arg); - return NULL; -} - -static const char * -dims_config_set_encryption_algorithm(cmd_parms *cmd, void *dummy, const char *arg) -{ - dims_config_rec *config = (dims_config_rec *) ap_get_module_config( - cmd->server->module_config, &dims_module); - config->encryption_algorithm = (char *) arg; - return NULL; -} - -static const char * -dims_config_set_default_output_format(cmd_parms *cmd, void *dummy, const char *arg) -{ - dims_config_rec *config = (dims_config_rec *) ap_get_module_config( - cmd->server->module_config, &dims_module); - char *output_format = (char *) arg; - char *s = output_format; - while (*s) { *s = toupper(*s); s++; } - config->default_output_format = output_format; - return NULL; -} - -static const char * -dims_config_set_user_agent_override(cmd_parms *cmd, void *dummy, const char *arg) -{ - dims_config_rec *config = (dims_config_rec *) ap_get_module_config( - cmd->server->module_config, &dims_module); - char *user_agent = (char *) arg; - config->user_agent_override = user_agent; - return NULL; -} - -static const char * -dims_config_set_user_agent_enabled(cmd_parms *cmd, void *dummy, const char *arg) -{ - dims_config_rec *config = (dims_config_rec *) ap_get_module_config( - cmd->server->module_config, &dims_module); - if(strcmp(arg, "true") == 0) { - config->user_agent_enabled = 1; - } - else { - config->user_agent_enabled = 0; - } - return NULL; -} - -static const char * -dims_config_set_client(cmd_parms *cmd, void *d, int argc, char *const argv[]) -{ - dims_config_rec *config = (dims_config_rec *) ap_get_module_config( - cmd->server->module_config, &dims_module); - - dims_client_config_rec *client_config = NULL; - - if(argc == 0) { - return NULL; - } - - if(argc >= 1) { - client_config = (dims_client_config_rec *) - apr_pcalloc(cmd->pool, - sizeof(dims_client_config_rec)); - - client_config->no_image_url = NULL; - client_config->cache_control_max_age = config->default_expire; - client_config->edge_control_downstream_ttl = -1; - client_config->trust_src = 0; - client_config->min_src_cache_control = -1; - client_config->max_src_cache_control = -1; - - switch(argc) { - case 8: - if(strcmp(argv[7], "-") != 0) { - client_config->secret_key = argv[7]; - } else { - client_config->secret_key = NULL; - } - case 7: - if(strcmp(argv[6], "-") != 0) { - if(atoi(argv[6]) <= 0 && strcmp(argv[6], "0") != 0) { - // erroneous value - client_config->max_src_cache_control = -2; - } - else { - client_config->max_src_cache_control = atoi(argv[6]); - } - } - case 6: - if(strcmp(argv[5], "-") != 0) { - if(atoi(argv[5]) <= 0 && strcmp(argv[5], "0") != 0) { - // erroneous value - client_config->min_src_cache_control = -2; - } - else { - client_config->min_src_cache_control = atoi(argv[5]); - } - } - case 5: - if(strcmp(argv[4], "trust") == 0) { - client_config->trust_src = 1; - } - case 4: - if(strcmp(argv[3], "-") != 0) { - client_config->edge_control_downstream_ttl = atoi(argv[3]); - } - case 3: - if(strcmp(argv[2], "-") != 0) { - client_config->cache_control_max_age = atoi(argv[2]); - } - case 2: - if(strcmp(argv[1], "-") != 0) { - client_config->no_image_url = argv[1]; - } - case 1: - client_config->id = argv[0]; - } - } - - apr_hash_set(config->clients, argv[0], APR_HASH_KEY_STRING, client_config); - - return NULL; -} - -static const char * -dims_config_set_no_image_url(cmd_parms *cmd, void *dummy, const char *arg) -{ - dims_config_rec *config = (dims_config_rec *) ap_get_module_config( - cmd->server->module_config, &dims_module); - config->no_image_url = (char *) arg; - return NULL; -} - -static const char * -dims_config_set_image_prefix(cmd_parms *cmd, void *dummy, const char *arg) -{ - dims_config_rec *config = (dims_config_rec *) ap_get_module_config( - cmd->server->module_config, &dims_module); - config->default_image_prefix = (char *) arg; - - if (strncmp(config->default_image_prefix, "https://", 8) != 0 && - strncmp(config->default_image_prefix, "http://", 7) != 0) { - return "DimsDefaultImagePrefix must start with 'https://' or 'http://'"; - } - - return NULL; -} - -static const char * -dims_config_set_imagemagick_disk_size(cmd_parms *cmd, void *dummy, const char *arg) -{ - dims_config_rec *config = (dims_config_rec *) ap_get_module_config( - cmd->server->module_config, &dims_module); - config->disk_size = atol(arg) * 1024 * 1024; - - return NULL; -} -static const char * -dims_config_set_secretkeyExpiryPeriod(cmd_parms *cmd, void *dummy, const char *arg) -{ - dims_config_rec *config = (dims_config_rec *) ap_get_module_config( - cmd->server->module_config, &dims_module); - config->max_expiry_period = atol(arg); - return NULL; -} -static const char * -dims_config_set_imagemagick_area_size(cmd_parms *cmd, void *dummy, const char *arg) -{ - dims_config_rec *config = (dims_config_rec *) ap_get_module_config( - cmd->server->module_config, &dims_module); - config->area_size = atol(arg) * 1024 * 1024; - return NULL; -} - -static const char * -dims_config_set_imagemagick_map_size(cmd_parms *cmd, void *dummy, const char *arg) -{ - dims_config_rec *config = (dims_config_rec *) ap_get_module_config( - cmd->server->module_config, &dims_module); - config->map_size = atol(arg) * 1024 * 1024; - return NULL; -} - -static const char * -dims_config_set_imagemagick_memory_size(cmd_parms *cmd, void *dummy, const char *arg) -{ - dims_config_rec *config = (dims_config_rec *) ap_get_module_config( - cmd->server->module_config, &dims_module); - config->memory_size = atol(arg) * 1024 * 1024; - return NULL; -} - -static void show_time(request_rec *r, apr_interval_time_t tsecs) -{ - int days, hrs, mins, secs; - - secs = (int)(tsecs % 60); - tsecs /= 60; - mins = (int)(tsecs % 60); - tsecs /= 60; - hrs = (int)(tsecs % 24); - days = (int)(tsecs / 24); - - ap_rprintf(r, "Uptime: "); - - if (days) ap_rprintf(r, " %d day%s", days, days == 1 ? "" : "s"); - if (hrs) ap_rprintf(r, " %d hour%s", hrs, hrs == 1 ? "" : "s"); - if (mins) ap_rprintf(r, " %d minute%s", mins, mins == 1 ? "" : "s"); - if (secs) ap_rprintf(r, " %d second%s", secs, secs == 1 ? "" : "s"); - - ap_rprintf(r, "\n"); -} - -/** - * This callback is called by the libcurl API to write data into - * memory as it's being downloaded. - * - * The memory allocated here must be freed manually as it's not - * allocated into an apache memory pool. - */ -static size_t -dims_write_image_cb(void *ptr, size_t size, size_t nmemb, void *data) -{ - dims_image_data_t *mem = (dims_image_data_t *) data; - size_t realsize = size * nmemb; - - /* Allocate more memory if needed. */ - if(mem->size - mem->used <= realsize) { - mem->size = mem->size == 0 ? realsize : (mem->size + realsize) * 1.25; - mem->data = (char *) realloc(mem->data, mem->size); - } - - if (mem->data) { - memcpy(&(mem->data[mem->used]), ptr, realsize); - mem->used += realsize; - } - - return realsize; -} - -static size_t -dims_write_header_cb(void *ptr, size_t size, size_t nmemb, void *data) -{ - dims_request_rec *d = (dims_request_rec *) data; - size_t realsize = size * nmemb; - char *start = (char *) ptr; - char *header = (char *) ptr; - char *key = NULL, *value = NULL; - - while(header < (start + realsize)) { - if(*header == ':') { - key = apr_pstrndup(d->pool, start, header - start); - while(*header == ' ') { - header++; - } - value = apr_pstrndup(d->pool, header, start + realsize - header - 2); - header = start + realsize; - } - header++; - } - - if(key && value && strcmp(key, "Cache-Control") == 0) { - d->cache_control = value; - } else if(key && value && strcmp(key, "Edge-Control") == 0) { - d->edge_control = value; - } else if(key && value && strcmp(key, "Last-Modified") == 0) { - d->last_modified = value; - } else if(key && value && strcmp(key, "ETag") == 0) { - d->etag = value; - } - - return realsize; -} +typedef struct dims_processed_image { + size_t length; + unsigned char *bytes; + char *format; +} dims_processed_image; /** * This callback is called by the MagicWand API during transformation @@ -534,8 +69,10 @@ dims_write_header_cb(void *ptr, size_t size, size_t nmemb, void *data) * ImageMagick is busy loading up the pixel cache. */ MagickBooleanType -dims_imagemagick_progress_cb(const char *text, const MagickOffsetType offset, - const MagickSizeType span, void *client_data) +dims_imagemagick_progress_cb(const char *text, + const MagickOffsetType offset, + const MagickSizeType span, + void *client_data) { dims_progress_rec *p = (dims_progress_rec *) client_data; @@ -544,9 +81,8 @@ dims_imagemagick_progress_cb(const char *text, const MagickOffsetType offset, //long complete = (long) 100L * (offset / (span - 1)); if(diff > p->d->config->imagemagick_timeout) { - p->d->status = DIMS_IMAGEMAGICK_TIMEOUT; ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, p->d->r, - "Imagemagick operation, '%s', " + "Imagemagick: operation '%s', " "timed out after %d ms. " "(max: %d), on request: %s", text, (int) diff, @@ -558,420 +94,120 @@ dims_imagemagick_progress_cb(const char *text, const MagickOffsetType offset, return MagickTrue; } -/* Converts a hex character to its integer value */ -char from_hex(char ch) { - return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10; -} - -/* Converts an integer value to its hex character*/ -char to_hex(char code) { - static char hex[] = "0123456789abcdef"; - return hex[code & 15]; -} - -/* Returns a url-encoded version of str */ -/* IMPORTANT: be sure to free() the returned string after use */ -char *url_encode(char *str) { - char *pstr = str, *buf = malloc(strlen(str) * 3 + 1), *pbuf = buf; - while (*pstr) { - if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == '~' || *pstr == ':' || *pstr == '/' || *pstr == '?' || *pstr == '=' || *pstr == '&') - *pbuf++ = *pstr; - else - *pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex(*pstr & 15); - pstr++; - } - *pbuf = '\0'; - return buf; -} - -static int -dims_curl_debug_cb(CURL *handle, - curl_infotype type, - char *data, - size_t size, - void *clientp) -{ - dims_request_rec *d = (dims_request_rec *) clientp; - switch(type) { - case CURLINFO_HEADER_OUT: - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, "Curl request header data: %s ", data); - break; - default: - break; - } -} - -CURLcode -dims_get_image_data(dims_request_rec *d, char *fetch_url, dims_image_data_t *data) -{ - CURL *curl_handle; - CURLcode code; - - dims_image_data_t image_data; - image_data.data = NULL; - image_data.size = 0; - image_data.used = 0; - int extra_time = 0; - - /* Allow for some extra time to download the NOIMAGE image. */ - void *s = NULL; - - if (d->status == DIMS_DOWNLOAD_TIMEOUT) { - extra_time += 500; - } - - apr_pool_userdata_get((void *) &s, DIMS_CURL_SHARED_KEY, - d->r->server->process->pool); - - /* Encode the fetch URL before downloading */ - if (!d->config->disable_encoded_fetch) { - fetch_url = url_encode(fetch_url); - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, "Encoded URL: %s ", fetch_url); - } - - curl_handle = curl_easy_init(); - curl_easy_setopt(curl_handle, CURLOPT_URL, fetch_url); - curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, dims_write_image_cb); - curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) &image_data); - curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, dims_write_header_cb); - curl_easy_setopt(curl_handle, CURLOPT_HEADERDATA, (void *) d); - curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT_MS, d->config->download_timeout + extra_time); - curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1); - curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1); - curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L); - curl_easy_setopt(curl_handle, CURLOPT_DEBUGFUNCTION, dims_curl_debug_cb); - curl_easy_setopt(curl_handle, CURLOPT_DEBUGDATA, d); - - /* Set the user agent to dims/ */ - if (d->config->user_agent_override != NULL && d->config->user_agent_enabled == 1) { - curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, d->config->user_agent_override); - } else if (d->config->user_agent_enabled == 1) { - char *dims_useragent = apr_psprintf(d->r->pool, "mod_dims/%s", MODULE_VERSION); - curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, dims_useragent); - } - - /* The curl shared handle allows this process to share DNS cache - * and prevents the DNS cache from going away after every request. - */ - if (s) { - dims_curl_rec *locks = (dims_curl_rec *) s; - curl_easy_setopt(curl_handle, CURLOPT_SHARE, locks->share); - } - - code = curl_easy_perform(curl_handle); - - curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &image_data.response_code); - curl_easy_cleanup(curl_handle); - - *data = image_data; - - if (!d->config->disable_encoded_fetch) { - free(fetch_url); - } - - return code; -} - /** * Fetch remote image. If successful the MagicWand will * have the new image loaded. */ -static int -dims_fetch_remote_image(dims_request_rec *d, const char *url) +static apr_status_t +dims_download_source_image(dims_request_rec *d, const char *url) { - dims_image_data_t image_data; - char *fetch_url = url ? (char *) url : d->no_image_url; - int extra_time = 0; apr_time_t start_time; + d->source_image = apr_palloc(d->pool, sizeof(dims_image_data_t)); + if (d->source_image == NULL) { + return HTTP_INTERNAL_SERVER_ERROR; + } - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, - "Loading image from %s", fetch_url); - - /* Allow file:/// references for NOIMAGE urls. */ - if(url == NULL && strncmp(fetch_url, "file:///", 8) == 0) { - char *filename = fetch_url + 7; - apr_finfo_t finfo; - apr_status_t status; - apr_time_t start_time; - - /* Read image from disk. */ - start_time = apr_time_now(); - status = apr_stat(&finfo, filename, APR_FINFO_SIZE, d->pool); - if(status != 0) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, d->r, - "mod_dims error, 'NOIMAGE image not found at %s', " - "on request: %s ", filename, d->r->uri); - return 1; - } - d->download_time = (apr_time_now() - start_time) / 1000; - d->original_image_size = finfo.size; - - start_time = apr_time_now(); - if(MagickReadImage(d->wand, filename) == MagickFalse) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, d->r, - "mod_dims error, 'Failed to load NOIMAGE image from %s', " - "on request: %s ", filename, d->r->uri); - return 1; - } - d->imagemagick_time += (apr_time_now() - start_time) / 1000; - } else { - CURLcode code = dims_get_image_data(d, fetch_url, &image_data); - - start_time = apr_time_now(); - if(code != 0) { - if(image_data.data) { - free(image_data.data); - } - - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, d->r, - "libcurl error, '%s', on request: %s ", - curl_easy_strerror(code), d->r->uri); - - d->status = DIMS_FAILURE; - d->fetch_http_status = 500; - if(code == CURLE_OPERATION_TIMEDOUT) { - d->status = DIMS_DOWNLOAD_TIMEOUT; - } - - d->download_time = (apr_time_now() - start_time) / 1000; - - return 1; - } - - d->download_time = (apr_time_now() - start_time) / 1000; - - // Don't set the fetch_http_status if we're downloading the NOIMAGE image. - if (url != NULL) { - d->fetch_http_status = image_data.response_code; - } - - if(image_data.response_code != 200) { - if(image_data.response_code == 404) { - d->status = DIMS_FILE_NOT_FOUND; - } - - if(image_data.data) { - free(image_data.data); - } - - return 1; - } + d->source_image->cache_control = NULL; + d->source_image->edge_control = NULL; + d->source_image->last_modified = NULL; - char *actual_image_data = image_data.data; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, "Downloading: %s", url); - // Ensure SVGs have the appropriate XML header. - if (image_data.size >= 4 && strncmp(image_data.data, "pool, "\n", image_data.data, NULL); - image_data.used += 55; - } - - start_time = apr_time_now(); - if(MagickReadImageBlob(d->wand, actual_image_data, image_data.used) - == MagickFalse) { - ExceptionType et; + start_time = apr_time_now(); + CURLcode code = dims_curl(d, url, d->source_image); + d->download_time = (apr_time_now() - start_time) / 1000; - if(image_data.data) { - free(image_data.data); - } + if(code != CURLE_OK) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, d->r, "Downloading: libcurl error, '%s'", curl_easy_strerror(code)); - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, d->r, - "ImageMagick error, '%s', on request: %s ", - MagickGetException(d->wand, &et), d->r->uri); + return HTTP_INTERNAL_SERVER_ERROR; + } - return 1; - } - d->imagemagick_time += (apr_time_now() - start_time) / 1000; + if(d->source_image->response_code != 200) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, d->r, "Downloading: libcurl response code, '%d'", d->source_image->response_code); - if(d->status != DIMS_DOWNLOAD_TIMEOUT) { - d->original_image_size = image_data.used; - } + return d->source_image->response_code; + } - free(image_data.data); + // Ensure SVGs have the appropriate XML header. + if (d->source_image->size >= 4 && strncmp(d->source_image->data, "source_image->data = + apr_pstrcat(d->pool, "\n", + d->source_image->data, NULL); + d->source_image->used += 55; } - return 0; + return d->source_image->response_code; } -static apr_status_t -dims_send_image(dims_request_rec *d) +static void +dims_send_error(dims_request_rec *d, int status) { - char buf[128]; - unsigned char *blob; - char *format; - char *content_type; - size_t length; - apr_time_t start_time; - int expire_time = 0; - - char *cache_control = NULL, - *edge_control = NULL; - - // variables referring to the src image - char *src_header; - char *src_start; - int src_len; - - char *src_max_age_str; - int src_max_age = 0; - - int trust_src_img = 0; - - format = MagickGetImageFormat(d->wand); - - MagickResetIterator(d->wand); + ap_assert(d != NULL); - start_time = apr_time_now(); - blob = MagickGetImagesBlob(d->wand, &length); - d->imagemagick_time += (apr_time_now() - start_time) / 1000; + request_rec *r = d->r; - /* Set the Content-Type based on the image format. */ - content_type = apr_psprintf(d->pool, "image/%s", format); - ap_content_type_tolower(content_type); - ap_set_content_type(d->r, content_type); - - if(d->status == DIMS_FILE_NOT_FOUND) { - d->r->status = HTTP_NOT_FOUND; - } else if (d->fetch_http_status != 0) { - d->r->status = d->fetch_http_status; - } else if(d->status != DIMS_SUCCESS) { - if (d->status == DIMS_BAD_URL - || d->status == DIMS_BAD_ARGUMENTS) { - d->r->status = HTTP_BAD_REQUEST; - } else { - //Includes DIMS_BAD_CLIENT, DIMS_DOWNLOAD_TIMEOUT, DIMS_IMAGEMAGICK_TIMEOUT, DIMS_HOSTNAME_NOT_IN_WHITELIST - d->r->status = HTTP_INTERNAL_SERVER_ERROR; - } - } + d->wand = NewMagickWand(); - if (blob == NULL) { - d->r->status = HTTP_BAD_REQUEST; + PixelWand *background = NewPixelWand(); + if (background == NULL) { + return; } - if(d->status == DIMS_SUCCESS && d->fetch_http_status == 200 && d->client_config) { - - // if the src image has a cache_control header, parse out the max-age - if(d->cache_control) { + PixelSetColor(background, d->client_config != NULL ? + d->client_config->error_image_background : + d->config->error_image_background); - // Ex. max-age=3600 - src_header = d->cache_control; - src_start = src_header; - src_len = strlen(src_header); + MagickNewImage(d->wand, 1024, 1024, background); - while(src_header < (src_start + src_len)) { - if(*src_header == '=') { - src_header++; - while(*src_header == ' ') { - src_header++; - } - src_max_age_str = apr_pstrdup(d->pool, src_header); - src_max_age = atoi(src_max_age_str); - } - src_header++; - } - } - - // if we trust the src image and were able to parse its cache header - if(d->client_config->trust_src && src_max_age > 0) { - - // if the min and max config values were valid - if(d->client_config->min_src_cache_control >= -1 && - d->client_config->max_src_cache_control >= -1) { + MagickStatusType flags; + RectangleInfo rec; - // if the max-age value is between the min and max, use the src value - if( (d->client_config->min_src_cache_control == -1 || - src_max_age >= d->client_config->min_src_cache_control) && - (d->client_config->max_src_cache_control == -1 || - src_max_age <= d->client_config->max_src_cache_control)) { + /* Process operations. */ + int output_format_provided = false; + for (int i = 0; i < d->commands_list->nelts; i++) { + dims_command_t *cmd = &APR_ARRAY_IDX(d->commands_list, i, dims_command_t); - trust_src_img = 1; - } - else { // use the client configred default - trust_src_img = 0; - } - } - else { // invalid max/min, use defaults - trust_src_img = 0; - } - } - else { // don't trust src, and use client configured default - trust_src_img = 0; + if (strcmp(cmd->name, "format") == 0) { + output_format_provided = true; } + dims_operation_func *func = dims_operation_lookup(cmd->name); + if (func != NULL) { + char *err = NULL; + apr_status_t code; - if(trust_src_img) { - cache_control = apr_psprintf(d->pool, "max-age=%d, public", src_max_age); - if(d->client_config->edge_control_downstream_ttl != -1) { - edge_control = apr_psprintf(d->pool, "downstream-ttl=%d", src_max_age); - } - expire_time = src_max_age; - } - else { - cache_control = apr_psprintf(d->pool, "max-age=%d, public", - d->client_config->cache_control_max_age); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, "Executing: %s => %s", cmd->name, cmd->arg); - if(d->client_config->edge_control_downstream_ttl != -1) { - edge_control = apr_psprintf(d->pool, "downstream-ttl=%d", - d->client_config->edge_control_downstream_ttl); + if ((code = func(d, cmd->arg, &err)) != DIMS_SUCCESS) { + continue; } - expire_time = d->client_config->cache_control_max_age; } - } else if(d->status == DIMS_SUCCESS && d->fetch_http_status == 200) { - expire_time = d->config->default_expire; - cache_control = apr_psprintf(d->pool, "max-age=%d, public", expire_time); - } else { - expire_time = d->config->no_image_expire; - cache_control = apr_psprintf(d->pool, "max-age=%d, public", expire_time); - } - - if(cache_control) { - apr_table_set(d->r->headers_out, "Cache-Control", cache_control); - } - - if(edge_control) { - apr_table_set(d->r->headers_out, "Edge-Control", edge_control); - } - - if(d->filename && d->config->include_disposition) { - char *disposition = apr_psprintf(d->pool, "inline; filename=\"%s\"", d->filename); - apr_table_set(d->r->headers_out, "Content-Disposition", disposition); - } else if(d->content_disposition_filename && d->send_content_disposition) { - char *disposition = apr_psprintf(d->pool, "attachment; filename=\"%s\"", d->content_disposition_filename); - apr_table_set(d->r->headers_out, "Content-Disposition", disposition); - } - - if(expire_time) { - char buf[APR_RFC822_DATE_LEN]; - apr_time_t e = apr_time_now() + ((long long) expire_time * 1000L * 1000L); - apr_rfc822_date(buf, e); - apr_table_set(d->r->headers_out, "Expires", buf); + MagickMergeImageLayers(d->wand, TrimBoundsLayer); } - if(d->status == DIMS_SUCCESS) { - snprintf(buf, 128, "DIMS_CLIENT_%s", d->client_id); - apr_table_set(d->r->notes, "DIMS_CLIENT", d->client_id); - apr_table_set(d->r->subprocess_env, buf, d->client_id); + // Set the format for the error image. + if (!output_format_provided && d->config->default_output_format) { + MagickSetImageFormat(d->wand, d->config->default_output_format); + } else { + MagickSetImageFormat(d->wand, "JPEG"); } - char *etag = NULL; - if (d->etag) { - etag = ap_md5(d->pool, - (unsigned char *) apr_pstrcat(d->pool, d->request_hash, d->etag, NULL)); - } else if (d->last_modified) { - etag = ap_md5(d->pool, - (unsigned char *) apr_pstrcat(d->pool, d->request_hash, d->last_modified, NULL)); - } + MagickResetIterator(d->wand); - if (etag) { - apr_table_set(d->r->headers_out, "ETag", etag); - } + size_t length; + unsigned char *blob = MagickGetImagesBlob(d->wand, &length); + char *format = MagickGetImageFormat(d->wand); - MagickSizeType image_size = 0; - MagickGetImageLength(d->wand, &image_size); + // Set the Content-Type based on the image format. + char *content_type = apr_psprintf(r->pool, "image/%s", format); + ap_content_type_tolower(content_type); + ap_set_content_type(r, content_type); if (blob != NULL) { char content_length[256] = ""; - snprintf(content_length, sizeof(content_length), "%zu", (size_t)image_size); + snprintf(content_length, sizeof(content_length), "%zu", (size_t) length); apr_table_set(d->r->headers_out, "Content-Length", content_length); ap_rwrite(blob, length, d->r); @@ -979,88 +215,108 @@ dims_send_image(dims_request_rec *d) apr_table_set(d->r->headers_out, "Content-Length", "0"); } + // Calculate the cache control headers. + char buf[APR_RFC822_DATE_LEN]; + apr_time_t e = apr_time_now() + ((long long) d->config->error_image_expire * 1000L * 1000L); + apr_rfc822_date(buf, e); + apr_table_set(d->r->headers_out, "Expires", buf); + + d->r->status = status; ap_rflush(d->r); - MagickRelinquishMemory(blob); - MagickRelinquishMemory(format); DestroyMagickWand(d->wand); + d->wand = NULL; - /* After the image is sent record stats about this request. */ - if(d->status == DIMS_SUCCESS) { - apr_atomic_inc32(&stats->success_count); - } else { - apr_atomic_inc32(&stats->failure_count); - } + DestroyPixelWand(background); + MagickRelinquishMemory(format); + MagickRelinquishMemory(blob); +} - if(d->status == DIMS_DOWNLOAD_TIMEOUT) { - apr_atomic_inc32(&stats->download_timeout_count); - } else if(d->status == DIMS_IMAGEMAGICK_TIMEOUT) { - apr_atomic_inc32(&stats->imagemagick_timeout_count); - } +static void +dims_send_image(dims_request_rec *d, dims_processed_image *image) +{ + ap_assert(d != NULL); + ap_assert(image != NULL); + ap_assert(image->bytes != NULL); + ap_assert(image->format != NULL); - /* Record metrics for logging. */ - snprintf(buf, 128, "%d", d->status); - apr_table_set(d->r->notes, "DIMS_STATUS", buf); + request_rec *r = d->r; - snprintf(buf, 128, "%ld", d->original_image_size); - apr_table_set(d->r->notes, "DIMS_ORIG_BYTES", buf); + // Set the Content-Type based on the image format. + char *format = image->format; + char *content_type = apr_psprintf(r->pool, "image/%s", format); + ap_content_type_tolower(content_type); + ap_set_content_type(r, content_type); - snprintf(buf, 128, "%ld", d->download_time); - apr_table_set(d->r->notes, "DIMS_DL_TIME", buf); + // Calculate the cache control headers. + int max_age = d->client_config->cache_control_max_age; + int expire_time = d->client_config->cache_control_max_age; + int downstream_ttl = d->client_config->edge_control_downstream_ttl; + char *cache_control = NULL, + *edge_control = NULL; - snprintf(buf, 128, "%ld", (apr_time_now() - d->start_time) / 1000); - apr_table_set(d->r->notes, "DIMS_TOTAL_TIME", buf); + // Use 'max-age' from the source image only if the client trusts the source and the + // source image has a 'max-age' that falls within the configured min and max values. + int trust_src_img = 0; + if (d->client_config->trust_src && d->source_image->max_age > 0) { + // The source image domain is trusted and the source image has a max-age set. + + int min = d->client_config->min_src_cache_control; + int max = d->client_config->max_src_cache_control; + + if ((min == -1 || d->source_image->max_age >= min) && + (max == -1 || d->source_image->max_age <= max)) { + // The source image max-age falls within the configured min and max values. + + max_age = d->source_image->max_age; + expire_time = d->source_image->max_age; + downstream_ttl = d->source_image->max_age; + } + } - if(d->status != DIMS_DOWNLOAD_TIMEOUT && - d->status != DIMS_IMAGEMAGICK_TIMEOUT) { - snprintf(buf, 128, "%ld", d->imagemagick_time); - apr_table_set(d->r->notes, "DIMS_IM_TIME", buf); + cache_control = apr_psprintf(d->pool, "max-age=%d, public", max_age); + apr_table_set(d->r->headers_out, "Cache-Control", cache_control); + + if(downstream_ttl != -1) { + edge_control = apr_psprintf(d->pool, "downstream-ttl=%d", downstream_ttl); + apr_table_set(d->r->headers_out, "Edge-Control", edge_control); } - return OK; -} + if(d->content_disposition_filename && d->send_content_disposition) { + char *disposition = apr_psprintf(d->pool, "attachment; filename=\"%s\"", d->content_disposition_filename); + apr_table_set(d->r->headers_out, "Content-Disposition", disposition); + } -static apr_status_t -dims_cleanup(dims_request_rec *d, char *err_msg, int status) -{ - if(status != DIMS_IGNORE) { - d->status = status; + if(expire_time > 0) { + char buf[APR_RFC822_DATE_LEN]; + apr_time_t e = apr_time_now() + ((long long) expire_time * 1000L * 1000L); + apr_rfc822_date(buf, e); + apr_table_set(d->r->headers_out, "Expires", buf); } - if(d->wand) { - ExceptionType type; - char *msg = MagickGetException(d->wand, &type); + if (d->source_image->etag) { + char *etag = ap_md5(d->pool, (unsigned char *) + apr_pstrcat(d->pool, d->request_hash, d->source_image->etag, NULL)); - if(type != UndefinedException && msg) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, d->r, - "Imagemagick error, '%s', on request: %s ", - msg, d->r->uri); - } + apr_table_set(d->r->headers_out, "ETag", etag); + } else if (d->source_image->last_modified) { + char *etag = ap_md5(d->pool, (unsigned char *) + apr_pstrcat(d->pool, d->request_hash, d->source_image->last_modified, NULL)); - MagickRelinquishMemory(msg); - DestroyMagickWand(d->wand); - } - - if(err_msg) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, d->r, - "mod_dims error, '%s', on request: %s ", - err_msg, d->r->uri); + apr_table_set(d->r->headers_out, "ETag", etag); } - if(d->no_image_url) { - d->wand = NewMagickWand(); - if(!dims_fetch_remote_image(d, NULL)) { - return dims_send_image(d); - } - DestroyMagickWand(d->wand); - } - if ( status != DIMS_SUCCESS ) { - return HTTP_NOT_FOUND; - } - else { - return DECLINED; + unsigned char *blob = image->bytes; + size_t length = image->length; + if (blob != NULL) { + char *content_length = apr_psprintf(d->pool, "%d", length); + apr_table_set(d->r->headers_out, "Content-Length", content_length); + + ap_rwrite(blob, length, d->r); + ap_rflush(d->r); + } else { + apr_table_set(d->r->headers_out, "Content-Length", "0"); } - } /** @@ -1082,179 +338,138 @@ dims_set_optimal_geometry(dims_request_rec *d) { MagickStatusType flags; RectangleInfo rec; - const char *cmds = d->unparsed_commands; - - if(!d->wand) { - d->wand = NewMagickWand(); - } /* Process operations. */ - while(cmds < d->unparsed_commands + strlen(d->unparsed_commands)) { - char *command = ap_getword(d->pool, &cmds, '/'); + for (int i = 0; i < d->commands_list->nelts; i++) { + dims_command_t *cmd = &APR_ARRAY_IDX(d->commands_list, i, dims_command_t); - if(strcmp(command, "resize") == 0 || - strcmp(command, "legacy_thumbnail") == 0 || - strcmp(command, "thumbnail") == 0) { - char *args = ap_getword(d->pool, &cmds, '/'); + if(strcmp(cmd->name, "resize") == 0 || + strcmp(cmd->name, "legacy_thumbnail") == 0 || + strcmp(cmd->name, "thumbnail") == 0) { - flags = ParseAbsoluteGeometry(args, &rec); + flags = ParseAbsoluteGeometry(cmd->arg, &rec); if(flags & WidthValue && flags & HeightValue && !(flags & PercentValue)) { MagickSetSize(d->wand, rec.width, rec.height); return; } - } else { - if(strcmp(command, "") != 0) { - ap_getword(d->pool, &cmds, '/'); - } } } } /** - * This is the main code for processing images. It will parse - * the command string into individual commands and execute them. - * When it's finished it will write the content type header and - * image data to connection and flush the connection. - * - * Commands should always come in pairs, the command name followed - * by the commands arguments delimited by '/'. Example: - * - * thumbnail/78x110/quality/70 - * - * This would first execute the thumbnail command then it would - * set the quality of the image to 70 before writing the image - * to the connection. + * dims_process_image + * + * Processes an image based on image manipulation commands in the request, + * applying each transformation sequentially. + * + * On success, populates `processed_image_out` with the result. + * + * @d: Image request context. + * @processed_image_out: Output pointer for the processed image. + * + * @returns: APR_SUCCESS on success, or http error code if processing fails. */ -static apr_status_t -dims_process_image(dims_request_rec *d) -{ +static apr_status_t dims_process_image(dims_request_rec *d, dims_processed_image **processed_image_out) { + ap_assert(d != NULL); + ap_assert(processed_image_out != NULL); + + dims_processed_image *image = (dims_processed_image *) apr_palloc(d->pool, sizeof(dims_processed_image)); + if (image == NULL) { + return HTTP_INTERNAL_SERVER_ERROR; + } + *processed_image_out = image; + apr_time_t start_time = apr_time_now(); + d->wand = NewMagickWand(); + dims_set_optimal_geometry(d); + + if (MagickReadImageBlob(d->wand, d->source_image->data, d->source_image->used) == MagickFalse) { + ExceptionType et; + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, d->r, "ImageMagick: error reading image, '%s'", MagickGetException(d->wand, &et)); + + MAGICK_WAND_CLEANUP(d->wand, HTTP_INTERNAL_SERVER_ERROR); + } - /* Hook in the progress monitor. It gets passed a - * dims_progress_rec which keeps track of the start time. - */ - dims_progress_rec *progress_rec = (dims_progress_rec *) apr_palloc( - d->pool, sizeof(dims_progress_rec)); + // Hook in the progress monitor. This will allow us to timeout. + dims_progress_rec *progress_rec = (dims_progress_rec *) apr_palloc(d->pool, sizeof(dims_progress_rec)); + if (progress_rec == NULL) { + MAGICK_WAND_CLEANUP(d->wand, HTTP_INTERNAL_SERVER_ERROR); + } progress_rec->d = d; progress_rec->start_time = apr_time_now(); - - /* Setting the progress monitor from the MagickWand API does not - * seem to work. The monitor never gets called. - */ - SetImageProgressMonitor(GetImageFromMagickWand(d->wand), dims_imagemagick_progress_cb, - (void *) progress_rec); + SetImageProgressMonitor(GetImageFromMagickWand(d->wand), dims_imagemagick_progress_cb, (void *) progress_rec); int exc_strip_cmd = 0; - /* Convert image to RGB from CMYK. */ - if(MagickGetImageColorspace(d->wand) == CMYKColorspace) { - size_t number_profiles; - char **profiles; + // Convert image to RGB from CMYK. + if (MagickGetImageColorspace(d->wand) == CMYKColorspace) { + size_t number_profiles = 0; + char **profiles = NULL; profiles = MagickGetImageProfiles(d->wand, "icc", &number_profiles); - if (number_profiles == 0) { + if (profiles == NULL || number_profiles == 0) { MagickProfileImage(d->wand, "ICC", cmyk_icc, sizeof(cmyk_icc)); + } else { + MagickProfileImage(d->wand, "ICC", rgb_icc, sizeof(rgb_icc)); + MagickRelinquishMemory(profiles); } - MagickProfileImage(d->wand, "ICC", rgb_icc, sizeof(rgb_icc)); - - MagickRelinquishMemory((void *)profiles); } - /* - * Flip image orientation, if needed. - */ MagickAutoOrientImage(d->wand); - /* Flatten images (i.e animated gif) if there's an overlay or file type is `psd`. Otherwise, pass through. */ + // Flatten images (i.e animated gif) if there's an overlay or file type is `psd`. Otherwise, pass through. size_t images = MagickGetNumberImages(d->wand); bool should_flatten = false; if (images > 1) { - const char *cmds = d->unparsed_commands; - while(cmds < d->unparsed_commands + strlen(d->unparsed_commands)) { - char *command = ap_getword(d->pool, &cmds, '/'); + // Always flatten if there's a watermark command. + for (int i = 0; i < d->commands_list->nelts; i++) { + dims_command_t *cmd = &APR_ARRAY_IDX(d->commands_list, i, dims_command_t); - if (strcmp(command, "watermark") == 0) { + if (strcmp(cmd->name, "watermark") == 0) { should_flatten = true; break; } } char *input_format = MagickGetImageFormat(d->wand); - if (strcmp(input_format, "PSD") == 0 || strcmp(input_format, "psd") == 0) { should_flatten = true; } + MagickRelinquishMemory(input_format); if (should_flatten) { - for (int i = 1; i <= images - 1; i++) { + for (size_t i = 1; i <= images - 1; i++) { MagickSetIteratorIndex(d->wand, i); MagickRemoveImage(d->wand); } } } + // Passthrough images contain multiple frames if (images == 1 || should_flatten) { bool output_format_provided = false; - const char *cmds = d->unparsed_commands; - while(cmds < d->unparsed_commands + strlen(d->unparsed_commands)) { - char *command = ap_getword(d->pool, &cmds, '/'); - if (strcmp(command, "format") == 0) { + for (int i = 0; i < d->commands_list->nelts; i++) { + dims_command_t *cmd = &APR_ARRAY_IDX(d->commands_list, i, dims_command_t); + + if (strcmp(cmd->name, "format") == 0) { output_format_provided = true; } - - if(strlen(command) > 0) { - char *args = ap_getword(d->pool, &cmds, '/'); - - /* If the NOIMAGE image is being used for some reason then - * we don't want to crop it. - */ - if(d->use_no_image && - (strcmp(command, "crop") == 0 || - strcmp(command, "legacy_thumbnail") == 0 || - strcmp(command, "legacy_crop") == 0 || - strcmp(command, "thumbnail") == 0)) { - MagickStatusType flags; - RectangleInfo rec; - - flags = ParseAbsoluteGeometry(args, &rec); - - if(rec.width > 0 && rec.height == 0) { - args = apr_psprintf(d->pool, "%ld", rec.width); - } else if(rec.height > 0 && rec.width == 0) { - args = apr_psprintf(d->pool, "x%ld", rec.height); - } else if(rec.width > 0 && rec.height > 0) { - args = apr_psprintf(d->pool, "%ldx%ld", rec.width, rec.height); - } else { - return dims_cleanup(d, NULL, DIMS_BAD_ARGUMENTS); - } - - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, - "Rewriting command %s to 'resize' because a NOIMAGE " - "image is being processed.", command); - - command = "resize"; - } - // Check if the command is present and set flag. - if(strcmp(command, "strip") == 0) { - exc_strip_cmd = 1; - } + if (strcmp(cmd->name, "strip") == 0) { + exc_strip_cmd = 1; + } - dims_operation_func *func = - apr_hash_get(ops, command, APR_HASH_KEY_STRING); - if(func != NULL) { - char *err = NULL; - apr_status_t code; + dims_operation_func *func = dims_operation_lookup(cmd->name); + if (func != NULL) { + char *err = NULL; + apr_status_t code; - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, - "Executing command %s(%s), on request %s", - command, args, d->r->uri); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, "Executing: %s => %s", cmd->name, cmd->arg); - if((code = func(d, args, &err)) != DIMS_SUCCESS) { - return dims_cleanup(d, err, code); - } + if ((code = func(d, cmd->arg, &err)) != DIMS_SUCCESS) { + MAGICK_WAND_CLEANUP(d->wand, HTTP_INTERNAL_SERVER_ERROR); } } @@ -1269,1041 +484,409 @@ dims_process_image(dims_request_rec *d) char *err = NULL; apr_status_t code; - if((code = dims_format_operation(d, d->config->default_output_format, &err)) != DIMS_SUCCESS) { - return dims_cleanup(d, err, code); + if ((code = dims_format_operation(d, d->config->default_output_format, &err)) != DIMS_SUCCESS) { + MAGICK_WAND_CLEANUP(d->wand, HTTP_INTERNAL_SERVER_ERROR); } } } } - /* - * If the strip command was not executed from the loop, call it anyway with NULL args - */ - if(!exc_strip_cmd) { - dims_operation_func *strip_func = apr_hash_get(ops, "strip", APR_HASH_KEY_STRING); - if(strip_func != NULL) { - char *err = NULL; - apr_status_t code; + // If the strip command was not executed from the loop + if (!exc_strip_cmd && d->config->strip_metadata) { + char *err = NULL; + apr_status_t code; - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, - "Executing default strip command, on request %s", d->r->uri); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, "Executing: strip => true (config default)"); - if((code = strip_func(d, NULL, &err)) != DIMS_SUCCESS) { - return dims_cleanup(d, err, code); - } - } + if ((code = dims_strip_operation(d, NULL, &err)) != DIMS_SUCCESS) { + MAGICK_WAND_CLEANUP(d->wand, HTTP_INTERNAL_SERVER_ERROR); + } } - d->imagemagick_time += (apr_time_now() - start_time) / 1000; - - /* Disable timeouts at this point since the only thing left - * to do is save the image. - */ + // Unregister the progress monitor so we don't timeout on writing the image. SetImageProgressMonitor(GetImageFromMagickWand(d->wand), NULL, NULL); - return dims_send_image(d); -} + MagickResetIterator(d->wand); -static apr_status_t -dims_handle_request(dims_request_rec *d) -{ - apr_time_t now_time; - d->wand = NewMagickWand(); + image->format = MagickGetImageFormat(d->wand); + image->bytes = MagickGetImagesBlob(d->wand, &image->length); - /* Check to make sure the client id is valid. */ - if(*d->unparsed_commands == '/') { - d->unparsed_commands++; - } + d->imagemagick_time += (apr_time_now() - start_time) / 1000; - d->client_id = ap_getword(d->pool, (const char **) &d->unparsed_commands, '/'); + MAGICK_WAND_CLEANUP(d->wand, APR_SUCCESS); +} - if(!(d->client_config = - apr_hash_get(d->config->clients, d->client_id, APR_HASH_KEY_STRING))) { - return dims_cleanup(d, "Application ID is not valid", DIMS_BAD_CLIENT); - } +int +verify_dims4_signature(dims_request_rec *d) { + if(d->client_config->secret_key == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, + "Validating: Secret key not set for client '%s'", d->client_config->id); - if(d->client_config && d->client_config->no_image_url) { - d->no_image_url = d->client_config->no_image_url; + return DIMS_FAILURE; } - now_time = apr_time_now(); - if ( d->use_secret_key == 1 ) { - char *hash; - char *expires_str; - long expires; - char *gen_hash; - long now; - hash = ap_getword(d->pool, (const char**)&d->unparsed_commands,'/'); - expires_str = ap_getword(d->pool, (const char**)&d->unparsed_commands,'/'); - expires = atol( expires_str); - now = apr_time_sec(now_time); - if ( expires - now < 0 ) { - ap_log_rerror( APLOG_MARK, APLOG_DEBUG,0, d->r, "Image expired: %s now=%ld", d->r->uri,now); - return dims_cleanup( d, "Image Key has expired", DIMS_BAD_URL); - } - if ( expires - now > d->config->max_expiry_period && d->config->max_expiry_period >0 ) { - ap_log_rerror( APLOG_MARK, APLOG_DEBUG,0, d->r, - "Image expiry too far in the future:%s %s now=%ld",expires_str, d->r->uri,now); - return dims_cleanup(d, "Image key too far in the future", DIMS_BAD_URL); - } + // Standard signature params. + char *signature_params = apr_pstrcat(d->pool, + d->expiration, + d->client_config->secret_key, + d->commands, + d->image_url, + NULL); + if (signature_params == NULL) { + return DIMS_FAILURE; + } - // Throw all query params and their values into a hash table. - // This is used to derive additional signature params. - apr_hash_t *params = apr_hash_make(d->pool); - - if (d->r->args) { - const size_t args_len = strlen(d->r->args) + 1; - char *args = apr_pstrndup(d->r->pool, d->r->args, args_len); - char *token; - char *strtokstate; - - token = apr_strtok(args, "&", &strtokstate); - while (token) { - char *param = strtok(token, "="); - apr_hash_set(params, param, APR_HASH_KEY_STRING, apr_pstrdup(d->r->pool, param + strlen(param) + 1)); - token = apr_strtok(NULL, "&", &strtokstate); - } - } + // Concatenate additional params. + char *strtokstate = NULL; + char *keys = apr_hash_get(d->query_params, "_keys", APR_HASH_KEY_STRING); + if (keys != NULL) { + char *token = apr_strtok(keys, ",", &strtokstate); + while (token) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, "Validating: adding signature param '%s'", token); - // Convert %20 (space) back to '+' in commands. This fixes an issue with "+" being encoded as %20 by some clients. - char *commands = apr_pstrdup(d->r->pool, d->unparsed_commands); - char *s = commands; - while (*s) { - if (*s == ' ') { - *s = '+'; + char *value = apr_hash_get(d->query_params, token, APR_HASH_KEY_STRING); + if (value != NULL) { + signature_params = apr_pstrcat(d->pool, signature_params, value, NULL); } - - s++; - } - - // Standard signature params. - char *signature_params = apr_pstrcat(d->pool, expires_str, d->client_config->secret_key, commands, d->image_url, NULL); - - // Concatenate additional params. - char *token; - char *strtokstate; - token = apr_strtok(apr_hash_get(params, "_keys", APR_HASH_KEY_STRING), ",", &strtokstate); - while (token) { - signature_params = apr_pstrcat(d->pool, signature_params, apr_hash_get(params, token, APR_HASH_KEY_STRING), NULL); token = apr_strtok(NULL, ",", &strtokstate); } - - // Hash. - gen_hash = ap_md5(d->pool, (unsigned char *) signature_params); - - if(d->client_config->secret_key == NULL) { - gen_hash[7] = '\0'; - ap_log_rerror(APLOG_MARK, APLOG_DEBUG,0, d->r, - "Developer key not set for client '%s'", d->client_config->id); - return dims_cleanup(d, "Missing Developer Key", DIMS_BAD_CLIENT); - } else if (strncasecmp(hash, gen_hash, 6) != 0) { - gen_hash[7] = '\0'; - ap_log_rerror(APLOG_MARK, APLOG_DEBUG,0, d->r, - "Key Mismatch: wanted %6s got %6s [%s?url=%s]", gen_hash, hash, d->r->uri, d->image_url); - return dims_cleanup(d, "Key mismatch", DIMS_BAD_URL); - } - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, - "secret key (%s) to validated (%s:%s)", hash, d->unparsed_commands,d->image_url); } - d->request_hash = ap_md5(d->pool, - (unsigned char *) apr_pstrcat(d->pool, d->client_id, - d->unparsed_commands, d->image_url, NULL)); - - dims_set_optimal_geometry(d); - - if (d->image_url && *d->image_url == '/') { - request_rec *sub_req = ap_sub_req_lookup_uri(d->image_url, d->r, NULL); - - if (d->config->default_image_prefix != NULL) { - d->image_url = apr_pstrcat(d->r->pool, d->config->default_image_prefix, d->image_url, NULL); - } else if (sub_req && sub_req->canonical_filename) { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, "Looking up image locally: %s", sub_req->canonical_filename); - d->filename = sub_req->canonical_filename; - } else { - const char *req_server; - char *req_port; - int port; + // Calculate the signature. + char *signature = ap_md5(d->pool, (unsigned char *) signature_params); + if (strncasecmp(d->signature, signature, 6) != 0) { + signature[7] = '\0'; - port = ap_get_server_port(d->r); - req_server = ap_get_server_name_for_url(d->r); - req_port = ap_is_default_port(port, d->r) ? "" : apr_psprintf(d->r->pool, ":%u", port); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, "Validating: invalid dims4 signature, expected %6s got %6s", signature, d->signature); - d->image_url = apr_psprintf(d->r->pool, "%s://%s%s%s", - (char *) ap_http_scheme(d->r), req_server, req_port, d->image_url); - - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, "Expanded relative URI to fully qualified URL since no local file existed: %s", d->image_url); - } + return DIMS_FAILURE; } - if(d->filename) { - /* Handle local images. */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, "Validating: signature valid -> '%s'", signature); - apr_finfo_t finfo; - apr_status_t status; - apr_time_t start_time; + return DIMS_SUCCESS; +} - /* Read image from disk. */ - start_time = apr_time_now(); - status = apr_stat(&finfo, d->filename, APR_FINFO_SIZE, d->pool); - if(status != 0) { - return dims_cleanup(d, "Unable to stat image file", DIMS_FILE_NOT_FOUND); - } - d->download_time = (apr_time_now() - start_time) / 1000; - d->original_image_size = finfo.size; - - start_time = apr_time_now(); - MAGICK_CHECK(MagickReadImage(d->wand, d->filename), d); - d->imagemagick_time += (apr_time_now() - start_time) / 1000; - - return dims_process_image(d); - } else if(d->image_url || d->no_image_url) { - /* Handle remote images. */ - - char *fetch_url = NULL; - - char *hostname, *state = "exact"; - apr_uri_t uri; - int found = 0, done = 0; - - /* Check to make sure the URLs hostname is in the whitelist. Wildcards - * are handled by repeatedly checking the hash for a match after removing - * each part of the hostname until a match is found. If a match is found - * and it's value is set to "glob" the match will be accepted. - */ - if(apr_uri_parse(d->pool, d->image_url, &uri) != APR_SUCCESS) { - return dims_cleanup(d, "Invalid URL in request.", DIMS_BAD_URL); - } +int +verify_dims3_allowlist(dims_request_rec *d) { + char *hostname, *state = apr_pstrdup(d->pool, "exact"); + apr_uri_t uri; + int found = 0, done = 0; - char *filename = strrchr(uri.path, '/'); - if (!filename || !uri.hostname) { - return dims_cleanup(d, "Invalid URL in request.", DIMS_BAD_URL); - } + /* Check to make sure the URLs hostname is in the whitelist. Wildcards + * are handled by repeatedly checking the hash for a match after removing + * each part of the hostname until a match is found. If a match is found + * and it's value is set to "glob" the match will be accepted. + */ + if(apr_uri_parse(d->pool, d->image_url, &uri) != APR_SUCCESS) { + return DIMS_FAILURE; + } - if (*filename == '/') { - d->filename = ++filename; - } + char *filename = strrchr(uri.path, '/'); + if (!filename || !uri.hostname) { + return DIMS_FAILURE; + } - hostname = uri.hostname; - if ( d->use_secret_key == 1 ) { + hostname = uri.hostname; + while(!done) { + char *value = (char *) apr_table_get(d->config->whitelist, hostname); + if(value && strcmp(value, state) == 0) { done = found = 1; - } - while(!done) { - char *value = (char *) apr_table_get(d->config->whitelist, hostname); - if(value && strcmp(value, state) == 0) { - done = found = 1; - } else { - hostname = strstr(hostname, "."); - if(!hostname) { - done = 1; - } else { - hostname++; - } - state = "glob"; - } - } - - if(found) { - fetch_url = d->image_url; } else { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, d->r, - "Requested URL has hostname that is not in the " - "whitelist. (%s)", uri.hostname); - return dims_cleanup(d, NULL, DIMS_HOSTNAME_NOT_IN_WHITELIST); - } - - /* Fetch the image into a buffer. */ - if(fetch_url && dims_fetch_remote_image(d, fetch_url) != 0) { - /* If image failed to download replace it with - * the NOIMAGE image. - */ - if(dims_fetch_remote_image(d, NULL) != 0) { - return DECLINED; + hostname = strstr(hostname, "."); + if(!hostname) { + done = 1; + } else { + hostname++; } - d->use_no_image = 1; + state = apr_pstrdup(d->pool, "glob"); } - - return dims_process_image(d); } - return dims_cleanup(d, NULL, DIMS_FAILURE); + return found; } -/** - * dims_sizer - return the size of the image (height: X\n width: X) - */ + static apr_status_t -dims_sizer(dims_request_rec *d) +dims_handle_request(dims_request_rec *d) { - apr_time_t now_time; - - apr_uri_t uri; - long width, height; + char buf[128]; - d->wand = NewMagickWand(); - now_time = apr_time_now(); - if(!d->image_url ) { - return DECLINED; - } - if(apr_uri_parse(d->pool, d->image_url, &uri) != APR_SUCCESS) { - return dims_cleanup(d, "Invalid URL in request.", DIMS_BAD_URL); - } - if(dims_fetch_remote_image(d, d->image_url ) != 0) { - return dims_cleanup(d, "Unable to get image file", DIMS_FILE_NOT_FOUND); - } - - width = MagickGetImageWidth(d->wand); - height = MagickGetImageHeight(d->wand); - DestroyMagickWand(d->wand); - ap_set_content_type(d->r, "text/plain"); - ap_rprintf(d->r, "{\n\t\"height\": %ld,\n\t\"width\": %ld\n}", height, width ); - return OK; + /* Set initial notes to be logged by mod_log_config. */ + apr_table_setn(d->r->notes, "DIMS_STATUS", "0"); + apr_table_setn(d->r->notes, "DIMS_ORIG_BYTES", "-"); + apr_table_setn(d->r->notes, "DIMS_DL_TIME", "-"); + apr_table_setn(d->r->notes, "DIMS_IM_TIME", "-"); -} + // Download image. + apr_status_t status = dims_download_source_image(d, d->image_url); + if (status != 200) { + dims_send_error(d, status); -int -aes_errors(const char *message, size_t length, void *u) -{ - request_rec *r = (request_rec *) u; - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "%s", message); - return 0; -} + return status; + } -static char * -aes_128_decrypt(request_rec *r, unsigned char *key, unsigned char *encrypted_text, int encrypted_length) -{ - EVP_CIPHER_CTX *ctx; + // Execute Imagemagick commands. + dims_processed_image *image; + status = dims_process_image(d, &image); + if (status != APR_SUCCESS) { + dims_send_error(d, status); - if (!(ctx = EVP_CIPHER_CTX_new())) { - ERR_print_errors_cb(aes_errors, r); - return NULL; + return status; } - if (!EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, NULL)) { - ERR_print_errors_cb(aes_errors, r); - EVP_CIPHER_CTX_free(ctx); - return NULL; - } + // Serve the image. + dims_send_image(d, image); - int decrypted_length; - int plaintext_length, out_length; - char *plaintext = apr_palloc(r->pool, encrypted_length * sizeof(char)); - if (EVP_DecryptUpdate(ctx, (unsigned char *) plaintext, &out_length, encrypted_text, encrypted_length)) { - plaintext_length = out_length; + MagickRelinquishMemory(image->bytes); + MagickRelinquishMemory(image->format); - if (!EVP_DecryptFinal_ex(ctx, (unsigned char *) plaintext + out_length, &plaintext_length)) { - ERR_print_errors_cb(aes_errors, r); - EVP_CIPHER_CTX_free(ctx); - return NULL; - } + /* Record metrics for logging. */ + snprintf(buf, 128, "%ld", d->source_image->used); + apr_table_set(d->r->notes, "DIMS_ORIG_BYTES", buf); - plaintext_length += out_length; - plaintext[plaintext_length] = '\0'; - } else { - ERR_print_errors_cb(aes_errors, r); - EVP_CIPHER_CTX_free(ctx); - return NULL; - } + snprintf(buf, 128, "%ld", d->download_time); + apr_table_set(d->r->notes, "DIMS_DL_TIME", buf); - EVP_CIPHER_CTX_free(ctx); + snprintf(buf, 128, "%ld", (apr_time_now() - d->start_time) / 1000); + apr_table_set(d->r->notes, "DIMS_TOTAL_TIME", buf); - return plaintext; -} + snprintf(buf, 128, "%ld", d->imagemagick_time); + apr_table_set(d->r->notes, "DIMS_IM_TIME", buf); -static char * -aes_128_gcm_decrypt(request_rec *r, unsigned char *key, unsigned char *base64_encrypted_text) { - EVP_CIPHER_CTX *ctx; - int ret; - int plaintext_length = 0; - int out_length; - char *plaintext; - - // Decode the Base64 input - int encrypted_length = apr_base64_decode_len((const char *)base64_encrypted_text); - unsigned char *encrypted_data = apr_palloc(r->pool, encrypted_length); - int decoded_length = apr_base64_decode((char *)encrypted_data, (const char *)base64_encrypted_text); - - // Extract IV (12 bytes), ciphertext, and tag (16 bytes) - unsigned char *iv = encrypted_data; - unsigned char *encrypted_text = encrypted_data + 12; // 12-byte IV - int ciphertext_length = decoded_length - 12 - 16; // 16-byte tag at the end - unsigned char *tag = encrypted_text + ciphertext_length; // 16-byte tag - - // Initialize the context - if (!(ctx = EVP_CIPHER_CTX_new())) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to create new EVP_CIPHER_CTX"); - ERR_print_errors_cb(aes_errors, r); - return NULL; - } + return HTTP_OK; +} - // Initialize the decryption operation - if (!EVP_DecryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, NULL, NULL)) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "EVP_DecryptInit_ex failed (1)"); - ERR_print_errors_cb(aes_errors, r); - EVP_CIPHER_CTX_free(ctx); +static dims_request_rec * +dims_create_request(request_rec *r) +{ + dims_request_rec *request = (dims_request_rec *) apr_palloc(r->pool, sizeof(dims_request_rec)); + if (request == NULL) { return NULL; } - // Set the IV length, if necessary - if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 12, NULL)) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "EVP_CIPHER_CTX_ctrl failed to set IV length"); - ERR_print_errors_cb(aes_errors, r); - EVP_CIPHER_CTX_free(ctx); - return NULL; - } + dims_config_rec *config = (dims_config_rec *) ap_get_module_config(r->server->module_config, &dims_module); - // Set the key and IV - if (!EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv)) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "EVP_DecryptInit_ex failed (2)"); - ERR_print_errors_cb(aes_errors, r); - EVP_CIPHER_CTX_free(ctx); - return NULL; - } + request->r = r; + request->pool = r->pool; + request->wand = NULL; + request->config = config; + request->client_config = NULL; + request->image_url = NULL; + request->request_hash = NULL; + request->start_time = apr_time_now(); + request->download_time = 0; + request->imagemagick_time = 0; + request->send_content_disposition = 0; + request->content_disposition_filename = NULL; + request->commands_list = apr_array_make(r->pool, 10, sizeof(dims_command_t)); - plaintext = apr_palloc(r->pool, ciphertext_length + 1); // +1 for null terminator - if (!plaintext) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Memory allocation failed"); - EVP_CIPHER_CTX_free(ctx); - return NULL; - } + return request; +} - // Provide the message to be decrypted and obtain the plaintext output - if (!EVP_DecryptUpdate(ctx, (unsigned char *)plaintext, &out_length, encrypted_text, ciphertext_length)) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "EVP_DecryptUpdate failed"); - ERR_print_errors_cb(aes_errors, r); - EVP_CIPHER_CTX_free(ctx); - return NULL; - } +static char * +dims_encode_spaces(apr_pool_t *pool, char *str) +{ + char *copy = apr_pstrdup(pool, str); - plaintext_length = out_length; + char *s = copy; + while (*s) { + if (*s == ' ') { + *s = '+'; + } - // Set expected tag value (must be done after EVP_DecryptUpdate) - if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, tag)) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "EVP_CIPHER_CTX_ctrl failed to set tag"); - ERR_print_errors_cb(aes_errors, r); - EVP_CIPHER_CTX_free(ctx); - return NULL; + s++; } - // Finalize the decryption - ret = EVP_DecryptFinal_ex(ctx, (unsigned char *)plaintext + plaintext_length, &out_length); - - EVP_CIPHER_CTX_free(ctx); - - if (ret > 0) { - plaintext_length += out_length; - plaintext[plaintext_length] = '\0'; // Explicitly add the null terminator - return plaintext; - } else { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "EVP_DecryptFinal_ex failed"); - ERR_print_errors_cb(aes_errors, r); - return NULL; - } + return copy; } -/** - * The apache handler. Apache will call this method when a request - * for /dims/, /dims3/, /dims4/ or an image is recieved. - * - * Depending on how this function is called it will do one of three - * things: - * - * 1) Transform old-style request into a new-style request and - * pass it along to the dims_handle_newstyle function. - * - * 2) Parse out the URL and commands and pass them along - * to the dims_handle_newstyle function. - * - * 3) Load the image from the filesystem and pass it along - * with the commands (r->path_info) to dims_process_image. - */ -static apr_status_t -dims_handler(request_rec *r) +static apr_hash_t * +dims_parse_args(request_rec *r) { - dims_request_rec *d = (dims_request_rec *) - apr_palloc(r->pool, sizeof(dims_request_rec)); + apr_hash_t *query_params = apr_hash_make(r->pool); - d->r = r; - d->pool = r->pool; - d->wand = NULL; - d->config = (dims_config_rec *) ap_get_module_config(r->server->module_config, &dims_module); - d->client_config = NULL; - d->no_image_url = d->config->no_image_url; - d->use_no_image = 0; - d->image_url = NULL; - d->filename = NULL; - d->cache_control = NULL; - d->edge_control = NULL; - d->etag = NULL; - d->last_modified = NULL; - d->request_hash = NULL; - d->status = APR_SUCCESS; - d->fetch_http_status = 0; - d->start_time = apr_time_now(); - d->download_time = 0; - d->imagemagick_time = 0; - d->use_secret_key=0; - d->optimize_resize = d->config->optimize_resize; - d->send_content_disposition = 0; - d->content_disposition_filename = NULL; + if (r->args == NULL) { + return query_params; + } - /* Set initial notes to be logged by mod_log_config. */ - apr_table_setn(r->notes, "DIMS_STATUS", "0"); - apr_table_setn(r->notes, "DIMS_ORIG_BYTES", "-"); - apr_table_setn(r->notes, "DIMS_DL_TIME", "-"); - apr_table_setn(r->notes, "DIMS_IM_TIME", "-"); - - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, - "Handler %s : %s", r->handler, r->uri); - /* Handle old-style DIMS parameters. */ - if(strcmp(r->handler, "dims-local") == 0 && - (r->path_info && strlen(r->path_info) != 0)) { - /* Handle local filesystem images w/DIMS parameters. */ - d->filename = r->canonical_filename; - d->unparsed_commands = r->path_info; - - return dims_handle_request(d); - } else if(r->uri && strncmp(r->uri, "/dims/", 6) == 0) { - int status = 0; - char appid[50], b[10], w[10], h[10], q[10]; - char *fixed_url, *url; - - /* Translate provided parameters into new-style parameters. */ - b[0] = w[0] = h[0] = q[0] = '-'; - status = sscanf(r->uri + 5, - "/%49[^/]/%9[^/]/%9[^/]/%9[^/]/%9[^/]/", - (char *) &appid, (char *) &b, (char *) &w, (char *) &h, - (char *) &q); - - if(status != 5) { - return dims_cleanup(d, NULL, DIMS_BAD_URL); - } + const size_t args_len = strlen(r->args) + 1; + char *args = apr_pstrndup(r->pool, r->args, args_len); + char *token; + char *strtokstate; - int bitmap = (b[0] != '-') ? atoi(b) : -1; - double width = (w[0] != '-') ? atof(w) : 0; - double height = (h[0] != '-') ? atof(h) : 0; - int quality = (q[0] != '-') ? atoi(q) : 0; + token = apr_strtok(args, "&", &strtokstate); + while (token) { + char *param = strtok(token, "="); + apr_hash_set(query_params, param, APR_HASH_KEY_STRING, apr_pstrdup(r->pool, param + strlen(param) + 1)); + token = apr_strtok(NULL, "&", &strtokstate); + } - if(bitmap == -1) { - return dims_cleanup(d, NULL, DIMS_BAD_URL); - } + return query_params; +} - /* HACK: If URL has "http:/" instead of "http://", correct it. */ - url = strstr(r->uri, "http:/"); - if(url && *(url + 6) != '/') { - fixed_url = apr_psprintf(r->pool, "http://%s", url + 6); - } else if(!url) { - return dims_cleanup(d, NULL, DIMS_BAD_URL); - } else { - fixed_url = url; - } +static apr_array_header_t * +dims_parse_commands(dims_request_rec *request, char *commands) { + apr_array_header_t *commands_list = apr_array_make(request->pool, 10, sizeof(dims_command_t)); - char *commands = apr_psprintf(r->pool, "%s", appid); + const char *cmds = commands; + while(cmds < commands + strlen(commands)) { + char *command = ap_getword(request->pool, &cmds, '/'); - if(bitmap & LEGACY_DIMS_RESIZE && bitmap & LEGACY_DIMS_CROP) { - if(!width && !height) { - return dims_cleanup(d, NULL, DIMS_BAD_ARGUMENTS); - } - commands = apr_psprintf(r->pool, "%s/legacy_thumbnail/%ldx%ld", - commands, (long) width, (long) height); - } else if(bitmap & LEGACY_DIMS_CROP || bitmap & LEGACY_DIMS_RESIZE) { - char *cmd = (bitmap & LEGACY_DIMS_RESIZE) ? "resize" : "legacy_crop"; - - if(width && !height) { - commands = apr_psprintf(r->pool, "%s/%s/%ld", - commands, cmd, (long) width); - } else if(height && !width) { - commands = apr_psprintf(r->pool, "%s/%s/x%ld", - commands, cmd, (long) height); - } else if(width && height) { - commands = apr_psprintf(r->pool, "%s/%s/%ldx%ld", - commands, cmd, (long) width, (long) height); - } else { - return dims_cleanup(d, NULL, DIMS_BAD_ARGUMENTS); + if(strlen(command) > 0) { + char *args = ap_getword(request->pool, &cmds, '/'); + dims_command_t *cmd = (dims_command_t *) apr_palloc(request->pool, sizeof(dims_command_t)); + if (cmd == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, request->r, "Memory allocation failed for dims_command_t"); + return commands_list; } - } + cmd->name = command; + cmd->arg = dims_encode_spaces(request->pool, args); - if(bitmap & LEGACY_DIMS_JPG) { - commands = apr_psprintf(r->pool, "%s/format/jpg", - commands); - } else if(bitmap & LEGACY_DIMS_PNG) { - commands = apr_psprintf(r->pool, "%s/format/png", - commands); - } else if(bitmap & LEGACY_DIMS_GIF) { - commands = apr_psprintf(r->pool, "%s/format/gif", - commands); + APR_ARRAY_PUSH(commands_list, dims_command_t) = *cmd; } + } - if(bitmap & LEGACY_DIMS_SHARPEN) { - commands = apr_psprintf(r->pool, "%s/sharpen/0.0x1.5", - commands); - } + return commands_list; +} - if(quality > 0 && quality <= 100) { - commands = apr_psprintf(r->pool, "%s/quality/%d", - commands, quality); - } +static char * +dims_decrypt_eurl(request_rec *r, unsigned char *secret_key, char *eurl) +{ + // Hash secret via SHA-1. + unsigned char hash[SHA_DIGEST_LENGTH]; + SHA1(secret_key, strlen((char *) secret_key), hash); - /* Locate pointer to the image URL. */ - d->image_url = fixed_url; - d->unparsed_commands = commands; - - return dims_handle_request(d); - } else if ((strcmp(r->handler, "dims3") == 0) || - (r->uri && strncmp(r->uri, "/dims3/", 7) == 0) || - (strcmp(r->handler, "dims4") == 0 )) { - /* Handle new-style DIMS parameters. */ - char *p, *url = NULL, *fixed_url = NULL, *commands = NULL, *eurl = NULL; - if (( strcmp( r->handler,"dims4") == 0)) { - d->use_secret_key = 1; - } + // Convert to hex. + char hex[SHA_DIGEST_LENGTH * 2 + 1]; + if (apr_escape_hex(hex, hash, SHA_DIGEST_LENGTH, 0, NULL) != APR_SUCCESS) { + return NULL; + } - char *unparsed_commands = apr_pstrdup(r->pool, r->uri + 7); - d->client_id = ap_getword(d->pool, (const char **) &unparsed_commands, '/'); + // Use first 16 bytes. + unsigned char key[17]; + strncpy((char *) key, hex, 16); + key[16] = '\0'; - if(!(d->client_config = - apr_hash_get(d->config->clients, d->client_id, APR_HASH_KEY_STRING))) { - return dims_cleanup(d, "Application ID is not valid", DIMS_BAD_CLIENT); - } + // Force key to uppercase + unsigned char *s = key; + while (*s) { *s = toupper(*s); s++; } - /* Check first if URL is passed as a query parameter. */ - if(r->args) { - const size_t args_len = strlen(r->args) + 1; - char *args = apr_pstrndup(d->r->pool, d->r->args, args_len); - char *token; - char *strtokstate; - token = apr_strtok(args, "&", &strtokstate); - while (token) { - if(strncmp(token, "url=", 4) == 0) { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, "ARG: %s", token); - fixed_url = apr_pstrdup(r->pool, token + 4); - ap_unescape_url(fixed_url); - - if (strcmp(fixed_url, "") == 0) { - return dims_cleanup(d, NULL, DIMS_BAD_URL); - } - } else if (strncmp(token, "download=1", 10) == 0) { - d->send_content_disposition = 1; - - } else if (strncmp(token, "eurl=", 4) == 0) { - eurl = apr_pstrdup(r->pool, token + 5); - - // Hash secret via SHA-1. - unsigned char *secret = (unsigned char *) d->client_config->secret_key; - unsigned char hash[SHA_DIGEST_LENGTH]; - SHA1(secret, strlen((char *) secret), hash); - - // Convert to hex. - char hex[SHA_DIGEST_LENGTH * 2 + 1]; - if (apr_escape_hex(hex, hash, SHA_DIGEST_LENGTH, 0, NULL) != APR_SUCCESS) { - return dims_cleanup(d, "URL Decryption Failed", DIMS_FAILURE); - } - - // Use first 16 bytes. - unsigned char key[17]; - strncpy((char *) key, hex, 16); - key[16] = '\0'; - - // Force key to uppercase - unsigned char *s = key; - while (*s) { *s = toupper(*s); s++; } - - if (d->config->encryption_algorithm != NULL && - strncmp((char *)d->config->encryption_algorithm, "AES/GCM/NoPadding", strlen("AES/GCM/NoPadding")) == 0) { - - fixed_url = aes_128_gcm_decrypt(r, key, eurl); - } else { - //Default is AES/ECB/PKCS5Padding - unsigned char *encrypted_text = apr_palloc(r->pool, apr_base64_decode_len(eurl)); - int encrypted_length = apr_base64_decode((char *) encrypted_text, eurl); - fixed_url = aes_128_decrypt(r, key, encrypted_text, encrypted_length); - } - if (fixed_url == NULL) { - return dims_cleanup(d, "URL Decryption Failed", DIMS_FAILURE); - } - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, "Decrypted URL: %s", fixed_url); - break; - - } else if (strncmp(token, "optimizeResize=", 4) == 0) { - d->optimize_resize = atof(token + 15); - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, "Overriding optimize resize: %f", d->optimize_resize); - } - token = apr_strtok(NULL, "&", &strtokstate); - } - } + return aes_128_gcm_decrypt(r, key, eurl); +} - /* Parse out URL to image. - * HACK: If URL has "http:/" instead of "http://", correct it. - */ - commands = apr_pstrdup(r->pool, r->uri); - if(fixed_url == NULL) { - url = strstr(r->uri, "http:/"); - if(url && *(url + 6) != '/') { - fixed_url = apr_psprintf(r->pool, "http://%s", url + 6); - } else if(!url) { - return dims_cleanup(d, NULL, DIMS_BAD_URL); - } else { - fixed_url = url; - } +static apr_status_t +dims_request_parse(dims_request_rec *request, int dims4) +{ + request_rec *r = request->r; - /* Strip URL off URI. This leaves only the tranformation parameters. */ - p = strstr(commands, "http:/"); - if(!p) return dims_cleanup(d, NULL, DIMS_BAD_URL); - *p = '\0'; - } + char *unparsed_commands = apr_pstrdup(r->pool, r->uri + 7); + request->client_id = ap_getword(r->pool, (const char **) &unparsed_commands, '/'); + request->query_params = dims_parse_args(r); - // Convert '+' in the fixed_url to ' '. - char *image_url = apr_pstrdup(d->r->pool, fixed_url); - char *s = image_url; - while (*s) { - if (*s == '+') { - *s = ' '; - } + if (dims4) { + request->signature = ap_getword(r->pool, (const char **) &unparsed_commands, '/'); + request->expiration = ap_getword(r->pool, (const char **) &unparsed_commands, '/'); + } - s++; - } + request->commands = dims_encode_spaces(r->pool, unparsed_commands); + request->commands_list = dims_parse_commands(request, request->commands); - d->image_url = image_url; - d->unparsed_commands = commands + 6; + char *download = apr_hash_get(request->query_params, "download", APR_HASH_KEY_STRING); + if (download != NULL && *download == '1') { + request->send_content_disposition = 1; + } - /* Calculate image filename for use with content disposition. */ - apr_uri_t uri; - if (apr_uri_parse(r->pool, d->image_url, &uri) == APR_SUCCESS) { - if (!uri.path) { - return dims_cleanup(d, NULL, DIMS_BAD_URL); - } + // Determine the source image URL. + char *url = apr_hash_get(request->query_params, "url", APR_HASH_KEY_STRING); + char *eurl = apr_hash_get(request->query_params, "eurl", APR_HASH_KEY_STRING); + if (eurl != NULL) { + request->image_url = dims_decrypt_eurl(r, request->config->secret_key, eurl); - const char *path = apr_filepath_name_get(uri.path); - d->content_disposition_filename = apr_pstrdup(d->r->pool, path); + if (request->image_url == NULL) { + return DIMS_FAILURE; } + } else if (url != NULL) { + request->image_url = dims_encode_spaces(r->pool, url); + } else { + return DIMS_FAILURE; + } - return dims_handle_request(d); - } else if(strcmp(r->handler, "dims-status") == 0) { - apr_time_t uptime; - - ap_set_content_type(r, "text/plain"); - ap_rvputs(r, "ALIVE\n\n", NULL); - - uptime = (apr_uint32_t) apr_time_sec(apr_time_now() - - ap_scoreboard_image->global->restart_time); - - show_time(r, uptime); - - ap_rprintf(r, "Restart time: %s\n", - ap_ht_time(r->pool, - ap_scoreboard_image->global->restart_time, - "%A, %d-%b-%Y %H:%M:%S %Z", 0)); - - ap_rprintf(r, "\nmod_dims version: %s (%s)\n", MODULE_VERSION, MODULE_RELEASE); - ap_rprintf(r, "ImageMagick version: %s\n", GetMagickVersion(NULL)); - ap_rprintf(r, "libcurl version: %s\n", curl_version()); - - ap_rprintf(r, "\nDetails\n-------\n"); - - ap_rprintf(r, "Successful requests: %d\n", - apr_atomic_read32(&stats->success_count)); - ap_rprintf(r, "Failed requests: %d\n\n", - apr_atomic_read32(&stats->failure_count)); - ap_rprintf(r, "Download timeouts: %d\n", - apr_atomic_read32(&stats->download_timeout_count)); - ap_rprintf(r, "Imagemagick Timeouts: %d\n", - apr_atomic_read32(&stats->imagemagick_timeout_count)); - - ap_rflush(r); - return OK; - } else if(strcmp(r->handler, "dims-sizer") == 0) { - char *url, *fixed_url; - url = strstr(r->uri, "http:/"); - if(url && *(url + 6) != '/') { - fixed_url = apr_psprintf(r->pool, "http://%s", url + 6); - } else if(!url) { - return dims_cleanup(d, NULL, DIMS_BAD_URL); - } else { - fixed_url = url; - } - d->image_url = fixed_url; - d->unparsed_commands = NULL; + request->request_hash = ap_md5(r->pool, + apr_pstrcat(r->pool, + request->client_id, + request->commands, + request->image_url, + NULL)); + // Calculate image filename for use with content disposition. + apr_uri_t uri; + if (apr_uri_parse(r->pool, request->image_url, &uri) == APR_SUCCESS) { + if (!uri.path) { + return DIMS_FAILURE; + } - return dims_sizer(d); + const char *path = apr_filepath_name_get(uri.path); + request->content_disposition_filename = apr_pstrdup(r->pool, path); } - return DECLINED; + return DIMS_SUCCESS; } -static int -dims_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t* ptemp, server_rec *s) +apr_status_t +dims_handle_dims3(request_rec *r) { - dims_config_rec *config = (dims_config_rec *) - ap_get_module_config(s->module_config, &dims_module); - apr_status_t status; - apr_size_t retsize; - - ap_add_version_component(p, "mod_dims/" MODULE_VERSION); - - MagickWandGenesis(); - MagickSetResourceLimit(AreaResource, config->area_size); - MagickSetResourceLimit(DiskResource, config->disk_size); - MagickSetResourceLimit(MemoryResource, config->memory_size); - MagickSetResourceLimit(MapResource, config->map_size); - - ops = apr_hash_make(p); - apr_hash_set(ops, "strip", APR_HASH_KEY_STRING, dims_strip_operation); - apr_hash_set(ops, "resize", APR_HASH_KEY_STRING, dims_resize_operation); - apr_hash_set(ops, "crop", APR_HASH_KEY_STRING, dims_crop_operation); - apr_hash_set(ops, "thumbnail", APR_HASH_KEY_STRING, dims_thumbnail_operation); - apr_hash_set(ops, "legacy_thumbnail", APR_HASH_KEY_STRING, dims_legacy_thumbnail_operation); - apr_hash_set(ops, "legacy_crop", APR_HASH_KEY_STRING, dims_legacy_crop_operation); - apr_hash_set(ops, "quality", APR_HASH_KEY_STRING, dims_quality_operation); - apr_hash_set(ops, "sharpen", APR_HASH_KEY_STRING, dims_sharpen_operation); - apr_hash_set(ops, "format", APR_HASH_KEY_STRING, dims_format_operation); - apr_hash_set(ops, "brightness", APR_HASH_KEY_STRING, dims_brightness_operation); - apr_hash_set(ops, "flipflop", APR_HASH_KEY_STRING, dims_flipflop_operation); - apr_hash_set(ops, "sepia", APR_HASH_KEY_STRING, dims_sepia_operation); - apr_hash_set(ops, "grayscale", APR_HASH_KEY_STRING, dims_grayscale_operation); - apr_hash_set(ops, "autolevel", APR_HASH_KEY_STRING, dims_autolevel_operation); - apr_hash_set(ops, "rotate", APR_HASH_KEY_STRING, dims_rotate_operation); - apr_hash_set(ops, "invert", APR_HASH_KEY_STRING, dims_invert_operation); - apr_hash_set(ops, "watermark", APR_HASH_KEY_STRING, dims_watermark_operation); - - /* Init APR's atomic functions */ - status = apr_atomic_init(p); - if (status != APR_SUCCESS) - return HTTP_INTERNAL_SERVER_ERROR; - - /* If there was a memory block already assigned, destroy it */ - if (shm) { - status = apr_shm_destroy(shm); - if (status != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, - "mod_dims : Couldn't destroy old memory block\n"); - return status; - } else { - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, - "mod_dims : Old Shared memory block, destroyed."); - } - } - - /* Create shared memory block */ - status = apr_shm_create(&shm, sizeof(dims_stats_rec), NULL, p); - if (status != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, - "mod_dims : Error creating shm block\n"); + dims_request_rec *d = dims_create_request(r); + int status = dims_request_parse(d, 0); + if (status != DIMS_SUCCESS) { return status; } - /* Check size of shared memory block */ - retsize = apr_shm_size_get(shm); - if (retsize != sizeof(dims_stats_rec)) { - ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, - "mod_dims : Error allocating shared memory block\n"); - return status; - } + if(!(d->client_config = apr_hash_get(d->config->clients, d->client_id, APR_HASH_KEY_STRING))) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, "Validating: Client '%s' not found", d->client_id); - /* Init shm block */ - stats = apr_shm_baseaddr_get(shm); - if (stats == NULL) { - ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, - "mod_dims : Error creating status block.\n"); - return status; - } - memset(stats, 0, retsize); + dims_send_error(d, HTTP_UNAUTHORIZED); - if (retsize < sizeof(dims_stats_rec)) { - ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, - "mod_dims : Not enough memory allocated!! Giving up"); - return HTTP_INTERNAL_SERVER_ERROR; + return DIMS_FAILURE; } - stats->success_count = 1; - stats->failure_count = 0; - stats->download_timeout_count = 0; - stats->imagemagick_timeout_count = 0; - - return OK; -} + // Verify allowlist (dims3 only). + if (verify_dims3_allowlist(d)) { + dims_send_error(d, HTTP_UNAUTHORIZED); -void -lock_share(CURL *handle, curl_lock_data data, - curl_lock_access access, void *userptr) -{ - dims_curl_rec *locks = (dims_curl_rec *) userptr; - - switch(data) { - case CURL_LOCK_DATA_DNS: - apr_thread_mutex_lock(locks->dns_mutex); - break; - default: - apr_thread_mutex_lock(locks->share_mutex); + return HTTP_UNAUTHORIZED; } -} -void unlock_share(CURL *handle, curl_lock_data data, void *userptr) -{ - dims_curl_rec *locks = (dims_curl_rec *) userptr; - - switch(data) { - case CURL_LOCK_DATA_DNS: - apr_thread_mutex_unlock(locks->dns_mutex); - break; - default: - apr_thread_mutex_unlock(locks->share_mutex); - } + return dims_handle_request(d); } -static apr_status_t -dims_child_cleanup(void *data) +apr_status_t +dims_handle_dims4(request_rec *r) { - dims_curl_rec *locks = (dims_curl_rec *) data; + dims_request_rec *d = dims_create_request(r); + if (d == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Memory allocation failed for dims_request_rec"); + return HTTP_INTERNAL_SERVER_ERROR; + } - curl_share_cleanup(locks->share); - curl_global_cleanup(); + int status = dims_request_parse(d, 1); + if (status != DIMS_SUCCESS) { + return status; + } - apr_thread_mutex_destroy(locks->share_mutex); - apr_thread_mutex_destroy(locks->dns_mutex); + if (!(d->client_config = apr_hash_get(d->config->clients, d->client_id, APR_HASH_KEY_STRING))) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, "Validating: Client '%s' not found", d->client_id); - apr_pool_userdata_set(NULL, DIMS_CURL_SHARED_KEY, NULL, - locks->s->process->pool); + dims_send_error(d, HTTP_UNAUTHORIZED); - MagickWandTerminus(); + return DIMS_FAILURE; + } - return APR_SUCCESS; -} + // Verify signature (dims4 only). + if (verify_dims4_signature(d)) { + dims_send_error(d, HTTP_UNAUTHORIZED); -static void -dims_child_init(apr_pool_t *p, server_rec *s) -{ - MagickWandGenesis(); - curl_global_init(CURL_GLOBAL_ALL); - - dims_curl_rec *locks = - (dims_curl_rec *) apr_pcalloc(p, sizeof(dims_curl_rec)); - - locks->s = s; - locks->share = curl_share_init(); - - apr_thread_mutex_create(&locks->share_mutex, APR_THREAD_MUTEX_DEFAULT, p); - apr_thread_mutex_create(&locks->dns_mutex, APR_THREAD_MUTEX_DEFAULT, p); - - curl_share_setopt(locks->share, CURLSHOPT_LOCKFUNC, lock_share); - curl_share_setopt(locks->share, CURLSHOPT_UNLOCKFUNC, unlock_share); - curl_share_setopt(locks->share, CURLSHOPT_USERDATA, (void *) locks); - curl_share_setopt(locks->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); - - /* We have to associate our handle/locks with the process->pool otherwise - * we won't be able to get at it from the remote_fetch_image function. This - * pool doesn't seem to go away when the child process goes away so we - * have to register the clean up method below. - */ - apr_pool_userdata_set(locks, DIMS_CURL_SHARED_KEY, NULL, s->process->pool); - - /* Register cleanup with the 'p' pool so we can clean up the locks and - * shared curl handle when this process dies. - */ - apr_pool_cleanup_register(p, locks, dims_child_cleanup, dims_child_cleanup); -} + return HTTP_BAD_REQUEST; + } -static void -dims_register_hooks(apr_pool_t *p) -{ - ap_hook_post_config(dims_init, NULL, NULL, APR_HOOK_MIDDLE); - ap_hook_child_init(dims_child_init, NULL, NULL,APR_HOOK_MIDDLE); - ap_hook_handler(dims_handler, NULL, NULL, APR_HOOK_MIDDLE); + return dims_handle_request(d); } - -static const command_rec dims_commands[] = -{ - AP_INIT_TAKE_ARGV("DimsAddWhitelist", - dims_config_set_whitelist, NULL, RSRC_CONF, - "Add whitelist hostname for DIMS URL requests."), - AP_INIT_TAKE_ARGV("DimsAddClient", - dims_config_set_client, NULL, RSRC_CONF, - "Add a client with optional no image url, max-age and downstream-ttl settings."), - AP_INIT_TAKE_ARGV("DimsIgnoreDefaultOutputFormat", - dims_config_set_ignore_default_output_format, NULL, RSRC_CONF, - "Add input formats that shouldn't be converted to the default output format."), - AP_INIT_TAKE1("DimsDefaultImageURL", - dims_config_set_no_image_url, NULL, RSRC_CONF, - "Default image if processing fails or original image doesn't exist."), - AP_INIT_TAKE1("DimsDefaultImagePrefix", - dims_config_set_image_prefix, NULL, RSRC_CONF, - "Default image prefix if URL is relative."), - AP_INIT_TAKE1("DimsCacheExpire", - dims_config_set_default_expire, NULL, RSRC_CONF, - "Default expire time for Cache-Control/Expires/Edge-Control headers, in seconds." - "The default is 86400"), - AP_INIT_TAKE1("DimsNoImageCacheExpire", - dims_config_set_no_image_expire, NULL, RSRC_CONF, - "Default expire time for Cache-Control/Expires/Edge-Control headers for NOIMAGE image, in seconds." - "The default is 60"), - AP_INIT_TAKE1("DimsDownloadTimeout", - dims_config_set_download_timeout, NULL, RSRC_CONF, - "Timeout for downloading remote images." - "The default is 3000."), - AP_INIT_TAKE1("DimsImagemagickTimeout", - dims_config_set_imagemagick_timeout, NULL, RSRC_CONF, - "Timeout for processing images." - "The default is 3000."), - AP_INIT_TAKE1("DimsImagemagickMemorySize", - dims_config_set_imagemagick_memory_size, NULL, RSRC_CONF, - "Maximum amount of memory in megabytes to use for pixel cache." - "The default is 512mb."), - AP_INIT_TAKE1("DimsImagemagickAreaSize", - dims_config_set_imagemagick_area_size, NULL, RSRC_CONF, - "Maximum amount of memory in megabytes that any one image can use." - "The default is 128mb."), - AP_INIT_TAKE1("DimsImagemagickMapSize", - dims_config_set_imagemagick_map_size, NULL, RSRC_CONF, - "Maximum amount of memory map in megabytes to use for the pixel cache." - "The default is 1024mb."), - AP_INIT_TAKE1("DimsImagemagickDiskSize", - dims_config_set_imagemagick_disk_size, NULL, RSRC_CONF, - "Maximum amount of disk space in megabytes to use for the pixel cache." - "The default is 1024mb."), - AP_INIT_TAKE1("DimsSecretMaxExpiryPeriod", - dims_config_set_secretkeyExpiryPeriod, NULL, RSRC_CONF, - "How long in the future (in seconds) can the expiry date on the URL be requesting. 0 = forever" - "The default is 0."), - AP_INIT_TAKE1("DimsStripMetadata", - dims_config_set_strip_metadata, NULL, RSRC_CONF, - "Should DIMS strip the metadata from the image, true OR false." - "The default is true."), - AP_INIT_TAKE1("DimsIncludeDisposition", - dims_config_set_include_disposition, NULL, RSRC_CONF, - "Should DIMS include Content-Disposition header, true OR false." - "The default is false."), - AP_INIT_TAKE1("DimsOptimizeResize", - dims_config_set_optimize_resize, NULL, RSRC_CONF, - "Should DIMS optimize resize operations. This has a slight impact on image quality. 0 = disabled" - "The default is 0."), - AP_INIT_TAKE1("DimsDisableEncodedFetch", - dims_config_set_encoded_fetch, NULL, RSRC_CONF, - "Should DIMS encode image url before fetching it." - "The default is 0."), - AP_INIT_TAKE1("DimsEncryptionAlgorithm", - dims_config_set_encryption_algorithm, NULL, RSRC_CONF, - "What algorithm should DIMS user to decrypt the 'eurl' parameter." - "The default is AES/ECB/PKCS5Padding."), - AP_INIT_TAKE1("DimsDefaultOutputFormat", - dims_config_set_default_output_format, NULL, RSRC_CONF, - "Default output format if 'format' command is not present in the request."), - AP_INIT_TAKE1("DimsUserAgentEnabled", - dims_config_set_user_agent_enabled, NULL, RSRC_CONF, - "Enable DIMS User-Agent header ('dims/'), true OR false." - "The default is false."), - AP_INIT_TAKE1("DimsUserAgentOverride", - dims_config_set_user_agent_override, NULL, RSRC_CONF, - "Override DIMS User-Agent header" - "The default is 'dims/."), - {NULL} -}; - -module AP_MODULE_DECLARE_DATA dims_module = -{ - STANDARD20_MODULE_STUFF, - NULL, /* dir config creater */ - NULL, /* dir merger --- default is to override */ - dims_create_config, /* server config */ - NULL, /* merge server config */ - dims_commands, /* command apr_table_t */ - dims_register_hooks /* register hooks */ -}; diff --git a/src/mod_dims.h b/src/mod_dims.h index 189414e..d269aa4 100644 --- a/src/mod_dims.h +++ b/src/mod_dims.h @@ -17,6 +17,9 @@ #ifndef _MOD_DIMS_H #define _MOD_DIMS_H +#define MODULE_RELEASE "$Revision: $" +#define MODULE_VERSION "4.0.0alpha" + #include #include #include @@ -33,170 +36,14 @@ #include #include -#include +#include #include -#define LEGACY_DIMS_RESIZE 1 -#define LEGACY_DIMS_REFORMAT 2 -#define LEGACY_DIMS_CROP 4 -#define LEGACY_DIMS_SHARPEN 8 -#define LEGACY_DIMS_JPG 256 -#define LEGACY_DIMS_GIF 512 -#define LEGACY_DIMS_PNG 1024 - -#define DIMS_IGNORE -1 -#define DIMS_SUCCESS 0 #define DIMS_FAILURE 1 -#define DIMS_DOWNLOAD_TIMEOUT 2 -#define DIMS_IMAGEMAGICK_TIMEOUT 4 -#define DIMS_BAD_CLIENT 8 -#define DIMS_BAD_URL 16 -#define DIMS_BAD_ARGUMENTS 32 -#define DIMS_HOSTNAME_NOT_IN_WHITELIST 64 -#define DIMS_FILE_NOT_FOUND 128 - -typedef struct dims_request_rec dims_request_rec; -typedef struct dims_config_rec dims_config_rec; -typedef struct dims_client_config_rec dims_client_config_rec; -typedef struct { - char *data; - size_t size; - size_t used; - long response_code; -} dims_image_data_t; - -typedef apr_status_t(dims_operation_func) (dims_request_rec *, char *args, char **err); -void smartCrop(MagickWand *wand, int resolution, unsigned long cropWidth, unsigned long cropHeight); -CURLcode dims_get_image_data(dims_request_rec *d, char *fetch_url, dims_image_data_t *data); - -dims_operation_func - dims_strip_operation, - dims_resize_operation, - dims_crop_operation, - dims_thumbnail_operation, - dims_sharpen_operation, - dims_quality_operation, - dims_format_operation, - dims_legacy_thumbnail_operation, - dims_smart_crop_operation, - dims_brightness_operation, - dims_flipflop_operation, - dims_sepia_operation, - dims_grayscale_operation, - dims_autolevel_operation, - dims_rotate_operation, - dims_invert_operation, - dims_watermark_operation, - dims_legacy_crop_operation; - -struct dims_config_rec { - int download_timeout; - int imagemagick_timeout; - - apr_table_t *whitelist; - apr_hash_t *clients; - apr_table_t *ignore_default_output_format; - - char *no_image_url; - long no_image_expire; - long default_expire; - int strip_metadata; - float optimize_resize; - int include_disposition; - int disable_encoded_fetch; - char *default_output_format; - - MagickSizeType area_size; - MagickSizeType memory_size; - MagickSizeType map_size; - MagickSizeType disk_size; - - int curl_queue_size; - char *secret_key; - char *encryption_algorithm; - long max_expiry_period; - char *cache_dir; - char *default_image_prefix; - - char *user_agent_override; - int user_agent_enabled; -}; - -struct dims_client_config_rec { - char *id; - char *no_image_url; - int cache_control_max_age; - int edge_control_downstream_ttl; - int trust_src; - int min_src_cache_control; - int max_src_cache_control; - char *secret_key; -}; - -struct dims_request_rec { - request_rec *r; - - apr_pool_t *pool; - - MagickWand *wand; - - /* Client ID of this request. */ - char *client_id; - - /* The URL to the image being manipulated. */ - char *image_url; - int use_no_image; - - /* The URL to the NOIMAGE image in case of failures. */ - char *no_image_url; - - /* The filename if this is a local request. */ - char *filename; - - /* The unparsed commands (resize, crop, etc). */ - char *unparsed_commands; - - /* The original image size in bytes. */ - long original_image_size; - - /* The sample factor for optimizing resizing. */ - float optimize_resize; - - /* The global configuration. */ - dims_config_rec *config; - - /* The client specific configuration, if available. */ - dims_client_config_rec *client_config; - - /* The cache headers from the downloaded image. */ - char *cache_control; - char *edge_control; - char *last_modified; - char *etag; - char *request_hash; - - /* The current status of this request. If downloading - * or manipulating the image times out this will - * be set to DIMS_*_TIMEOUT. If everything is ok it will - * be set to DIMS_SUCCESS. - */ - apr_status_t status; - - /* The HTTP status code from fetching the original image */ - apr_status_t fetch_http_status; - - /* Time this request started. Used for statistics. */ - apr_time_t start_time; - apr_time_t download_time; - apr_time_t imagemagick_time; - - /* Use a whitelist, or use a secret key passed on the URI */ - int use_secret_key; +#define DIMS_SUCCESS 0 - /* Should Content-Disposition header bet set. */ - int send_content_disposition; - char *content_disposition_filename; -}; +apr_status_t dims_handle_dims3(request_rec *d); +apr_status_t dims_handle_dims4(request_rec *d); #endif diff --git a/src/mod_dims_ops.c b/src/mod_dims_ops.c index 335ff6d..b97ee0d 100644 --- a/src/mod_dims_ops.c +++ b/src/mod_dims_ops.c @@ -15,6 +15,9 @@ */ #include "mod_dims.h" +#include "mod_dims_ops.h" +#include "curl.h" +#include "request.h" #include #include @@ -22,14 +25,53 @@ #include #include -#include +#include +#include + +static operations_rec ops[] = { + {"strip", dims_strip_operation}, + {"resize", dims_resize_operation}, + {"crop", dims_crop_operation}, + {"thumbnail", dims_thumbnail_operation}, + {"legacy_thumbnail", dims_legacy_thumbnail_operation}, + {"legacy_crop", dims_legacy_crop_operation}, + {"quality", dims_quality_operation}, + {"sharpen", dims_sharpen_operation}, + {"format", dims_format_operation}, + {"brightness", dims_brightness_operation}, + {"flipflop", dims_flipflop_operation}, + {"sepia", dims_sepia_operation}, + {"grayscale", dims_grayscale_operation}, + {"autolevel", dims_autolevel_operation}, + {"rotate", dims_rotate_operation}, + {"invert", dims_invert_operation}, + {"watermark", dims_watermark_operation}, + {NULL, NULL} +}; + +dims_operation_func * +dims_operation_lookup(char *name) { + operations_rec *op_ptr = ops; + + if (name == NULL) { + return NULL; + } + + while (op_ptr->name != NULL) { + if (strcmp(name, op_ptr->name) == 0) { + return op_ptr->func; + } + + ++op_ptr; + } + + return NULL; +} #define MAGICK_CHECK(func, rec) \ do { \ apr_status_t code = func; \ - if(rec->status == DIMS_IMAGEMAGICK_TIMEOUT) {\ - return DIMS_IMAGEMAGICK_TIMEOUT; \ - } else if(code == MagickFalse) {\ + if(code == MagickFalse) {\ return DIMS_FAILURE; \ } \ } while(0) @@ -52,43 +94,13 @@ static DimsGravity gravities[] = { {NULL, CenterGravity} }; -/* -apr_status_t -dims_smart_crop_operation (dims_request_rec *d, char *args, char **err) { - MagickStatusType flags; - RectangleInfo rec; - ExceptionInfo ex_info; - - flags = ParseGravityGeometry(GetImageFromMagickWand(d->wand), args, &rec, &ex_info); - if(!(flags & AllValues)) { - *err = "Parsing crop geometry failed"; - return DIMS_FAILURE; - } - - // MAGICK_CHECK(MagickResizeImage(d->wand, rec.width, rec.height, UndefinedFilter, 1), d); - smartCrop(d->wand, 20, rec.width, rec.height); - - return DIMS_SUCCESS; -} -*/ - apr_status_t dims_strip_operation (dims_request_rec *d, char *args, char **err) { - - /* If args is passed from the user and - * a) it equals true, strip the image. - * b) it equals false, don't strip the image. - * c) it is neither true/false, strip based on config value. - * If args is NULL, strip based on config value. - */ - if(args != NULL) { - if(strcmp(args, "true") == 0 || ( strcmp(args, "false") != 0 && d->config->strip_metadata )) { + if (args != NULL) { + if (strcmp(args, "true") == 0 || ( strcmp(args, "false") != 0 && d->config->strip_metadata)) { MAGICK_CHECK(MagickStripImage(d->wand), d); } } - else if(d->config->strip_metadata) { - MAGICK_CHECK(MagickStripImage(d->wand), d); - } return DIMS_SUCCESS; } @@ -98,7 +110,8 @@ dims_resize_operation (dims_request_rec *d, char *args, char **err) { MagickStatusType flags; RectangleInfo rec; - flags = ParseSizeGeometry(GetImageFromMagickWand(d->wand), args, &rec); + SetGeometry(GetImageFromMagickWand(d->wand), &rec); + flags = ParseMetaGeometry(args, &rec.x, &rec.y, &rec.width, &rec.height); if(!(flags & AllValues)) { *err = "Parsing thumbnail geometry failed"; return DIMS_FAILURE; @@ -111,23 +124,6 @@ dims_resize_operation (dims_request_rec *d, char *args, char **err) { } MagickRelinquishMemory(format); - if (d->optimize_resize) { - size_t orig_width; - size_t orig_height; - - RectangleInfo sampleRec = rec; - sampleRec.width *= d->optimize_resize; - sampleRec.height *= d->optimize_resize; - - orig_width = MagickGetImageWidth(d->wand); - orig_height = MagickGetImageHeight(d->wand); - - if(sampleRec.width < orig_width && sampleRec.height < orig_height) { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, "Sampling image down to %zdx%zd before resizing.", sampleRec.width, sampleRec.height); - MAGICK_CHECK(MagickSampleImage(d->wand, sampleRec.width, sampleRec.height), d); - } - } - MAGICK_CHECK(MagickScaleImage(d->wand, rec.width, rec.height), d); return DIMS_SUCCESS; @@ -154,7 +150,8 @@ dims_thumbnail_operation (dims_request_rec *d, char *args, char **err) { RectangleInfo rec; char *resize_args = apr_psprintf(d->pool, "%s^", args); - flags = ParseSizeGeometry(GetImageFromMagickWand(d->wand), resize_args, &rec); + SetGeometry(GetImageFromMagickWand(d->wand), &rec); + flags = ParseMetaGeometry(resize_args, &rec.x, &rec.y, &rec.width, &rec.height); if(!(flags & AllValues)) { *err = "Parsing thumbnail (resize) geometry failed"; return DIMS_FAILURE; @@ -167,23 +164,6 @@ dims_thumbnail_operation (dims_request_rec *d, char *args, char **err) { } MagickRelinquishMemory(format); - if (d->optimize_resize) { - size_t orig_width; - size_t orig_height; - - RectangleInfo sampleRec = rec; - sampleRec.width *= d->optimize_resize; - sampleRec.height *= d->optimize_resize; - - orig_width = MagickGetImageWidth(d->wand); - orig_height = MagickGetImageHeight(d->wand); - - if(sampleRec.width < orig_width && sampleRec.height < orig_height) { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, "Sampling image down to %zdx%zd before resizing.", sampleRec.width, sampleRec.height); - MAGICK_CHECK(MagickSampleImage(d->wand, sampleRec.width, sampleRec.height), d); - } - } - MAGICK_CHECK(MagickThumbnailImage(d->wand, rec.width, rec.height), d); if(!(flags & PercentValue)) { @@ -207,25 +187,6 @@ dims_crop_operation (dims_request_rec *d, char *args, char **err) { RectangleInfo rec; ExceptionInfo *ex_info = AcquireExceptionInfo(); - /* Replace spaces with '+'. This happens when some user agents inadvertantly - * escape the '+' as %20 which gets converted to a space. - * - * Example: - * - * 900x900%20350%200 is '900x900 350 0' which is an invalid, the following code - * coverts this to '900x900+350+0'. - * - */ - char *s = args; - while (*s) { - if (*s == ' ') { - *s = '+'; - } - - s++; - } - - flags = ParseGravityGeometry(GetImageFromMagickWand(d->wand), args, &rec, ex_info); if(!(flags & AllValues)) { DestroyExceptionInfo(ex_info); @@ -361,7 +322,6 @@ dims_watermark_operation (dims_request_rec *d, char *args, char **err) { token = apr_strtok(args, "&", &strtokstate); while (token) { if (strncmp(token, "overlay=", 4) == 0) { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, "ARG: %s", token); overlay_url = apr_pstrdup(d->r->pool, token + 8); ap_unescape_url(overlay_url); } @@ -425,7 +385,7 @@ dims_watermark_operation (dims_request_rec *d, char *args, char **err) { // Write to disk. } else { dims_image_data_t image_data; - CURLcode code = dims_get_image_data(d, overlay_url, &image_data); + CURLcode code = dims_curl(d, overlay_url, &image_data); if (MagickReadImageBlob(overlay_wand, image_data.data, image_data.used) == MagickFalse) { if (image_data.data) { @@ -565,10 +525,6 @@ dims_legacy_crop_operation (dims_request_rec *d, char *args, char **err) { x = (width / 2) - (rec.width / 2); y = (height / 2) - (rec.height / 2); - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, - "legacy_crop will crop to %ldx%ld+%d+%d", - rec.width, rec.height, x, y); - MAGICK_CHECK(MagickCropImage(d->wand, rec.width, rec.height, x, y), d); return DIMS_SUCCESS; @@ -582,7 +538,8 @@ dims_legacy_thumbnail_operation (dims_request_rec *d, char *args, char **err) { int x, y; char *resize_args = apr_psprintf(d->pool, "%s^", args); - flags = ParseSizeGeometry(GetImageFromMagickWand(d->wand), resize_args, &rec); + SetGeometry(GetImageFromMagickWand(d->wand), &rec); + flags = ParseMetaGeometry(resize_args, &rec.x, &rec.y, &rec.width, &rec.height); if(!(flags & AllValues)) { *err = "Parsing thumbnail (resize) geometry failed"; return DIMS_FAILURE; @@ -601,9 +558,6 @@ dims_legacy_thumbnail_operation (dims_request_rec *d, char *args, char **err) { MAGICK_CHECK(MagickScaleImage(d->wand, rec.width, rec.height), d); } - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, - "legacy_thumbnail will resize to %ldx%ld", rec.width, rec.height); - flags = ParseAbsoluteGeometry(args, &rec); if(!(flags & AllValues)) { *err = "Parsing thumbnail (crop) geometry failed"; @@ -615,9 +569,6 @@ dims_legacy_thumbnail_operation (dims_request_rec *d, char *args, char **err) { x = (width / 2) - (rec.width / 2); y = (height / 2) - (rec.height / 2); - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, d->r, - "legacy_thumbnail will crop to %ldx%ld+%d+%d", rec.width, rec.height, x, y); - MAGICK_CHECK(MagickCropImage(d->wand, rec.width, rec.height, x, y), d); return DIMS_SUCCESS; diff --git a/src/mod_dims_ops.h b/src/mod_dims_ops.h new file mode 100644 index 0000000..6c8c053 --- /dev/null +++ b/src/mod_dims_ops.h @@ -0,0 +1,36 @@ + +#ifndef _MOD_DIMS_OPS_H +#define _MOD_DIMS_OPS_H + +#include "request.h" + +typedef apr_status_t(dims_operation_func) (dims_request_rec *, char *args, char **err); + +typedef struct { + char *name; + dims_operation_func *func; +} operations_rec; + +dims_operation_func + dims_strip_operation, + dims_resize_operation, + dims_crop_operation, + dims_thumbnail_operation, + dims_sharpen_operation, + dims_quality_operation, + dims_format_operation, + dims_legacy_thumbnail_operation, + dims_smart_crop_operation, + dims_brightness_operation, + dims_flipflop_operation, + dims_sepia_operation, + dims_grayscale_operation, + dims_autolevel_operation, + dims_rotate_operation, + dims_invert_operation, + dims_watermark_operation, + dims_legacy_crop_operation; + +dims_operation_func *dims_operation_lookup(char *name); + +#endif \ No newline at end of file diff --git a/src/module.c b/src/module.c new file mode 100644 index 0000000..bedcbbb --- /dev/null +++ b/src/module.c @@ -0,0 +1,25 @@ + +#include "mod_dims.h" +#include "handler.h" +#include "directives.h" +#include "module.h" +#include "initialize.h" + +void +dims_register_hooks(apr_pool_t *p) +{ + ap_hook_post_config(dims_init, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_child_init(dims_child_init, NULL, NULL,APR_HOOK_MIDDLE); + ap_hook_handler(dims_handler, NULL, NULL, APR_HOOK_MIDDLE); +} + +module AP_MODULE_DECLARE_DATA dims_module = +{ + STANDARD20_MODULE_STUFF, + NULL, /* dir config creater */ + NULL, /* dir merger --- default is to override */ + dims_create_config, /* server config */ + NULL, /* merge server config */ + dims_directives, /* command apr_table_t */ + dims_register_hooks /* register hooks */ +}; \ No newline at end of file diff --git a/src/module.h b/src/module.h new file mode 100644 index 0000000..5eb927a --- /dev/null +++ b/src/module.h @@ -0,0 +1,9 @@ + +#ifndef _MODULE_H_ +#define _MODULE_H_ + +#include "configuration.h" + +extern module AP_MODULE_DECLARE_DATA dims_module; + +#endif \ No newline at end of file diff --git a/src/request.h b/src/request.h new file mode 100644 index 0000000..fff5cad --- /dev/null +++ b/src/request.h @@ -0,0 +1,70 @@ +#ifndef _REQUEST_H +#define _REQUEST_H + +#include +#include + +#include "configuration.h" + +typedef struct dims_image_data_t { + char *data; + size_t size; + size_t used; + long response_code; + + // Cache control headers + char *cache_control; + char *edge_control; + char *last_modified; + char *etag; + int max_age; +} dims_image_data_t; + +typedef struct dims_command_t { + char *name; + char *arg; +} dims_command_t; + +typedef struct dims_request_rec { + request_rec *r; + + apr_pool_t *pool; + + MagickWand *wand; + + /* Client ID of this request. */ + char *client_id; + char *request_hash; + + /* The URL to the image being manipulated. */ + char *image_url; + + /* The parsed commands with the signature and expiration timestamp removed. */ + char *commands; + apr_hash_t *query_params; + apr_array_header_t *commands_list; + + /* The source image. */ + dims_image_data_t *source_image; + + /* The global configuration. */ + dims_config_rec *config; + + /* The client specific configuration, if available. */ + dims_client_config_rec *client_config; + + /* Time this request started. Used for statistics. */ + apr_time_t start_time; + apr_time_t download_time; + apr_time_t imagemagick_time; + + /* Use a whitelist, or use a secret key passed on the URI */ + char *signature; + char *expiration; + + /* Should Content-Disposition header bet set. */ + int send_content_disposition; + char *content_disposition_filename; +} dims_request_rec; + +#endif \ No newline at end of file diff --git a/src/status.c b/src/status.c new file mode 100644 index 0000000..17ef50c --- /dev/null +++ b/src/status.c @@ -0,0 +1,30 @@ + +#include +#include +#include +#include +#include "mod_dims.h" + +apr_status_t +status_handler(request_rec *r) { + apr_time_t uptime; + + ap_set_content_type(r, "text/plain"); + ap_rvputs(r, "ALIVE\n\n", NULL); + + uptime = (apr_uint32_t) apr_time_sec(apr_time_now() - + ap_scoreboard_image->global->restart_time); + + ap_rprintf(r, "Restart time: %s\n", + ap_ht_time(r->pool, + ap_scoreboard_image->global->restart_time, + "%A, %d-%b-%Y %H:%M:%S %Z", 0)); + + ap_rprintf(r, "\nmod_dims version: %s (%s)\n", MODULE_VERSION, MODULE_RELEASE); + ap_rprintf(r, "ImageMagick version: %s\n", GetMagickVersion(NULL)); + ap_rprintf(r, "libcurl version: %s\n", curl_version()); + + ap_rflush(r); + + return OK; +} \ No newline at end of file diff --git a/src/status.h b/src/status.h new file mode 100644 index 0000000..0754504 --- /dev/null +++ b/src/status.h @@ -0,0 +1,9 @@ +#ifndef _STATUS_H_ +#define _STATUS_H_ + +#include +#include + +apr_status_t status_handler(request_rec *r); + +#endif \ No newline at end of file