diff --git a/CMakeLists.txt b/CMakeLists.txt index 00c5a7c..a332c12 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,23 +1,32 @@ cmake_minimum_required (VERSION 3.2) project(libravatarserv - VERSION 0.0.0 + VERSION 0.1.0 LANGUAGES CXX ) include(GNUInstallDirs) find_package(PkgConfig REQUIRED) -pkg_check_modules(LIBPNG REQUIRED libpng) +pkg_check_modules(MAGICPP REQUIRED Magick++) +pkg_check_modules(LIBCRYPTOPP REQUIRED libcryptopp) +pkg_check_modules(LIBXDG_BASEDIR REQUIRED libxdg-basedir) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -g -Og") +string(REPLACE ";" " " MAGICPP_CFLAGS_STRING "${MAGICPP_CFLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${MAGICPP_CFLAGS_STRING}") +set(CMAKE_CXX_FLAGS_DEBUG + "${CMAKE_CXX_FLAGS_DEBUG} -Wall -pedantic -Wextra -g -Og") include_directories(${PROJECT_BINARY_DIR}) -include_directories(${LIBPNG_INCLUDE_DIRS}) +include_directories(${MAGICPP_INCLUDE_DIRS}) +include_directories(${LIBCRYPTOPP_INCLUDE_DIRS}) +include_directories(${LIBXDG_BASEDIR_INCLUDE_DIRS}) -link_directories(${LIBPNG_LIBRARY_DIRS}) +link_directories(${MAGICPP_LIBRARY_DIRS}) +link_directories(${LIBCRYPTOPP_LIBRARY_DIRS}) +link_directories(${LIBXDG_BASEDIR_LIBRARY_DIRS}) # Write version in header configure_file( @@ -26,6 +35,8 @@ configure_file( ) file(GLOB sources src/*.cpp) -add_executable(${CMAKE_PROJECT_NAME} ${sources}) -target_link_libraries(${CMAKE_PROJECT_NAME} ${LIBPNG_LIBRARIES}) +add_executable(${CMAKE_PROJECT_NAME} "${sources}") +target_link_libraries(${CMAKE_PROJECT_NAME} + "${MAGICPP_LDFLAGS} ${LIBCRYPTOPP_LDFLAGS}" + "${LIBXDG_BASEDIR_LDFLAGS} -lstdc++fs") install(TARGETS ${CMAKE_PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/src/hash.cpp b/src/hash.cpp new file mode 100644 index 0000000..5efd37e --- /dev/null +++ b/src/hash.cpp @@ -0,0 +1,71 @@ +/* This file is part of libravatarserv. + * Copyright © 2018 tastytea + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1 +#include +#include +#include +#include +#include "libravatarserv.hpp" + +using global::avatar_dir; +using namespace hash; + +const string hash::md5(const string &text) +{ + using namespace CryptoPP; + Weak::MD5 hash; + string digest; + + StringSource s(text, true, + new HashFilter(hash, + new HexEncoder(new StringSink(digest)))); + std::transform(digest.begin(), digest.end(), digest.begin(), ::tolower); + return digest; +} + +const string hash::sha256(const string &text) +{ + using namespace CryptoPP; + SHA256 hash; + string digest; + + StringSource s(text, true, + new HashFilter(hash, + new HexEncoder(new StringSink(digest)))); + + std::transform(digest.begin(), digest.end(), digest.begin(), ::tolower); + return digest; +} + +bool hash::fill_table() +{ + + + + for (const fs::path &path : fs::recursive_directory_iterator(avatar_dir)) + { + if (fs::is_regular_file(path)) + { + string email = path.filename(); + std::transform(email.begin(), email.end(), email.begin(), ::tolower); + table.insert({ md5(email), email }); + table.insert({ sha256(email), email }); + } + } + return true; +} diff --git a/src/image.cpp b/src/image.cpp new file mode 100644 index 0000000..2e87965 --- /dev/null +++ b/src/image.cpp @@ -0,0 +1,63 @@ +/* This file is part of libravatarserv. + * Copyright © 2018 tastytea + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include "libravatarserv.hpp" + +using std::cerr; +using std::endl; +using global::avatar_dir; +using namespace image; + +const Image image::get(const string &digest, const uint16_t size) +{ + uint8_t error = 0; + Magick::Image img; + fs::path filename; + + const auto &entry = hash::table.find(digest); + if (entry == hash::table.end()) + { + filename = avatar_dir / "default.png"; + if (!fs::is_regular_file(filename)) + { + cerr << "Error: User not found and no default image set.\n"; + return { 2, img }; + } + } + else + { + filename = avatar_dir / entry->second; + } + + try + { + img.read(filename); + img.resize(Magick::Geometry(size, size)); + } + catch (const Magick::Exception &e) + { + cerr << "Error: " << e.what() << endl; + error = 2; + } + catch (const std::exception &e) + { + cerr << "Error: " << e.what() << endl; + error = 5; + } + + return { error, img }; +} diff --git a/src/libravatarserv.cpp b/src/libravatarserv.cpp index a515ae6..f1394b6 100644 --- a/src/libravatarserv.cpp +++ b/src/libravatarserv.cpp @@ -16,18 +16,101 @@ #include #include // getenv() -#include +#include +#include "libravatarserv.hpp" using std::cout; +using std::cerr; +using std::endl; +using global::avatar_dir; -int main(int argc, char *argv[]) +// Global variables +std::map hash::table; +fs::path global::avatar_dir = ""; + + +int main() { - cout << "Content-type: image/png\r\n\r\n"; - cout << std::getenv("REQUEST_URI") << "\r\n"; + const char *tmp = std::getenv("REQUEST_URI"); + if (tmp == nullptr) + { + cout << "Status: 404 Not Found\n\n"; + cerr << "Error: ${REQUEST_URI} is empty.\n"; + return 1; + } + const string request = tmp; - png::image image(argv[1]); + if (!find_avatar_dir()) + { + cout << "Status: 404 Not Found\n\n"; + cerr << "Error: No avatars found.\n"; + return 3; + } + hash::fill_table(); - image.write("/dev/stdout"); + if (request.substr(0, 8) != "/avatar/" || + request.find('/', 8) != std::string::npos) + { + cout << "Status: 404 Not Found\n\n"; + cerr << "Error: Invalid URL.\n"; + return 1; + } + cout << "Content-type: image/png\n\n"; + cout.flush(); // We need to flush before we use /dev/stdout directly. + + string digest = request.substr(8); + std::transform(digest.begin(), digest.end(), digest.begin(), ::tolower); + + image::Image answer = image::get(digest, 80); + if (answer.error == 0) + { + // answer.image.write("test.png"); + answer.image.write("/dev/stdout"); + } + else + { + cerr << "Error " << std::to_string(answer.error) << ": Could not open file.\n"; + } return 0; } + +bool find_avatar_dir() +{ + const char *envdir = std::getenv("AVATAR_DIR"); + if (envdir != nullptr) + { + if (fs::is_directory(envdir)) + { + avatar_dir = envdir; + } + else + { + return false; + } + } + else + { + xdgHandle xdg; + xdgInitHandle(&xdg); + auto data_dirs = xdgDataDirectories(&xdg); + const uint8_t size = sizeof(data_dirs) / sizeof(*data_dirs); + + for (uint8_t index = 0; index <= size; ++index) + { + const string searchdir = data_dirs[index] + string("/libravatarserv"); + if (fs::is_directory(searchdir)) + { + avatar_dir = searchdir; + break; + } + } + xdgWipeHandle(&xdg); + if (avatar_dir.empty()) + { + return false; + } + } + + return true; +} diff --git a/src/libravatarserv.hpp b/src/libravatarserv.hpp index 186ee8a..0bbecc5 100644 --- a/src/libravatarserv.hpp +++ b/src/libravatarserv.hpp @@ -17,6 +17,50 @@ #ifndef LIBRAVATARSERV_HPP #define LIBRAVATARSERV_HPP +#if __cplusplus >= 201703L + #include +#else + #include +#endif +#include +#include +#include +#if __cplusplus >= 201703L + namespace fs = std::filesystem; +#else + namespace fs = std::experimental::filesystem; +#endif +using std::string; +using std::uint16_t; +using std::uint8_t; + +int main(); +bool find_avatar_dir(); + +namespace global +{ + extern fs::path avatar_dir; +} + +namespace hash // hash.cpp +{ + extern std::map table; + + const string md5(const string &text); + const string sha256(const string &text); + bool fill_table(); +} + +namespace image //image.cpp +{ + struct Image + { + const uint8_t error; + Magick::Image image; + }; + + const Image get(const string &digest, const uint16_t size); +} #endif // LIBRAVATARSERV_HPP