From f669126d605313daa67604384f1917f9c98d04d5 Mon Sep 17 00:00:00 2001 From: Ivan Subotic <400790+subotic@users.noreply.github.com> Date: Wed, 20 Mar 2024 21:28:18 +0100 Subject: [PATCH 1/6] refactor: parsing the IIIF URL --- .gitignore | 1 + CMakeLists.txt | 52 ++- Dockerfile.sipi-dev-env | 1 + include/SipiIO.h | 2 +- include/formats/SipiIOJ2k.h | 2 +- include/formats/SipiIOJpeg.h | 2 +- include/formats/SipiIOPng.h | 2 +- include/formats/SipiIOTiff.h | 2 +- nix-cmake | 40 ++ shttps/Server.cpp | 3 +- src/SipiCache.cpp | 2 +- src/SipiError.cpp | 8 +- include/SipiError.h => src/SipiError.hpp | 7 +- src/SipiHttpServer.cpp | 417 +++++++++--------- .../SipiHttpServer.hpp | 0 src/SipiImage.cpp | 2 +- include/SipiImage.h => src/SipiImage.hpp | 28 +- .../SipiImageError.hpp | 0 src/SipiLua.cpp | 4 +- src/formats/SipiIOJ2k.cpp | 2 +- src/formats/SipiIOJpeg.cpp | 2 +- src/formats/SipiIOTiff.cpp | 4 +- src/handlers/iiif_handler.cpp | 25 ++ src/handlers/iiif_handler.hpp | 36 ++ src/iiifparser/SipiIdentifier.cpp | 2 +- src/iiifparser/SipiQualityFormat.cpp | 2 +- src/iiifparser/SipiRegion.cpp | 2 +- src/iiifparser/SipiRotation.cpp | 2 +- src/iiifparser/SipiSize.cpp | 2 +- src/metadata/SipiExif.cpp | 2 +- src/metadata/SipiIcc.cpp | 4 +- src/metadata/SipiIptc.cpp | 2 +- src/metadata/SipiXmp.cpp | 2 +- src/sipi.cpp | 20 +- test/CMakeLists.txt | 4 + test/approval/CMakeLists.txt | 2 +- test/unit/configuration/CMakeLists.txt | 3 +- test/unit/handlers/CMakeLists.txt | 23 + test/unit/handlers/iiif_handler_test.cpp | 22 + test/unit/handlers/main.cpp | 7 + test/unit/sipiimage/CMakeLists.txt | 105 +++-- test/unit/sipiimage/sipiimage.cpp | 2 +- 42 files changed, 534 insertions(+), 318 deletions(-) create mode 100755 nix-cmake rename include/SipiError.h => src/SipiError.hpp (96%) rename include/SipiHttpServer.h => src/SipiHttpServer.hpp (100%) rename include/SipiImage.h => src/SipiImage.hpp (97%) rename include/SipiImageError.h => src/SipiImageError.hpp (100%) create mode 100644 src/handlers/iiif_handler.cpp create mode 100644 src/handlers/iiif_handler.hpp create mode 100644 test/unit/handlers/CMakeLists.txt create mode 100644 test/unit/handlers/iiif_handler_test.cpp create mode 100644 test/unit/handlers/main.cpp diff --git a/.gitignore b/.gitignore index 80395db6..c3e4442c 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ cmake-build-debug-remote/ cmake-build-debug-inside-docker/ cmake-build-dockerdebug/ cmake-build-relwithdebinfo-inside-docker/ +cmake-build-relwithdebinfo-nix-develop/ # ignore cache cache/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c7c2e4a..bd6233ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,25 +103,42 @@ option(CMAKE_BUILD_TYPE "The default build type is RelWithDebInfo" RelWithDebInf # # Here we determine the compiler and compiler version. We need clang >= 15 or g++ >= 13 # -if(CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "15.0") - message(FATAL_ERROR "Clang version must be 15.0.0 or greater! Aborting...") - set(ENV(CXX) "clang++") - set(ENV(CC) "clang") - endif() -elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "13.0") message(FATAL_ERROR "Requires GCC 13.0 or greater.") set(ENV(CXX) "g++") set(ENV(CC) "gcc") endif() +elseif(CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "15.0") + message(FATAL_ERROR "Clang version must be 15.0.0 or greater! Aborting...") + set(ENV(CXX) "clang++") + set(ENV(CC) "clang") + endif() else() message(WARNING "You are using an unsupported compiler (${CMAKE_CXX_COMPILER_ID}). Compilation has only been tested with Clang and GCC.") endif() -# -# Set the C++ standard to C++14 (goal is to move to C++23) -# -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") + +# Set C++ standard to C++23 +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Simple C++ test program +set(CXX_TEST_PROGRAM " + #include + #include + int main() { return 0; } +") + +# Check if the test program compiles with C++23 +include(CheckCXXSourceCompiles) +check_cxx_source_compiles("${CXX_TEST_PROGRAM}" CXX23_SUPPORTED) + +if(CXX23_SUPPORTED) + message(STATUS "C++23 is supported.") +else() + message(STATUS "C++23 is not supported.") +endif() # # Statically compile the C++ standard library into the executable @@ -410,7 +427,8 @@ include_directories( add_executable(sipi src/sipi.cpp src/SipiConf.cpp include/SipiConf.h - src/SipiError.cpp include/SipiError.h + src/SipiError.cpp + src/SipiError.hpp include/AdobeRGB1998_icc.h include/USWebCoatedSWOP_icc.h include/CLI11.hpp src/metadata/SipiIcc.cpp include/metadata/SipiIcc.h @@ -418,12 +436,15 @@ add_executable(sipi src/metadata/SipiIptc.cpp include/metadata/SipiIptc.h src/metadata/SipiExif.cpp include/metadata/SipiExif.h src/metadata/SipiEssentials.cpp include/metadata/SipiEssentials.h - src/SipiImage.cpp include/SipiImage.h include/SipiImageError.h + src/SipiImage.cpp + src/SipiImage.hpp + src/SipiImageError.hpp src/formats/SipiIOTiff.cpp include/formats/SipiIOTiff.h src/formats/SipiIOJ2k.cpp include/formats/SipiIOJ2k.h src/formats/SipiIOJpeg.cpp include/formats/SipiIOJpeg.h src/formats/SipiIOPng.cpp include/formats/SipiIOPng.h - src/SipiHttpServer.cpp include/SipiHttpServer.h + src/SipiHttpServer.cpp + src/SipiHttpServer.hpp src/SipiCache.cpp include/SipiCache.h src/SipiLua.cpp include/SipiLua.h src/iiifparser/SipiRotation.cpp include/iiifparser/SipiRotation.h @@ -447,7 +468,8 @@ add_executable(sipi shttps/makeunique.h src/SipiFilenameHash.cpp include/SipiFilenameHash.h include/iiifparser/SipiIdentifier.h src/iiifparser/SipiIdentifier.cpp - include/SipiImageError.h) + src/handlers/iiif_handler.cpp + src/handlers/iiif_handler.hpp) add_dependencies(sipi icc_profiles) diff --git a/Dockerfile.sipi-dev-env b/Dockerfile.sipi-dev-env index aeda1848..ff3922ef 100644 --- a/Dockerfile.sipi-dev-env +++ b/Dockerfile.sipi-dev-env @@ -12,6 +12,7 @@ RUN apt-get update \ rsync \ tar \ grep \ + build-essential \ && apt-get clean # Set GCC 13 as the default gcc and g++ diff --git a/include/SipiIO.h b/include/SipiIO.h index b7a2dd18..a9abe3f3 100644 --- a/include/SipiIO.h +++ b/include/SipiIO.h @@ -30,7 +30,7 @@ #include #include -#include "SipiImage.h" +#include "../src/SipiImage.hpp" #include "iiifparser/SipiRegion.h" #include "iiifparser/SipiSize.h" diff --git a/include/formats/SipiIOJ2k.h b/include/formats/SipiIOJ2k.h index 9433b733..e9b70385 100644 --- a/include/formats/SipiIOJ2k.h +++ b/include/formats/SipiIOJ2k.h @@ -30,7 +30,7 @@ #include "tiff.h" #include "tiffio.h" -#include "SipiImage.h" +#include "../../src/SipiImage.hpp" //#include "metadata/SipiExif.h" #include "SipiIO.h" diff --git a/include/formats/SipiIOJpeg.h b/include/formats/SipiIOJpeg.h index 3798497d..a04950f6 100644 --- a/include/formats/SipiIOJpeg.h +++ b/include/formats/SipiIOJpeg.h @@ -27,7 +27,7 @@ #include -#include "SipiImage.h" +#include "../../src/SipiImage.hpp" #include "SipiIO.h" namespace Sipi { diff --git a/include/formats/SipiIOPng.h b/include/formats/SipiIOPng.h index f1508da4..e93574c4 100644 --- a/include/formats/SipiIOPng.h +++ b/include/formats/SipiIOPng.h @@ -27,7 +27,7 @@ #include -#include "SipiImage.h" +#include "../../src/SipiImage.hpp" #include "SipiIO.h" namespace Sipi { diff --git a/include/formats/SipiIOTiff.h b/include/formats/SipiIOTiff.h index 60d6547e..83c9e71a 100755 --- a/include/formats/SipiIOTiff.h +++ b/include/formats/SipiIOTiff.h @@ -30,7 +30,7 @@ #include "tiff.h" #include "tiffio.h" -#include "SipiImage.h" +#include "../../src/SipiImage.hpp" //#include "metadata/SipiExif.h" #include "SipiIO.h" diff --git a/nix-cmake b/nix-cmake new file mode 100755 index 00000000..3df55a72 --- /dev/null +++ b/nix-cmake @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 + +# let's say you have a C++ project in Nix that you want to work on with CLion so that the Nix dependencies are available +# put this script in your project directory +# then, in Settings -> Build, Execution, Deployment -> Toolchains set CMake to this script +# if you need any extra nix-shell arguments, add them to the invocation at the bottom + +import os +import sys +import shlex + + +def is_test_invocation(args): + # This function checks if the current invocation is for testing. + # You might check for specific CTest arguments or a pattern that matches your testing setup. + # For simplicity, this example looks for a '--test' flag which you might need to adjust + return '--test' in args or 'ctest' in args + + +scriptDir = os.path.dirname(os.path.realpath(__file__)) + +args = list(map(shlex.quote, sys.argv[1:])) + +# Use the cmakeFlags set by Nix if not running --build or tests +if "--build" not in args and not is_test_invocation(args): + args.insert(0, "$cmakeFlags") + +cwd = os.getcwd() +cmd = 'cd ' + cwd + ' && cmake ' + ' '.join(args) + +if is_test_invocation(args): + # Modify cmd for test execution + # Here you might need to adjust the command to work with CTest specifically. + # This could involve using a different command or altering the existing one to suit test execution. + cmd = 'cd ' + cwd + ' && ctest ' + ' '.join(args) # Adjust this line as necessary + +os.chdir(scriptDir) +os.execvp("nix", [ + "nix", "develop", "--command", "bash", "-c", cmd +]) diff --git a/shttps/Server.cpp b/shttps/Server.cpp index d1055492..368c2152 100644 --- a/shttps/Server.cpp +++ b/shttps/Server.cpp @@ -1222,8 +1222,7 @@ namespace shttps { // Adds a route to the server. The route is a combination of an HTTP method, a path, and request handler. - void Server::add_route(Connection::HttpMethod method_p, const std::string &path_p, RequestHandler handler_p, - void *handler_data_p) { + void Server::add_route(Connection::HttpMethod method_p, const std::string &path_p, RequestHandler handler_p, void *handler_data_p) { handler[method_p][path_p] = handler_p; handler_data[method_p][path_p] = handler_data_p; } diff --git a/src/SipiCache.cpp b/src/SipiCache.cpp index 85797457..989f711d 100644 --- a/src/SipiCache.cpp +++ b/src/SipiCache.cpp @@ -52,7 +52,7 @@ #include "SipiCache.h" #include "shttps/Global.h" -#include "SipiError.h" +#include "SipiError.hpp" static const char __file__[] = __FILE__; diff --git a/src/SipiError.cpp b/src/SipiError.cpp index 76c8c08c..797a3ba7 100755 --- a/src/SipiError.cpp +++ b/src/SipiError.cpp @@ -20,12 +20,12 @@ * You should have received a copy of the GNU Affero General Public * License along with Sipi. If not, see . */ -#include +#include #include #include -#include "SipiError.h" +#include "SipiError.hpp" namespace Sipi { @@ -39,7 +39,7 @@ namespace Sipi { errno_p) {} //============================================================================ - std::string SipiError::to_string(void) const { + std::string SipiError::to_string() const { std::ostringstream errStream; errStream << "Sipi Error at [" << file << ": " << line << "]"; if (sysErrno != 0) errStream << " (system error: " << std::strerror(sysErrno) << ")"; @@ -49,7 +49,7 @@ namespace Sipi { //============================================================================ std::ostream &operator<<(std::ostream &outStream, const SipiError &rhs) { - std::string errStr = rhs.to_string(); + const std::string errStr = rhs.to_string(); outStream << errStr << std::endl; // TODO: remove the endl, the logging code should do it return outStream; } diff --git a/include/SipiError.h b/src/SipiError.hpp similarity index 96% rename from include/SipiError.h rename to src/SipiError.hpp index e5059fcb..5320ad88 100755 --- a/include/SipiError.h +++ b/src/SipiError.hpp @@ -32,13 +32,10 @@ * the line number and a description of what went wrong. */ -#include #include -#include -#include #include -#include "shttps/Error.h" +#include "../shttps/Error.h" /** * \namespace Sipi Used for all sipi things. @@ -46,7 +43,7 @@ namespace Sipi { /*! - * \class SipiError "SipiError.h" + * \class SipiError "SipiError.hpp" * \brief Class that implements the error handling with exceptions * * Used for giving error messages while throwing diff --git a/src/SipiHttpServer.cpp b/src/SipiHttpServer.cpp index f3bb23e3..ca891bff 100644 --- a/src/SipiHttpServer.cpp +++ b/src/SipiHttpServer.cpp @@ -44,8 +44,8 @@ #include #include -#include "SipiImage.h" -#include "SipiError.h" +#include "SipiImage.hpp" +#include "SipiError.hpp" #include "iiifparser/SipiSize.h" #include "iiifparser/SipiRegion.h" #include "iiifparser/SipiRotation.h" @@ -55,10 +55,12 @@ // #include "Salsah.h" #include "shttps/Global.h" -#include "SipiHttpServer.h" #include "shttps/Connection.h" #include "shttps/Parsing.h" +#include "SipiHttpServer.hpp" +#include "handlers/iiif_handler.hpp" + #include "jansson.h" #include "favicon.h" @@ -1752,143 +1754,210 @@ namespace Sipi { void *dummy) { auto *serv = static_cast(user_data); - - enum { - IIIF, - INFO_JSON, - KNORA_JSON, - REDIRECT, - FILE_DOWNLOAD, - UNDEFINED - } request_type = UNDEFINED; - bool prefix_as_path = serv->prefix_as_path(); + std::string uri = conn_obj.uri(); // Has form "/pre/fix/es.../BAU_1_000441077_2_1.j2k/full/,1000/0/default.jpg" - std::vector parts; - size_t pos = 0; - size_t old_pos = 0; - // - // IIIF URi schema: - // {scheme}://{server}{/prefix}/{identifier}/{region}/{size}/{rotation}/{quality}.{format} - // - // The slashes "/" separate the different parts... - // - while ((pos = uri.find('/', pos)) != std::string::npos) { - pos++; - if (pos == 1) { // if first char is a token skip it! + handlers::iiif_handler::RequestType request_type = [&]()-> handlers::iiif_handler::RequestType { + + handlers::iiif_handler::RequestType request_type = handlers::iiif_handler::UNDEFINED; + + std::vector parts; + size_t pos = 0; + size_t old_pos = 0; + + // + // IIIF URi schema: + // {scheme}://{server}{/prefix}/{identifier}/{region}/{size}/{rotation}/{quality}.{format} + // + // The slashes "/" separate the different parts... + // + while ((pos = uri.find('/', pos)) != std::string::npos) { + pos++; + if (pos == 1) { // if first char is a token skip it! + old_pos = pos; + continue; + } + parts.push_back(shttps::urldecode(uri.substr(old_pos, pos - old_pos - 1))); old_pos = pos; - continue; } - parts.push_back(shttps::urldecode(uri.substr(old_pos, pos - old_pos - 1))); - old_pos = pos; - } - if (old_pos != uri.length()) { - parts.push_back(shttps::urldecode(uri.substr(old_pos, std::string::npos))); - } + if (old_pos != uri.length()) { + parts.push_back(shttps::urldecode(uri.substr(old_pos, std::string::npos))); + } - if (parts.empty()) { - send_error(conn_obj, Connection::BAD_REQUEST, "No parameters/path given"); - return; - } + if (parts.empty()) { + send_error(conn_obj, Connection::BAD_REQUEST, "No parameters/path given"); + return handlers::iiif_handler::UNDEFINED; + } - std::vector params; + std::vector params; - // - // below are regex expressions for the different parts of the IIIF URL - // - std::string qualform_ex = R"(^(color|gray|bitonal|default)\.(jpg|tif|png|jp2)$)"; - std::string rotation_ex = R"(^!?[-+]?[0-9]*\.?[0-9]*$)"; - std::string size_ex = R"(^(\^?max)|(\^?pct:[0-9]*\.?[0-9]*)|(\^?[0-9]*,)|(\^?,[0-9]*)|(\^?!?[0-9]*,[0-9]*)$)"; - std::string region_ex = R"(^(full)|(square)|([0-9]+,[0-9]+,[0-9]+,[0-9]+)|(pct:[0-9]*\.?[0-9]*,[0-9]*\.?[0-9]*,[0-9]*\.?[0-9]*,[0-9]*\.?[0-9]*)$)"; - - bool qualform_ok = false; - if (!parts.empty()) - qualform_ok = std::regex_match(parts[parts.size() - 1], std::regex(qualform_ex)); - - bool rotation_ok = false; - if (parts.size() > 1) - rotation_ok = std::regex_match(parts[parts.size() - 2], std::regex(rotation_ex)); - - bool size_ok = false; - if (parts.size() > 2) - size_ok = std::regex_match(parts[parts.size() - 3], std::regex(size_ex)); - - bool region_ok = false; - if (parts.size() > 3) - region_ok = std::regex_match(parts[parts.size() - 4], std::regex(region_ex)); - - if ((pos = parts[parts.size() - 1].find('.', 0)) != std::string::npos) { - std::string fname_body = parts[parts.size() - 1].substr(0, pos); - std::string fname_extension = parts[parts.size() - 1].substr(pos + 1, std::string::npos); // - // we will serve IIIF syntax based image + // below are regex expressions for the different parts of the IIIF URL // - if (qualform_ok && rotation_ok && size_ok && region_ok) { - if (parts.size() >= 6) { // we have a prefix - std::stringstream prefix; - for (int i = 0; i < (parts.size() - 5); i++) { - if (i > 0) - prefix << "/"; - prefix << parts[i]; - } - params.push_back(prefix.str()); // iiif_prefix - } - else if (parts.size() == 5) { // we have no prefix - params.emplace_back(""); // iiif_prefix - } - else { - std::stringstream errmsg; - errmsg << "IIIF url not correctly formatted:"; - if (!qualform_ok) - errmsg << " Error in quality: \"" << parts[parts.size() - 1] << "\"!"; - if (!rotation_ok) - errmsg << " Error in rotation: \"" << parts[parts.size() - 2] << "\"!"; - if (!size_ok) - errmsg << " Error in size: \"" << parts[parts.size() - 3] << "\"!"; - if (!region_ok) - errmsg << " Error in region: \"" << parts[parts.size() - 4] << "\"!"; - send_error(conn_obj, Connection::BAD_REQUEST, errmsg.str()); - return; - } - params.push_back(parts[parts.size() - 5]); // iiif_identifier - params.push_back(parts[parts.size() - 4]); // iiif_region - params.push_back(parts[parts.size() - 3]); // iiif_size - params.push_back(parts[parts.size() - 2]); // iiif_rotation - params.push_back(parts[parts.size() - 1]); // iiif_qualityformat - request_type = IIIF; - } - else if ((fname_body == "info") && (fname_extension == "json")) { + std::string qualform_ex = R"(^(color|gray|bitonal|default)\.(jpg|tif|png|jp2)$)"; + std::string rotation_ex = R"(^!?[-+]?[0-9]*\.?[0-9]*$)"; + std::string size_ex = R"(^(\^?max)|(\^?pct:[0-9]*\.?[0-9]*)|(\^?[0-9]*,)|(\^?,[0-9]*)|(\^?!?[0-9]*,[0-9]*)$)"; + std::string region_ex = R"(^(full)|(square)|([0-9]+,[0-9]+,[0-9]+,[0-9]+)|(pct:[0-9]*\.?[0-9]*,[0-9]*\.?[0-9]*,[0-9]*\.?[0-9]*,[0-9]*\.?[0-9]*)$)"; + + bool qualform_ok = false; + if (!parts.empty()) + qualform_ok = std::regex_match(parts[parts.size() - 1], std::regex(qualform_ex)); + + bool rotation_ok = false; + if (parts.size() > 1) + rotation_ok = std::regex_match(parts[parts.size() - 2], std::regex(rotation_ex)); + + bool size_ok = false; + if (parts.size() > 2) + size_ok = std::regex_match(parts[parts.size() - 3], std::regex(size_ex)); + + bool region_ok = false; + if (parts.size() > 3) + region_ok = std::regex_match(parts[parts.size() - 4], std::regex(region_ex)); + + if ((pos = parts[parts.size() - 1].find('.', 0)) != std::string::npos) { + std::string fname_body = parts[parts.size() - 1].substr(0, pos); + std::string fname_extension = parts[parts.size() - 1].substr(pos + 1, std::string::npos); // - // we have something like "http:://{server}/{prefix}/{id}/info.json + // we will serve IIIF syntax based image // - if (parts.size() >= 3) { // we have a prefix - std::stringstream prefix; - for (int i = 0; i < (parts.size() - 2); i++) { - if (i > 0) - prefix << "/"; - prefix << parts[i]; + if (qualform_ok && rotation_ok && size_ok && region_ok) { + if (parts.size() >= 6) { // we have a prefix + std::stringstream prefix; + for (int i = 0; i < (parts.size() - 5); i++) { + if (i > 0) + prefix << "/"; + prefix << parts[i]; + } + params.push_back(prefix.str()); // iiif_prefix } - params.push_back(prefix.str()); // iiif_prefix + else if (parts.size() == 5) { // we have no prefix + params.emplace_back(""); // iiif_prefix + } + else { + std::stringstream errmsg; + errmsg << "IIIF url not correctly formatted:"; + if (!qualform_ok) + errmsg << " Error in quality: \"" << parts[parts.size() - 1] << "\"!"; + if (!rotation_ok) + errmsg << " Error in rotation: \"" << parts[parts.size() - 2] << "\"!"; + if (!size_ok) + errmsg << " Error in size: \"" << parts[parts.size() - 3] << "\"!"; + if (!region_ok) + errmsg << " Error in region: \"" << parts[parts.size() - 4] << "\"!"; + send_error(conn_obj, Connection::BAD_REQUEST, errmsg.str()); + return handlers::iiif_handler::UNDEFINED; + } + params.push_back(parts[parts.size() - 5]); // iiif_identifier + params.push_back(parts[parts.size() - 4]); // iiif_region + params.push_back(parts[parts.size() - 3]); // iiif_size + params.push_back(parts[parts.size() - 2]); // iiif_rotation + params.push_back(parts[parts.size() - 1]); // iiif_qualityformat + request_type = handlers::iiif_handler::IIIF; + } + else if ((fname_body == "info") && (fname_extension == "json")) { + // + // we have something like "http:://{server}/{prefix}/{id}/info.json + // + if (parts.size() >= 3) { // we have a prefix + std::stringstream prefix; + for (int i = 0; i < (parts.size() - 2); i++) { + if (i > 0) + prefix << "/"; + prefix << parts[i]; + } + params.push_back(prefix.str()); // iiif_prefix + } + else if (parts.size() == 2) { // we have no prefix + params.push_back(""); // iiif_prefix + } + else { + send_error(conn_obj, Connection::BAD_REQUEST, "IIIF url not correctly formatted!"); + return handlers::iiif_handler::UNDEFINED; + } + params.push_back(parts[parts.size() - 2]); // iiif_identifier + request_type = handlers::iiif_handler::INFO_JSON; } - else if (parts.size() == 2) { // we have no prefix - params.push_back(""); // iiif_prefix + else if ((fname_body == "knora") && (fname_extension == "json")) { + // + // we have something like "http:://{server}/{prefix}/{id}/knora.json + // + if (parts.size() >= 3) { // we have a prefix + std::stringstream prefix; + for (int i = 0; i < (parts.size() - 2); i++) { + if (i > 0) + prefix << "/"; + prefix << parts[i]; + } + params.push_back(prefix.str()); // iiif_prefix + } + else if (parts.size() == 2) { // we have no prefix + params.emplace_back(""); // iiif_prefix + } + else { + send_error(conn_obj, Connection::BAD_REQUEST, "IIIF url not correctly formatted!"); + return handlers::iiif_handler::UNDEFINED; + } + params.push_back(parts[parts.size() - 2]); // iiif_identifier + request_type = handlers::iiif_handler::KNORA_JSON; } else { - send_error(conn_obj, Connection::BAD_REQUEST, "IIIF url not correctly formatted!"); - return; + // + // we have something like "http:://{server}/{prefix}/{id}" with id as "body.ext" + // + if (qualform_ok || rotation_ok || size_ok || region_ok) { + std::stringstream errmsg; + errmsg << "IIIF url not correctly formatted:"; + if (!qualform_ok && !parts.empty()) + errmsg << " Error in quality: \"" << parts[parts.size() - 1] << "\"!"; + if (!rotation_ok && (parts.size() > 1)) + errmsg << " Error in rotation: \"" << parts[parts.size() - 2] << "\"!"; + if (!size_ok && (parts.size() > 2)) + errmsg << " Error in size: \"" << parts[parts.size() - 3] << "\"!"; + if (!region_ok && (parts.size() > 3)) + errmsg << " Error in region: \"" << parts[parts.size() - 4] << "\"!"; + send_error(conn_obj, Connection::BAD_REQUEST, errmsg.str()); + return handlers::iiif_handler::UNDEFINED; + } + if (parts.size() >= 2) { // we have a prefix + std::stringstream prefix; + for (int i = 0; i < (parts.size() - 1); i++) { + if (i > 0) + prefix << "/"; + prefix << parts[i]; + } + params.push_back(prefix.str()); // iiif_prefix + } + else if (parts.size() == 1) { // we have no prefix + params.emplace_back(""); // iiif_prefix + } + else { + std::stringstream errmsg; + errmsg << "IIIF url not correctly formatted:"; + if (!qualform_ok && (!parts.empty())) + errmsg << " Error in quality: \"" << parts[parts.size() - 1] << "\"!"; + if (!rotation_ok && (parts.size() > 1)) + errmsg << " Error in rotation: \"" << parts[parts.size() - 2] << "\"!"; + if (!size_ok && (parts.size() > 2)) + errmsg << " Error in size: \"" << parts[parts.size() - 3] << "\"!"; + if (!region_ok && (parts.size() > 3)) + errmsg << " Error in region: \"" << parts[parts.size() - 4] << "\"!"; + send_error(conn_obj, Connection::BAD_REQUEST, errmsg.str()); + return handlers::iiif_handler::UNDEFINED; + } + params.push_back(parts[parts.size() - 1]); // iiif_identifier + request_type = handlers::iiif_handler::REDIRECT; } - params.push_back(parts[parts.size() - 2]); // iiif_identifier - request_type = INFO_JSON; } - else if ((fname_body == "knora") && (fname_extension == "json")) { - // - // we have something like "http:://{server}/{prefix}/{id}/knora.json - // + else if (parts[parts.size() - 1] == "file") { if (parts.size() >= 3) { // we have a prefix + // + // we have something like "http:://{server}/{prefix}/{id}/file + // std::stringstream prefix; for (int i = 0; i < (parts.size() - 2); i++) { if (i > 0) @@ -1897,24 +1966,27 @@ namespace Sipi { } params.push_back(prefix.str()); // iiif_prefix } - else if (parts.size() == 2) { // we have no prefix + else if (parts.size() == 2) { // we have no prefix + // + // we have something like "http:://{server}/{id}/file + // params.emplace_back(""); // iiif_prefix } else { send_error(conn_obj, Connection::BAD_REQUEST, "IIIF url not correctly formatted!"); - return; + return handlers::iiif_handler::UNDEFINED; } params.push_back(parts[parts.size() - 2]); // iiif_identifier - request_type = KNORA_JSON; + request_type = handlers::iiif_handler::FILE_DOWNLOAD; } else { // - // we have something like "http:://{server}/{prefix}/{id}" with id as "body.ext" + // we have something like "http:://{server}/{prefix}/{id}" with id as "body_without_ext" // if (qualform_ok || rotation_ok || size_ok || region_ok) { std::stringstream errmsg; errmsg << "IIIF url not correctly formatted:"; - if (!qualform_ok && !parts.empty()) + if (!qualform_ok && (parts.size() > 0)) errmsg << " Error in quality: \"" << parts[parts.size() - 1] << "\"!"; if (!rotation_ok && (parts.size() > 1)) errmsg << " Error in rotation: \"" << parts[parts.size() - 2] << "\"!"; @@ -1923,7 +1995,7 @@ namespace Sipi { if (!region_ok && (parts.size() > 3)) errmsg << " Error in region: \"" << parts[parts.size() - 4] << "\"!"; send_error(conn_obj, Connection::BAD_REQUEST, errmsg.str()); - return; + return handlers::iiif_handler::UNDEFINED; } if (parts.size() >= 2) { // we have a prefix std::stringstream prefix; @@ -1940,7 +2012,7 @@ namespace Sipi { else { std::stringstream errmsg; errmsg << "IIIF url not correctly formatted:"; - if (!qualform_ok && (!parts.empty())) + if (!qualform_ok && (parts.size() > 0)) errmsg << " Error in quality: \"" << parts[parts.size() - 1] << "\"!"; if (!rotation_ok && (parts.size() > 1)) errmsg << " Error in rotation: \"" << parts[parts.size() - 2] << "\"!"; @@ -1949,108 +2021,39 @@ namespace Sipi { if (!region_ok && (parts.size() > 3)) errmsg << " Error in region: \"" << parts[parts.size() - 4] << "\"!"; send_error(conn_obj, Connection::BAD_REQUEST, errmsg.str()); - return; + return handlers::iiif_handler::UNDEFINED; } params.push_back(parts[parts.size() - 1]); // iiif_identifier - request_type = REDIRECT; - } - } - else if (parts[parts.size() - 1] == "file") { - if (parts.size() >= 3) { // we have a prefix - // - // we have something like "http:://{server}/{prefix}/{id}/file - // - std::stringstream prefix; - for (int i = 0; i < (parts.size() - 2); i++) { - if (i > 0) - prefix << "/"; - prefix << parts[i]; - } - params.push_back(prefix.str()); // iiif_prefix - } - else if (parts.size() == 2) { // we have no prefix - // - // we have something like "http:://{server}/{id}/file - // - params.emplace_back(""); // iiif_prefix - } - else { - send_error(conn_obj, Connection::BAD_REQUEST, "IIIF url not correctly formatted!"); - return; - } - params.push_back(parts[parts.size() - 2]); // iiif_identifier - request_type = FILE_DOWNLOAD; - } - else { - // - // we have something like "http:://{server}/{prefix}/{id}" with id as "body_without_ext" - // - if (qualform_ok || rotation_ok || size_ok || region_ok) { - std::stringstream errmsg; - errmsg << "IIIF url not correctly formatted:"; - if (!qualform_ok && (parts.size() > 0)) - errmsg << " Error in quality: \"" << parts[parts.size() - 1] << "\"!"; - if (!rotation_ok && (parts.size() > 1)) - errmsg << " Error in rotation: \"" << parts[parts.size() - 2] << "\"!"; - if (!size_ok && (parts.size() > 2)) - errmsg << " Error in size: \"" << parts[parts.size() - 3] << "\"!"; - if (!region_ok && (parts.size() > 3)) - errmsg << " Error in region: \"" << parts[parts.size() - 4] << "\"!"; - send_error(conn_obj, Connection::BAD_REQUEST, errmsg.str()); - return; - } - if (parts.size() >= 2) { // we have a prefix - std::stringstream prefix; - for (int i = 0; i < (parts.size() - 1); i++) { - if (i > 0) - prefix << "/"; - prefix << parts[i]; - } - params.push_back(prefix.str()); // iiif_prefix - } - else if (parts.size() == 1) { // we have no prefix - params.emplace_back(""); // iiif_prefix + request_type = handlers::iiif_handler::REDIRECT; } - else { - std::stringstream errmsg; - errmsg << "IIIF url not correctly formatted:"; - if (!qualform_ok && (parts.size() > 0)) - errmsg << " Error in quality: \"" << parts[parts.size() - 1] << "\"!"; - if (!rotation_ok && (parts.size() > 1)) - errmsg << " Error in rotation: \"" << parts[parts.size() - 2] << "\"!"; - if (!size_ok && (parts.size() > 2)) - errmsg << " Error in size: \"" << parts[parts.size() - 3] << "\"!"; - if (!region_ok && (parts.size() > 3)) - errmsg << " Error in region: \"" << parts[parts.size() - 4] << "\"!"; - send_error(conn_obj, Connection::BAD_REQUEST, errmsg.str()); - return; - } - params.push_back(parts[parts.size() - 1]); // iiif_identifier - request_type = REDIRECT; - } + return request_type; + }(); + + // FIXME: remove this and pass the one returned from the parse function + std::vector params; switch (request_type) { - case IIIF: { + case handlers::iiif_handler::IIIF: { serve_iiif(conn_obj, luaserver, serv, prefix_as_path, uri, params); return; } - case INFO_JSON: { + case handlers::iiif_handler::INFO_JSON: { serve_info_json_file(conn_obj, serv, luaserver, params, prefix_as_path); return; } - case KNORA_JSON: { + case handlers::iiif_handler::KNORA_JSON: { serve_knora_json_file(conn_obj, serv, luaserver, params, prefix_as_path); return; } - case REDIRECT: { + case handlers::iiif_handler::REDIRECT: { serve_redirect(conn_obj, params); return; } - case FILE_DOWNLOAD: { + case handlers::iiif_handler::FILE_DOWNLOAD: { serve_file_download(conn_obj, luaserver, serv, prefix_as_path, params); return; } - case UNDEFINED: { + case handlers::iiif_handler::UNDEFINED: { send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, "Unknown internal error!"); return; } diff --git a/include/SipiHttpServer.h b/src/SipiHttpServer.hpp similarity index 100% rename from include/SipiHttpServer.h rename to src/SipiHttpServer.hpp diff --git a/src/SipiImage.cpp b/src/SipiImage.cpp index c9be72b7..0b31c012 100755 --- a/src/SipiImage.cpp +++ b/src/SipiImage.cpp @@ -35,7 +35,7 @@ #include "shttps/Global.h" #include "shttps/Hash.h" -#include "SipiImage.h" +#include "SipiImage.hpp" #include "formats/SipiIOTiff.h" #include "formats/SipiIOJ2k.h" #include "formats/SipiIOJpeg.h" diff --git a/include/SipiImage.h b/src/SipiImage.hpp similarity index 97% rename from include/SipiImage.h rename to src/SipiImage.hpp index 19d602fb..56131722 100755 --- a/include/SipiImage.h +++ b/src/SipiImage.hpp @@ -36,19 +36,19 @@ #include #include -#include "SipiError.h" -#include "SipiIO.h" -#include "formats/SipiIOTiff.h" -#include "metadata/SipiXmp.h" -#include "metadata/SipiIcc.h" -#include "metadata/SipiIptc.h" -#include "metadata/SipiExif.h" -#include "metadata/SipiEssentials.h" -#include "iiifparser/SipiRegion.h" -#include "iiifparser/SipiSize.h" +#include "SipiError.hpp" +#include "../include/SipiIO.h" +#include "../include/formats/SipiIOTiff.h" +#include "../include/metadata/SipiXmp.h" +#include "../include/metadata/SipiIcc.h" +#include "../include/metadata/SipiIptc.h" +#include "../include/metadata/SipiExif.h" +#include "../include/metadata/SipiEssentials.h" +#include "../include/iiifparser/SipiRegion.h" +#include "../include/iiifparser/SipiSize.h" -#include "shttps/Connection.h" -#include "shttps/Hash.h" +#include "../shttps/Connection.h" +#include "../shttps/Hash.h" /*! @@ -492,8 +492,8 @@ class SipiImageError final : public std::exception { * this into account as well. */ void removeExtraSamples(const bool force_gray_alpha = false) { - const auto content_channels = (photo == SEPARATED ? 4 : 3); - const auto extra_channels = static_cast(es.size()); + const size_t content_channels = (photo == SEPARATED ? 4 : 3); + const size_t extra_channels = es.size(); for (size_t i = content_channels; i < (extra_channels + content_channels); i++) { removeChannel(i, force_gray_alpha); } diff --git a/include/SipiImageError.h b/src/SipiImageError.hpp similarity index 100% rename from include/SipiImageError.h rename to src/SipiImageError.hpp diff --git a/src/SipiLua.cpp b/src/SipiLua.cpp index 80203300..7cd7b6c2 100644 --- a/src/SipiLua.cpp +++ b/src/SipiLua.cpp @@ -30,9 +30,9 @@ #include "shttps/Connection.h" #include "shttps/Parsing.h" -#include "SipiImage.h" +#include "SipiImage.hpp" #include "SipiLua.h" -#include "SipiHttpServer.h" +#include "SipiHttpServer.hpp" #include "Error.h" namespace Sipi { diff --git a/src/formats/SipiIOJ2k.cpp b/src/formats/SipiIOJ2k.cpp index f141cd6c..58099014 100644 --- a/src/formats/SipiIOJ2k.cpp +++ b/src/formats/SipiIOJ2k.cpp @@ -38,7 +38,7 @@ #include "shttps/Connection.h" #include "shttps/Global.h" -#include "SipiError.h" +#include "../SipiError.hpp" #include "SipiIOJ2k.h" diff --git a/src/formats/SipiIOJpeg.cpp b/src/formats/SipiIOJpeg.cpp index 233e074a..2b63a12b 100644 --- a/src/formats/SipiIOJpeg.cpp +++ b/src/formats/SipiIOJpeg.cpp @@ -30,7 +30,7 @@ #include #include -#include "SipiError.h" +#include "../SipiError.hpp" #include "SipiIOJpeg.h" #include "SipiCommon.h" #include "shttps/Connection.h" diff --git a/src/formats/SipiIOTiff.cpp b/src/formats/SipiIOTiff.cpp index 56a15f98..27e74753 100755 --- a/src/formats/SipiIOTiff.cpp +++ b/src/formats/SipiIOTiff.cpp @@ -36,9 +36,9 @@ #include #include "shttps/Connection.h" -#include "SipiError.h" +#include "../SipiError.hpp" #include "SipiIOTiff.h" -#include "SipiImage.h" +#include "../SipiImage.hpp" #include "tif_dir.h" // libtiff internals; for _TIFFFieldArray diff --git a/src/handlers/iiif_handler.cpp b/src/handlers/iiif_handler.cpp new file mode 100644 index 00000000..af1d4cf1 --- /dev/null +++ b/src/handlers/iiif_handler.cpp @@ -0,0 +1,25 @@ +// +// Created by Ivan Subotic on 20.03.2024. +// + +#include "iiif_handler.hpp" + +namespace handlers::iiif_handler { + + // Implementation of the parse_iiif_url function + auto parse_iiif_url(const std::string &url) noexcept -> std::expected { + IIIFUrlParseResult result = {IIIF, {}}; + + + if (url.empty()) { + // Return an unexpected result with an error message + return std::unexpected("URL is empty"); + } + + // Parsing logic here... + // Populate result based on parsing + + return result; + } + +} // namespace handlers::iiif_handler diff --git a/src/handlers/iiif_handler.hpp b/src/handlers/iiif_handler.hpp new file mode 100644 index 00000000..5f0b7a46 --- /dev/null +++ b/src/handlers/iiif_handler.hpp @@ -0,0 +1,36 @@ +// +// Created by Ivan Subotic on 20.03.2024. +// + +#ifndef IIIF_HANDLER_HPP +#define IIIF_HANDLER_HPP + +#include +#include +#include + +namespace handlers::iiif_handler { + + enum RequestType { + IIIF, + INFO_JSON, + KNORA_JSON, + REDIRECT, + FILE_DOWNLOAD, + UNDEFINED + }; + + + // Struct to hold the result of parsing an IIIF URL + struct IIIFUrlParseResult { + RequestType request_type; + std::vector params; + }; + + // Free function to parse an IIIF URL returning either the result or an error message + [[nodiscard]] auto parse_iiif_url(const std::string &url) noexcept -> std::expected; + +} // namespace handlers::iiif_handler + + +#endif //IIIF_HANDLER_HPP diff --git a/src/iiifparser/SipiIdentifier.cpp b/src/iiifparser/SipiIdentifier.cpp index 557be2d1..0cb376d2 100644 --- a/src/iiifparser/SipiIdentifier.cpp +++ b/src/iiifparser/SipiIdentifier.cpp @@ -5,7 +5,7 @@ #include #include "shttps/Connection.h" -#include "SipiError.h" +#include "../SipiError.hpp" #include "SipiIdentifier.h" static const char __file__[] = __FILE__; diff --git a/src/iiifparser/SipiQualityFormat.cpp b/src/iiifparser/SipiQualityFormat.cpp index 49e4dcfd..7831caa7 100644 --- a/src/iiifparser/SipiQualityFormat.cpp +++ b/src/iiifparser/SipiQualityFormat.cpp @@ -35,7 +35,7 @@ #include #include -#include "SipiError.h" +#include "../SipiError.hpp" #include "SipiQualityFormat.h" diff --git a/src/iiifparser/SipiRegion.cpp b/src/iiifparser/SipiRegion.cpp index 95676a6d..7ba339cb 100644 --- a/src/iiifparser/SipiRegion.cpp +++ b/src/iiifparser/SipiRegion.cpp @@ -35,7 +35,7 @@ #include #include -#include "SipiError.h" +#include "../SipiError.hpp" #include "SipiRegion.h" static const char __file__[] = __FILE__; diff --git a/src/iiifparser/SipiRotation.cpp b/src/iiifparser/SipiRotation.cpp index d54ee7bb..08b33f62 100644 --- a/src/iiifparser/SipiRotation.cpp +++ b/src/iiifparser/SipiRotation.cpp @@ -36,7 +36,7 @@ #include #include -#include "SipiError.h" +#include "../SipiError.hpp" #include "SipiRotation.h" #include "shttps/Parsing.h" diff --git a/src/iiifparser/SipiSize.cpp b/src/iiifparser/SipiSize.cpp index 7f62a063..c147ea36 100644 --- a/src/iiifparser/SipiSize.cpp +++ b/src/iiifparser/SipiSize.cpp @@ -38,7 +38,7 @@ #include "shttps/Global.h" #include "shttps/Parsing.h" -#include "SipiError.h" +#include "../SipiError.hpp" #include "SipiSize.h" static const char __file__[] = __FILE__; diff --git a/src/metadata/SipiExif.cpp b/src/metadata/SipiExif.cpp index 4e5b58c9..840a62cf 100755 --- a/src/metadata/SipiExif.cpp +++ b/src/metadata/SipiExif.cpp @@ -23,7 +23,7 @@ #include #include -#include "SipiError.h" +#include "../SipiError.hpp" #include "SipiExif.h" static const char file_[] = __FILE__; diff --git a/src/metadata/SipiIcc.cpp b/src/metadata/SipiIcc.cpp index cb63098d..ee3df53a 100755 --- a/src/metadata/SipiIcc.cpp +++ b/src/metadata/SipiIcc.cpp @@ -26,12 +26,12 @@ static const char __file__[] = __FILE__; -#include "SipiError.h" +#include "../SipiError.hpp" #include "AdobeRGB1998_icc.h" #include "USWebCoatedSWOP_icc.h" #include "Rec709-Rec1886_icc.h" -#include "SipiImage.h" +#include "../SipiImage.hpp" #include "shttps/makeunique.h" namespace Sipi { diff --git a/src/metadata/SipiIptc.cpp b/src/metadata/SipiIptc.cpp index 3d62ff47..ac20ab21 100755 --- a/src/metadata/SipiIptc.cpp +++ b/src/metadata/SipiIptc.cpp @@ -20,7 +20,7 @@ * You should have received a copy of the GNU Affero General Public * License along with Sipi. If not, see . */ -#include "SipiError.h" +#include "../SipiError.hpp" #include "SipiIptc.h" #include diff --git a/src/metadata/SipiXmp.cpp b/src/metadata/SipiXmp.cpp index 50865a93..c7fda57a 100755 --- a/src/metadata/SipiXmp.cpp +++ b/src/metadata/SipiXmp.cpp @@ -23,7 +23,7 @@ #include #include -#include "SipiError.h" +#include "../SipiError.hpp" #include "SipiXmp.h" /*! diff --git a/src/sipi.cpp b/src/sipi.cpp index 6a785e8d..ee7b7b14 100644 --- a/src/sipi.cpp +++ b/src/sipi.cpp @@ -45,8 +45,8 @@ #include "shttps/Server.h" #include "SipiLua.h" -#include "SipiImage.h" -#include "SipiHttpServer.h" +#include "SipiImage.hpp" +#include "SipiHttpServer.hpp" #include "SipiFilenameHash.h" #include "CLI11.hpp" @@ -730,13 +730,13 @@ int main(int argc, char *argv[]) { if (result) { std::cerr << "Files identical!" << std::endl; } else { - float diffval = 0.; - int maxdiff = 0; - int max_x, max_y; - for (int y = 0; y < img1.getNy(); y++) { - for (int x = 0; x < img1.getNx(); x++) { - for (int c = 0; c < img1.getNc(); c++) { - int dv = img1.getPixel(x, y, c) - img2.getPixel(x, y, c); + double diffval = 0.; + size_t maxdiff = 0; + size_t max_x, max_y; + for (size_t y = 0; y < img1.getNy(); y++) { + for (size_t x = 0; x < img1.getNx(); x++) { + for (size_t c = 0; c < img1.getNc(); c++) { + size_t dv = img1.getPixel(x, y, c) - img2.getPixel(x, y, c); if (dv > maxdiff) { maxdiff = dv; max_x = x; @@ -746,7 +746,7 @@ int main(int argc, char *argv[]) { } } } - diffval /= (img1.getNy() * img1.getNx() * img1.getNc()); + diffval /= static_cast(img1.getNy() * img1.getNx() * img1.getNc()); std::cerr << "Files differ: avg: " << diffval << " max: " << maxdiff << "(" << max_x << ", " << max_y << ") See diff.tif" << std::endl; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2a2121b3..7f000062 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -74,3 +74,7 @@ add_subdirectory(unit/configuration) # SipiImage tests # To only run this single test, run from inside the build directory '(cd test/unit && ./sipiimage/sipiimage)' add_subdirectory(unit/sipiimage) + +# Handlers tests +# To only run this single test, run from inside the build directory '(cd test/unit && ./handlers/handlers)' +add_subdirectory(unit/handlers) diff --git a/test/approval/CMakeLists.txt b/test/approval/CMakeLists.txt index 225b0565..e7e940ac 100644 --- a/test/approval/CMakeLists.txt +++ b/test/approval/CMakeLists.txt @@ -2,7 +2,7 @@ # Google Test Approval Tests Starter # ----------------------- set(TEST_NAME sipi.approvaltests) -set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD 23) set(SOURCE_FILES main.cpp googletest_starter_test.cpp diff --git a/test/unit/configuration/CMakeLists.txt b/test/unit/configuration/CMakeLists.txt index 64fbf00b..404179f4 100644 --- a/test/unit/configuration/CMakeLists.txt +++ b/test/unit/configuration/CMakeLists.txt @@ -21,7 +21,8 @@ file(GLOB SRCS *.cpp) add_executable(configuration ${SRCS} ${PROJECT_SOURCE_DIR}/src/SipiConf.cpp ${PROJECT_SOURCE_DIR}/include/SipiConf.h - ${PROJECT_SOURCE_DIR}/src/SipiError.cpp ${PROJECT_SOURCE_DIR}/include/SipiError.h + ${PROJECT_SOURCE_DIR}/src/SipiError.cpp + ${PROJECT_SOURCE_DIR}/src/SipiError.hpp ${PROJECT_SOURCE_DIR}/shttps/Global.h ${PROJECT_SOURCE_DIR}/shttps/Error.cpp ${PROJECT_SOURCE_DIR}/shttps/Error.h ${PROJECT_SOURCE_DIR}/shttps/Hash.cpp ${PROJECT_SOURCE_DIR}/shttps/Hash.h diff --git a/test/unit/handlers/CMakeLists.txt b/test/unit/handlers/CMakeLists.txt new file mode 100644 index 00000000..1e22dc90 --- /dev/null +++ b/test/unit/handlers/CMakeLists.txt @@ -0,0 +1,23 @@ +# ----------------------- +# Handlers unit tests +# ----------------------- +set(TEST_NAME sipi_handlers_tests) +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +include_directories(${CMAKE_SOURCE_DIR}/src) + +set(IIIF_HANDLER_SOURCE_FILES + ${CMAKE_SOURCE_DIR}/src/handlers/iiif_handler.cpp + ${CMAKE_SOURCE_DIR}/src/handlers/iiif_handler.hpp +) + +set(SOURCE_FILES + main.cpp + iiif_handler_test.cpp + ${IIIF_HANDLER_SOURCE_FILES} +) +add_executable(${TEST_NAME} ${SOURCE_FILES}) +target_link_libraries(${TEST_NAME} libgtest) +add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) +set_property(TEST ${TEST_NAME} PROPERTY LABELS unit) diff --git a/test/unit/handlers/iiif_handler_test.cpp b/test/unit/handlers/iiif_handler_test.cpp new file mode 100644 index 00000000..44eac0aa --- /dev/null +++ b/test/unit/handlers/iiif_handler_test.cpp @@ -0,0 +1,22 @@ +#include "gtest/gtest.h" + +#include "handlers/iiif_handler.hpp" + +TEST(iiif_handler, parse_correct_iiif_url) { + using namespace handlers::iiif_handler; + EXPECT_TRUE(42 == 42); + + IIIFUrlParseResult parse_result = {IIIF, {}}; + + auto result = parse_iiif_url("http://example.com/iiif/2/image.jpg/full/200,/0/default.jpg"); + EXPECT_TRUE(result.has_value()); +} + +TEST(iiif_handler, parse_empty_iiif_url) { + using namespace handlers::iiif_handler; + EXPECT_TRUE(42 == 42); + + auto result = parse_iiif_url(""); + EXPECT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), "URL is empty"); +} diff --git a/test/unit/handlers/main.cpp b/test/unit/handlers/main.cpp new file mode 100644 index 00000000..7d174e04 --- /dev/null +++ b/test/unit/handlers/main.cpp @@ -0,0 +1,7 @@ +#include "gtest/gtest.h" + +int main(int argc, char **argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/test/unit/sipiimage/CMakeLists.txt b/test/unit/sipiimage/CMakeLists.txt index 18f8c6d2..516aaa08 100644 --- a/test/unit/sipiimage/CMakeLists.txt +++ b/test/unit/sipiimage/CMakeLists.txt @@ -19,43 +19,78 @@ file(GLOB SRCS *.cpp) add_executable( sipiimage ${SRCS} - ${PROJECT_SOURCE_DIR}/src/SipiConf.cpp ${PROJECT_SOURCE_DIR}/include/SipiConf.h - ${PROJECT_SOURCE_DIR}/src/SipiError.cpp ${PROJECT_SOURCE_DIR}/include/SipiError.h - ${PROJECT_SOURCE_DIR}/src/metadata/SipiIcc.cpp ${PROJECT_SOURCE_DIR}/include/metadata/SipiIcc.h - ${PROJECT_SOURCE_DIR}/src/metadata/SipiXmp.cpp ${PROJECT_SOURCE_DIR}/include/metadata/SipiXmp.h - ${PROJECT_SOURCE_DIR}/src/metadata/SipiIptc.cpp ${PROJECT_SOURCE_DIR}/include/metadata/SipiIptc.h - ${PROJECT_SOURCE_DIR}/src/metadata/SipiExif.cpp ${PROJECT_SOURCE_DIR}/include/metadata/SipiExif.h - ${PROJECT_SOURCE_DIR}/src/metadata/SipiEssentials.cpp ${PROJECT_SOURCE_DIR}/include/metadata/SipiEssentials.h - ${PROJECT_SOURCE_DIR}/src/SipiImage.cpp ${PROJECT_SOURCE_DIR}/include/SipiImage.h ${PROJECT_SOURCE_DIR}/include/SipiImageError.h + ${PROJECT_SOURCE_DIR}/src/SipiConf.cpp + ${PROJECT_SOURCE_DIR}/include/SipiConf.h + ${PROJECT_SOURCE_DIR}/src/SipiError.cpp + ${PROJECT_SOURCE_DIR}/src/SipiError.hpp + ${PROJECT_SOURCE_DIR}/src/metadata/SipiIcc.cpp + ${PROJECT_SOURCE_DIR}/include/metadata/SipiIcc.h + ${PROJECT_SOURCE_DIR}/src/metadata/SipiXmp.cpp + ${PROJECT_SOURCE_DIR}/include/metadata/SipiXmp.h + ${PROJECT_SOURCE_DIR}/src/metadata/SipiIptc.cpp + ${PROJECT_SOURCE_DIR}/include/metadata/SipiIptc.h + ${PROJECT_SOURCE_DIR}/src/metadata/SipiExif.cpp + ${PROJECT_SOURCE_DIR}/include/metadata/SipiExif.h + ${PROJECT_SOURCE_DIR}/src/metadata/SipiEssentials.cpp + ${PROJECT_SOURCE_DIR}/include/metadata/SipiEssentials.h + ${PROJECT_SOURCE_DIR}/src/SipiImage.cpp + ${PROJECT_SOURCE_DIR}/src/SipiImage.hpp + ${PROJECT_SOURCE_DIR}/src/SipiImageError.hpp ${PROJECT_SOURCE_DIR}/include/SipiIO.h - ${PROJECT_SOURCE_DIR}/src/formats/SipiIOTiff.cpp ${PROJECT_SOURCE_DIR}/include/formats/SipiIOTiff.h - ${PROJECT_SOURCE_DIR}/src/formats/SipiIOJ2k.cpp ${PROJECT_SOURCE_DIR}/include/formats/SipiIOJ2k.h - ${PROJECT_SOURCE_DIR}/src/formats/SipiIOJpeg.cpp ${PROJECT_SOURCE_DIR}/include/formats/SipiIOJpeg.h - ${PROJECT_SOURCE_DIR}/src/formats/SipiIOPng.cpp ${PROJECT_SOURCE_DIR}/include/formats/SipiIOPng.h - ${PROJECT_SOURCE_DIR}/src/SipiHttpServer.cpp ${PROJECT_SOURCE_DIR}/include/SipiHttpServer.h - ${PROJECT_SOURCE_DIR}/src/SipiCache.cpp ${PROJECT_SOURCE_DIR}/include/SipiCache.h - ${PROJECT_SOURCE_DIR}/src/SipiLua.cpp ${PROJECT_SOURCE_DIR}/include/SipiLua.h - ${PROJECT_SOURCE_DIR}/src/iiifparser/SipiIdentifier.cpp ${PROJECT_SOURCE_DIR}/include/iiifparser/SipiIdentifier.h - ${PROJECT_SOURCE_DIR}/src/iiifparser/SipiRotation.cpp ${PROJECT_SOURCE_DIR}/include/iiifparser/SipiRotation.h - ${PROJECT_SOURCE_DIR}/src/iiifparser/SipiQualityFormat.cpp ${PROJECT_SOURCE_DIR}/include/iiifparser/SipiQualityFormat.h - ${PROJECT_SOURCE_DIR}/src/iiifparser/SipiRegion.cpp ${PROJECT_SOURCE_DIR}/include/iiifparser/SipiRegion.h - ${PROJECT_SOURCE_DIR}/src/iiifparser/SipiSize.cpp ${PROJECT_SOURCE_DIR}/include/iiifparser/SipiSize.h - ${PROJECT_SOURCE_DIR}/src/SipiCommon.cpp ${PROJECT_SOURCE_DIR}/include/SipiCommon.h + ${PROJECT_SOURCE_DIR}/src/formats/SipiIOTiff.cpp + ${PROJECT_SOURCE_DIR}/include/formats/SipiIOTiff.h + ${PROJECT_SOURCE_DIR}/src/formats/SipiIOJ2k.cpp + ${PROJECT_SOURCE_DIR}/include/formats/SipiIOJ2k.h + ${PROJECT_SOURCE_DIR}/src/formats/SipiIOJpeg.cpp + ${PROJECT_SOURCE_DIR}/include/formats/SipiIOJpeg.h + ${PROJECT_SOURCE_DIR}/src/formats/SipiIOPng.cpp + ${PROJECT_SOURCE_DIR}/include/formats/SipiIOPng.h + ${PROJECT_SOURCE_DIR}/src/SipiHttpServer.cpp + ${PROJECT_SOURCE_DIR}/src/SipiHttpServer.hpp + ${PROJECT_SOURCE_DIR}/src/SipiCache.cpp + ${PROJECT_SOURCE_DIR}/include/SipiCache.h + ${PROJECT_SOURCE_DIR}/src/SipiLua.cpp + ${PROJECT_SOURCE_DIR}/include/SipiLua.h + ${PROJECT_SOURCE_DIR}/src/iiifparser/SipiIdentifier.cpp + ${PROJECT_SOURCE_DIR}/include/iiifparser/SipiIdentifier.h + ${PROJECT_SOURCE_DIR}/src/iiifparser/SipiRotation.cpp + ${PROJECT_SOURCE_DIR}/include/iiifparser/SipiRotation.h + ${PROJECT_SOURCE_DIR}/src/iiifparser/SipiQualityFormat.cpp + ${PROJECT_SOURCE_DIR}/include/iiifparser/SipiQualityFormat.h + ${PROJECT_SOURCE_DIR}/src/iiifparser/SipiRegion.cpp + ${PROJECT_SOURCE_DIR}/include/iiifparser/SipiRegion.h + ${PROJECT_SOURCE_DIR}/src/iiifparser/SipiSize.cpp + ${PROJECT_SOURCE_DIR}/include/iiifparser/SipiSize.h + ${PROJECT_SOURCE_DIR}/src/SipiCommon.cpp + ${PROJECT_SOURCE_DIR}/include/SipiCommon.h ${PROJECT_SOURCE_DIR}/shttps/Global.h - ${PROJECT_SOURCE_DIR}/shttps/Error.cpp ${PROJECT_SOURCE_DIR}/shttps/Error.h - ${PROJECT_SOURCE_DIR}/shttps/Hash.cpp ${PROJECT_SOURCE_DIR}/shttps/Hash.h - ${PROJECT_SOURCE_DIR}/shttps/SockStream.cpp ${PROJECT_SOURCE_DIR}/shttps/SockStream.h - ${PROJECT_SOURCE_DIR}/shttps/ChunkReader.cpp ${PROJECT_SOURCE_DIR}/shttps/ChunkReader.h - ${PROJECT_SOURCE_DIR}/shttps/Connection.cpp ${PROJECT_SOURCE_DIR}/shttps/Connection.h - ${PROJECT_SOURCE_DIR}/shttps/LuaServer.cpp ${PROJECT_SOURCE_DIR}/shttps/LuaServer.h - ${PROJECT_SOURCE_DIR}/shttps/LuaSqlite.cpp ${PROJECT_SOURCE_DIR}/shttps/LuaSqlite.h - ${PROJECT_SOURCE_DIR}/shttps/Parsing.cpp ${PROJECT_SOURCE_DIR}/shttps/Parsing.h - ${PROJECT_SOURCE_DIR}/shttps/ThreadControl.cpp ${PROJECT_SOURCE_DIR}/shttps/ThreadControl.h - ${PROJECT_SOURCE_DIR}/shttps/SocketControl.cpp ${PROJECT_SOURCE_DIR}/shttps/SocketControl.h - ${PROJECT_SOURCE_DIR}/shttps/Server.cpp ${PROJECT_SOURCE_DIR}/shttps/Server.h - ${PROJECT_SOURCE_DIR}/shttps/jwt.c ${PROJECT_SOURCE_DIR}/shttps/jwt.h - ${PROJECT_SOURCE_DIR}/shttps/makeunique.h ${PROJECT_SOURCE_DIR}/src/SipiFilenameHash.cpp ${PROJECT_SOURCE_DIR}/include/SipiFilenameHash.h - ../../../include/SipiImageError.h + ${PROJECT_SOURCE_DIR}/shttps/Error.cpp + ${PROJECT_SOURCE_DIR}/shttps/Error.h + ${PROJECT_SOURCE_DIR}/shttps/Hash.cpp + ${PROJECT_SOURCE_DIR}/shttps/Hash.h + ${PROJECT_SOURCE_DIR}/shttps/SockStream.cpp + ${PROJECT_SOURCE_DIR}/shttps/SockStream.h + ${PROJECT_SOURCE_DIR}/shttps/ChunkReader.cpp + ${PROJECT_SOURCE_DIR}/shttps/ChunkReader.h + ${PROJECT_SOURCE_DIR}/shttps/Connection.cpp + ${PROJECT_SOURCE_DIR}/shttps/Connection.h + ${PROJECT_SOURCE_DIR}/shttps/LuaServer.cpp + ${PROJECT_SOURCE_DIR}/shttps/LuaServer.h + ${PROJECT_SOURCE_DIR}/shttps/LuaSqlite.cpp + ${PROJECT_SOURCE_DIR}/shttps/LuaSqlite.h + ${PROJECT_SOURCE_DIR}/shttps/Parsing.cpp + ${PROJECT_SOURCE_DIR}/shttps/Parsing.h + ${PROJECT_SOURCE_DIR}/shttps/ThreadControl.cpp + ${PROJECT_SOURCE_DIR}/shttps/ThreadControl.h + ${PROJECT_SOURCE_DIR}/shttps/SocketControl.cpp + ${PROJECT_SOURCE_DIR}/shttps/SocketControl.h + ${PROJECT_SOURCE_DIR}/shttps/Server.cpp + ${PROJECT_SOURCE_DIR}/shttps/Server.h + ${PROJECT_SOURCE_DIR}/shttps/jwt.c + ${PROJECT_SOURCE_DIR}/shttps/jwt.h + ${PROJECT_SOURCE_DIR}/shttps/makeunique.h + ${PROJECT_SOURCE_DIR}/src/SipiFilenameHash.cpp + ${PROJECT_SOURCE_DIR}/include/SipiFilenameHash.h ) add_dependencies(sipiimage icc_profiles) diff --git a/test/unit/sipiimage/sipiimage.cpp b/test/unit/sipiimage/sipiimage.cpp index b71c6d99..f7752d7b 100644 --- a/test/unit/sipiimage/sipiimage.cpp +++ b/test/unit/sipiimage/sipiimage.cpp @@ -1,6 +1,6 @@ #include "gtest/gtest.h" -#include "../../../include/SipiImage.h" +#include "../../../src/SipiImage.hpp" //small function to check if file exist inline bool exists_file(const std::string &name) { From 022d3914f5407ae9d57b05b4287c1c8ba0ab8703 Mon Sep 17 00:00:00 2001 From: Ivan Subotic <400790+subotic@users.noreply.github.com> Date: Wed, 20 Mar 2024 22:49:51 +0100 Subject: [PATCH 2/6] refactor: IIIF URL parsing --- CMakeLists.txt | 6 +- src/handlers/iiif_handler.cpp | 263 ++++++++++++++++++++++- src/handlers/iiif_handler.hpp | 43 +++- test/unit/handlers/CMakeLists.txt | 11 +- test/unit/handlers/iiif_handler_test.cpp | 28 ++- 5 files changed, 329 insertions(+), 22 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bd6233ea..852207c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,10 +123,12 @@ endif() set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_REQUIRED_FLAGS "-std=c++23") # or the appropriate flag for your compiler + # Simple C++ test program set(CXX_TEST_PROGRAM " - #include #include + #include int main() { return 0; } ") @@ -137,7 +139,7 @@ check_cxx_source_compiles("${CXX_TEST_PROGRAM}" CXX23_SUPPORTED) if(CXX23_SUPPORTED) message(STATUS "C++23 is supported.") else() - message(STATUS "C++23 is not supported.") + message(FATAL_ERROR "C++23 is not supported.") endif() # diff --git a/src/handlers/iiif_handler.cpp b/src/handlers/iiif_handler.cpp index af1d4cf1..569e5385 100644 --- a/src/handlers/iiif_handler.cpp +++ b/src/handlers/iiif_handler.cpp @@ -4,22 +4,269 @@ #include "iiif_handler.hpp" +#include + +#include "Connection.h" + namespace handlers::iiif_handler { // Implementation of the parse_iiif_url function - auto parse_iiif_url(const std::string &url) noexcept -> std::expected { - IIIFUrlParseResult result = {IIIF, {}}; + auto parse_iiif_url(const std::string &uri) noexcept -> std::expected { + + RequestType request_type { UNDEFINED }; + + std::vector parts; + size_t pos = 0; + size_t old_pos = 0; + // + // IIIF URi schema: + // {scheme}://{server}{/prefix}/{identifier}/{region}/{size}/{rotation}/{quality}.{format} + // + // The slashes "/" separate the different parts... + // + while ((pos = uri.find('/', pos)) != std::string::npos) { + pos++; + if (pos == 1) { + // if first char is a token skip it! + old_pos = pos; + continue; + } + parts.push_back(shttps::urldecode(uri.substr(old_pos, pos - old_pos - 1))); + old_pos = pos; + } + + if (old_pos != uri.length()) { + parts.push_back(shttps::urldecode(uri.substr(old_pos, std::string::npos))); + } - if (url.empty()) { - // Return an unexpected result with an error message - return std::unexpected("URL is empty"); + if (parts.empty()) { + return std::unexpected("No parameters/path given"); } - // Parsing logic here... - // Populate result based on parsing + std::vector params; + + // + // below are regex expressions for the different parts of the IIIF URL + // + std::string qualform_ex = R"(^(color|gray|bitonal|default)\.(jpg|tif|png|jp2)$)"; + std::string rotation_ex = R"(^!?[-+]?[0-9]*\.?[0-9]*$)"; + std::string size_ex = R"(^(\^?max)|(\^?pct:[0-9]*\.?[0-9]*)|(\^?[0-9]*,)|(\^?,[0-9]*)|(\^?!?[0-9]*,[0-9]*)$)"; + std::string region_ex = + R"(^(full)|(square)|([0-9]+,[0-9]+,[0-9]+,[0-9]+)|(pct:[0-9]*\.?[0-9]*,[0-9]*\.?[0-9]*,[0-9]*\.?[0-9]*,[0-9]*\.?[0-9]*)$)"; + + bool qualform_ok = false; + if (!parts.empty()) + qualform_ok = std::regex_match(parts[parts.size() - 1], std::regex(qualform_ex)); + + bool rotation_ok = false; + if (parts.size() > 1) + rotation_ok = std::regex_match(parts[parts.size() - 2], std::regex(rotation_ex)); + + bool size_ok = false; + if (parts.size() > 2) + size_ok = std::regex_match(parts[parts.size() - 3], std::regex(size_ex)); + + bool region_ok = false; + if (parts.size() > 3) + region_ok = std::regex_match(parts[parts.size() - 4], std::regex(region_ex)); + + if ((pos = parts[parts.size() - 1].find('.', 0)) != std::string::npos) { + std::string fname_body = parts[parts.size() - 1].substr(0, pos); + std::string fname_extension = parts[parts.size() - 1].substr(pos + 1, std::string::npos); + // + // we will serve IIIF syntax based image + // + if (qualform_ok && rotation_ok && size_ok && region_ok) { + if (parts.size() >= 6) { + // we have a prefix + std::stringstream prefix; + for (int i = 0; i < (parts.size() - 5); i++) { + if (i > 0) + prefix << "/"; + prefix << parts[i]; + } + params.push_back(prefix.str()); // iiif_prefix + } else if (parts.size() == 5) { + // we have no prefix + params.emplace_back(""); // iiif_prefix + } else { + std::stringstream errmsg; + errmsg << "IIIF url not correctly formatted:"; + if (!qualform_ok) + errmsg << " Error in quality: \"" << parts[parts.size() - 1] << "\"!"; + if (!rotation_ok) + errmsg << " Error in rotation: \"" << parts[parts.size() - 2] << "\"!"; + if (!size_ok) + errmsg << " Error in size: \"" << parts[parts.size() - 3] << "\"!"; + if (!region_ok) + errmsg << " Error in region: \"" << parts[parts.size() - 4] << "\"!"; + return std::unexpected(errmsg.str()); + } + params.push_back(parts[parts.size() - 5]); // iiif_identifier + params.push_back(parts[parts.size() - 4]); // iiif_region + params.push_back(parts[parts.size() - 3]); // iiif_size + params.push_back(parts[parts.size() - 2]); // iiif_rotation + params.push_back(parts[parts.size() - 1]); // iiif_qualityformat + request_type = IIIF; + } else if ((fname_body == "info") && (fname_extension == "json")) { + // + // we have something like "http:://{server}/{prefix}/{id}/info.json + // + if (parts.size() >= 3) { + // we have a prefix + std::stringstream prefix; + for (int i = 0; i < (parts.size() - 2); i++) { + if (i > 0) + prefix << "/"; + prefix << parts[i]; + } + params.push_back(prefix.str()); // iiif_prefix + } else if (parts.size() == 2) { + // we have no prefix + params.push_back(""); // iiif_prefix + } else { + return std::unexpected("IIIF url not correctly formatted!"); + } + params.push_back(parts[parts.size() - 2]); // iiif_identifier + request_type = INFO_JSON; + } else if ((fname_body == "knora") && (fname_extension == "json")) { + // + // we have something like "http:://{server}/{prefix}/{id}/knora.json + // + if (parts.size() >= 3) { + // we have a prefix + std::stringstream prefix; + for (int i = 0; i < (parts.size() - 2); i++) { + if (i > 0) + prefix << "/"; + prefix << parts[i]; + } + params.push_back(prefix.str()); // iiif_prefix + } else if (parts.size() == 2) { + // we have no prefix + params.emplace_back(""); // iiif_prefix + } else { + return std::unexpected("IIIF url not correctly formatted!"); + } + params.push_back(parts[parts.size() - 2]); // iiif_identifier + request_type = KNORA_JSON; + } else { + // + // we have something like "http:://{server}/{prefix}/{id}" with id as "body.ext" + // + if (qualform_ok || rotation_ok || size_ok || region_ok) { + std::stringstream errmsg; + errmsg << "IIIF url not correctly formatted:"; + if (!qualform_ok && !parts.empty()) + errmsg << " Error in quality: \"" << parts[parts.size() - 1] << "\"!"; + if (!rotation_ok && (parts.size() > 1)) + errmsg << " Error in rotation: \"" << parts[parts.size() - 2] << "\"!"; + if (!size_ok && (parts.size() > 2)) + errmsg << " Error in size: \"" << parts[parts.size() - 3] << "\"!"; + if (!region_ok && (parts.size() > 3)) + errmsg << " Error in region: \"" << parts[parts.size() - 4] << "\"!"; + return std::unexpected(errmsg.str()); + } + if (parts.size() >= 2) { + // we have a prefix + std::stringstream prefix; + for (int i = 0; i < (parts.size() - 1); i++) { + if (i > 0) + prefix << "/"; + prefix << parts[i]; + } + params.push_back(prefix.str()); // iiif_prefix + } else if (parts.size() == 1) { + // we have no prefix + params.emplace_back(""); // iiif_prefix + } else { + std::stringstream errmsg; + errmsg << "IIIF url not correctly formatted:"; + if (!qualform_ok && (!parts.empty())) + errmsg << " Error in quality: \"" << parts[parts.size() - 1] << "\"!"; + if (!rotation_ok && (parts.size() > 1)) + errmsg << " Error in rotation: \"" << parts[parts.size() - 2] << "\"!"; + if (!size_ok && (parts.size() > 2)) + errmsg << " Error in size: \"" << parts[parts.size() - 3] << "\"!"; + if (!region_ok && (parts.size() > 3)) + errmsg << " Error in region: \"" << parts[parts.size() - 4] << "\"!"; + return std::unexpected(errmsg.str()); + } + params.push_back(parts[parts.size() - 1]); // iiif_identifier + request_type = REDIRECT; + } + } else if (parts[parts.size() - 1] == "file") { + if (parts.size() >= 3) { + // we have a prefix + // + // we have something like "http:://{server}/{prefix}/{id}/file + // + std::stringstream prefix; + for (int i = 0; i < (parts.size() - 2); i++) { + if (i > 0) + prefix << "/"; + prefix << parts[i]; + } + params.push_back(prefix.str()); // iiif_prefix + } else if (parts.size() == 2) { + // we have no prefix + // + // we have something like "http:://{server}/{id}/file + // + params.emplace_back(""); // iiif_prefix + } else { + return std::unexpected("IIIF url not correctly formatted!"); + } + params.push_back(parts[parts.size() - 2]); // iiif_identifier + request_type = FILE_DOWNLOAD; + } else { + // + // we have something like "http:://{server}/{prefix}/{id}" with id as "body_without_ext" + // + if (qualform_ok || rotation_ok || size_ok || region_ok) { + std::stringstream errmsg; + errmsg << "IIIF url not correctly formatted:"; + if (!qualform_ok && (parts.size() > 0)) + errmsg << " Error in quality: \"" << parts[parts.size() - 1] << "\"!"; + if (!rotation_ok && (parts.size() > 1)) + errmsg << " Error in rotation: \"" << parts[parts.size() - 2] << "\"!"; + if (!size_ok && (parts.size() > 2)) + errmsg << " Error in size: \"" << parts[parts.size() - 3] << "\"!"; + if (!region_ok && (parts.size() > 3)) + errmsg << " Error in region: \"" << parts[parts.size() - 4] << "\"!"; + return std::unexpected(errmsg.str()); + } + if (parts.size() >= 2) { + // we have a prefix + std::stringstream prefix; + for (int i = 0; i < (parts.size() - 1); i++) { + if (i > 0) + prefix << "/"; + prefix << parts[i]; + } + params.push_back(prefix.str()); // iiif_prefix + } else if (parts.size() == 1) { + // we have no prefix + params.emplace_back(""); // iiif_prefix + } else { + std::stringstream errmsg; + errmsg << "IIIF url not correctly formatted:"; + if (!qualform_ok && (parts.size() > 0)) + errmsg << " Error in quality: \"" << parts[parts.size() - 1] << "\"!"; + if (!rotation_ok && (parts.size() > 1)) + errmsg << " Error in rotation: \"" << parts[parts.size() - 2] << "\"!"; + if (!size_ok && (parts.size() > 2)) + errmsg << " Error in size: \"" << parts[parts.size() - 3] << "\"!"; + if (!region_ok && (parts.size() > 3)) + errmsg << " Error in region: \"" << parts[parts.size() - 4] << "\"!"; + return std::unexpected(errmsg.str()); + } + params.push_back(parts[parts.size() - 1]); // iiif_identifier + request_type = REDIRECT; + } - return result; + return IIIFUrlParseResult{request_type, params}; } } // namespace handlers::iiif_handler diff --git a/src/handlers/iiif_handler.hpp b/src/handlers/iiif_handler.hpp index 5f0b7a46..8b36d796 100644 --- a/src/handlers/iiif_handler.hpp +++ b/src/handlers/iiif_handler.hpp @@ -6,6 +6,7 @@ #define IIIF_HANDLER_HPP #include +#include #include #include @@ -20,15 +21,55 @@ namespace handlers::iiif_handler { UNDEFINED }; + inline std::string requestTypeToString(const RequestType type) { + switch (type) { + case IIIF: + return "IIIF"; + case INFO_JSON: + return "INFO_JSON"; + case KNORA_JSON: + return "KNORA_JSON"; + case REDIRECT: + return "REDIRECT"; + case FILE_DOWNLOAD: + return "FILE_DOWNLOAD"; + case UNDEFINED: + return "UNDEFINED"; + default: + return "Unknown RequestType"; + } + } + // Struct to hold the result of parsing an IIIF URL struct IIIFUrlParseResult { RequestType request_type; std::vector params; + + [[nodiscard]] std::string to_string() const { + std::stringstream result; + + // Assuming you have a function to convert RequestType to string + // If not, you'll need to implement this according to your enum + std::string requestTypeStr = requestTypeToString(request_type); + + result << "request_type: " << requestTypeStr << ", params: "; + + // Concatenate params + for (size_t i = 0; i < params.size(); ++i) { + result << params[i]; + if (i != params.size() - 1) { + result << ", "; // Add a separator between the params, but not after the last one + } + } + + return result.str(); + } }; // Free function to parse an IIIF URL returning either the result or an error message - [[nodiscard]] auto parse_iiif_url(const std::string &url) noexcept -> std::expected; + [[nodiscard]] auto + parse_iiif_url(const std::string &uri) noexcept -> std::expected; } // namespace handlers::iiif_handler diff --git a/test/unit/handlers/CMakeLists.txt b/test/unit/handlers/CMakeLists.txt index 1e22dc90..40e8fae5 100644 --- a/test/unit/handlers/CMakeLists.txt +++ b/test/unit/handlers/CMakeLists.txt @@ -5,9 +5,18 @@ set(TEST_NAME sipi_handlers_tests) set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) -include_directories(${CMAKE_SOURCE_DIR}/src) +include_directories( + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/shttps +) set(IIIF_HANDLER_SOURCE_FILES + ${CMAKE_SOURCE_DIR}/shttps/ChunkReader.cpp + ${CMAKE_SOURCE_DIR}/shttps/ChunkReader.h + ${CMAKE_SOURCE_DIR}/shttps/Connection.cpp + ${CMAKE_SOURCE_DIR}/shttps/Connection.h + ${CMAKE_SOURCE_DIR}/shttps/Error.cpp + ${CMAKE_SOURCE_DIR}/shttps/Error.h ${CMAKE_SOURCE_DIR}/src/handlers/iiif_handler.cpp ${CMAKE_SOURCE_DIR}/src/handlers/iiif_handler.hpp ) diff --git a/test/unit/handlers/iiif_handler_test.cpp b/test/unit/handlers/iiif_handler_test.cpp index 44eac0aa..505c20b6 100644 --- a/test/unit/handlers/iiif_handler_test.cpp +++ b/test/unit/handlers/iiif_handler_test.cpp @@ -2,21 +2,29 @@ #include "handlers/iiif_handler.hpp" -TEST(iiif_handler, parse_correct_iiif_url) { - using namespace handlers::iiif_handler; - EXPECT_TRUE(42 == 42); - - IIIFUrlParseResult parse_result = {IIIF, {}}; +using namespace handlers::iiif_handler; - auto result = parse_iiif_url("http://example.com/iiif/2/image.jpg/full/200,/0/default.jpg"); +TEST(iiif_handler, parse_correct_iiif_url) { + const auto result = parse_iiif_url("http://example.com/iiif/2/image.jpg/full/200,/0/default.jpg"); EXPECT_TRUE(result.has_value()); } TEST(iiif_handler, parse_empty_iiif_url) { - using namespace handlers::iiif_handler; - EXPECT_TRUE(42 == 42); + const auto result = parse_iiif_url(""); + EXPECT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), "No parameters/path given"); +} + +TEST(iiif_handler, parse_iiif_url_needing_redirect) { + const auto result = parse_iiif_url("https://iiif.dasch.swiss/0812/3KtDiJm4XxY-1PUUCffsF4S.jpx"); + EXPECT_TRUE(result.has_value()); + std::cout << result.value().to_string() << std::endl; + EXPECT_EQ(result->request_type, REDIRECT); +} - auto result = parse_iiif_url(""); +TEST(iiif_handler, not_parse_incomplete_iiif_url) { + const auto result = parse_iiif_url("https://iiif.dasch.swiss/0812"); EXPECT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), "URL is empty"); + std::cout << result.value().to_string() << std::endl; + EXPECT_EQ(result.error(), "No parameters/path given"); } From 396d239cc9432c6284c5944cf4b12c4a7db5b202 Mon Sep 17 00:00:00 2001 From: Ivan Subotic <400790+subotic@users.noreply.github.com> Date: Wed, 20 Mar 2024 22:54:30 +0100 Subject: [PATCH 3/6] build: emit errors when tests are failing --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index a211aebc..41ae0a2f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,7 @@ ENV BUILD_TAG=${BUILD_TAG} RUN cmake -B build -S . -DCMAKE_BUILD_TYPE=RelWithDebInfo --log-context \ && cd build \ && cmake --build . --parallel 1 \ - && ctest + && ctest --output-on-failure # STAGE 2: Setup FROM $UBUNTU_BASE as final From e1796cc8c6a5cbd3de518849e6b3b97162d37bcd Mon Sep 17 00:00:00 2001 From: Ivan Subotic <400790+subotic@users.noreply.github.com> Date: Wed, 20 Mar 2024 23:13:46 +0100 Subject: [PATCH 4/6] refactor: IIIF URL parsing --- CMakeLists.txt | 2 + src/SipiHttpServer.cpp | 286 ++--------------------------- test/unit/sipiimage/CMakeLists.txt | 2 + 3 files changed, 16 insertions(+), 274 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 852207c2..57c410a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -447,6 +447,8 @@ add_executable(sipi src/formats/SipiIOPng.cpp include/formats/SipiIOPng.h src/SipiHttpServer.cpp src/SipiHttpServer.hpp + src/handlers/iiif_handler.cpp + src/handlers/iiif_handler.hpp src/SipiCache.cpp include/SipiCache.h src/SipiLua.cpp include/SipiLua.h src/iiifparser/SipiRotation.cpp include/iiifparser/SipiRotation.h diff --git a/src/SipiHttpServer.cpp b/src/SipiHttpServer.cpp index ca891bff..eb6373f7 100644 --- a/src/SipiHttpServer.cpp +++ b/src/SipiHttpServer.cpp @@ -1754,283 +1754,21 @@ namespace Sipi { void *dummy) { auto *serv = static_cast(user_data); - bool prefix_as_path = serv->prefix_as_path(); + const bool prefix_as_path = serv->prefix_as_path(); + const auto uri = conn_obj.uri(); // has form "/pre/fix/es.../BAU_1_000441077_2_1.j2k/full/,1000/0/default.jpg" + std::vector params {}; + auto request_type {handlers::iiif_handler::UNDEFINED}; - std::string uri = conn_obj.uri(); // Has form "/pre/fix/es.../BAU_1_000441077_2_1.j2k/full/,1000/0/default.jpg" + auto parse_url_result = handlers::iiif_handler::parse_iiif_url(uri); - - handlers::iiif_handler::RequestType request_type = [&]()-> handlers::iiif_handler::RequestType { - - handlers::iiif_handler::RequestType request_type = handlers::iiif_handler::UNDEFINED; - - std::vector parts; - size_t pos = 0; - size_t old_pos = 0; - - // - // IIIF URi schema: - // {scheme}://{server}{/prefix}/{identifier}/{region}/{size}/{rotation}/{quality}.{format} - // - // The slashes "/" separate the different parts... - // - while ((pos = uri.find('/', pos)) != std::string::npos) { - pos++; - if (pos == 1) { // if first char is a token skip it! - old_pos = pos; - continue; - } - parts.push_back(shttps::urldecode(uri.substr(old_pos, pos - old_pos - 1))); - old_pos = pos; - } - - if (old_pos != uri.length()) { - parts.push_back(shttps::urldecode(uri.substr(old_pos, std::string::npos))); - } - - if (parts.empty()) { - send_error(conn_obj, Connection::BAD_REQUEST, "No parameters/path given"); - return handlers::iiif_handler::UNDEFINED; - } - - std::vector params; - - // - // below are regex expressions for the different parts of the IIIF URL - // - std::string qualform_ex = R"(^(color|gray|bitonal|default)\.(jpg|tif|png|jp2)$)"; - std::string rotation_ex = R"(^!?[-+]?[0-9]*\.?[0-9]*$)"; - std::string size_ex = R"(^(\^?max)|(\^?pct:[0-9]*\.?[0-9]*)|(\^?[0-9]*,)|(\^?,[0-9]*)|(\^?!?[0-9]*,[0-9]*)$)"; - std::string region_ex = R"(^(full)|(square)|([0-9]+,[0-9]+,[0-9]+,[0-9]+)|(pct:[0-9]*\.?[0-9]*,[0-9]*\.?[0-9]*,[0-9]*\.?[0-9]*,[0-9]*\.?[0-9]*)$)"; - - bool qualform_ok = false; - if (!parts.empty()) - qualform_ok = std::regex_match(parts[parts.size() - 1], std::regex(qualform_ex)); - - bool rotation_ok = false; - if (parts.size() > 1) - rotation_ok = std::regex_match(parts[parts.size() - 2], std::regex(rotation_ex)); - - bool size_ok = false; - if (parts.size() > 2) - size_ok = std::regex_match(parts[parts.size() - 3], std::regex(size_ex)); - - bool region_ok = false; - if (parts.size() > 3) - region_ok = std::regex_match(parts[parts.size() - 4], std::regex(region_ex)); - - if ((pos = parts[parts.size() - 1].find('.', 0)) != std::string::npos) { - std::string fname_body = parts[parts.size() - 1].substr(0, pos); - std::string fname_extension = parts[parts.size() - 1].substr(pos + 1, std::string::npos); - // - // we will serve IIIF syntax based image - // - if (qualform_ok && rotation_ok && size_ok && region_ok) { - if (parts.size() >= 6) { // we have a prefix - std::stringstream prefix; - for (int i = 0; i < (parts.size() - 5); i++) { - if (i > 0) - prefix << "/"; - prefix << parts[i]; - } - params.push_back(prefix.str()); // iiif_prefix - } - else if (parts.size() == 5) { // we have no prefix - params.emplace_back(""); // iiif_prefix - } - else { - std::stringstream errmsg; - errmsg << "IIIF url not correctly formatted:"; - if (!qualform_ok) - errmsg << " Error in quality: \"" << parts[parts.size() - 1] << "\"!"; - if (!rotation_ok) - errmsg << " Error in rotation: \"" << parts[parts.size() - 2] << "\"!"; - if (!size_ok) - errmsg << " Error in size: \"" << parts[parts.size() - 3] << "\"!"; - if (!region_ok) - errmsg << " Error in region: \"" << parts[parts.size() - 4] << "\"!"; - send_error(conn_obj, Connection::BAD_REQUEST, errmsg.str()); - return handlers::iiif_handler::UNDEFINED; - } - params.push_back(parts[parts.size() - 5]); // iiif_identifier - params.push_back(parts[parts.size() - 4]); // iiif_region - params.push_back(parts[parts.size() - 3]); // iiif_size - params.push_back(parts[parts.size() - 2]); // iiif_rotation - params.push_back(parts[parts.size() - 1]); // iiif_qualityformat - request_type = handlers::iiif_handler::IIIF; - } - else if ((fname_body == "info") && (fname_extension == "json")) { - // - // we have something like "http:://{server}/{prefix}/{id}/info.json - // - if (parts.size() >= 3) { // we have a prefix - std::stringstream prefix; - for (int i = 0; i < (parts.size() - 2); i++) { - if (i > 0) - prefix << "/"; - prefix << parts[i]; - } - params.push_back(prefix.str()); // iiif_prefix - } - else if (parts.size() == 2) { // we have no prefix - params.push_back(""); // iiif_prefix - } - else { - send_error(conn_obj, Connection::BAD_REQUEST, "IIIF url not correctly formatted!"); - return handlers::iiif_handler::UNDEFINED; - } - params.push_back(parts[parts.size() - 2]); // iiif_identifier - request_type = handlers::iiif_handler::INFO_JSON; - } - else if ((fname_body == "knora") && (fname_extension == "json")) { - // - // we have something like "http:://{server}/{prefix}/{id}/knora.json - // - if (parts.size() >= 3) { // we have a prefix - std::stringstream prefix; - for (int i = 0; i < (parts.size() - 2); i++) { - if (i > 0) - prefix << "/"; - prefix << parts[i]; - } - params.push_back(prefix.str()); // iiif_prefix - } - else if (parts.size() == 2) { // we have no prefix - params.emplace_back(""); // iiif_prefix - } - else { - send_error(conn_obj, Connection::BAD_REQUEST, "IIIF url not correctly formatted!"); - return handlers::iiif_handler::UNDEFINED; - } - params.push_back(parts[parts.size() - 2]); // iiif_identifier - request_type = handlers::iiif_handler::KNORA_JSON; - } - else { - // - // we have something like "http:://{server}/{prefix}/{id}" with id as "body.ext" - // - if (qualform_ok || rotation_ok || size_ok || region_ok) { - std::stringstream errmsg; - errmsg << "IIIF url not correctly formatted:"; - if (!qualform_ok && !parts.empty()) - errmsg << " Error in quality: \"" << parts[parts.size() - 1] << "\"!"; - if (!rotation_ok && (parts.size() > 1)) - errmsg << " Error in rotation: \"" << parts[parts.size() - 2] << "\"!"; - if (!size_ok && (parts.size() > 2)) - errmsg << " Error in size: \"" << parts[parts.size() - 3] << "\"!"; - if (!region_ok && (parts.size() > 3)) - errmsg << " Error in region: \"" << parts[parts.size() - 4] << "\"!"; - send_error(conn_obj, Connection::BAD_REQUEST, errmsg.str()); - return handlers::iiif_handler::UNDEFINED; - } - if (parts.size() >= 2) { // we have a prefix - std::stringstream prefix; - for (int i = 0; i < (parts.size() - 1); i++) { - if (i > 0) - prefix << "/"; - prefix << parts[i]; - } - params.push_back(prefix.str()); // iiif_prefix - } - else if (parts.size() == 1) { // we have no prefix - params.emplace_back(""); // iiif_prefix - } - else { - std::stringstream errmsg; - errmsg << "IIIF url not correctly formatted:"; - if (!qualform_ok && (!parts.empty())) - errmsg << " Error in quality: \"" << parts[parts.size() - 1] << "\"!"; - if (!rotation_ok && (parts.size() > 1)) - errmsg << " Error in rotation: \"" << parts[parts.size() - 2] << "\"!"; - if (!size_ok && (parts.size() > 2)) - errmsg << " Error in size: \"" << parts[parts.size() - 3] << "\"!"; - if (!region_ok && (parts.size() > 3)) - errmsg << " Error in region: \"" << parts[parts.size() - 4] << "\"!"; - send_error(conn_obj, Connection::BAD_REQUEST, errmsg.str()); - return handlers::iiif_handler::UNDEFINED; - } - params.push_back(parts[parts.size() - 1]); // iiif_identifier - request_type = handlers::iiif_handler::REDIRECT; - } - } - else if (parts[parts.size() - 1] == "file") { - if (parts.size() >= 3) { // we have a prefix - // - // we have something like "http:://{server}/{prefix}/{id}/file - // - std::stringstream prefix; - for (int i = 0; i < (parts.size() - 2); i++) { - if (i > 0) - prefix << "/"; - prefix << parts[i]; - } - params.push_back(prefix.str()); // iiif_prefix - } - else if (parts.size() == 2) { // we have no prefix - // - // we have something like "http:://{server}/{id}/file - // - params.emplace_back(""); // iiif_prefix - } - else { - send_error(conn_obj, Connection::BAD_REQUEST, "IIIF url not correctly formatted!"); - return handlers::iiif_handler::UNDEFINED; - } - params.push_back(parts[parts.size() - 2]); // iiif_identifier - request_type = handlers::iiif_handler::FILE_DOWNLOAD; - } - else { - // - // we have something like "http:://{server}/{prefix}/{id}" with id as "body_without_ext" - // - if (qualform_ok || rotation_ok || size_ok || region_ok) { - std::stringstream errmsg; - errmsg << "IIIF url not correctly formatted:"; - if (!qualform_ok && (parts.size() > 0)) - errmsg << " Error in quality: \"" << parts[parts.size() - 1] << "\"!"; - if (!rotation_ok && (parts.size() > 1)) - errmsg << " Error in rotation: \"" << parts[parts.size() - 2] << "\"!"; - if (!size_ok && (parts.size() > 2)) - errmsg << " Error in size: \"" << parts[parts.size() - 3] << "\"!"; - if (!region_ok && (parts.size() > 3)) - errmsg << " Error in region: \"" << parts[parts.size() - 4] << "\"!"; - send_error(conn_obj, Connection::BAD_REQUEST, errmsg.str()); - return handlers::iiif_handler::UNDEFINED; - } - if (parts.size() >= 2) { // we have a prefix - std::stringstream prefix; - for (int i = 0; i < (parts.size() - 1); i++) { - if (i > 0) - prefix << "/"; - prefix << parts[i]; - } - params.push_back(prefix.str()); // iiif_prefix - } - else if (parts.size() == 1) { // we have no prefix - params.emplace_back(""); // iiif_prefix - } - else { - std::stringstream errmsg; - errmsg << "IIIF url not correctly formatted:"; - if (!qualform_ok && (parts.size() > 0)) - errmsg << " Error in quality: \"" << parts[parts.size() - 1] << "\"!"; - if (!rotation_ok && (parts.size() > 1)) - errmsg << " Error in rotation: \"" << parts[parts.size() - 2] << "\"!"; - if (!size_ok && (parts.size() > 2)) - errmsg << " Error in size: \"" << parts[parts.size() - 3] << "\"!"; - if (!region_ok && (parts.size() > 3)) - errmsg << " Error in region: \"" << parts[parts.size() - 4] << "\"!"; - send_error(conn_obj, Connection::BAD_REQUEST, errmsg.str()); - return handlers::iiif_handler::UNDEFINED; - } - params.push_back(parts[parts.size() - 1]); // iiif_identifier - request_type = handlers::iiif_handler::REDIRECT; - } - return request_type; - }(); - - // FIXME: remove this and pass the one returned from the parse function - std::vector params; + if (parse_url_result.has_value()) { + params = parse_url_result.value().params; + request_type = parse_url_result.value().request_type; + } else { + send_error(conn_obj, Connection::BAD_REQUEST, parse_url_result.error()); + return; + } switch (request_type) { case handlers::iiif_handler::IIIF: { diff --git a/test/unit/sipiimage/CMakeLists.txt b/test/unit/sipiimage/CMakeLists.txt index 516aaa08..228b4e11 100644 --- a/test/unit/sipiimage/CMakeLists.txt +++ b/test/unit/sipiimage/CMakeLists.txt @@ -47,6 +47,8 @@ add_executable( ${PROJECT_SOURCE_DIR}/include/formats/SipiIOPng.h ${PROJECT_SOURCE_DIR}/src/SipiHttpServer.cpp ${PROJECT_SOURCE_DIR}/src/SipiHttpServer.hpp + ${PROJECT_SOURCE_DIR}/src/handlers/iiif_handler.cpp + ${PROJECT_SOURCE_DIR}/src/handlers/iiif_handler.hpp ${PROJECT_SOURCE_DIR}/src/SipiCache.cpp ${PROJECT_SOURCE_DIR}/include/SipiCache.h ${PROJECT_SOURCE_DIR}/src/SipiLua.cpp From f8d183fb4da4a357f033f1ee6140d71707173ba0 Mon Sep 17 00:00:00 2001 From: Ivan Subotic <400790+subotic@users.noreply.github.com> Date: Thu, 21 Mar 2024 21:16:37 +0100 Subject: [PATCH 5/6] fix: URI parsing and redirection --- .clang-format | 111 +- src/SipiHttpServer.cpp | 2940 +++++++++++----------- src/SipiHttpServer.hpp | 93 +- src/SipiImage.cpp | 2 +- src/SipiImage.hpp | 6 +- src/handlers/iiif_handler.cpp | 396 +-- src/handlers/iiif_handler.hpp | 35 +- src/metadata/SipiIcc.cpp | 2 +- test/unit/handlers/iiif_handler_test.cpp | 47 +- 9 files changed, 1858 insertions(+), 1774 deletions(-) diff --git a/.clang-format b/.clang-format index 9a8f8768..4895ff6c 100644 --- a/.clang-format +++ b/.clang-format @@ -1,47 +1,88 @@ ---- Language: Cpp -BasedOnStyle: LLVM -AccessModifierOffset: -4 +AccessModifierOffset: -2 +AlignAfterOpenBracket: BlockIndent # New in v14. For earlier clang-format versions, use AlwaysBreak instead. +AlignConsecutiveMacros: false AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false +AlignEscapedNewlines: DontAlign AlignOperands: false AlignTrailingComments: false +AllowAllArgumentsOnNextLine: false +AllowAllConstructorInitializersOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true AlwaysBreakTemplateDeclarations: Yes -BraceWrapping: - AfterCaseLabel: true - AfterClass: true - AfterControlStatement: true - AfterEnum: true - AfterFunction: true - AfterNamespace: true - AfterStruct: true - AfterUnion: true - AfterExternBlock: false - BeforeCatch: true - BeforeElse: true - BeforeLambdaBody: true - BeforeWhile: true - SplitEmptyFunction: true - SplitEmptyRecord: true - SplitEmptyNamespace: true +BinPackArguments: false +BinPackParameters: false +BreakBeforeBinaryOperators: NonAssignment BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: true BreakConstructorInitializers: AfterColon -ColumnLimit: 120 -IncludeCategories: - - Regex: '^<.*' - Priority: 1 - - Regex: '^".*' - Priority: 2 - - Regex: '.*' - Priority: 3 +BreakInheritanceList: AfterColon +BreakStringLiterals: false +ColumnLimit: 80 +CompactNamespaces: true +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +FixNamespaceComments: true +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^<.*\.h>' + Priority: 1 + SortPriority: 0 + CaseSensitive: false + - Regex: '^<.*' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 3 + SortPriority: 0 + CaseSensitive: false IncludeIsMainRegex: '([-_](test|unittest))?$' -IndentCaseBlocks: true +IndentCaseLabels: true +IndentPPDirectives: BeforeHash IndentWidth: 4 -InsertNewlineAtEOF: true -MacroBlockBegin: '' -MacroBlockEnd: '' -MaxEmptyLinesToKeep: 2 -NamespaceIndentation: All +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: false +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: Inner +PointerAlignment: Left +ReferenceAlignment: Left # New in v13. int &name ==> int& name +ReflowComments: false +SeparateDefinitionBlocks: Always # New in v14. +SortIncludes: CaseSensitive +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: false +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto TabWidth: 4 -... +UseTab: Never diff --git a/src/SipiHttpServer.cpp b/src/SipiHttpServer.cpp index eb6373f7..eaf11b01 100644 --- a/src/SipiHttpServer.cpp +++ b/src/SipiHttpServer.cpp @@ -70,1438 +70,1409 @@ using namespace shttps; static const char __file__[] = __FILE__; namespace Sipi { - /*! - * The name of the Lua function that checks permissions before a file is returned to an HTTP client. - */ - static const std::string iiif_preflight_funcname = "pre_flight"; - static const std::string file_preflight_funcname = "file_pre_flight"; - - typedef enum { - iiif_prefix = 0, //!< http://{url}/*{prefix}*/{id}/{region}/{size}/{rotation}/{quality}.{format} - iiif_identifier = 1, //!< http://{url}/{prefix}/*{id}*/{region}/{size}/{rotation}/{quality}.{format} - iiif_region = 2, //!< http://{url}/{prefix}/{id}/{region}/{size}/{rotation}/{quality}.{format} - iiif_size = 3, //!< http://{url}/{prefix}/{id}/{region}/*{size}*/{rotation}/{quality}.{format} - iiif_rotation = 4, //!< http://{url}/{prefix}/{id}/{region}/{size}/*{rotation}*/{quality}.{format} - iiif_qualityformat = 5, //!< http://{url}/{prefix}/{id}/{region}/{size}/{rotation}/*{quality}.{format}* - } IiifParams; - - /*! - * Sends an HTTP error response to the client, and logs the error if appropriate. - * - * \param conn_obj the server connection. - * \param code the HTTP status code to be returned. - * \param errmsg the error message to be returned. - */ - static void send_error(Connection &conn_obj, Connection::StatusCodes code, const std::string &errmsg) { - conn_obj.status(code); - conn_obj.setBuffer(); - conn_obj.header("Content-Type", "text/plain"); - - std::string http_err_name; - constexpr bool log_err(true); // True if the error should be logged. - - switch (code) { - case Connection::BAD_REQUEST: - http_err_name = "Bad Request"; - // log_err = false; - break; - case Connection::FORBIDDEN: - http_err_name = "Forbidden"; - // log_err = false; - break; - case Connection::UNAUTHORIZED: - http_err_name = "Unauthorized"; - break; - case Connection::NOT_FOUND: - http_err_name = "Not Found"; - // log_err = false; - break; - case Connection::INTERNAL_SERVER_ERROR: - http_err_name = "Internal Server Error"; - break; - case Connection::NOT_IMPLEMENTED: - http_err_name = "Not Implemented"; - // log_err = false; - break; - case Connection::SERVICE_UNAVAILABLE: - http_err_name = "Service Unavailable"; - break; - default: - http_err_name = "Unknown error"; - break; - } +/*! + * The name of the Lua function that checks permissions before a file is returned to an HTTP client. + */ +static const std::string iiif_preflight_funcname = "pre_flight"; +static const std::string file_preflight_funcname = "file_pre_flight"; + +typedef enum { + iiif_prefix = 0, //!< http://{url}/*{prefix}*/{id}/{region}/{size}/{rotation}/{quality}.{format} + iiif_identifier = 1, //!< http://{url}/{prefix}/*{id}*/{region}/{size}/{rotation}/{quality}.{format} + iiif_region = 2, //!< http://{url}/{prefix}/{id}/{region}/{size}/{rotation}/{quality}.{format} + iiif_size = 3, //!< http://{url}/{prefix}/{id}/{region}/*{size}*/{rotation}/{quality}.{format} + iiif_rotation = 4, //!< http://{url}/{prefix}/{id}/{region}/{size}/*{rotation}*/{quality}.{format} + iiif_qualityformat = 5, //!< http://{url}/{prefix}/{id}/{region}/{size}/{rotation}/*{quality}.{format}* +} IiifParams; + +/*! + * Sends an HTTP error response to the client, and logs the error if appropriate. + * + * \param conn_obj the server connection. + * \param code the HTTP status code to be returned. + * \param errmsg the error message to be returned. + */ +static void send_error(Connection& conn_obj, Connection::StatusCodes code, const std::string& errmsg) { + conn_obj.status(code); + conn_obj.setBuffer(); + conn_obj.header("Content-Type", "text/plain"); + + std::string http_err_name; + constexpr bool log_err(true); // True if the error should be logged. + + switch (code) { + case Connection::BAD_REQUEST: + http_err_name = "Bad Request"; + // log_err = false; + break; + case Connection::FORBIDDEN: + http_err_name = "Forbidden"; + // log_err = false; + break; + case Connection::UNAUTHORIZED: + http_err_name = "Unauthorized"; + break; + case Connection::NOT_FOUND: + http_err_name = "Not Found"; + // log_err = false; + break; + case Connection::INTERNAL_SERVER_ERROR: + http_err_name = "Internal Server Error"; + break; + case Connection::NOT_IMPLEMENTED: + http_err_name = "Not Implemented"; + // log_err = false; + break; + case Connection::SERVICE_UNAVAILABLE: + http_err_name = "Service Unavailable"; + break; + default: + http_err_name = "Unknown error"; + break; + } - // Send an error message to the client. - conn_obj << http_err_name; - if (!errmsg.empty()) { - conn_obj << ": " << errmsg; - } + // Send an error message to the client. + conn_obj << http_err_name; + if (!errmsg.empty()) { + conn_obj << ": " << errmsg; + } - conn_obj.flush(); - // Log the error if appropriate. - if (log_err) { - std::stringstream log_msg_stream; - log_msg_stream << "GET " << conn_obj.uri() << " failed (" << http_err_name << ")"; + conn_obj.flush(); + // Log the error if appropriate. + if (log_err) { + std::stringstream log_msg_stream; + log_msg_stream << "GET " << conn_obj.uri() << " failed (" << http_err_name << ")"; - if (!errmsg.empty()) { - log_msg_stream << ": " << errmsg; - } - syslog(LOG_ERR, "%s", log_msg_stream.str().c_str()); + if (!errmsg.empty()) { + log_msg_stream << ": " << errmsg; } + syslog(LOG_ERR, "%s", log_msg_stream.str().c_str()); } - //========================================================================= - - /*! - * Sends an HTTP error response to the client, and logs the error if appropriate. - * - * \param conn_obj the server connection. - * \param code the HTTP status code to be returned. - * \param err an exception describing the error. - */ - static void send_error(Connection &conn_obj, Connection::StatusCodes code, const SipiError &err) { - send_error(conn_obj, code, err.to_string()); - } - //========================================================================= - - /*! - * Sends an HTTP error response to the client, and logs the error if appropriate. - * - * \param conn_obj the server connection. - * \param code the HTTP status code to be returned. - * \param err an exception describing the error. - */ - static void send_error(Connection &conn_obj, Connection::StatusCodes code, const Error &err) { - send_error(conn_obj, code, err.to_string()); - } - //========================================================================= - - /*! - * Sends an HTTP error response to the client, and logs the error if appropriate. - * - * \param conn_obj the server connection. - * \param code the HTTP status code to be returned. - */ - static void send_error(Connection &conn_obj, Connection::StatusCodes code) { - send_error(conn_obj, code, ""); +} + +//========================================================================= + +/*! + * Sends an HTTP error response to the client, and logs the error if appropriate. + * + * \param conn_obj the server connection. + * \param code the HTTP status code to be returned. + * \param err an exception describing the error. + */ +static void send_error(Connection& conn_obj, Connection::StatusCodes code, const SipiError& err) { + send_error(conn_obj, code, err.to_string()); +} + +//========================================================================= + +/*! + * Sends an HTTP error response to the client, and logs the error if appropriate. + * + * \param conn_obj the server connection. + * \param code the HTTP status code to be returned. + * \param err an exception describing the error. + */ +static void send_error(Connection& conn_obj, Connection::StatusCodes code, const Error& err) { + send_error(conn_obj, code, err.to_string()); +} + +//========================================================================= + +/*! + * Sends an HTTP error response to the client, and logs the error if appropriate. + * + * \param conn_obj the server connection. + * \param code the HTTP status code to be returned. + */ +static void send_error(Connection& conn_obj, Connection::StatusCodes code) { + send_error(conn_obj, code, ""); +} + +//========================================================================= + +/*! + * Gets the IIIF prefix, IIIF identifier, and cookie from the HTTP request, and passes them to the Lua pre-flight function (whose + * name is given by the constant pre_flight_func_name). + * + * Returns the return values of the pre-flight function as a std::pair containing a permission string and (optionally) a file path. + * Throws SipiError if an error occurs. + * + * \param conn_obj the server connection. + * \param luaserver the Lua server that will be used to call the function. + * \param prefix the IIIF prefix. + * \param identifier the IIIF identifier. + * \return Pair of permission string and filepath + */ +static std::unordered_map +call_iiif_preflight(Connection& conn_obj, + shttps::LuaServer& luaserver, + const std::string& prefix, + const std::string& identifier) { + // The permission and optional file path that the pre_fight function returns. + std::unordered_map preflight_info; + // std::string permission; + // std::string infile; + + // The paramters to be passed to the pre-flight function. + std::vector> lvals; + + // The first parameter is the IIIF prefix. + std::shared_ptr iiif_prefix_param = std::make_shared(); + iiif_prefix_param->type = LuaValstruct::STRING_TYPE; + iiif_prefix_param->value.s = prefix; + lvals.push_back(iiif_prefix_param); + + // The second parameter is the IIIF identifier. + std::shared_ptr iiif_identifier_param = std::make_shared(); + iiif_identifier_param->type = LuaValstruct::STRING_TYPE; + iiif_identifier_param->value.s = identifier; + lvals.push_back(iiif_identifier_param); + + // The third parameter is the HTTP cookie. + std::shared_ptr cookie_param = std::make_shared(); + std::string cookie = conn_obj.header("cookie"); + cookie_param->type = LuaValstruct::STRING_TYPE; + cookie_param->value.s = cookie; + lvals.push_back(cookie_param); + + // Call the pre-flight function. + std::vector> rvals = luaserver.executeLuafunction(iiif_preflight_funcname, lvals); + + // If it returned nothing, that's an error. + if (rvals.empty()) { + std::ostringstream err_msg; + err_msg << "Lua function " << iiif_preflight_funcname << " must return at least one value"; + throw SipiError(__file__, __LINE__, err_msg.str()); } - //========================================================================= - - /*! - * Gets the IIIF prefix, IIIF identifier, and cookie from the HTTP request, and passes them to the Lua pre-flight function (whose - * name is given by the constant pre_flight_func_name). - * - * Returns the return values of the pre-flight function as a std::pair containing a permission string and (optionally) a file path. - * Throws SipiError if an error occurs. - * - * \param conn_obj the server connection. - * \param luaserver the Lua server that will be used to call the function. - * \param prefix the IIIF prefix. - * \param identifier the IIIF identifier. - * \return Pair of permission string and filepath - */ - static std::unordered_map - call_iiif_preflight(Connection &conn_obj, shttps::LuaServer &luaserver, const std::string &prefix, const std::string &identifier) { - // The permission and optional file path that the pre_fight function returns. - std::unordered_map preflight_info; - // std::string permission; - // std::string infile; - - // The paramters to be passed to the pre-flight function. - std::vector> lvals; - - // The first parameter is the IIIF prefix. - std::shared_ptr iiif_prefix_param = std::make_shared(); - iiif_prefix_param->type = LuaValstruct::STRING_TYPE; - iiif_prefix_param->value.s = prefix; - lvals.push_back(iiif_prefix_param); - - // The second parameter is the IIIF identifier. - std::shared_ptr iiif_identifier_param = std::make_shared(); - iiif_identifier_param->type = LuaValstruct::STRING_TYPE; - iiif_identifier_param->value.s = identifier; - lvals.push_back(iiif_identifier_param); - - // The third parameter is the HTTP cookie. - std::shared_ptr cookie_param = std::make_shared(); - std::string cookie = conn_obj.header("cookie"); - cookie_param->type = LuaValstruct::STRING_TYPE; - cookie_param->value.s = cookie; - lvals.push_back(cookie_param); - - // Call the pre-flight function. - std::vector> rvals = luaserver.executeLuafunction(iiif_preflight_funcname, lvals); - - // If it returned nothing, that's an error. - if (rvals.empty()) { + + // The first return value is the permission code. + auto permission_return_val = rvals.at(0); + + // The permission code can be a string or a table + if (permission_return_val->type == LuaValstruct::STRING_TYPE) { + preflight_info["type"] = permission_return_val->value.s; + } else if (permission_return_val->type == LuaValstruct::TABLE_TYPE) { + std::shared_ptr tmpv; + try { + tmpv = permission_return_val->value.table.at("type"); + } catch (const std::out_of_range& err) { std::ostringstream err_msg; - err_msg << "Lua function " << iiif_preflight_funcname << " must return at least one value"; + err_msg << "The permission value returned by Lua function " << iiif_preflight_funcname << + " has no type field!"; throw SipiError(__file__, __LINE__, err_msg.str()); } - - // The first return value is the permission code. - auto permission_return_val = rvals.at(0); - - // The permission code can be a string or a table - if (permission_return_val->type == LuaValstruct::STRING_TYPE) { - preflight_info["type"] = permission_return_val->value.s; + if (tmpv->type != LuaValstruct::STRING_TYPE) { + throw SipiError(__file__, __LINE__, "String value expected!"); } - else if (permission_return_val->type == LuaValstruct::TABLE_TYPE) { - std::shared_ptr tmpv; - try { - tmpv = permission_return_val->value.table.at("type"); - } - catch (const std::out_of_range &err) { - std::ostringstream err_msg; - err_msg << "The permission value returned by Lua function " << iiif_preflight_funcname << " has no type field!"; - throw SipiError(__file__, __LINE__, err_msg.str()); - } - if (tmpv->type != LuaValstruct::STRING_TYPE) { + preflight_info["type"] = tmpv->value.s; + for (const auto& keyval : permission_return_val->value.table) { + if (keyval.first == "type") + continue; + if (keyval.second->type != LuaValstruct::STRING_TYPE) { throw SipiError(__file__, __LINE__, "String value expected!"); } - preflight_info["type"] = tmpv->value.s; - for (const auto &keyval : permission_return_val->value.table) { - if (keyval.first == "type") - continue; - if (keyval.second->type != LuaValstruct::STRING_TYPE) { - throw SipiError(__file__, __LINE__, "String value expected!"); - } - preflight_info[keyval.first] = keyval.second->value.s; - } + preflight_info[keyval.first] = keyval.second->value.s; } - else { + } else { + std::ostringstream err_msg; + err_msg << "The permission value returned by Lua function " << iiif_preflight_funcname << " was not valid"; + throw SipiError(__file__, __LINE__, err_msg.str()); + } + + // + // check if permission type is valid + // + if ((preflight_info["type"] != "allow") && + (preflight_info["type"] != "login") && + (preflight_info["type"] != "clickthrough") && + (preflight_info["type"] != "kiosk") && + (preflight_info["type"] != "external") && + (preflight_info["type"] != "restrict") && + (preflight_info["type"] != "deny")) { + std::ostringstream err_msg; + err_msg << "The permission returned by Lua function " << iiif_preflight_funcname << " is not valid: " << + preflight_info["type"]; + throw SipiError(__file__, __LINE__, err_msg.str()); + } + + if (preflight_info["type"] == "deny") { + preflight_info["infile"] = ""; + } else { + if (rvals.size() < 2) { std::ostringstream err_msg; - err_msg << "The permission value returned by Lua function " << iiif_preflight_funcname << " was not valid"; + err_msg << "Lua function " << iiif_preflight_funcname + << " returned other permission than 'deny', but it did not return a file path"; throw SipiError(__file__, __LINE__, err_msg.str()); } - // - // check if permission type is valid - // - if ((preflight_info["type"] != "allow") && - (preflight_info["type"] != "login") && - (preflight_info["type"] != "clickthrough") && - (preflight_info["type"] != "kiosk") && - (preflight_info["type"] != "external") && - (preflight_info["type"] != "restrict") && - (preflight_info["type"] != "deny")) - { + auto infile_return_val = rvals.at(1); + + // The file path must be a string. + if (infile_return_val->type == LuaValstruct::STRING_TYPE) { + preflight_info["infile"] = infile_return_val->value.s; + } else { std::ostringstream err_msg; - err_msg << "The permission returned by Lua function " << iiif_preflight_funcname << " is not valid: " << preflight_info["type"]; + err_msg << "The file path returned by Lua function " << iiif_preflight_funcname << " was not a string"; throw SipiError(__file__, __LINE__, err_msg.str()); } + } - if (preflight_info["type"] == "deny") { - preflight_info["infile"] = ""; - } - else { - if (rvals.size() < 2) { - std::ostringstream err_msg; - err_msg << "Lua function " << iiif_preflight_funcname - << " returned other permission than 'deny', but it did not return a file path"; - throw SipiError(__file__, __LINE__, err_msg.str()); - } + // Return the permission code and file path, if any, as a std::pair. + return preflight_info; +} - auto infile_return_val = rvals.at(1); +//========================================================================= + +static std::unordered_map +call_file_preflight(Connection& conn_obj, shttps::LuaServer& luaserver, const std::string& filepath) { + // The permission and optional file path that the pre_fight function returns. + std::unordered_map preflight_info; + // std::string permission; + // std::string infile; + + // The paramters to be passed to the pre-flight function. + std::vector> lvals; + + // The first parameter is the filepath. + std::shared_ptr file_path_param = std::make_shared(); + file_path_param->type = LuaValstruct::STRING_TYPE; + file_path_param->value.s = filepath; + lvals.push_back(file_path_param); + + // The second parameter is the HTTP cookie. + std::shared_ptr cookie_param = std::make_shared(); + std::string cookie = conn_obj.header("cookie"); + cookie_param->type = LuaValstruct::STRING_TYPE; + cookie_param->value.s = cookie; + lvals.push_back(cookie_param); + + // Call the pre-flight function. + std::vector> rvals = luaserver.executeLuafunction(file_preflight_funcname, lvals); + + // If it returned nothing, that's an error. + if (rvals.empty()) { + std::ostringstream err_msg; + err_msg << "Lua function " << file_preflight_funcname << " must return at least one value"; + throw SipiError(__file__, __LINE__, err_msg.str()); + } - // The file path must be a string. - if (infile_return_val->type == LuaValstruct::STRING_TYPE) { - preflight_info["infile"] = infile_return_val->value.s; - } - else { - std::ostringstream err_msg; - err_msg << "The file path returned by Lua function " << iiif_preflight_funcname << " was not a string"; - throw SipiError(__file__, __LINE__, err_msg.str()); - } - } + // The first return value is the permission code. + auto permission_return_val = rvals.at(0); - // Return the permission code and file path, if any, as a std::pair. - return preflight_info; - } - //========================================================================= - - static std::unordered_map - call_file_preflight(Connection &conn_obj, shttps::LuaServer &luaserver, const std::string &filepath) { - // The permission and optional file path that the pre_fight function returns. - std::unordered_map preflight_info; - // std::string permission; - // std::string infile; - - // The paramters to be passed to the pre-flight function. - std::vector> lvals; - - // The first parameter is the filepath. - std::shared_ptr file_path_param = std::make_shared(); - file_path_param->type = LuaValstruct::STRING_TYPE; - file_path_param->value.s = filepath; - lvals.push_back(file_path_param); - - // The second parameter is the HTTP cookie. - std::shared_ptr cookie_param = std::make_shared(); - std::string cookie = conn_obj.header("cookie"); - cookie_param->type = LuaValstruct::STRING_TYPE; - cookie_param->value.s = cookie; - lvals.push_back(cookie_param); - - // Call the pre-flight function. - std::vector> rvals = luaserver.executeLuafunction(file_preflight_funcname, lvals); - - // If it returned nothing, that's an error. - if (rvals.empty()) { + // The permission code must be a string or a table. + if (permission_return_val->type == LuaValstruct::STRING_TYPE) { + preflight_info["type"] = permission_return_val->value.s; + } else if (permission_return_val->type == LuaValstruct::TABLE_TYPE) { + std::shared_ptr tmpv; + try { + tmpv = permission_return_val->value.table.at("type"); + } catch (const std::out_of_range& err) { std::ostringstream err_msg; - err_msg << "Lua function " << file_preflight_funcname << " must return at least one value"; + err_msg << "The permission value returned by Lua function " << file_preflight_funcname << + " has no type field!"; throw SipiError(__file__, __LINE__, err_msg.str()); } - - // The first return value is the permission code. - auto permission_return_val = rvals.at(0); - - // The permission code must be a string or a table. - if (permission_return_val->type == LuaValstruct::STRING_TYPE) { - preflight_info["type"] = permission_return_val->value.s; + if (tmpv->type != LuaValstruct::STRING_TYPE) { + throw SipiError(__file__, __LINE__, "String value expected!"); } - else if (permission_return_val->type == LuaValstruct::TABLE_TYPE) { - std::shared_ptr tmpv; - try { - tmpv = permission_return_val->value.table.at("type"); - } - catch (const std::out_of_range &err) { - std::ostringstream err_msg; - err_msg << "The permission value returned by Lua function " << file_preflight_funcname << " has no type field!"; - throw SipiError(__file__, __LINE__, err_msg.str()); - } - if (tmpv->type != LuaValstruct::STRING_TYPE) { + preflight_info["type"] = tmpv->value.s; + for (const auto& keyval : permission_return_val->value.table) { + if (keyval.first == "type") + continue; + if (keyval.second->type != LuaValstruct::STRING_TYPE) { throw SipiError(__file__, __LINE__, "String value expected!"); } - preflight_info["type"] = tmpv->value.s; - for (const auto &keyval : permission_return_val->value.table) { - if (keyval.first == "type") - continue; - if (keyval.second->type != LuaValstruct::STRING_TYPE) { - throw SipiError(__file__, __LINE__, "String value expected!"); - } - preflight_info[keyval.first] = keyval.second->value.s; - } + preflight_info[keyval.first] = keyval.second->value.s; } - else { + } else { + std::ostringstream err_msg; + err_msg << "The permission value returned by Lua function " << file_preflight_funcname << " was not valid"; + throw SipiError(__file__, __LINE__, err_msg.str()); + } + + // + // check if permission type is valid + // + if ((preflight_info["type"] != "allow") && + (preflight_info["type"] != "login") && + (preflight_info["type"] != "restrict") && + (preflight_info["type"] != "deny")) { + std::ostringstream err_msg; + err_msg << "The permission returned by Lua function " << file_preflight_funcname << " is not valid: " << + preflight_info["type"]; + throw SipiError(__file__, __LINE__, err_msg.str()); + } + + if (preflight_info["type"] == "deny") { + preflight_info["infile"] = ""; + } else { + if (rvals.size() < 2) { std::ostringstream err_msg; - err_msg << "The permission value returned by Lua function " << file_preflight_funcname << " was not valid"; + err_msg << "Lua function " << file_preflight_funcname + << " returned other permission than 'deny', but it did not return a file path"; throw SipiError(__file__, __LINE__, err_msg.str()); } - // - // check if permission type is valid - // - if ((preflight_info["type"] != "allow") && - (preflight_info["type"] != "login") && - (preflight_info["type"] != "restrict") && - (preflight_info["type"] != "deny")) - { + auto infile_return_val = rvals.at(1); + + // The file path must be a string. + if (infile_return_val->type == LuaValstruct::STRING_TYPE) { + preflight_info["infile"] = infile_return_val->value.s; + } else { std::ostringstream err_msg; - err_msg << "The permission returned by Lua function " << file_preflight_funcname << " is not valid: " << preflight_info["type"]; + err_msg << "The file path returned by Lua function " << file_preflight_funcname << " was not a string"; throw SipiError(__file__, __LINE__, err_msg.str()); } + } - if (preflight_info["type"] == "deny") { - preflight_info["infile"] = ""; - } - else { - if (rvals.size() < 2) { - std::ostringstream err_msg; - err_msg << "Lua function " << file_preflight_funcname - << " returned other permission than 'deny', but it did not return a file path"; - throw SipiError(__file__, __LINE__, err_msg.str()); - } - - auto infile_return_val = rvals.at(1); + // Return the permission code and file path, if any, as a std::pair. + return preflight_info; +} - // The file path must be a string. - if (infile_return_val->type == LuaValstruct::STRING_TYPE) { - preflight_info["infile"] = infile_return_val->value.s; - } - else { - std::ostringstream err_msg; - err_msg << "The file path returned by Lua function " << file_preflight_funcname << " was not a string"; - throw SipiError(__file__, __LINE__, err_msg.str()); - } +//========================================================================= + + +// +// ToDo: Prepare for IIIF Authentication API !!!! +// +/** + * This internal method checks if the image file is readable and + * uses the pre_flight script to check permissions. + * + * @param conn_obj Connection object + * @param serv The Server instance + * @param luaserver The Lua server instance + * @param params the IIIF parameters + * @param prefix_as_path + * @return Pair of strings with permissions and filepath + */ +static std::unordered_map check_file_access( + Connection& conn_obj, + SipiHttpServer* serv, + shttps::LuaServer& luaserver, + std::vector& params, + bool prefix_as_path) { + std::string infile; + + SipiIdentifier sid = SipiIdentifier(params[iiif_identifier]); + std::unordered_map pre_flight_info; + if (luaserver.luaFunctionExists(iiif_preflight_funcname)) { + pre_flight_info = call_iiif_preflight(conn_obj, + luaserver, + urldecode(params[iiif_prefix]), + sid.getIdentifier()); // may throw SipiError + infile = pre_flight_info["infile"]; + } else { + if (prefix_as_path) { + infile = serv->imgroot() + "/" + urldecode(params[iiif_prefix]) + "/" + sid.getIdentifier(); + } else { + infile = serv->imgroot() + "/" + urldecode(sid.getIdentifier()); } - - // Return the permission code and file path, if any, as a std::pair. - return preflight_info; + pre_flight_info["type"] = "allow"; } - //========================================================================= - - // - // ToDo: Prepare for IIIF Authentication API !!!! + // test if we have access to the file // - /** - * This internal method checks if the image file is readable and - * uses the pre_flight script to check permissions. - * - * @param conn_obj Connection object - * @param serv The Server instance - * @param luaserver The Lua server instance - * @param params the IIIF parameters - * @param prefix_as_path - * @return Pair of strings with permissions and filepath - */ - static std::unordered_map check_file_access( - Connection &conn_obj, - SipiHttpServer *serv, - shttps::LuaServer &luaserver, - std::vector ¶ms, - bool prefix_as_path) - { - std::string infile; - - SipiIdentifier sid = SipiIdentifier(params[iiif_identifier]); - std::unordered_map pre_flight_info; - if (luaserver.luaFunctionExists(iiif_preflight_funcname)) { - pre_flight_info = call_iiif_preflight(conn_obj, luaserver, urldecode(params[iiif_prefix]), - sid.getIdentifier()); // may throw SipiError - infile = pre_flight_info["infile"]; - } - else { - if (prefix_as_path) { - infile = serv->imgroot() + "/" + urldecode(params[iiif_prefix]) + "/" + sid.getIdentifier(); - } - else { - infile = serv->imgroot() + "/" + urldecode(sid.getIdentifier()); - } - pre_flight_info["type"] = "allow"; - } - // - // test if we have access to the file - // - if (access(infile.c_str(), R_OK) != 0) { // test, if file exists - throw SipiError(__file__, __LINE__, "Cannot read image file: " + infile); - } - pre_flight_info["infile"] = infile; - return pre_flight_info; + if (access(infile.c_str(), R_OK) != 0) { + // test, if file exists + throw SipiError(__file__, __LINE__, "Cannot read image file: " + infile); } - //========================================================================= - - std::pair - SipiHttpServer::get_canonical_url( - size_t tmp_w, - size_t tmp_h, - const std::string &host, - const std::string &prefix, - const std::string &identifier, - std::shared_ptr region, - std::shared_ptr size, - SipiRotation &rotation, - SipiQualityFormat &quality_format, int pagenum) - { - static constexpr int canonical_len = 127; + pre_flight_info["infile"] = infile; + return pre_flight_info; +} - char canonical_region[canonical_len + 1]; - char canonical_size[canonical_len + 1]; +//========================================================================= + +std::pair +SipiHttpServer::get_canonical_url( + size_t tmp_w, + size_t tmp_h, + const std::string& host, + const std::string& prefix, + const std::string& identifier, + std::shared_ptr region, + std::shared_ptr size, + SipiRotation& rotation, + SipiQualityFormat& quality_format, + int pagenum) { + static constexpr int canonical_len = 127; + + char canonical_region[canonical_len + 1]; + char canonical_size[canonical_len + 1]; + + int tmp_r_x = 0, tmp_r_y = 0, tmp_red = 0; + size_t tmp_r_w = 0, tmp_r_h = 0; + bool tmp_ro = false; + + if (region->getType() != SipiRegion::FULL) { + region->crop_coords(tmp_w, tmp_h, tmp_r_x, tmp_r_y, tmp_r_w, tmp_r_h); + } - int tmp_r_x = 0, tmp_r_y = 0, tmp_red = 0; - size_t tmp_r_w = 0, tmp_r_h = 0; - bool tmp_ro = false; + region->canonical(canonical_region, canonical_len); - if (region->getType() != SipiRegion::FULL) { - region->crop_coords(tmp_w, tmp_h, tmp_r_x, tmp_r_y, tmp_r_w, tmp_r_h); + if (size->getType() != SipiSize::FULL) { + try { + size->get_size(tmp_w, tmp_h, tmp_r_w, tmp_r_h, tmp_red, tmp_ro); + } catch (Sipi::SipiSizeError& err) { + throw SipiError(__file__, __LINE__, "SipiSize error!"); } + } - region->canonical(canonical_region, canonical_len); + size->canonical(canonical_size, canonical_len); + float angle; + const bool mirror = rotation.get_rotation(angle); + char canonical_rotation[canonical_len + 1]; - if (size->getType() != SipiSize::FULL) { - try { - size->get_size(tmp_w, tmp_h, tmp_r_w, tmp_r_h, tmp_red, tmp_ro); + if (mirror || (angle != 0.0)) { + if ((angle - floorf(angle)) < 1.0e-6) { + // it's an integer + if (mirror) { + (void)snprintf(canonical_rotation, canonical_len, "!%ld", std::lround(angle)); + } else { + (void)snprintf(canonical_rotation, canonical_len, "%ld", std::lround(angle)); } - catch (Sipi::SipiSizeError &err) { - throw SipiError(__file__, __LINE__, "SipiSize error!"); + } else { + if (mirror) { + (void)snprintf(canonical_rotation, canonical_len, "!%1.1f", angle); + } else { + (void)snprintf(canonical_rotation, canonical_len, "%1.1f", angle); } } + } else { + (void)snprintf(canonical_rotation, canonical_len, "0"); + } - size->canonical(canonical_size, canonical_len); - float angle; - const bool mirror = rotation.get_rotation(angle); - char canonical_rotation[canonical_len + 1]; - - if (mirror || (angle != 0.0)) { - if ((angle - floorf(angle)) < 1.0e-6) { // it's an integer - if (mirror) { - (void)snprintf(canonical_rotation, canonical_len, "!%ld", std::lround(angle)); - } - else { - (void)snprintf(canonical_rotation, canonical_len, "%ld", std::lround(angle)); - } - } - else { - if (mirror) { - (void)snprintf(canonical_rotation, canonical_len, "!%1.1f", angle); - } - else { - (void)snprintf(canonical_rotation, canonical_len, "%1.1f", angle); - } - } - } - else { - (void)snprintf(canonical_rotation, canonical_len, "0"); + constexpr unsigned canonical_header_len = 511; + char canonical_header[canonical_header_len + 1]; + char ext[5]; + + switch (quality_format.format()) { + case SipiQualityFormat::JPG: { + ext[0] = 'j'; + ext[1] = 'p'; + ext[2] = 'g'; + ext[3] = '\0'; + break; // jpg + } + case SipiQualityFormat::JP2: { + ext[0] = 'j'; + ext[1] = 'p'; + ext[2] = '2'; + ext[3] = '\0'; + break; // jp2 + } + case SipiQualityFormat::TIF: { + ext[0] = 't'; + ext[1] = 'i'; + ext[2] = 'f'; + ext[3] = '\0'; + break; // tif + } + case SipiQualityFormat::PNG: { + ext[0] = 'p'; + ext[1] = 'n'; + ext[2] = 'g'; + ext[3] = '\0'; + break; // png + } + default: { + throw SipiError(__file__, + __LINE__, + "Unsupported file format requested! Supported are .jpg, .jp2, .tif, .png"); } + } - constexpr unsigned canonical_header_len = 511; - char canonical_header[canonical_header_len + 1]; - char ext[5]; - - switch (quality_format.format()) { - case SipiQualityFormat::JPG: { - ext[0] = 'j'; - ext[1] = 'p'; - ext[2] = 'g'; - ext[3] = '\0'; - break; // jpg - } - case SipiQualityFormat::JP2: { - ext[0] = 'j'; - ext[1] = 'p'; - ext[2] = '2'; - ext[3] = '\0'; - break; // jp2 + std::string format; + if (quality_format.quality() != SipiQualityFormat::DEFAULT) { + switch (quality_format.quality()) { + case SipiQualityFormat::COLOR: { + format = "/color."; + break; } - case SipiQualityFormat::TIF: { - ext[0] = 't'; - ext[1] = 'i'; - ext[2] = 'f'; - ext[3] = '\0'; - break; // tif + case SipiQualityFormat::GRAY: { + format = "/gray."; + break; } - case SipiQualityFormat::PNG: { - ext[0] = 'p'; - ext[1] = 'n'; - ext[2] = 'g'; - ext[3] = '\0'; - break; // png + case SipiQualityFormat::BITONAL: { + format = "/bitonal."; + break; } default: { - throw SipiError(__file__, __LINE__, - "Unsupported file format requested! Supported are .jpg, .jp2, .tif, .png"); + format = "/default."; } } + } else { + format = "/default."; + } - std::string format; - if (quality_format.quality() != SipiQualityFormat::DEFAULT) { - switch (quality_format.quality()) { - case SipiQualityFormat::COLOR: { - format = "/color."; - break; - } - case SipiQualityFormat::GRAY: { - format = "/gray."; - break; - } - case SipiQualityFormat::BITONAL: { - format = "/bitonal."; - break; - } - default: { - format = "/default."; - } - } - } - else { - format = "/default."; - } + std::string fullid = identifier; + if (pagenum > 0) + fullid += "@" + std::to_string(pagenum); + (void)snprintf(canonical_header, + canonical_header_len, + ";rel=\"canonical\"", + host.c_str(), + prefix.c_str(), + fullid.c_str(), + canonical_region, + canonical_size, + canonical_rotation, + ext); + std::string canonical = host + "/" + prefix + "/" + fullid + "/" + std::string(canonical_region) + "/" + + std::string(canonical_size) + "/" + std::string(canonical_rotation) + format + + std::string(ext); + + return make_pair(std::string(canonical_header), canonical); +} - std::string fullid = identifier; - if (pagenum > 0) - fullid += "@" + std::to_string(pagenum); - (void)snprintf(canonical_header, canonical_header_len, - ";rel=\"canonical\"", host.c_str(), prefix.c_str(), - fullid.c_str(), canonical_region, canonical_size, canonical_rotation, ext); - std::string canonical = host + "/" + prefix + "/" + fullid + "/" + std::string(canonical_region) + "/" + - std::string(canonical_size) + "/" + std::string(canonical_rotation) + format + - std::string(ext); +//========================================================================= - return make_pair(std::string(canonical_header), canonical); +static void serve_redirect(Connection& conn_obj, const std::vector& params) { + conn_obj.setBuffer(); + conn_obj.status(Connection::SEE_OTHER); + const std::string host = conn_obj.host(); + std::string redirect; + + if (conn_obj.secure()) { + redirect += "https://" + host + "/"; + } else { + redirect += "http://" + host + "/"; } - //========================================================================= - - static void serve_redirect(Connection &conn_obj, std::vector params) { - conn_obj.setBuffer(); - conn_obj.status(Connection::SEE_OTHER); - const std::string host = conn_obj.host(); - std::string redirect; - if (conn_obj.secure()) - { - redirect = - std::string("https://") + host + "/" + params[iiif_prefix] + "/" + params[iiif_identifier] + - "/info.json"; - } - else - { - redirect = - std::string("http://") + host + "/" + params[iiif_prefix] + "/" + params[iiif_identifier] + - "/info.json"; - } - - conn_obj.header("Location", redirect); - conn_obj.header("Content-Type", "text/plain"); - conn_obj << "Redirect to " << redirect; - syslog(LOG_INFO, "GET: redirect to %s", redirect.c_str()); - conn_obj.flush(); + + if (!params[iiif_prefix].empty()) { + redirect += params[iiif_prefix] + "/" + params[iiif_identifier] + "/info.json"; + } else { + redirect += params[iiif_identifier] + "/info.json"; } - //========================================================================= + + conn_obj.header("Location", redirect); + conn_obj.header("Content-Type", "text/plain"); + conn_obj << "Redirect to " << redirect; + syslog(LOG_INFO, "GET: redirect to %s", redirect.c_str()); + conn_obj.flush(); +} + +//========================================================================= + +// +// ToDo: Prepare for IIIF Authentication API !!!! +// +/** + * This internal method serves the IIIF info.json file. + * + * @param conn_obj Connection object + * @param serv The Server instance + * @param luaserver The Lua server instance + * @param params the IIIF parameters + * @param prefix_as_path + */ +static void serve_info_json_file( + Connection& conn_obj, + SipiHttpServer* serv, + shttps::LuaServer& luaserver, + std::vector& params, + bool prefix_as_path) { + Connection::StatusCodes http_status = Connection::StatusCodes::OK; // - // ToDo: Prepare for IIIF Authentication API !!!! + // here we start the lua script which checks for permissions // - /** - * This internal method serves the IIIF info.json file. - * - * @param conn_obj Connection object - * @param serv The Server instance - * @param luaserver The Lua server instance - * @param params the IIIF parameters - * @param prefix_as_path - */ - static void serve_info_json_file( - Connection &conn_obj, - SipiHttpServer *serv, - shttps::LuaServer &luaserver, - std::vector ¶ms, - bool prefix_as_path) - { - Connection::StatusCodes http_status = Connection::StatusCodes::OK; + std::unordered_map access; + try { + access = check_file_access(conn_obj, serv, luaserver, params, prefix_as_path); + } catch (SipiError& err) { + send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err); + return; + } - // - // here we start the lua script which checks for permissions - // - std::unordered_map access; - try { - access = check_file_access(conn_obj, serv, luaserver, params, prefix_as_path); - } - catch (SipiError &err) { - send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err); - return; - } + std::string actual_mimetype = shttps::Parsing::getBestFileMimetype(access["infile"]); - std::string actual_mimetype = shttps::Parsing::getBestFileMimetype(access["infile"]); + bool is_image_file = ((actual_mimetype == "image/tiff") || + (actual_mimetype == "image/jpeg") || + (actual_mimetype == "image/png") || + (actual_mimetype == "image/jpx") || + (actual_mimetype == "image/jp2")); - bool is_image_file = ((actual_mimetype == "image/tiff") || - (actual_mimetype == "image/jpeg") || - (actual_mimetype == "image/png") || - (actual_mimetype == "image/jpx") || - (actual_mimetype == "image/jp2")); + json_t* root = json_object(); + SipiIdentifier sid = SipiIdentifier(params[iiif_identifier]); + int pagenum = sid.getPage(); - json_t *root = json_object(); - SipiIdentifier sid = SipiIdentifier(params[iiif_identifier]); - int pagenum = sid.getPage(); + if (is_image_file) { + json_object_set_new(root, "@context", json_string("http://iiif.io/api/image/3/context.json")); + } else { + json_object_set_new(root, "@context", json_string("http://sipi.io/api/file/3/context.json")); + } - if (is_image_file) { - json_object_set_new(root, "@context", json_string("http://iiif.io/api/image/3/context.json")); + std::string host = conn_obj.header("host"); + std::string id; + if (params[iiif_prefix] == "") { + if (conn_obj.secure()) { + id = std::string("https://") + host + "/" + params[iiif_identifier]; + } else { + id = std::string("http://") + host + "/" + params[iiif_identifier]; } - else { - json_object_set_new(root, "@context", json_string("http://sipi.io/api/file/3/context.json")); + } else { + if (conn_obj.secure()) { + id = std::string("https://") + host + "/" + params[iiif_prefix] + "/" + params[iiif_identifier]; + } else { + id = std::string("http://") + host + "/" + params[iiif_prefix] + "/" + params[iiif_identifier]; } + } + json_object_set_new(root, "id", json_string(id.c_str())); - std::string host = conn_obj.header("host"); - std::string id; - if (params[iiif_prefix] == "") { - if (conn_obj.secure()) { - id = std::string("https://") + host + "/" + params[iiif_identifier]; - } - else { - id = std::string("http://") + host + "/" + params[iiif_identifier]; - } - } - else { - if (conn_obj.secure()) { - id = std::string("https://") + host + "/" + params[iiif_prefix] + "/" + params[iiif_identifier]; - } - else { - id = std::string("http://") + host + "/" + params[iiif_prefix] + "/" + params[iiif_identifier]; - } - } - json_object_set_new(root, "id", json_string(id.c_str())); + if (is_image_file) { + json_object_set_new(root, "type", json_string("ImageService3")); + json_object_set_new(root, "protocol", json_string("http://iiif.io/api/image")); + json_object_set_new(root, "profile", json_string("level2")); + } else { + json_object_set_new(root, "internalMimeType", json_string(actual_mimetype.c_str())); - if (is_image_file) { - json_object_set_new(root, "type", json_string("ImageService3")); - json_object_set_new(root, "protocol", json_string("http://iiif.io/api/image")); - json_object_set_new(root, "profile", json_string("level2")); + struct stat fstatbuf; + if (stat(access["infile"].c_str(), &fstatbuf) != 0) { + throw Error(__file__, __LINE__, "Cannot fstat file!"); } - else { - json_object_set_new(root, "internalMimeType", json_string(actual_mimetype.c_str())); + json_object_set_new(root, "fileSize", json_integer(fstatbuf.st_size)); + } - struct stat fstatbuf; - if (stat(access["infile"].c_str(), &fstatbuf) != 0) { - throw Error(__file__, __LINE__, "Cannot fstat file!"); + // + // IIIF Authentication API stuff + // + if ((access["type"] == "login") || (access["type"] == "clickthrough") || (access["type"] == "kiosk") || (access[ + "type"] == "external")) { + json_t* service = json_object(); + try { + std::string cookieUrl = access.at("cookieUrl"); + json_object_set_new(service, "@context", json_string("http://iiif.io/api/auth/1/context.json")); + json_object_set_new(service, "@id", json_string(cookieUrl.c_str())); + if (access["type"] == "login") { + json_object_set_new(service, "profile", json_string("http://iiif.io/api/auth/1/login")); + } else if (access["type"] == "clickthrough") { + json_object_set_new(service, "profile", json_string("http://iiif.io/api/auth/1/clickthrough")); + } else if (access["type"] == "kiosk") { + json_object_set_new(service, "profile", json_string("http://iiif.io/api/auth/1/kiosk")); + } else if (access["type"] == "external") { + json_object_set_new(service, "profile", json_string("http://iiif.io/api/auth/1/external")); + } + for (auto& item : access) { + if (item.first == "cookieUrl") + continue; + if (item.first == "tokenUrl") + continue; + if (item.first == "logoutUrl") + continue; + if (item.first == "infile") + continue; + if (item.first == "type") + continue; + json_object_set_new(service, item.first.c_str(), json_string(item.second.c_str())); } - json_object_set_new(root, "fileSize", json_integer(fstatbuf.st_size)); + json_t* subservices = json_array(); + try { + std::string tokenUrl = access.at("tokenUrl"); + json_t* token_service = json_object(); + json_object_set_new(token_service, "@id", json_string(tokenUrl.c_str())); + json_object_set_new(token_service, "profile", json_string("http://iiif.io/api/auth/1/token")); + json_array_append_new(subservices, token_service); + } catch (const std::out_of_range& err) { + send_error(conn_obj, + Connection::INTERNAL_SERVER_ERROR, + "Pre_flight_script has login type but no tokenUrl!"); + return; + } + try { + std::string logoutUrl = access.at("logoutUrl"); + json_t* logout_service = json_object(); + json_object_set_new(logout_service, "@id", json_string(logoutUrl.c_str())); + json_object_set_new(logout_service, "profile", json_string("http://iiif.io/api/auth/1/logout")); + json_array_append_new(subservices, logout_service); + } catch (const std::out_of_range& err) {} + json_object_set_new(service, "service", subservices); + } catch (const std::out_of_range& err) { + send_error(conn_obj, + Connection::INTERNAL_SERVER_ERROR, + "Pre_flight_script has login type but no cookieUrl!"); + return; } + json_t* services = json_array(); + json_array_append_new(services, service); + json_object_set_new(root, "service", services); + http_status = Connection::StatusCodes::UNAUTHORIZED; + } + + if (is_image_file) { + size_t width, height; + size_t t_width, t_height; + int clevels; + int numpages = 0; // - // IIIF Authentication API stuff + // get cache info // - if ((access["type"] == "login") || (access["type"] == "clickthrough") || (access["type"] == "kiosk") || (access["type"] == "external")) { - json_t *service = json_object(); + std::shared_ptr cache = serv->cache(); + if ((cache == nullptr) || !cache-> + getSize(access["infile"], width, height, t_width, t_height, clevels, pagenum)) { + Sipi::SipiImage tmpimg; + Sipi::SipiImgInfo info; try { - std::string cookieUrl = access.at("cookieUrl"); - json_object_set_new(service, "@context", json_string("http://iiif.io/api/auth/1/context.json")); - json_object_set_new(service, "@id", json_string(cookieUrl.c_str())); - if (access["type"] == "login") { - json_object_set_new(service, "profile", json_string("http://iiif.io/api/auth/1/login")); - } - else if (access["type"] == "clickthrough") { - json_object_set_new(service, "profile", json_string("http://iiif.io/api/auth/1/clickthrough")); - } - else if (access["type"] == "kiosk") { - json_object_set_new(service, "profile", json_string("http://iiif.io/api/auth/1/kiosk")); - } - else if (access["type"] == "external") { - json_object_set_new(service, "profile", json_string("http://iiif.io/api/auth/1/external")); - } - for (auto &item : access) { - if (item.first == "cookieUrl") - continue; - if (item.first == "tokenUrl") - continue; - if (item.first == "logoutUrl") - continue; - if (item.first == "infile") - continue; - if (item.first == "type") - continue; - json_object_set_new(service, item.first.c_str(), json_string(item.second.c_str())); - } - json_t *subservices = json_array(); - try { - std::string tokenUrl = access.at("tokenUrl"); - json_t *token_service = json_object(); - json_object_set_new(token_service, "@id", json_string(tokenUrl.c_str())); - json_object_set_new(token_service, "profile", json_string("http://iiif.io/api/auth/1/token")); - json_array_append_new(subservices, token_service); - } - catch (const std::out_of_range &err) { - send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, "Pre_flight_script has login type but no tokenUrl!"); - return; - } - try { - std::string logoutUrl = access.at("logoutUrl"); - json_t *logout_service = json_object(); - json_object_set_new(logout_service, "@id", json_string(logoutUrl.c_str())); - json_object_set_new(logout_service, "profile", json_string("http://iiif.io/api/auth/1/logout")); - json_array_append_new(subservices, logout_service); - } - catch (const std::out_of_range &err) { - } - json_object_set_new(service, "service", subservices); + info = tmpimg.getDim(access["infile"]); + } catch (SipiImageError& err) { + send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err.to_string()); + return; } - catch (const std::out_of_range &err) { - send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, "Pre_flight_script has login type but no cookieUrl!"); + if (info.success == SipiImgInfo::FAILURE) { + send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, "Error getting image dimensions!"); return; } - json_t *services = json_array(); - json_array_append_new(services, service); - json_object_set_new(root, "service", services); - http_status = Connection::StatusCodes::UNAUTHORIZED; + width = info.width; + height = info.height; + t_width = info.tile_width; + t_height = info.tile_height; + clevels = info.clevels; + numpages = info.numpages; } - if (is_image_file) { - size_t width, height; - size_t t_width, t_height; - int clevels; - int numpages = 0; - - // - // get cache info - // - std::shared_ptr cache = serv->cache(); - if ((cache == nullptr) || !cache->getSize(access["infile"], width, height, t_width, t_height, clevels, pagenum)) { - Sipi::SipiImage tmpimg; - Sipi::SipiImgInfo info; - try { - info = tmpimg.getDim(access["infile"]); - } - catch (SipiImageError &err) { - send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err.to_string()); - return; - } - if (info.success == SipiImgInfo::FAILURE) { - send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, "Error getting image dimensions!"); - return; - } - width = info.width; - height = info.height; - t_width = info.tile_width; - t_height = info.tile_height; - clevels = info.clevels; - numpages = info.numpages; - } - - // - // basic info - // - json_object_set_new(root, "width", json_integer(width)); - json_object_set_new(root, "height", json_integer(height)); - if (numpages > 0) { - json_object_set_new(root, "numpages", json_integer(numpages)); - } - - json_t *sizes = json_array(); - const int cnt = clevels > 0 ? clevels : 5; + // + // basic info + // + json_object_set_new(root, "width", json_integer(width)); + json_object_set_new(root, "height", json_integer(height)); + if (numpages > 0) { + json_object_set_new(root, "numpages", json_integer(numpages)); + } + + json_t* sizes = json_array(); + const int cnt = clevels > 0 ? clevels : 5; + for (int i = 1; i < cnt; i++) { + SipiSize size(i); + size_t w, h; + int r; + bool ro; + size.get_size(width, height, w, h, r, ro); + if ((w < 128) && (h < 128)) + break; + json_t* sobj = json_object(); + json_object_set_new(sobj, "width", json_integer(w)); + json_object_set_new(sobj, "height", json_integer(h)); + json_array_append_new(sizes, sobj); + } + json_object_set_new(root, "sizes", sizes); + + if (t_width > 0 && t_height > 0) { + json_t* tiles = json_array(); + json_t* tileobj = json_object(); + json_object_set_new(tileobj, "width", json_integer(t_width)); + json_object_set_new(tileobj, "height", json_integer(t_height)); + json_t* scaleFactors = json_array(); for (int i = 1; i < cnt; i++) { - SipiSize size(i); - size_t w, h; - int r; - bool ro; - size.get_size(width, height, w, h, r, ro); - if ((w < 128) && (h < 128)) - break; - json_t *sobj = json_object(); - json_object_set_new(sobj, "width", json_integer(w)); - json_object_set_new(sobj, "height", json_integer(h)); - json_array_append_new(sizes, sobj); - } - json_object_set_new(root, "sizes", sizes); - - if (t_width > 0 && t_height > 0) { - json_t *tiles = json_array(); - json_t *tileobj = json_object(); - json_object_set_new(tileobj, "width", json_integer(t_width)); - json_object_set_new(tileobj, "height", json_integer(t_height)); - json_t *scaleFactors = json_array(); - for (int i = 1; i < cnt; i++) - { - json_array_append_new(scaleFactors, json_integer(i)); - } - json_object_set_new(tileobj, "scaleFactors", scaleFactors); - json_array_append_new(tiles, tileobj); - json_object_set_new(root, "tiles", tiles); - } - - const char *extra_formats_str[] = {"tif", "jp2"}; - json_t *extra_formats = json_array(); - for (unsigned int i = 0; i < sizeof(extra_formats_str) / sizeof(char *); i++) { - json_array_append_new(extra_formats, json_string(extra_formats_str[i])); - } - json_object_set_new(root, "extraFormats", extra_formats); - - json_t *prefformats = json_array(); // ToDo: should be settable from LUA preflight (get info from DB) - json_array_append_new(prefformats, json_string("jpg")); - json_array_append_new(prefformats, json_string("tif")); - json_array_append_new(prefformats, json_string("jp2")); - json_array_append_new(prefformats, json_string("png")); - json_object_set_new(root, "preferredFormats", prefformats); - - // - // extra features - // - const char *extraFeaturesList[] = { - "baseUriRedirect", - "canonicalLinkHeader", - "cors", - "jsonldMediaType", - "mirroring", - "profileLinkHeader", - "regionByPct", - "regionByPx", - "regionSquare", - "rotationArbitrary", - "rotationBy90s", - "sizeByConfinedWh", - "sizeByH", - "sizeByPct", - "sizeByW", - "sizeByWh", - "sizeUpscaling"}; - json_t *extraFeatures = json_array(); - for (unsigned int i = 0; i < sizeof(extraFeaturesList) / sizeof(char *); i++) { - json_array_append_new(extraFeatures, json_string(extraFeaturesList[i])); + json_array_append_new(scaleFactors, json_integer(i)); } - - json_object_set_new(root, "extraFeatures", extraFeatures); + json_object_set_new(tileobj, "scaleFactors", scaleFactors); + json_array_append_new(tiles, tileobj); + json_object_set_new(root, "tiles", tiles); } - conn_obj.status(http_status); - conn_obj.setBuffer(); // we want buffered output, since we send JSON text... - conn_obj.header("Access-Control-Allow-Origin", "*"); - const std::string contenttype = conn_obj.header("accept"); - if (is_image_file) { - if (!contenttype.empty() && (contenttype == "application/ld+json")) { - conn_obj.header("Content-Type", "application/ld+json;profile=\"http://iiif.io/api/image/3/context.json\""); - } - else { - conn_obj.header("Content-Type", "application/json"); - conn_obj.header("Link", - "; rel=\"http://www.w3.org/ns/json-ld#context\"; type=\"application/ld+json\""); - } - } - else { - if (!contenttype.empty() && (contenttype == "application/ld+json")) { - conn_obj.header("Content-Type", "application/ld+json;profile=\"http://sipi.io/api/file/3/context.json\""); - } - else { - conn_obj.header("Content-Type", "application/json"); - conn_obj.header("Link", - "; rel=\"http://www.w3.org/ns/json-ld#context\"; type=\"application/ld+json\""); - } + const char* extra_formats_str[] = {"tif", "jp2"}; + json_t* extra_formats = json_array(); + for (unsigned int i = 0; i < sizeof(extra_formats_str) / sizeof(char*); i++) { + json_array_append_new(extra_formats, json_string(extra_formats_str[i])); } + json_object_set_new(root, "extraFormats", extra_formats); - char *json_str = json_dumps(root, JSON_INDENT(3)); - conn_obj.sendAndFlush(json_str, strlen(json_str)); - free(json_str); - json_decref(root); - } - //========================================================================= - - /** - * \brief This internal method serves the knora.json file, e.g., https://server/prefix/identifier.jp2/knora.json. - * \param conn_obj - * \param serv - * \param luaserver - * \param params - * \param prefix_as_path - */ - static void serve_knora_json_file( - Connection &conn_obj, - SipiHttpServer *serv, - shttps::LuaServer &luaserver, - std::vector ¶ms, - bool prefix_as_path) - { - conn_obj.setBuffer(); // we want buffered output, since we send JSON text... - - // set the origin - const std::string origin = conn_obj.header("origin"); - syslog(LOG_DEBUG, "knora_send_info: host header %s", origin.c_str()); - if (origin.empty()) { - conn_obj.header("Access-Control-Allow-Origin", "*"); - } - else { - conn_obj.header("Access-Control-Allow-Origin", origin); - } + json_t* prefformats = json_array(); // ToDo: should be settable from LUA preflight (get info from DB) + json_array_append_new(prefformats, json_string("jpg")); + json_array_append_new(prefformats, json_string("tif")); + json_array_append_new(prefformats, json_string("jp2")); + json_array_append_new(prefformats, json_string("png")); + json_object_set_new(root, "preferredFormats", prefformats); // - // here we start the lua script which checks for permissions + // extra features // - std::unordered_map access; - try { - access = check_file_access(conn_obj, serv, luaserver, params, prefix_as_path); + const char* extraFeaturesList[] = { + "baseUriRedirect", + "canonicalLinkHeader", + "cors", + "jsonldMediaType", + "mirroring", + "profileLinkHeader", + "regionByPct", + "regionByPx", + "regionSquare", + "rotationArbitrary", + "rotationBy90s", + "sizeByConfinedWh", + "sizeByH", + "sizeByPct", + "sizeByW", + "sizeByWh", + "sizeUpscaling"}; + json_t* extraFeatures = json_array(); + for (unsigned int i = 0; i < sizeof(extraFeaturesList) / sizeof(char*); i++) { + json_array_append_new(extraFeatures, json_string(extraFeaturesList[i])); + } + + json_object_set_new(root, "extraFeatures", extraFeatures); + } + conn_obj.status(http_status); + conn_obj.setBuffer(); // we want buffered output, since we send JSON text... + + conn_obj.header("Access-Control-Allow-Origin", "*"); + const std::string contenttype = conn_obj.header("accept"); + if (is_image_file) { + if (!contenttype.empty() && (contenttype == "application/ld+json")) { + conn_obj.header("Content-Type", "application/ld+json;profile=\"http://iiif.io/api/image/3/context.json\""); + } else { + conn_obj.header("Content-Type", "application/json"); + conn_obj.header("Link", + "; rel=\"http://www.w3.org/ns/json-ld#context\"; type=\"application/ld+json\""); } - catch (SipiError &err) { - send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err); - return; + } else { + if (!contenttype.empty() && (contenttype == "application/ld+json")) { + conn_obj.header("Content-Type", "application/ld+json;profile=\"http://sipi.io/api/file/3/context.json\""); + } else { + conn_obj.header("Content-Type", "application/json"); + conn_obj.header("Link", + "; rel=\"http://www.w3.org/ns/json-ld#context\"; type=\"application/ld+json\""); } + } - std::string infile = access["infile"]; + char* json_str = json_dumps(root, JSON_INDENT(3)); + conn_obj.sendAndFlush(json_str, strlen(json_str)); + free(json_str); + json_decref(root); +} - SipiIdentifier sid = SipiIdentifier(params[iiif_identifier]); +//========================================================================= - conn_obj.header("Content-Type", "application/json"); +/** + * \brief This internal method serves the knora.json file, e.g., https://server/prefix/identifier.jp2/knora.json. + * \param conn_obj + * \param serv + * \param luaserver + * \param params + * \param prefix_as_path + */ +static void serve_knora_json_file( + Connection& conn_obj, + SipiHttpServer* serv, + shttps::LuaServer& luaserver, + std::vector& params, + bool prefix_as_path) { + conn_obj.setBuffer(); // we want buffered output, since we send JSON text... + + // set the origin + const std::string origin = conn_obj.header("origin"); + syslog(LOG_DEBUG, "knora_send_info: host header %s", origin.c_str()); + if (origin.empty()) { + conn_obj.header("Access-Control-Allow-Origin", "*"); + } else { + conn_obj.header("Access-Control-Allow-Origin", origin); + } - json_t *root = json_object(); - json_object_set_new(root, "@context", json_string("http://sipi.io/api/file/3/context.json")); + // + // here we start the lua script which checks for permissions + // + std::unordered_map access; + try { + access = check_file_access(conn_obj, serv, luaserver, params, prefix_as_path); + } catch (SipiError& err) { + send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err); + return; + } - std::string host = conn_obj.header("host"); - std::string id; - if (params[iiif_prefix] == "") { - if (conn_obj.secure()) { - id = std::string("https://") + host + "/" + params[iiif_identifier]; - } - else { - id = std::string("http://") + host + "/" + params[iiif_identifier]; - } - } - else { - if (conn_obj.secure()) { - id = std::string("https://") + host + "/" + params[iiif_prefix] + "/" + params[iiif_identifier]; - } - else { - id = std::string("http://") + host + "/" + params[iiif_prefix] + "/" + params[iiif_identifier]; - } - } - json_object_set_new(root, "id", json_string(id.c_str())); + std::string infile = access["infile"]; - // - // read sidecar file if available - // - size_t pos = infile.rfind("."); - std::string sidecarname = infile.substr(0, pos) + ".info"; + SipiIdentifier sid = SipiIdentifier(params[iiif_identifier]); - std::ifstream sidecar(sidecarname); - std::string orig_filename = ""; - std::string orig_checksum = ""; - std::string derivative_checksum = ""; + conn_obj.header("Content-Type", "application/json"); - std::double_t sidecar_duration = -1; - std::double_t sidecar_fps = -1; - std::double_t sidecar_height = -1; - std::double_t sidecar_width = -1; + json_t* root = json_object(); + json_object_set_new(root, "@context", json_string("http://sipi.io/api/file/3/context.json")); - if (sidecar.good()) - { - std::stringstream ss; - ss << sidecar.rdbuf(); // read the file - json_t *scroot; - json_error_t error; - scroot = json_loads(ss.str().c_str(), 0, &error); - const char *key; - json_t *value; - if (scroot) { - void *iter = json_object_iter(scroot); - while (iter) { - key = json_object_iter_key(iter); - value = json_object_iter_value(iter); - if (std::strcmp("originalFilename", key) == 0) { - orig_filename = json_string_value(value); - } - else if (std::strcmp("checksumOriginal", key) == 0) { - orig_checksum = json_string_value(value); - } - else if (std::strcmp("checksumDerivative", key) == 0) { - derivative_checksum = json_string_value(value); - } - else if (std::strcmp("duration", key) == 0) - { - sidecar_duration = json_number_value(value); - } - else if (std::strcmp("fps", key) == 0) - { - sidecar_fps = json_number_value(value); - } - else if (std::strcmp("height", key) == 0) - { - sidecar_height = json_number_value(value); - } - else if (std::strcmp("width", key) == 0) - { - sidecar_width = json_number_value(value); - } - - iter = json_object_iter_next(scroot, iter); - } - } - else { - orig_filename = infile; - } - json_decref(scroot); + std::string host = conn_obj.header("host"); + std::string id; + if (params[iiif_prefix] == "") { + if (conn_obj.secure()) { + id = std::string("https://") + host + "/" + params[iiif_identifier]; + } else { + id = std::string("http://") + host + "/" + params[iiif_identifier]; } - - if (!orig_checksum.empty()) { - json_object_set_new(root, "checksumOriginal", json_string(orig_checksum.c_str())); + } else { + if (conn_obj.secure()) { + id = std::string("https://") + host + "/" + params[iiif_prefix] + "/" + params[iiif_identifier]; + } else { + id = std::string("http://") + host + "/" + params[iiif_prefix] + "/" + params[iiif_identifier]; } - if (!derivative_checksum.empty()) { - json_object_set_new(root, "checksumDerivative", json_string(derivative_checksum.c_str())); + } + json_object_set_new(root, "id", json_string(id.c_str())); + + // + // read sidecar file if available + // + size_t pos = infile.rfind("."); + std::string sidecarname = infile.substr(0, pos) + ".info"; + + std::ifstream sidecar(sidecarname); + std::string orig_filename = ""; + std::string orig_checksum = ""; + std::string derivative_checksum = ""; + + std::double_t sidecar_duration = -1; + std::double_t sidecar_fps = -1; + std::double_t sidecar_height = -1; + std::double_t sidecar_width = -1; + + if (sidecar.good()) { + std::stringstream ss; + ss << sidecar.rdbuf(); // read the file + json_t* scroot; + json_error_t error; + scroot = json_loads(ss.str().c_str(), 0, &error); + const char* key; + json_t* value; + if (scroot) { + void* iter = json_object_iter(scroot); + while (iter) { + key = json_object_iter_key(iter); + value = json_object_iter_value(iter); + if (std::strcmp("originalFilename", key) == 0) { + orig_filename = json_string_value(value); + } else if (std::strcmp("checksumOriginal", key) == 0) { + orig_checksum = json_string_value(value); + } else if (std::strcmp("checksumDerivative", key) == 0) { + derivative_checksum = json_string_value(value); + } else if (std::strcmp("duration", key) == 0) { + sidecar_duration = json_number_value(value); + } else if (std::strcmp("fps", key) == 0) { + sidecar_fps = json_number_value(value); + } else if (std::strcmp("height", key) == 0) { + sidecar_height = json_number_value(value); + } else if (std::strcmp("width", key) == 0) { + sidecar_width = json_number_value(value); + } + + iter = json_object_iter_next(scroot, iter); + } + } else { + orig_filename = infile; } + json_decref(scroot); + } - std::string actual_mimetype = shttps::Parsing::getBestFileMimetype(infile); - if ((actual_mimetype == "image/tiff") || - (actual_mimetype == "image/jpeg") || - (actual_mimetype == "image/png") || - (actual_mimetype == "image/jpx") || - (actual_mimetype == "image/jp2")) - { + if (!orig_checksum.empty()) { + json_object_set_new(root, "checksumOriginal", json_string(orig_checksum.c_str())); + } + if (!derivative_checksum.empty()) { + json_object_set_new(root, "checksumDerivative", json_string(derivative_checksum.c_str())); + } - int width, height; - // - // get cache info - // - // std::shared_ptr cache = serv->cache(); + std::string actual_mimetype = shttps::Parsing::getBestFileMimetype(infile); + if ((actual_mimetype == "image/tiff") || + (actual_mimetype == "image/jpeg") || + (actual_mimetype == "image/png") || + (actual_mimetype == "image/jpx") || + (actual_mimetype == "image/jp2")) { + int width, height; + // + // get cache info + // + // std::shared_ptr cache = serv->cache(); - Sipi::SipiImage tmpimg; - Sipi::SipiImgInfo info; - try { - info = tmpimg.getDim(infile); - } - catch (SipiImageError &err) { - send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err.to_string()); - return; - } - if (info.success == SipiImgInfo::FAILURE) { - send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, "Error getting image dimensions!"); - return; - } - width = info.width; - height = info.height; + Sipi::SipiImage tmpimg; + Sipi::SipiImgInfo info; + try { + info = tmpimg.getDim(infile); + } catch (SipiImageError& err) { + send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err.to_string()); + return; + } + if (info.success == SipiImgInfo::FAILURE) { + send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, "Error getting image dimensions!"); + return; + } + width = info.width; + height = info.height; - json_object_set_new(root, "width", json_integer(width)); - json_object_set_new(root, "height", json_integer(height)); - if (info.numpages > 0) { - json_object_set_new(root, "numpages", json_integer(info.numpages)); - } - //json_object_set_new(root, "internalMimeType", json_string(info.internalmimetype.c_str())); - json_object_set_new(root, "internalMimeType", json_string(actual_mimetype.c_str())); - if (info.success == SipiImgInfo::ALL) { - json_object_set_new(root, "originalMimeType", json_string(info.origmimetype.c_str())); - json_object_set_new(root, "originalFilename", json_string(info.origname.c_str())); - } - char *json_str = json_dumps(root, JSON_INDENT(3)); - conn_obj.sendAndFlush(json_str, strlen(json_str)); - free(json_str); + json_object_set_new(root, "width", json_integer(width)); + json_object_set_new(root, "height", json_integer(height)); + if (info.numpages > 0) { + json_object_set_new(root, "numpages", json_integer(info.numpages)); } - else if (actual_mimetype == "video/mp4") - { + //json_object_set_new(root, "internalMimeType", json_string(info.internalmimetype.c_str())); + json_object_set_new(root, "internalMimeType", json_string(actual_mimetype.c_str())); + if (info.success == SipiImgInfo::ALL) { + json_object_set_new(root, "originalMimeType", json_string(info.origmimetype.c_str())); + json_object_set_new(root, "originalFilename", json_string(info.origname.c_str())); + } + char* json_str = json_dumps(root, JSON_INDENT(3)); + conn_obj.sendAndFlush(json_str, strlen(json_str)); + free(json_str); + } else if (actual_mimetype == "video/mp4") { + json_object_set_new(root, "internalMimeType", json_string(actual_mimetype.c_str())); - json_object_set_new(root, "internalMimeType", json_string(actual_mimetype.c_str())); + struct stat fstatbuf; + if (stat(infile.c_str(), &fstatbuf) != 0) { + throw Error(__file__, __LINE__, "Cannot fstat file!"); + } + json_object_set_new(root, "fileSize", json_integer(fstatbuf.st_size)); - struct stat fstatbuf; - if (stat(infile.c_str(), &fstatbuf) != 0) - { - throw Error(__file__, __LINE__, "Cannot fstat file!"); - } - json_object_set_new(root, "fileSize", json_integer(fstatbuf.st_size)); + if (!orig_filename.empty()) { + json_object_set_new(root, "originalFilename", json_string(orig_filename.c_str())); + } - if (!orig_filename.empty()) { - json_object_set_new(root, "originalFilename", json_string(orig_filename.c_str())); - } + if (sidecar_duration >= 0) { + json_object_set_new(root, "duration", json_real(sidecar_duration)); + } - if (sidecar_duration >= 0) { - json_object_set_new(root, "duration", json_real(sidecar_duration)); - } + if (sidecar_fps >= 0) { + json_object_set_new(root, "fps", json_real(sidecar_fps)); + } - if (sidecar_fps >= 0) { - json_object_set_new(root, "fps", json_real(sidecar_fps)); - } + if (sidecar_height >= 0) { + json_object_set_new(root, "height", json_real(sidecar_height)); + } - if (sidecar_height >= 0) { - json_object_set_new(root, "height", json_real(sidecar_height)); - } + if (sidecar_width >= 0) { + json_object_set_new(root, "width", json_real(sidecar_width)); + } - if (sidecar_width >= 0) { - json_object_set_new(root, "width", json_real(sidecar_width)); - } + char* json_str = json_dumps(root, JSON_INDENT(3)); + conn_obj.sendAndFlush(json_str, strlen(json_str)); + free(json_str); + } else { + json_object_set_new(root, "internalMimeType", json_string(actual_mimetype.c_str())); - char *json_str = json_dumps(root, JSON_INDENT(3)); - conn_obj.sendAndFlush(json_str, strlen(json_str)); - free(json_str); + struct stat fstatbuf; + if (stat(infile.c_str(), &fstatbuf) != 0) { + throw Error(__file__, __LINE__, "Cannot fstat file!"); } - else - { - json_object_set_new(root, "internalMimeType", json_string(actual_mimetype.c_str())); + json_object_set_new(root, "fileSize", json_integer(fstatbuf.st_size)); + json_object_set_new(root, "originalFilename", json_string(orig_filename.c_str())); - struct stat fstatbuf; - if (stat(infile.c_str(), &fstatbuf) != 0) { - throw Error(__file__, __LINE__, "Cannot fstat file!"); - } - json_object_set_new(root, "fileSize", json_integer(fstatbuf.st_size)); - json_object_set_new(root, "originalFilename", json_string(orig_filename.c_str())); + char* json_str = json_dumps(root, JSON_INDENT(3)); + conn_obj.sendAndFlush(json_str, strlen(json_str)); + free(json_str); + } + json_decref(root); +} - char *json_str = json_dumps(root, JSON_INDENT(3)); - conn_obj.sendAndFlush(json_str, strlen(json_str)); - free(json_str); - } - json_decref(root); +//========================================================================= + +/** + * \brief The internal function handels serving of raw files part for the iiif_handler. + * This is an extension of the IIIF Image API, which allows for the delivery of raw files. + * This is useful for delivering files that are not images, such as PDFs, audio, video, etc., and that + * cannot be accesed otherwise through the IIIF Image API. + * \param conn_obj + * \param luaserver + * \param serv + * \param prefix_as_path + * \param params + */ +static void serve_file_download(Connection& conn_obj, + shttps::LuaServer& luaserver, + SipiHttpServer* serv, + bool prefix_as_path, + std::vector params) { + std::string requested_file; + if (prefix_as_path && (!params[iiif_prefix].empty())) { + requested_file = serv->imgroot() + "/" + urldecode(params[iiif_prefix]) + "/" + + urldecode(params[iiif_identifier]); + } else { + requested_file = serv->imgroot() + "/" + urldecode(params[iiif_identifier]); } - //========================================================================= - - /** - * \brief The internal function handels serving of raw files part for the iiif_handler. - * This is an extension of the IIIF Image API, which allows for the delivery of raw files. - * This is useful for delivering files that are not images, such as PDFs, audio, video, etc., and that - * cannot be accesed otherwise through the IIIF Image API. - * \param conn_obj - * \param luaserver - * \param serv - * \param prefix_as_path - * \param params - */ - static void serve_file_download(Connection &conn_obj, shttps::LuaServer &luaserver, SipiHttpServer *serv, bool prefix_as_path, std::vector params) { - std::string requested_file; - if (prefix_as_path && (!params[iiif_prefix].empty())) { - requested_file = serv->imgroot() + "/" + urldecode(params[iiif_prefix]) + "/" + - urldecode(params[iiif_identifier]); + if (luaserver.luaFunctionExists(file_preflight_funcname)) { + std::unordered_map pre_flight_info; + try { + pre_flight_info = call_file_preflight(conn_obj, luaserver, requested_file); + } catch (SipiError& err) { + send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err); + return; } - else { - requested_file = serv->imgroot() + "/" + urldecode(params[iiif_identifier]); + if (pre_flight_info["type"] == "allow") { + requested_file = pre_flight_info["infile"]; + } else if (pre_flight_info["type"] == "restrict") { + requested_file = pre_flight_info["infile"]; + } else { + send_error(conn_obj, Connection::UNAUTHORIZED, "Unauthorized access"); + return; } - if (luaserver.luaFunctionExists(file_preflight_funcname)) { - std::unordered_map pre_flight_info; - try { - pre_flight_info = call_file_preflight(conn_obj, luaserver, requested_file); - } - catch (SipiError &err) { - send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err); - return; - } - if (pre_flight_info["type"] == "allow") { - requested_file = pre_flight_info["infile"]; - } - else if (pre_flight_info["type"] == "restrict") { - requested_file = pre_flight_info["infile"]; - } - else { - send_error(conn_obj, Connection::UNAUTHORIZED, "Unauthorized access"); - return; - } + } + if (access(requested_file.c_str(), R_OK) == 0) { + std::string actual_mimetype = shttps::Parsing::getBestFileMimetype(requested_file); + // + // first we get the filesize and time using fstat + // + struct stat fstatbuf{}; + + if (stat(requested_file.c_str(), &fstatbuf) != 0) { + syslog(LOG_ERR, "Cannot fstat file %s ", requested_file.c_str()); + send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR); } - if (access(requested_file.c_str(), R_OK) == 0) { - std::string actual_mimetype = shttps::Parsing::getBestFileMimetype(requested_file); + size_t fsize = fstatbuf.st_size; + #ifdef __APPLE__ + struct timespec rawtime = fstatbuf.st_mtimespec; + #else + struct timespec rawtime = fstatbuf.st_mtim; + #endif + char timebuf[100]; + std::strftime(timebuf, sizeof timebuf, "%a, %d %b %Y %H:%M:%S %Z", std::gmtime(&rawtime.tv_sec)); + + std::string range = conn_obj.header("range"); + if (range.empty()) { + // no "Content-Length" since send_file() will add this + conn_obj.header("Content-Type", actual_mimetype); + conn_obj.header("Cache-Control", "public, must-revalidate, max-age=0"); + conn_obj.header("Pragma", "no-cache"); + conn_obj.header("Accept-Ranges", "bytes"); + conn_obj.header("Last-Modified", timebuf); + conn_obj.header("Content-Transfer-Encoding: binary"); + conn_obj.sendFile(requested_file); + } else { // - // first we get the filesize and time using fstat + // now we parse the range // - struct stat fstatbuf{}; - - if (stat(requested_file.c_str(), &fstatbuf) != 0) { - syslog(LOG_ERR, "Cannot fstat file %s ", requested_file.c_str()); - send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR); - } - size_t fsize = fstatbuf.st_size; -#ifdef __APPLE__ - struct timespec rawtime = fstatbuf.st_mtimespec; -#else - struct timespec rawtime = fstatbuf.st_mtim; -#endif - char timebuf[100]; - std::strftime(timebuf, sizeof timebuf, "%a, %d %b %Y %H:%M:%S %Z", std::gmtime(&rawtime.tv_sec)); - - std::string range = conn_obj.header("range"); - if (range.empty()) { - // no "Content-Length" since send_file() will add this - conn_obj.header("Content-Type", actual_mimetype); - conn_obj.header("Cache-Control", "public, must-revalidate, max-age=0"); - conn_obj.header("Pragma", "no-cache"); - conn_obj.header("Accept-Ranges", "bytes"); - conn_obj.header("Last-Modified", timebuf); - conn_obj.header("Content-Transfer-Encoding: binary"); - conn_obj.sendFile(requested_file); - } - else { - // - // now we parse the range - // - std::regex re(R"(bytes=\s*(\d+)-(\d*)[\D.*]?)"); - std::cmatch m; - int start = 0; // lets assume beginning of file - int end = fsize - 1; // lets assume whole file - if (std::regex_match(range.c_str(), m, re)) { - if (m.size() < 2) { - throw Error(__file__, __LINE__, "Range expression invalid!"); - } - start = std::stoi(m[1]); - if ((m.size() > 1) && !m[2].str().empty()) { - end = std::stoi(m[2]); - } - } - else { + std::regex re(R"(bytes=\s*(\d+)-(\d*)[\D.*]?)"); + std::cmatch m; + int start = 0; // lets assume beginning of file + int end = fsize - 1; // lets assume whole file + if (std::regex_match(range.c_str(), m, re)) { + if (m.size() < 2) { throw Error(__file__, __LINE__, "Range expression invalid!"); } - - // no "Content-Length" since send_file() will add this - conn_obj.status(Connection::PARTIAL_CONTENT); - conn_obj.header("Content-Type", actual_mimetype); - conn_obj.header("Cache-Control", "public, must-revalidate, max-age=0"); - conn_obj.header("Pragma", "no-cache"); - conn_obj.header("Accept-Ranges", "bytes"); - std::stringstream ss; - ss << "bytes " << start << "-" << end << "/" << fsize; - conn_obj.header("Content-Range", ss.str()); - conn_obj.header("Content-Disposition", std::string("inline; filename=") + urldecode(params[iiif_identifier])); - conn_obj.header("Content-Transfer-Encoding: binary"); - conn_obj.header("Last-Modified", timebuf); - conn_obj.sendFile(requested_file, 8192, start, end); + start = std::stoi(m[1]); + if ((m.size() > 1) && !m[2].str().empty()) { + end = std::stoi(m[2]); + } + } else { + throw Error(__file__, __LINE__, "Range expression invalid!"); } - conn_obj.flush(); - } - else { - syslog(LOG_WARNING, "GET: %s not accessible", requested_file.c_str()); - send_error(conn_obj, Connection::NOT_FOUND); - conn_obj.flush(); + + // no "Content-Length" since send_file() will add this + conn_obj.status(Connection::PARTIAL_CONTENT); + conn_obj.header("Content-Type", actual_mimetype); + conn_obj.header("Cache-Control", "public, must-revalidate, max-age=0"); + conn_obj.header("Pragma", "no-cache"); + conn_obj.header("Accept-Ranges", "bytes"); + std::stringstream ss; + ss << "bytes " << start << "-" << end << "/" << fsize; + conn_obj.header("Content-Range", ss.str()); + conn_obj.header("Content-Disposition", + std::string("inline; filename=") + urldecode(params[iiif_identifier])); + conn_obj.header("Content-Transfer-Encoding: binary"); + conn_obj.header("Last-Modified", timebuf); + conn_obj.sendFile(requested_file, 8192, start, end); } + conn_obj.flush(); + } else { + syslog(LOG_WARNING, "GET: %s not accessible", requested_file.c_str()); + send_error(conn_obj, Connection::NOT_FOUND); + conn_obj.flush(); } +} - /** - * @brief The internal function handels the serving of IIIFs part for the iiif_handler. - * This function gets the parameters from the request, calls the preflight function, which checks for permissions, - * deals with watermarks and size restrictions, gets the mimetype of the file, gets the cache info, gets the image - * and sends the image to the client - * @param conn_obj - * @param luaserver - * @param server - * @param prefix_as_path - * @param uri the raw URI from the request. - * @param params the parsed parameters from the URI. - */ - static void serve_iiif(Connection &conn_obj, shttps::LuaServer &luaserver, SipiHttpServer *server, bool prefix_as_path, const std::string &uri, std::vector params) { - // - // getting the identifier (which in case of a PDF or multipage TIFF my contain a page id (identifier@pagenum) - // - SipiIdentifier sid = urldecode(params[iiif_identifier]); +/** + * @brief The internal function handels the serving of IIIFs part for the iiif_handler. + * This function gets the parameters from the request, calls the preflight function, which checks for permissions, + * deals with watermarks and size restrictions, gets the mimetype of the file, gets the cache info, gets the image + * and sends the image to the client + * @param conn_obj + * @param luaserver + * @param server + * @param prefix_as_path + * @param uri the raw URI from the request. + * @param params the parsed parameters from the URI. + */ +static void serve_iiif(Connection& conn_obj, + shttps::LuaServer& luaserver, + SipiHttpServer* server, + bool prefix_as_path, + const std::string& uri, + std::vector params) { + // + // getting the identifier (which in case of a PDF or multipage TIFF my contain a page id (identifier@pagenum) + // + SipiIdentifier sid = urldecode(params[iiif_identifier]); - // - // getting IIIF parameters - // - auto region = std::make_shared(); - auto size = std::make_shared(); - SipiRotation rotation; - SipiQualityFormat quality_format; + // + // getting IIIF parameters + // + auto region = std::make_shared(); + auto size = std::make_shared(); + SipiRotation rotation; + SipiQualityFormat quality_format; + try { + region = std::make_shared(params[iiif_region]); + size = std::make_shared(params[iiif_size]); + rotation = SipiRotation(params[iiif_rotation]); + quality_format = SipiQualityFormat(params[iiif_qualityformat]); + } catch (Sipi::SipiError& err) { + send_error(conn_obj, Connection::BAD_REQUEST, err); + return; + } + + // + // here we start the lua script which checks for permissions + // + std::string infile; // path to the input file on the server + std::string watermark; // path to watermark file, or empty, if no watermark required + auto restricted_size = std::make_shared(); // size of restricted image. (SizeType::FULL if unrestricted) + + if (luaserver.luaFunctionExists(iiif_preflight_funcname)) { + std::unordered_map pre_flight_info; try { - region = std::make_shared(params[iiif_region]); - size = std::make_shared(params[iiif_size]); - rotation = SipiRotation(params[iiif_rotation]); - quality_format = SipiQualityFormat(params[iiif_qualityformat]); - } - catch (Sipi::SipiError &err) { - send_error(conn_obj, Connection::BAD_REQUEST, err); + pre_flight_info = call_iiif_preflight(conn_obj, luaserver, params[iiif_prefix], sid.getIdentifier()); + } catch (SipiError& err) { + send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err); return; } + infile = pre_flight_info["infile"]; - // - // here we start the lua script which checks for permissions - // - std::string infile; // path to the input file on the server - std::string watermark; // path to watermark file, or empty, if no watermark required - auto restricted_size = std::make_shared(); // size of restricted image. (SizeType::FULL if unrestricted) - - if (luaserver.luaFunctionExists(iiif_preflight_funcname)) { - std::unordered_map pre_flight_info; - try { - pre_flight_info = call_iiif_preflight(conn_obj, luaserver, params[iiif_prefix], sid.getIdentifier()); - } - catch (SipiError &err) { - send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err); - return; - } - infile = pre_flight_info["infile"]; - - if (pre_flight_info["type"] != "allow") { - if (pre_flight_info["type"] == "restrict") { - bool ok = false; - try{ - watermark = pre_flight_info.at("watermark"); - ok = true; - } - catch (const std::out_of_range &err) { - ; // do nothing, no watermark... - } - try { - std::string raw_size_str = pre_flight_info.at("size"); - restricted_size = std::make_shared(raw_size_str); - ok = true; - } - catch (const std::out_of_range &err) { - ; // do nothing, no size restriction - } - if (!ok) { - send_error(conn_obj, Connection::UNAUTHORIZED, "Unauthorized access"); - return; - } + if (pre_flight_info["type"] != "allow") { + if (pre_flight_info["type"] == "restrict") { + bool ok = false; + try { + watermark = pre_flight_info.at("watermark"); + ok = true; + } catch (const std::out_of_range& err) { + ; // do nothing, no watermark... + } + try { + std::string raw_size_str = pre_flight_info.at("size"); + restricted_size = std::make_shared(raw_size_str); + ok = true; + } catch (const std::out_of_range& err) { + ; // do nothing, no size restriction } - else { + if (!ok) { send_error(conn_obj, Connection::UNAUTHORIZED, "Unauthorized access"); return; } + } else { + send_error(conn_obj, Connection::UNAUTHORIZED, "Unauthorized access"); + return; } } - else { - if (prefix_as_path && (!params[iiif_prefix].empty())) { - infile = server->imgroot() + "/" + params[iiif_prefix] + "/" + sid.getIdentifier(); - } - else { - infile = server->imgroot() + "/" + sid.getIdentifier(); - } - } - - // - // determine the mimetype of the file in the SIPI repo - // - SipiQualityFormat::FormatType in_format = SipiQualityFormat::UNSUPPORTED; - - std::string actual_mimetype = shttps::Parsing::getFileMimetype(infile).first; - if (actual_mimetype == "image/tiff") - in_format = SipiQualityFormat::TIF; - if (actual_mimetype == "image/jpeg") - in_format = SipiQualityFormat::JPG; - if (actual_mimetype == "image/png") - in_format = SipiQualityFormat::PNG; - if ((actual_mimetype == "image/jpx") || (actual_mimetype == "image/jp2")) - in_format = SipiQualityFormat::JP2; - - if (access(infile.c_str(), R_OK) != 0) { // test, if file exists - syslog(LOG_INFO, "File %s not found", infile.c_str()); - send_error(conn_obj, Connection::NOT_FOUND); - return; + } else { + if (prefix_as_path && (!params[iiif_prefix].empty())) { + infile = server->imgroot() + "/" + params[iiif_prefix] + "/" + sid.getIdentifier(); + } else { + infile = server->imgroot() + "/" + sid.getIdentifier(); } + } - float angle; - bool mirror = rotation.get_rotation(angle); + // + // determine the mimetype of the file in the SIPI repo + // + SipiQualityFormat::FormatType in_format = SipiQualityFormat::UNSUPPORTED; + + std::string actual_mimetype = shttps::Parsing::getFileMimetype(infile).first; + if (actual_mimetype == "image/tiff") + in_format = SipiQualityFormat::TIF; + if (actual_mimetype == "image/jpeg") + in_format = SipiQualityFormat::JPG; + if (actual_mimetype == "image/png") + in_format = SipiQualityFormat::PNG; + if ((actual_mimetype == "image/jpx") || (actual_mimetype == "image/jp2")) + in_format = SipiQualityFormat::JP2; + + if (access(infile.c_str(), R_OK) != 0) { + // test, if file exists + syslog(LOG_INFO, "File %s not found", infile.c_str()); + send_error(conn_obj, Connection::NOT_FOUND); + return; + } - // - // get cache info - // - std::shared_ptr cache = server->cache(); - size_t img_w = 0, img_h = 0; - size_t tile_w = 0, tile_h = 0; - int clevels = 0; - int numpages = 0; + float angle; + bool mirror = rotation.get_rotation(angle); - // - // get image dimensions by accessing the file, needed for get_canonical... - // - if ((cache == nullptr) || !cache->getSize(infile, img_w, img_h, tile_w, tile_h, clevels, numpages)) { - Sipi::SipiImgInfo info; - try { - Sipi::SipiImage img; - info = img.getDim(infile); - } - catch (SipiImageError &err) { - send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err.to_string()); - return; - } - if (info.success == SipiImgInfo::FAILURE) { - send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, "Couldn't get image dimensions!"); - return; - } - img_w = info.width; - img_h = info.height; - tile_w = info.tile_width; - tile_h = info.tile_height; - clevels = info.clevels; - numpages = info.numpages; - } + // + // get cache info + // + std::shared_ptr cache = server->cache(); + size_t img_w = 0, img_h = 0; + size_t tile_w = 0, tile_h = 0; + int clevels = 0; + int numpages = 0; - size_t tmp_r_w{0L}, tmp_r_h{0L}; - int tmp_red{0}; - bool tmp_ro{false}; + // + // get image dimensions by accessing the file, needed for get_canonical... + // + if ((cache == nullptr) || !cache->getSize(infile, img_w, img_h, tile_w, tile_h, clevels, numpages)) { + Sipi::SipiImgInfo info; try { - size->get_size(img_w, img_h, tmp_r_w, tmp_r_h, tmp_red, tmp_ro); - restricted_size->get_size(img_w, img_h, tmp_r_w, tmp_r_h, tmp_red, tmp_ro); - } - catch (Sipi::SipiSizeError &err) { - send_error(conn_obj, Connection::BAD_REQUEST, err.to_string()); + Sipi::SipiImage img; + info = img.getDim(infile); + } catch (SipiImageError& err) { + send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err.to_string()); return; } - catch (Sipi::SipiError &err) { - send_error(conn_obj, Connection::BAD_REQUEST, err); + if (info.success == SipiImgInfo::FAILURE) { + send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, "Couldn't get image dimensions!"); return; } + img_w = info.width; + img_h = info.height; + tile_w = info.tile_width; + tile_h = info.tile_height; + clevels = info.clevels; + numpages = info.numpages; + } - // if restricted size is set and smaller, we use it - if (!restricted_size->undefined() && (*size > *restricted_size)) { - size = restricted_size; - } + size_t tmp_r_w{0L}, tmp_r_h{0L}; + int tmp_red{0}; + bool tmp_ro{false}; + try { + size->get_size(img_w, img_h, tmp_r_w, tmp_r_h, tmp_red, tmp_ro); + restricted_size->get_size(img_w, img_h, tmp_r_w, tmp_r_h, tmp_red, tmp_ro); + } catch (Sipi::SipiSizeError& err) { + send_error(conn_obj, Connection::BAD_REQUEST, err.to_string()); + return; + } + catch (Sipi::SipiError& err) { + send_error(conn_obj, Connection::BAD_REQUEST, err); + return; + } - //..................................................................... - // here we start building the canonical URL - // - std::pair canonical_info; - try { - canonical_info = Sipi::SipiHttpServer::get_canonical_url(img_w, img_h, conn_obj.host(), params[iiif_prefix], sid.getIdentifier(), region, size, rotation, quality_format, sid.getPage()); - } - catch (Sipi::SipiError &err) { - send_error(conn_obj, Connection::BAD_REQUEST, err); - return; - } + // if restricted size is set and smaller, we use it + if (!restricted_size->undefined() && (*size > *restricted_size)) { + size = restricted_size; + } - std::string canonical_header = canonical_info.first; - std::string canonical = canonical_info.second; + //..................................................................... + // here we start building the canonical URL + // + std::pair canonical_info; + try { + canonical_info = Sipi::SipiHttpServer::get_canonical_url(img_w, + img_h, + conn_obj.host(), + params[iiif_prefix], + sid.getIdentifier(), + region, + size, + rotation, + quality_format, + sid.getPage()); + } catch (Sipi::SipiError& err) { + send_error(conn_obj, Connection::BAD_REQUEST, err); + return; + } - // now we check if we can send the file directly - // - if ((region->getType() == SipiRegion::FULL) && (size->getType() == SipiSize::FULL) && (angle == 0.0) && - (!mirror) && watermark.empty() && (quality_format.format() == in_format) && - (quality_format.quality() == SipiQualityFormat::DEFAULT)) - { - conn_obj.status(Connection::OK); - conn_obj.header("Cache-Control", "must-revalidate, post-check=0, pre-check=0"); - conn_obj.header("Link", canonical_header); + std::string canonical_header = canonical_info.first; + std::string canonical = canonical_info.second; - // set the header (mimetype) - switch (quality_format.format()) { + // now we check if we can send the file directly + // + if ((region->getType() == SipiRegion::FULL) && (size->getType() == SipiSize::FULL) && (angle == 0.0) && + (!mirror) && watermark.empty() && (quality_format.format() == in_format) && + (quality_format.quality() == SipiQualityFormat::DEFAULT)) { + conn_obj.status(Connection::OK); + conn_obj.header("Cache-Control", "must-revalidate, post-check=0, pre-check=0"); + conn_obj.header("Link", canonical_header); + + // set the header (mimetype) + switch (quality_format.format()) { case SipiQualityFormat::TIF: conn_obj.header("Content-Type", "image/tiff"); break; @@ -1515,34 +1486,34 @@ namespace Sipi { conn_obj.header("Content-Type", "image/jp2"); break; default: {} - } - try { - conn_obj.sendFile(infile); - } - catch (shttps::InputFailure iofail) { - syslog(LOG_WARNING, "Browser unexpectedly closed connection"); - } - catch (Sipi::SipiError &err) { - send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err); - } - return; - } // finish sending unmodified file in toto + } + try { + conn_obj.sendFile(infile); + } catch (shttps::InputFailure iofail) { + syslog(LOG_WARNING, "Browser unexpectedly closed connection"); + } + catch (Sipi::SipiError& err) { + send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err); + } + return; + } // finish sending unmodified file in toto - // we only allow the cache if the file is not watermarked - if (cache != nullptr) { - //!> - //!> here we check if the file is in the cache. If so, it's being blocked from deletion - //!> - std::string cachefile = cache->check(infile, canonical, true); // we block the file from being deleted if successfull + // we only allow the cache if the file is not watermarked + if (cache != nullptr) { + //!> + //!> here we check if the file is in the cache. If so, it's being blocked from deletion + //!> + std::string cachefile = cache->check(infile, canonical, true); + // we block the file from being deleted if successfull - if (!cachefile.empty()) { - syslog(LOG_DEBUG, "Using cachefile %s", cachefile.c_str()); - conn_obj.status(Connection::OK); - conn_obj.header("Cache-Control", "must-revalidate, post-check=0, pre-check=0"); - conn_obj.header("Link", canonical_header); + if (!cachefile.empty()) { + syslog(LOG_DEBUG, "Using cachefile %s", cachefile.c_str()); + conn_obj.status(Connection::OK); + conn_obj.header("Cache-Control", "must-revalidate, post-check=0, pre-check=0"); + conn_obj.header("Link", canonical_header); - // set the header (mimetype) - switch (quality_format.format()) { + // set the header (mimetype) + switch (quality_format.format()) { case SipiQualityFormat::TIF: conn_obj.header("Content-Type", "image/tiff"); break; @@ -1556,59 +1527,56 @@ namespace Sipi { conn_obj.header("Content-Type", "image/jp2"); break; default: {} - } + } - try { - //!> send the file from cache - conn_obj.sendFile(cachefile); - //!> from now on the cache file can be deleted again - } - catch (shttps::InputFailure err) { - // -1 was thrown - syslog(LOG_WARNING, "Browser unexpectedly closed connection"); - cache->deblock(cachefile); - return; - } - catch (Sipi::SipiError &err) { - syslog(LOG_ERR, "Error sending cache file: \"%s\": %s", cachefile.c_str(), err.to_string().c_str()); - send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err); - cache->deblock(cachefile); - return; - } + try { + //!> send the file from cache + conn_obj.sendFile(cachefile); + //!> from now on the cache file can be deleted again + } catch (shttps::InputFailure err) { + // -1 was thrown + syslog(LOG_WARNING, "Browser unexpectedly closed connection"); + cache->deblock(cachefile); + return; + } + catch (Sipi::SipiError& err) { + syslog(LOG_ERR, "Error sending cache file: \"%s\": %s", cachefile.c_str(), err.to_string().c_str()); + send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err); cache->deblock(cachefile); return; } cache->deblock(cachefile); + return; } + cache->deblock(cachefile); + } - Sipi::SipiImage img; + Sipi::SipiImage img; + try { + img.read(infile, region, size, quality_format.format() == SipiQualityFormat::JPG, server->scaling_quality()); + } catch (const SipiImageError& err) { + send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err.to_string()); + return; + } + catch (const SipiSizeError& err) { + send_error(conn_obj, Connection::BAD_REQUEST, err.to_string()); + return; + } + + // + // now we rotate + // + if (mirror || (angle != 0.0)) { try { - img.read(infile, region, size, quality_format.format() == SipiQualityFormat::JPG, server->scaling_quality()); - } - catch (const SipiImageError &err) { - send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err.to_string()); - return; - } - catch (const SipiSizeError &err) { - send_error(conn_obj, Connection::BAD_REQUEST, err.to_string()); + img.rotate(angle, mirror); + } catch (Sipi::SipiError& err) { + send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err); return; } + } - // - // now we rotate - // - if (mirror || (angle != 0.0)) { - try { - img.rotate(angle, mirror); - } - catch (Sipi::SipiError &err) { - send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err); - return; - } - } - - if (quality_format.quality() != SipiQualityFormat::DEFAULT) { - switch (quality_format.quality()) { + if (quality_format.quality() != SipiQualityFormat::DEFAULT) { + switch (quality_format.quality()) { case SipiQualityFormat::COLOR: img.convertToIcc(SipiIcc(icc_sRGB), 8); break; // for now, force 8 bit/sample @@ -1622,46 +1590,44 @@ namespace Sipi { send_error(conn_obj, Connection::BAD_REQUEST, "Invalid quality specificer"); return; } - } } + } - // - // let's add a watermark if necessary - // - if (!watermark.empty()) { + // + // let's add a watermark if necessary + // + if (!watermark.empty()) { + try { + img.add_watermark(watermark); + } catch (Sipi::SipiError& err) { + send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err); + syslog(LOG_ERR, "GET %s: error adding watermark: %s", uri.c_str(), err.to_string().c_str()); + return; + } + catch (std::exception& err) { + send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err.what()); + syslog(LOG_ERR, "GET %s: error adding watermark: %s", uri.c_str(), err.what()); + return; + } + syslog(LOG_INFO, "GET %s: adding watermark", uri.c_str()); + } + + img.connection(&conn_obj); + conn_obj.header("Cache-Control", "must-revalidate, post-check=0, pre-check=0"); + std::string cachefile; + + try { + if (cache != nullptr) { try { - img.add_watermark(watermark); - } - catch (Sipi::SipiError &err) { + //!> open the cache file to write into. + cachefile = cache->getNewCacheFileName(); + conn_obj.openCacheFile(cachefile); + } catch (const shttps::Error& err) { send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err); - syslog(LOG_ERR, "GET %s: error adding watermark: %s", uri.c_str(), err.to_string().c_str()); - return; - } - catch (std::exception &err) { - send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err.what()); - syslog(LOG_ERR, "GET %s: error adding watermark: %s", uri.c_str(), err.what()); return; } - syslog(LOG_INFO, "GET %s: adding watermark", uri.c_str()); } - - img.connection(&conn_obj); - conn_obj.header("Cache-Control", "must-revalidate, post-check=0, pre-check=0"); - std::string cachefile; - - try { - if (cache != nullptr) { - try { - //!> open the cache file to write into. - cachefile = cache->getNewCacheFileName(); - conn_obj.openCacheFile(cachefile); - } - catch (const shttps::Error &err) { - send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err); - return; - } - } - switch (quality_format.format()) { + switch (quality_format.format()) { case SipiQualityFormat::JPG: { conn_obj.status(Connection::OK); conn_obj.header("Link", canonical_header); @@ -1709,147 +1675,151 @@ namespace Sipi { << "Unsupported file format requested! Supported are .jpg, .jp2, .tif, .png\n"; conn_obj.flush(); } - } + } - if (conn_obj.isCacheFileOpen()) { - conn_obj.closeCacheFile(); - //!> - //!> ATTENTION!!! Here we change the list of available cache files - //!> - cache->add(infile, canonical, cachefile, img_w, img_h, tile_w, tile_h, clevels, numpages); - } + if (conn_obj.isCacheFileOpen()) { + conn_obj.closeCacheFile(); + //!> + //!> ATTENTION!!! Here we change the list of available cache files + //!> + cache->add(infile, canonical, cachefile, img_w, img_h, tile_w, tile_h, clevels, numpages); } - catch (Sipi::SipiError &err) { - if (cache != nullptr) { - conn_obj.closeCacheFile(); - unlink(cachefile.c_str()); - } - send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err); - return; + } catch (Sipi::SipiError& err) { + if (cache != nullptr) { + conn_obj.closeCacheFile(); + unlink(cachefile.c_str()); } - catch (Sipi::SipiImageError &err) { - if (cache != nullptr) { - conn_obj.closeCacheFile(); - unlink(cachefile.c_str()); - } - send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err.what()); - return; + send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err); + return; + } + catch (Sipi::SipiImageError& err) { + if (cache != nullptr) { + conn_obj.closeCacheFile(); + unlink(cachefile.c_str()); } + send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, err.what()); + return; + } - conn_obj.flush(); + conn_obj.flush(); +} + +/** + * \brief The iiif_handler function is the main entry point for the IIIF route. + * It parses the URI and calls one of the appropriate functionss (serve_iiif, serve_info, serve_knora_info, ) to handle the request. + * \param conn_obj + * \param luaserver + * \param user_data + * \param dummy + */ +static void iiif_handler( + Connection& conn_obj, + shttps::LuaServer& luaserver, + void* user_data, + void* dummy) { + auto* serv = static_cast(user_data); + const bool prefix_as_path = serv->prefix_as_path(); + const auto uri = conn_obj.uri(); // has form "/pre/fix/es.../BAU_1_000441077_2_1.j2k/full/,1000/0/default.jpg" + + std::vector params{}; + auto request_type{handlers::iiif_handler::UNDEFINED}; + + if (auto parse_url_result = handlers::iiif_handler::parse_iiif_uri(uri); parse_url_result.has_value()) { + params = parse_url_result.value().params; + request_type = parse_url_result.value().request_type; + } else { + send_error(conn_obj, Connection::BAD_REQUEST, parse_url_result.error()); + return; } - /** - * \brief The iiif_handler function is the main entry point for the IIIF route. - * It parses the URI and calls one of the appropriate functionss (serve_iiif, serve_info, serve_knora_info, ) to handle the request. - * \param conn_obj - * \param luaserver - * \param user_data - * \param dummy - */ - static void iiif_handler( - Connection &conn_obj, - shttps::LuaServer &luaserver, - void *user_data, - void *dummy) { - - auto *serv = static_cast(user_data); - const bool prefix_as_path = serv->prefix_as_path(); - const auto uri = conn_obj.uri(); // has form "/pre/fix/es.../BAU_1_000441077_2_1.j2k/full/,1000/0/default.jpg" - - std::vector params {}; - auto request_type {handlers::iiif_handler::UNDEFINED}; - - auto parse_url_result = handlers::iiif_handler::parse_iiif_url(uri); - - if (parse_url_result.has_value()) { - params = parse_url_result.value().params; - request_type = parse_url_result.value().request_type; - } else { - send_error(conn_obj, Connection::BAD_REQUEST, parse_url_result.error()); + switch (request_type) { + case handlers::iiif_handler::IIIF: { + serve_iiif(conn_obj, luaserver, serv, prefix_as_path, uri, params); return; } - - switch (request_type) { - case handlers::iiif_handler::IIIF: { - serve_iiif(conn_obj, luaserver, serv, prefix_as_path, uri, params); - return; - } - case handlers::iiif_handler::INFO_JSON: { - serve_info_json_file(conn_obj, serv, luaserver, params, prefix_as_path); - return; - } - case handlers::iiif_handler::KNORA_JSON: { - serve_knora_json_file(conn_obj, serv, luaserver, params, prefix_as_path); - return; - } - case handlers::iiif_handler::REDIRECT: { - serve_redirect(conn_obj, params); - return; - } - case handlers::iiif_handler::FILE_DOWNLOAD: { - serve_file_download(conn_obj, luaserver, serv, prefix_as_path, params); - return; - } - case handlers::iiif_handler::UNDEFINED: { - send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, "Unknown internal error!"); - return; - } + case handlers::iiif_handler::INFO_JSON: { + serve_info_json_file(conn_obj, serv, luaserver, params, prefix_as_path); + return; + } + case handlers::iiif_handler::KNORA_JSON: { + serve_knora_json_file(conn_obj, serv, luaserver, params, prefix_as_path); + return; + } + case handlers::iiif_handler::REDIRECT: { + serve_redirect(conn_obj, params); + return; + } + case handlers::iiif_handler::FILE_DOWNLOAD: { + serve_file_download(conn_obj, luaserver, serv, prefix_as_path, params); + return; + } + case handlers::iiif_handler::UNDEFINED: { + send_error(conn_obj, Connection::INTERNAL_SERVER_ERROR, "Unknown internal error!"); + return; } } - //========================================================================= +} - static void favicon_handler(Connection &conn_obj, shttps::LuaServer &luaserver, void *user_data, void *dummy) { - conn_obj.status(Connection::OK); - conn_obj.header("Content-Type", "image/x-icon"); - conn_obj.send(favicon_ico, favicon_ico_len); - } - //========================================================================= - - SipiHttpServer::SipiHttpServer(int port_p, const size_t nthreads_p, const std::string userid_str, - const std::string &logfile_p, const std::string &loglevel_p) : Server::Server(port_p, - nthreads_p, - userid_str, - logfile_p, - loglevel_p) - { - _salsah_prefix = "imgrep"; +//========================================================================= + +static void favicon_handler(Connection& conn_obj, shttps::LuaServer& luaserver, void* user_data, void* dummy) { + conn_obj.status(Connection::OK); + conn_obj.header("Content-Type", "image/x-icon"); + conn_obj.send(favicon_ico, favicon_ico_len); +} + +//========================================================================= + +SipiHttpServer::SipiHttpServer(int port_p, + const size_t nthreads_p, + const std::string userid_str, + const std::string& logfile_p, + const std::string& loglevel_p) : + Server::Server(port_p, + nthreads_p, + userid_str, + logfile_p, + loglevel_p) { + _salsah_prefix = "imgrep"; + _cache = nullptr; + _scaling_quality = {HIGH, HIGH, HIGH, HIGH}; +} + +//========================================================================= + +void SipiHttpServer::cache(const std::string& cachedir_p, + size_t max_cachesize_p, + size_t max_nfiles_p, + float cache_hysteresis_p) { + try { + _cache = std::make_shared(cachedir_p, max_cachesize_p, max_nfiles_p, cache_hysteresis_p); + } catch (const SipiError& err) { _cache = nullptr; - _scaling_quality = {HIGH, HIGH, HIGH, HIGH}; + syslog(LOG_WARNING, "Couldn't open cache directory %s: %s", cachedir_p.c_str(), err.to_string().c_str()); } - //========================================================================= +} - void SipiHttpServer::cache(const std::string &cachedir_p, size_t max_cachesize_p, size_t max_nfiles_p, - float cache_hysteresis_p) { - try { - _cache = std::make_shared(cachedir_p, max_cachesize_p, max_nfiles_p, cache_hysteresis_p); - } - catch (const SipiError &err) { - _cache = nullptr; - syslog(LOG_WARNING, "Couldn't open cache directory %s: %s", cachedir_p.c_str(), err.to_string().c_str()); - } - } - //========================================================================= +//========================================================================= - // here we add the main IIIF route to the server (iiif_handler) - void SipiHttpServer::run() { - const int old_ll = setlogmask(LOG_MASK(LOG_INFO)); - syslog(LOG_INFO, "SipiHttpServer starting ..."); - // - // setting the image root - // - syslog(LOG_INFO, "Serving images from %s", _imgroot.c_str()); - syslog(LOG_DEBUG, "Salsah prefix: %s", _salsah_prefix.c_str()); - setlogmask(old_ll); +// here we add the main IIIF route to the server (iiif_handler) +void SipiHttpServer::run() { + const int old_ll = setlogmask(LOG_MASK(LOG_INFO)); + syslog(LOG_INFO, "SipiHttpServer starting ..."); + // + // setting the image root + // + syslog(LOG_INFO, "Serving images from %s", _imgroot.c_str()); + syslog(LOG_DEBUG, "Salsah prefix: %s", _salsah_prefix.c_str()); + setlogmask(old_ll); - add_route(Connection::GET, "/favicon.ico", favicon_handler); - add_route(Connection::GET, "/", iiif_handler); + add_route(Connection::GET, "/favicon.ico", favicon_handler); + add_route(Connection::GET, "/", iiif_handler); - user_data(this); + user_data(this); - // in shttps::Server::run(), add additional routes are added, namely the ones for the LUA scripts - Server::run(); - } - //========================================================================= + // in shttps::Server::run(), add additional routes are added, namely the ones for the LUA scripts + Server::run(); +} +//========================================================================= } diff --git a/src/SipiHttpServer.hpp b/src/SipiHttpServer.hpp index 88818c48..c011e5d8 100644 --- a/src/SipiHttpServer.hpp +++ b/src/SipiHttpServer.hpp @@ -46,15 +46,16 @@ #include "SipiIO.h" -namespace Sipi { - +namespace Sipi +{ /*! * The class SipiHttpServer implements a webserver that can be used to serve images using the IIIF * API. For details on the API look for \url http://iiif.io . I implemented support for * cross domain scripting (CORS according to \url http://www.html5rocks.com/en/tutorials/cors/). As a * special feature we support acces to the old PHP-based salsah version (this is a bad hack!) */ - class SipiHttpServer : public shttps::Server { + class SipiHttpServer : public shttps::Server + { private: protected: pid_t _pid{}; @@ -80,26 +81,26 @@ namespace Sipi { * \param loglevel_p Loglevel (DEBUG, INFO, WARNING, ERROR, CRITICAL) */ explicit SipiHttpServer(int port_p, size_t nthreads_p = 4, std::string userid_str = "", - const std::string &logfile_p = "sipi.log", const std::string &loglevel_p = "DEBUG"); + const std::string& logfile_p = "sipi.log", const std::string& loglevel_p = "DEBUG"); void run() override; static std::pair - get_canonical_url(size_t img_w, size_t img_h, const std::string &host, const std::string &prefix, - const std::string &identifier, std::shared_ptr region, - std::shared_ptr size, SipiRotation &rotation, - SipiQualityFormat &quality_format, int pagenum = 0); + get_canonical_url(size_t img_w, size_t img_h, const std::string& host, const std::string& prefix, + const std::string& identifier, std::shared_ptr region, + std::shared_ptr size, SipiRotation& rotation, + SipiQualityFormat& quality_format, int pagenum = 0); pid_t pid() const { return _pid; } - void imgroot(const std::string &imgroot_p) { _imgroot = imgroot_p; } + void imgroot(const std::string& imgroot_p) { _imgroot = imgroot_p; } std::string imgroot() { return _imgroot; } std::string salsah_prefix() { return _salsah_prefix; } - void salsah_prefix(const std::string &salsah_prefix) { _salsah_prefix = salsah_prefix; } + void salsah_prefix(const std::string& salsah_prefix) { _salsah_prefix = salsah_prefix; } bool prefix_as_path() const { return _prefix_as_path; } @@ -107,69 +108,97 @@ namespace Sipi { std::vector dirs_to_exclude() { return _dirs_to_exclude; } - void dirs_to_exclude(const std::vector &dirs_to_exclude) { _dirs_to_exclude = dirs_to_exclude; } + void dirs_to_exclude(const std::vector& dirs_to_exclude) { _dirs_to_exclude = dirs_to_exclude; } void jpeg_quality(int jpeg_quality_p) { _jpeg_quality = jpeg_quality_p; } void j2k_compression_profiles( - const std::unordered_map &j2k_compression_profiles) { + const std::unordered_map& j2k_compression_profiles) + { _j2k_compression_profiles = j2k_compression_profiles; } int jpeg_quality() const { return _jpeg_quality; } - void scaling_quality(std::map jpeg_quality_p) { - if (jpeg_quality_p["jpk"] == "high") { + void scaling_quality(std::map jpeg_quality_p) + { + if (jpeg_quality_p["jpk"] == "high") + { _scaling_quality.jk2 = HIGH; - } else if (jpeg_quality_p["jpk"] == "medium") { + } + else if (jpeg_quality_p["jpk"] == "medium") + { _scaling_quality.jk2 = MEDIUM; - } else if (jpeg_quality_p["jpk"] == "low") { + } + else if (jpeg_quality_p["jpk"] == "low") + { _scaling_quality.jk2 = LOW; - } else { + } + else + { _scaling_quality.jk2 = HIGH; } - if (jpeg_quality_p["jpeg"] == "high") { + if (jpeg_quality_p["jpeg"] == "high") + { _scaling_quality.jpeg = HIGH; - } else if (jpeg_quality_p["jpeg"] == "medium") { + } + else if (jpeg_quality_p["jpeg"] == "medium") + { _scaling_quality.jpeg = MEDIUM; - } else if (jpeg_quality_p["jpeg"] == "low") { + } + else if (jpeg_quality_p["jpeg"] == "low") + { _scaling_quality.jpeg = LOW; - } else { + } + else + { _scaling_quality.jpeg = HIGH; } - if (jpeg_quality_p["tiff"] == "high") { + if (jpeg_quality_p["tiff"] == "high") + { _scaling_quality.tiff = HIGH; - } else if (jpeg_quality_p["tiff"] == "medium") { + } + else if (jpeg_quality_p["tiff"] == "medium") + { _scaling_quality.tiff = MEDIUM; - } else if (jpeg_quality_p["tiff"] == "low") { + } + else if (jpeg_quality_p["tiff"] == "low") + { _scaling_quality.tiff = LOW; - } else { + } + else + { _scaling_quality.tiff = HIGH; } - if (jpeg_quality_p["png"] == "high") { + if (jpeg_quality_p["png"] == "high") + { _scaling_quality.png = HIGH; - } else if (jpeg_quality_p["png"] == "medium") { + } + else if (jpeg_quality_p["png"] == "medium") + { _scaling_quality.png = MEDIUM; - } else if (jpeg_quality_p["png"] == "low") { + } + else if (jpeg_quality_p["png"] == "low") + { _scaling_quality.png = LOW; - } else { + } + else + { _scaling_quality.png = HIGH; } } ScalingQuality scaling_quality() const { return _scaling_quality; } - void cache(const std::string &cachedir_p, size_t max_cachesize_p = 0, size_t max_nfiles_p = 0, + void cache(const std::string& cachedir_p, size_t max_cachesize_p = 0, size_t max_nfiles_p = 0, float cache_hysteresis_p = 0.1); std::shared_ptr cache() { return _cache; } - }; - } #endif diff --git a/src/SipiImage.cpp b/src/SipiImage.cpp index 0b31c012..872bf2c8 100755 --- a/src/SipiImage.cpp +++ b/src/SipiImage.cpp @@ -1713,7 +1713,7 @@ namespace Sipi { /*==========================================================================*/ - bool SipiImage::operator==(const SipiImage &rhs) { + bool SipiImage::operator==(const SipiImage &rhs) const{ if ((nx != rhs.nx) || (ny != rhs.ny) || (nc != rhs.nc) || (bps != rhs.bps) || (photo != rhs.photo)) { return false; } diff --git a/src/SipiImage.hpp b/src/SipiImage.hpp index 56131722..1be6d433 100755 --- a/src/SipiImage.hpp +++ b/src/SipiImage.hpp @@ -182,8 +182,8 @@ class SipiImageError final : public std::exception { */ class SipiImage { static std::unordered_map > io; //!< member variable holding a map of I/O class instances for the different file formats - static byte bilinn(byte buf[], register int nx, register double x, register double y, register int c, register int n); - static word bilinn(word buf[], register int nx, register double x, register double y, register int c, register int n); + static byte bilinn(byte buf[], int nx, double x, double y, int c, int n); + static word bilinn(word buf[], int nx, double x, double y, int c, int n); void ensure_exif(); protected: @@ -634,7 +634,7 @@ class SipiImageError final : public std::exception { SipiImage &operator+(const SipiImage &rhs); - bool operator==(const SipiImage &rhs); + bool operator==(const SipiImage &rhs) const; /*! * The overloaded << operator which is used to write the error message to the output diff --git a/src/handlers/iiif_handler.cpp b/src/handlers/iiif_handler.cpp index 569e5385..3ba1f62d 100644 --- a/src/handlers/iiif_handler.cpp +++ b/src/handlers/iiif_handler.cpp @@ -10,200 +10,170 @@ namespace handlers::iiif_handler { - // Implementation of the parse_iiif_url function - auto parse_iiif_url(const std::string &uri) noexcept -> std::expected { +template +std::string vector_to_string(const std::vector& vec) { + std::ostringstream oss; + oss << "["; + for (size_t i = 0; i < vec.size(); ++i) { + if (i > 0) { + oss << ", "; + } + oss << vec[i]; + } + oss << "]"; + return oss.str(); +} - RequestType request_type { UNDEFINED }; - std::vector parts; - size_t pos = 0; - size_t old_pos = 0; +/** + * This function parses parts of an IIIF URI and returns a struct with the result. + * + * In general, the IIIF URI schema looks like this: + * {scheme}://{server}{/prefix}/{identifier}/{region}/{size}/{rotation}/{quality}.{format} + * + * The string that is passed to this function is expected to be already stripped + * off of the {schema} and {server} parts, thus only getting: + * {/prefix}/{identifier}/{region}/{size}/{rotation}/{quality}.{format}, + * e.g., "/iiif/2/image.jpg/full/200,/0/default.jpg". + */ +[[nodiscard]] +auto parse_iiif_uri(const std::string& uri) noexcept -> std::expected { - // - // IIIF URi schema: - // {scheme}://{server}{/prefix}/{identifier}/{region}/{size}/{rotation}/{quality}.{format} - // - // The slashes "/" separate the different parts... - // - while ((pos = uri.find('/', pos)) != std::string::npos) { - pos++; - if (pos == 1) { - // if first char is a token skip it! - old_pos = pos; - continue; - } - parts.push_back(shttps::urldecode(uri.substr(old_pos, pos - old_pos - 1))); + // std::cout << ">> parsing IIIF URI: " << uri << std::endl; + + RequestType request_type {UNDEFINED}; + + std::vector parts; + size_t pos = 0; + size_t old_pos = 0; + + // + // IIIF URi schema: + // {scheme}://{server}{/prefix}/{identifier}/{region}/{size}/{rotation}/{quality}.{format} + // + // The slashes "/" separate the different parts... + // + while ((pos = uri.find('/', pos)) != std::string::npos) { + pos++; + if (pos == 1) { + // if first char is a token skip it! old_pos = pos; + continue; } + parts.push_back(shttps::urldecode(uri.substr(old_pos, pos - old_pos - 1))); + old_pos = pos; + } - if (old_pos != uri.length()) { - parts.push_back(shttps::urldecode(uri.substr(old_pos, std::string::npos))); - } + if (old_pos != uri.length()) { + parts.push_back(shttps::urldecode(uri.substr(old_pos, std::string::npos))); + } - if (parts.empty()) { - return std::unexpected("No parameters/path given"); - } + if (parts.empty()) { + return std::unexpected("No parameters/path given"); + } - std::vector params; + // std::cout << ">> parts found: " << vector_to_string(parts) << std::endl; - // - // below are regex expressions for the different parts of the IIIF URL - // - std::string qualform_ex = R"(^(color|gray|bitonal|default)\.(jpg|tif|png|jp2)$)"; - std::string rotation_ex = R"(^!?[-+]?[0-9]*\.?[0-9]*$)"; - std::string size_ex = R"(^(\^?max)|(\^?pct:[0-9]*\.?[0-9]*)|(\^?[0-9]*,)|(\^?,[0-9]*)|(\^?!?[0-9]*,[0-9]*)$)"; - std::string region_ex = - R"(^(full)|(square)|([0-9]+,[0-9]+,[0-9]+,[0-9]+)|(pct:[0-9]*\.?[0-9]*,[0-9]*\.?[0-9]*,[0-9]*\.?[0-9]*,[0-9]*\.?[0-9]*)$)"; + std::vector params; + + // + // below are regex expressions for the different parts of the IIIF URL + // + std::string qualform_ex = R"(^(color|gray|bitonal|default)\.(jpg|tif|png|jp2)$)"; + std::string rotation_ex = R"(^!?[-+]?[0-9]*\.?[0-9]*$)"; + std::string size_ex = R"(^(\^?max)|(\^?pct:[0-9]*\.?[0-9]*)|(\^?[0-9]*,)|(\^?,[0-9]*)|(\^?!?[0-9]*,[0-9]*)$)"; + std::string region_ex = + R"(^(full)|(square)|([0-9]+,[0-9]+,[0-9]+,[0-9]+)|(pct:[0-9]*\.?[0-9]*,[0-9]*\.?[0-9]*,[0-9]*\.?[0-9]*,[0-9]*\.?[0-9]*)$)"; + + bool qualform_ok = false; + if (!parts.empty()) { + // check if last part is a valid quality format + qualform_ok = std::regex_match(parts[parts.size() - 1], std::regex(qualform_ex)); + } + + bool rotation_ok = false; + if (parts.size() > 1) { + // check if second last part is a valid rotation + rotation_ok = std::regex_match(parts[parts.size() - 2], std::regex(rotation_ex)); + } - bool qualform_ok = false; - if (!parts.empty()) - qualform_ok = std::regex_match(parts[parts.size() - 1], std::regex(qualform_ex)); + bool size_ok = false; + if (parts.size() > 2) { + // check if third last part is a valid size + size_ok = std::regex_match(parts[parts.size() - 3], std::regex(size_ex)); + } - bool rotation_ok = false; - if (parts.size() > 1) - rotation_ok = std::regex_match(parts[parts.size() - 2], std::regex(rotation_ex)); + bool region_ok = false; + if (parts.size() > 3) { + // check if fourth last part is a valid region + region_ok = std::regex_match(parts[parts.size() - 4], std::regex(region_ex)); + } - bool size_ok = false; - if (parts.size() > 2) - size_ok = std::regex_match(parts[parts.size() - 3], std::regex(size_ex)); + // analyze the last part of the URL and look for a dot + if ((pos = parts[parts.size() - 1].find('.', 0)) != std::string::npos) { - bool region_ok = false; - if (parts.size() > 3) - region_ok = std::regex_match(parts[parts.size() - 4], std::regex(region_ex)); + // we have a dot and we will split the last part into a body and an extension + // at this point we know that the last part could be either a quality format, a knora.json or a info.json + std::string fname_body = parts[parts.size() - 1].substr(0, pos); + std::string fname_extension = parts[parts.size() - 1].substr(pos + 1, std::string::npos); - if ((pos = parts[parts.size() - 1].find('.', 0)) != std::string::npos) { - std::string fname_body = parts[parts.size() - 1].substr(0, pos); - std::string fname_extension = parts[parts.size() - 1].substr(pos + 1, std::string::npos); + // Let's check if we have a valid IIIF URL + if (qualform_ok && rotation_ok && size_ok && region_ok) { // - // we will serve IIIF syntax based image + // we potentially have a valid IIIF URL // - if (qualform_ok && rotation_ok && size_ok && region_ok) { - if (parts.size() >= 6) { - // we have a prefix - std::stringstream prefix; - for (int i = 0; i < (parts.size() - 5); i++) { - if (i > 0) - prefix << "/"; - prefix << parts[i]; - } - params.push_back(prefix.str()); // iiif_prefix - } else if (parts.size() == 5) { - // we have no prefix - params.emplace_back(""); // iiif_prefix - } else { - std::stringstream errmsg; - errmsg << "IIIF url not correctly formatted:"; - if (!qualform_ok) - errmsg << " Error in quality: \"" << parts[parts.size() - 1] << "\"!"; - if (!rotation_ok) - errmsg << " Error in rotation: \"" << parts[parts.size() - 2] << "\"!"; - if (!size_ok) - errmsg << " Error in size: \"" << parts[parts.size() - 3] << "\"!"; - if (!region_ok) - errmsg << " Error in region: \"" << parts[parts.size() - 4] << "\"!"; - return std::unexpected(errmsg.str()); - } - params.push_back(parts[parts.size() - 5]); // iiif_identifier - params.push_back(parts[parts.size() - 4]); // iiif_region - params.push_back(parts[parts.size() - 3]); // iiif_size - params.push_back(parts[parts.size() - 2]); // iiif_rotation - params.push_back(parts[parts.size() - 1]); // iiif_qualityformat - request_type = IIIF; - } else if ((fname_body == "info") && (fname_extension == "json")) { - // - // we have something like "http:://{server}/{prefix}/{id}/info.json - // - if (parts.size() >= 3) { - // we have a prefix - std::stringstream prefix; - for (int i = 0; i < (parts.size() - 2); i++) { - if (i > 0) - prefix << "/"; - prefix << parts[i]; - } - params.push_back(prefix.str()); // iiif_prefix - } else if (parts.size() == 2) { - // we have no prefix - params.push_back(""); // iiif_prefix - } else { - return std::unexpected("IIIF url not correctly formatted!"); - } - params.push_back(parts[parts.size() - 2]); // iiif_identifier - request_type = INFO_JSON; - } else if ((fname_body == "knora") && (fname_extension == "json")) { - // - // we have something like "http:://{server}/{prefix}/{id}/knora.json - // - if (parts.size() >= 3) { - // we have a prefix - std::stringstream prefix; - for (int i = 0; i < (parts.size() - 2); i++) { - if (i > 0) - prefix << "/"; - prefix << parts[i]; - } - params.push_back(prefix.str()); // iiif_prefix - } else if (parts.size() == 2) { - // we have no prefix - params.emplace_back(""); // iiif_prefix - } else { - return std::unexpected("IIIF url not correctly formatted!"); + if (parts.size() >= 6) { + // we have a prefix + std::stringstream prefix; + for (size_t i = 0; i < (parts.size() - 5); i++) { + if (i > 0) + prefix << "/"; + prefix << parts[i]; } - params.push_back(parts[parts.size() - 2]); // iiif_identifier - request_type = KNORA_JSON; + params.push_back(prefix.str()); // iiif_prefix + } else if (parts.size() == 5) { + // we have no prefix + params.emplace_back(""); // iiif_prefix } else { - // - // we have something like "http:://{server}/{prefix}/{id}" with id as "body.ext" - // - if (qualform_ok || rotation_ok || size_ok || region_ok) { - std::stringstream errmsg; - errmsg << "IIIF url not correctly formatted:"; - if (!qualform_ok && !parts.empty()) - errmsg << " Error in quality: \"" << parts[parts.size() - 1] << "\"!"; - if (!rotation_ok && (parts.size() > 1)) - errmsg << " Error in rotation: \"" << parts[parts.size() - 2] << "\"!"; - if (!size_ok && (parts.size() > 2)) - errmsg << " Error in size: \"" << parts[parts.size() - 3] << "\"!"; - if (!region_ok && (parts.size() > 3)) - errmsg << " Error in region: \"" << parts[parts.size() - 4] << "\"!"; - return std::unexpected(errmsg.str()); - } - if (parts.size() >= 2) { - // we have a prefix - std::stringstream prefix; - for (int i = 0; i < (parts.size() - 1); i++) { - if (i > 0) - prefix << "/"; - prefix << parts[i]; - } - params.push_back(prefix.str()); // iiif_prefix - } else if (parts.size() == 1) { - // we have no prefix - params.emplace_back(""); // iiif_prefix - } else { - std::stringstream errmsg; - errmsg << "IIIF url not correctly formatted:"; - if (!qualform_ok && (!parts.empty())) - errmsg << " Error in quality: \"" << parts[parts.size() - 1] << "\"!"; - if (!rotation_ok && (parts.size() > 1)) - errmsg << " Error in rotation: \"" << parts[parts.size() - 2] << "\"!"; - if (!size_ok && (parts.size() > 2)) - errmsg << " Error in size: \"" << parts[parts.size() - 3] << "\"!"; - if (!region_ok && (parts.size() > 3)) - errmsg << " Error in region: \"" << parts[parts.size() - 4] << "\"!"; - return std::unexpected(errmsg.str()); + std::stringstream errmsg; + errmsg << "IIIF url not correctly formatted:"; + return std::unexpected(errmsg.str()); + } + params.push_back(parts[parts.size() - 5]); // iiif_identifier + params.push_back(parts[parts.size() - 4]); // iiif_region + params.push_back(parts[parts.size() - 3]); // iiif_size + params.push_back(parts[parts.size() - 2]); // iiif_rotation + params.push_back(parts[parts.size() - 1]); // iiif_qualityformat + request_type = IIIF; + } else if ((fname_body == "info") && (fname_extension == "json")) { + // + // we potentially have something like "http:://{server}/{prefix}/{id}/info.json + // + if (parts.size() >= 3) { + // we have a prefix + std::stringstream prefix; + for (size_t i = 0; i < (parts.size() - 2); i++) { + if (i > 0) + prefix << "/"; + prefix << parts[i]; } - params.push_back(parts[parts.size() - 1]); // iiif_identifier - request_type = REDIRECT; + params.push_back(prefix.str()); // iiif_prefix + } else if (parts.size() == 2) { + // we have no prefix + params.emplace_back(""); // iiif_prefix + } else { + return std::unexpected("IIIF url not correctly formatted!"); } - } else if (parts[parts.size() - 1] == "file") { + params.push_back(parts[parts.size() - 2]); // iiif_identifier + request_type = INFO_JSON; + } else if ((fname_body == "knora") && (fname_extension == "json")) { + // + // we potentially have something like "http:://{server}/{prefix}/{id}/knora.json + // if (parts.size() >= 3) { // we have a prefix - // - // we have something like "http:://{server}/{prefix}/{id}/file - // std::stringstream prefix; - for (int i = 0; i < (parts.size() - 2); i++) { + for (size_t i = 0; i < (parts.size() - 2); i++) { if (i > 0) prefix << "/"; prefix << parts[i]; @@ -211,36 +181,23 @@ namespace handlers::iiif_handler { params.push_back(prefix.str()); // iiif_prefix } else if (parts.size() == 2) { // we have no prefix - // - // we have something like "http:://{server}/{id}/file - // params.emplace_back(""); // iiif_prefix } else { return std::unexpected("IIIF url not correctly formatted!"); } params.push_back(parts[parts.size() - 2]); // iiif_identifier - request_type = FILE_DOWNLOAD; + request_type = KNORA_JSON; } else { // - // we have something like "http:://{server}/{prefix}/{id}" with id as "body_without_ext" + // we potentially have something like "/{prefix}/{id}" with id as "body.ext" // - if (qualform_ok || rotation_ok || size_ok || region_ok) { - std::stringstream errmsg; - errmsg << "IIIF url not correctly formatted:"; - if (!qualform_ok && (parts.size() > 0)) - errmsg << " Error in quality: \"" << parts[parts.size() - 1] << "\"!"; - if (!rotation_ok && (parts.size() > 1)) - errmsg << " Error in rotation: \"" << parts[parts.size() - 2] << "\"!"; - if (!size_ok && (parts.size() > 2)) - errmsg << " Error in size: \"" << parts[parts.size() - 3] << "\"!"; - if (!region_ok && (parts.size() > 3)) - errmsg << " Error in region: \"" << parts[parts.size() - 4] << "\"!"; - return std::unexpected(errmsg.str()); - } if (parts.size() >= 2) { // we have a prefix + // + // we have something like "/{prefix}/{id}" with id as "body.ext" + // std::stringstream prefix; - for (int i = 0; i < (parts.size() - 1); i++) { + for (size_t i = 0; i < (parts.size() - 1); i++) { if (i > 0) prefix << "/"; prefix << parts[i]; @@ -248,11 +205,14 @@ namespace handlers::iiif_handler { params.push_back(prefix.str()); // iiif_prefix } else if (parts.size() == 1) { // we have no prefix + // + // we have something like "/{id}" with id as "body.ext" + // params.emplace_back(""); // iiif_prefix } else { std::stringstream errmsg; errmsg << "IIIF url not correctly formatted:"; - if (!qualform_ok && (parts.size() > 0)) + if (!qualform_ok && (!parts.empty())) errmsg << " Error in quality: \"" << parts[parts.size() - 1] << "\"!"; if (!rotation_ok && (parts.size() > 1)) errmsg << " Error in rotation: \"" << parts[parts.size() - 2] << "\"!"; @@ -265,8 +225,58 @@ namespace handlers::iiif_handler { params.push_back(parts[parts.size() - 1]); // iiif_identifier request_type = REDIRECT; } - - return IIIFUrlParseResult{request_type, params}; + } else if (parts[parts.size() - 1] == "file") { + // + // we potentially have something like "/{prefix}/{id}/file + // + if (parts.size() >= 3) { + // we have a prefix + // + // we have something like "/{prefix}/{id}/file + // + std::stringstream prefix; + for (size_t i = 0; i < (parts.size() - 2); i++) { + if (i > 0) + prefix << "/"; + prefix << parts[i]; + } + params.push_back(prefix.str()); // iiif_prefix + } else if (parts.size() == 2) { + // we have no prefix + // + // we have something like "/{id}/file + // + params.emplace_back(""); // iiif_prefix + } else { + return std::unexpected("IIIF url not correctly formatted!"); + } + params.push_back(parts[parts.size() - 2]); // iiif_identifier + request_type = FILE_DOWNLOAD; + } else { + // + // we potentially have something like "/{prefix}/{id}" with id as "body_without_ext" + // + if (parts.size() >= 2) { + // we have a prefix + std::stringstream prefix; + for (size_t i = 0; i < (parts.size() - 1); i++) { + if (i > 0) + prefix << "/"; + prefix << parts[i]; + } + params.push_back(prefix.str()); // iiif_prefix + } else if (parts.size() == 1) { + // we have no prefix + params.emplace_back(""); // iiif_prefix + } else { + return std::unexpected("IIIF url not correctly formatted!"); + } + params.push_back(parts[parts.size() - 1]); // iiif_identifier + request_type = REDIRECT; } + // std::cout << ">> params: " << vector_to_string(params) << ", request_type: " << request_type << std::endl; + + return IIIFUriParseResult {request_type, params}; +} } // namespace handlers::iiif_handler diff --git a/src/handlers/iiif_handler.hpp b/src/handlers/iiif_handler.hpp index 8b36d796..d3044ad4 100644 --- a/src/handlers/iiif_handler.hpp +++ b/src/handlers/iiif_handler.hpp @@ -10,9 +10,10 @@ #include #include -namespace handlers::iiif_handler { - - enum RequestType { +namespace handlers::iiif_handler +{ + enum RequestType + { IIIF, INFO_JSON, KNORA_JSON, @@ -21,8 +22,10 @@ namespace handlers::iiif_handler { UNDEFINED }; - inline std::string requestTypeToString(const RequestType type) { - switch (type) { + inline std::string request_type_to_string(const RequestType type) + { + switch (type) + { case IIIF: return "IIIF"; case INFO_JSON: @@ -42,35 +45,43 @@ namespace handlers::iiif_handler { // Struct to hold the result of parsing an IIIF URL - struct IIIFUrlParseResult { + struct IIIFUriParseResult + { RequestType request_type; std::vector params; - [[nodiscard]] std::string to_string() const { + [[nodiscard]] std::string to_string() const + { std::stringstream result; // Assuming you have a function to convert RequestType to string // If not, you'll need to implement this according to your enum - std::string requestTypeStr = requestTypeToString(request_type); + const std::string requestTypeStr = request_type_to_string(request_type); result << "request_type: " << requestTypeStr << ", params: "; // Concatenate params - for (size_t i = 0; i < params.size(); ++i) { + for (size_t i = 0; i < params.size(); ++i) + { result << params[i]; - if (i != params.size() - 1) { + if (i != params.size() - 1) + { result << ", "; // Add a separator between the params, but not after the last one } } return result.str(); } + + [[nodiscard]] bool operator==(const IIIFUriParseResult& other) const + { + return request_type == other.request_type && params == other.params; + } }; // Free function to parse an IIIF URL returning either the result or an error message [[nodiscard]] auto - parse_iiif_url(const std::string &uri) noexcept -> std::expected; - + parse_iiif_uri(const std::string& uri) noexcept -> std::expected; } // namespace handlers::iiif_handler diff --git a/src/metadata/SipiIcc.cpp b/src/metadata/SipiIcc.cpp index ee3df53a..3c3b3167 100755 --- a/src/metadata/SipiIcc.cpp +++ b/src/metadata/SipiIcc.cpp @@ -235,7 +235,7 @@ namespace Sipi { std::vector data; if (buf != nullptr) { data.reserve(len); - for (int i = 0; i < len; i++) data.push_back(buf[i]); + for (size_t i = 0; i < len; i++) data.push_back(buf[i]); delete[] buf; } return data; diff --git a/test/unit/handlers/iiif_handler_test.cpp b/test/unit/handlers/iiif_handler_test.cpp index 505c20b6..d946f0f4 100644 --- a/test/unit/handlers/iiif_handler_test.cpp +++ b/test/unit/handlers/iiif_handler_test.cpp @@ -5,26 +5,49 @@ using namespace handlers::iiif_handler; TEST(iiif_handler, parse_correct_iiif_url) { - const auto result = parse_iiif_url("http://example.com/iiif/2/image.jpg/full/200,/0/default.jpg"); + const auto result = parse_iiif_uri("/iiif/2/image.jpg/full/200,/0/default.jpg"); EXPECT_TRUE(result.has_value()); } TEST(iiif_handler, parse_empty_iiif_url) { - const auto result = parse_iiif_url(""); + const auto result = parse_iiif_uri(""); EXPECT_FALSE(result.has_value()); EXPECT_EQ(result.error(), "No parameters/path given"); } -TEST(iiif_handler, parse_iiif_url_needing_redirect) { - const auto result = parse_iiif_url("https://iiif.dasch.swiss/0812/3KtDiJm4XxY-1PUUCffsF4S.jpx"); - EXPECT_TRUE(result.has_value()); - std::cout << result.value().to_string() << std::endl; - EXPECT_EQ(result->request_type, REDIRECT); +TEST(iiif_handler, parse_iiif_base_uri_needing_redirect) { + std::vector> valid_base_uris = { + {"/2", IIIFUriParseResult{REDIRECT, {"", "2"}}}, + {"/iiif/3", IIIFUriParseResult{REDIRECT, {"iiif", "3"}}}, + {"/iiif/3/image1", IIIFUriParseResult{REDIRECT, {"iiif/3", "image1"}}}, + {"/iiif/3/image2", IIIFUriParseResult{REDIRECT, {"iiif/3", "image2"}}}, + {"/prefix/12345", IIIFUriParseResult{REDIRECT, {"prefix", "12345"}}}, + {"/collections/item123", IIIFUriParseResult{REDIRECT, {"collections", "item123"}}}, + {"/iiif/v2/abcd1234", IIIFUriParseResult{REDIRECT, {"iiif/v2", "abcd1234"}}}, + {"/iiif/images/5678", IIIFUriParseResult{REDIRECT, {"iiif/images", "5678"}}}, + {"/iiif/3/4/uniqueImageIdentifier", IIIFUriParseResult{REDIRECT, {"iiif/3/4", "uniqueImageIdentifier"}}}, + {"/prefix/path/to/image", IIIFUriParseResult{REDIRECT, {"prefix/path/to", "image"}}}, + {"/iiif/3/special%2Fchars%3Fhere", IIIFUriParseResult{REDIRECT, {"iiif/3", "special/chars?here"}}}, + {"/iiif/images/xyz", IIIFUriParseResult{REDIRECT, {"iiif/images", "xyz"}}}, + {"/0812/3KtDiJm4XxY-1PUUCffsF4S.jpx", IIIFUriParseResult{REDIRECT, {"0812", "3KtDiJm4XxY-1PUUCffsF4S.jpx"}} + } + }; + + for (const auto& test_case: valid_base_uris) { + auto result = parse_iiif_uri(std::get<0>(test_case)); + EXPECT_EQ(result, std::get<1>(test_case)) << "URI should be valid but was considered invalid: " << std::get<0>(test_case) << ", error: " << result.error() << std::endl; + } } -TEST(iiif_handler, not_parse_incomplete_iiif_url) { - const auto result = parse_iiif_url("https://iiif.dasch.swiss/0812"); - EXPECT_FALSE(result.has_value()); - std::cout << result.value().to_string() << std::endl; - EXPECT_EQ(result.error(), "No parameters/path given"); +TEST(iiif_handler, not_parse_invalid_iiif_uris_missing_identifier_and_parameters) { + + std::vector invalid_uris = { + "/", + "//2/" + }; + + for (const auto& uri: invalid_uris) { + auto result = parse_iiif_uri(uri); + EXPECT_FALSE(result.has_value()) << "URI should be invalid but was considered valid: " << uri << ", parse_result: " << result->to_string() << std::endl; + } } From 036eb8345ba56bae806fec9e5313584f9d1ef923 Mon Sep 17 00:00:00 2001 From: Ivan Subotic <400790+subotic@users.noreply.github.com> Date: Fri, 22 Mar 2024 11:38:35 +0100 Subject: [PATCH 6/6] fix: URI parsing and redirection --- src/handlers/iiif_handler.cpp | 62 +++++++++++++++++++----- test/e2e/test_02_server.py | 4 +- test/unit/handlers/iiif_handler_test.cpp | 11 ++++- 3 files changed, 60 insertions(+), 17 deletions(-) diff --git a/src/handlers/iiif_handler.cpp b/src/handlers/iiif_handler.cpp index 3ba1f62d..caa55d7f 100644 --- a/src/handlers/iiif_handler.cpp +++ b/src/handlers/iiif_handler.cpp @@ -35,6 +35,9 @@ std::string vector_to_string(const std::vector& vec) { * off of the {schema} and {server} parts, thus only getting: * {/prefix}/{identifier}/{region}/{size}/{rotation}/{quality}.{format}, * e.g., "/iiif/2/image.jpg/full/200,/0/default.jpg". + * + * TODO: The parsing is not complete and needs to be extended to cover all cases. + * TODO: Property based testing would be useful here. */ [[nodiscard]] auto parse_iiif_uri(const std::string& uri) noexcept -> std::expected { @@ -80,7 +83,10 @@ auto parse_iiif_uri(const std::string& uri) noexcept -> std::expected std::expected= 6) { // we have a prefix @@ -135,9 +141,7 @@ auto parse_iiif_uri(const std::string& uri) noexcept -> std::expected std::expected std::expected= 2) { // we have a prefix // // we have something like "/{prefix}/{id}" with id as "body.ext" // + + // we have a prefix std::stringstream prefix; for (size_t i = 0; i < (parts.size() - 1); i++) { - if (i > 0) + if (parts[i].empty()) { + return std::unexpected("IIIF url not correctly formatted!"); + } + if (!prefix.str().empty()) { prefix << "/"; + } prefix << parts[i]; } params.push_back(prefix.str()); // iiif_prefix @@ -236,7 +261,7 @@ auto parse_iiif_uri(const std::string& uri) noexcept -> std::expected 0) + if (!prefix.str().empty()) prefix << "/"; prefix << parts[i]; } @@ -255,13 +280,28 @@ auto parse_iiif_uri(const std::string& uri) noexcept -> std::expected= 2) { // we have a prefix std::stringstream prefix; for (size_t i = 0; i < (parts.size() - 1); i++) { - if (i > 0) + if (parts[i].empty()) { + return std::unexpected("IIIF url not correctly formatted!"); + } + if (!prefix.str().empty()) { prefix << "/"; + } prefix << parts[i]; } params.push_back(prefix.str()); // iiif_prefix @@ -275,8 +315,6 @@ auto parse_iiif_uri(const std::string& uri) noexcept -> std::expected> params: " << vector_to_string(params) << ", request_type: " << request_type << std::endl; - return IIIFUriParseResult {request_type, params}; } } // namespace handlers::iiif_handler diff --git a/test/e2e/test_02_server.py b/test/e2e/test_02_server.py index 2070a88e..c6abef03 100644 --- a/test/e2e/test_02_server.py +++ b/test/e2e/test_02_server.py @@ -99,7 +99,7 @@ def test_deny(self, manager): manager.expect_status_code( "/knora/DenyLeaves.jpg/full/max/0/default.jpg", 401) - def test_iiifurl_parsing(self, manager): + def test_iiif_url_parsing(self, manager): """Return 400 for invalid IIIF URL's""" manager.expect_status_code("/unit//lena512.jp2", 400) manager.expect_status_code("/unit/lena512.jp2/max/0/default.jpg", 400) @@ -764,5 +764,3 @@ def test_exif_gps(self, manager): #print('response_json-------------------------------') #print(response_json) #print('response_json-------------------------------') - - diff --git a/test/unit/handlers/iiif_handler_test.cpp b/test/unit/handlers/iiif_handler_test.cpp index d946f0f4..b8cbf7fb 100644 --- a/test/unit/handlers/iiif_handler_test.cpp +++ b/test/unit/handlers/iiif_handler_test.cpp @@ -39,11 +39,18 @@ TEST(iiif_handler, parse_iiif_base_uri_needing_redirect) { } } -TEST(iiif_handler, not_parse_invalid_iiif_uris_missing_identifier_and_parameters) { +TEST(iiif_handler, not_parse_invalid_iiif_uris) { std::vector invalid_uris = { "/", - "//2/" + "//2/", + "/unit//lena512.jp2", + "/unit/lena512.jp2/max/0/default.jpg", + "/unit/lena512.jp2/full/max/default.jpg", + "/unit/lena512.jp2/full/max/!/default.jpg", + "/unit/lena512.jp2/full/max/0/jpg", + "/knora/67352ccc-d1b0-11e1-89ae-279075081939.jp2/full/max/0/default.aN", + "/knora/67352ccc-d1b0-11e1-89ae-279075081939.jp2/full/max/0/BFTP=w.jpg", }; for (const auto& uri: invalid_uris) {