diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..c8f20c7 --- /dev/null +++ b/.clang-format @@ -0,0 +1,119 @@ +# -*- mode: yaml -*- +# Written for clang-format 13. +# https://releases.llvm.org/13.0.0/tools/clang/docs/ClangFormatStyleOptions.html +--- +DisableFormat: false +Language: Cpp + +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: Left +AlignConsecutiveAssignments: None +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: None +AlignConsecutiveMacros: None +AlignEscapedNewlines: DontAlign +AlignOperands: Align +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: false +AllowAllConstructorInitializersOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: true +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Inline +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +# AttributeMacros: ['__capability', '__output', '__ununsed'] +BinPackArguments: true +BinPackParameters: true +BitFieldColonSpacing: Both +# BraceWrapping: # If BreakBeforeBraces is set to Custom. +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBraces: Attach +BreakBeforeConceptDeclarations: true +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma +BreakStringLiterals: true +ColumnLimit: 80 +# CommentPragmas: +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +EmptyLineBeforeAccessModifier: LogicalBlock +FixNamespaceComments: true +# ForEachMacros: ['FOREACH', 'RANGES_FOR', 'Q_FOREACH', 'BOOST_FOREACH' ] +# IfMacros: ['IF'] +IncludeBlocks: Regroup +IncludeCategories: # stdlib headers into own group. + - Regex: '^[^\.]+$' + Priority: 4 +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: false +IndentExternBlock: NoIndent +IndentGotoLabels: false +IndentPPDirectives: AfterHash +IndentRequires: true # Not sure yet +IndentWidth: 4 +IndentWrappedFunctionNames: false +InsertTrailingCommas: None +KeepEmptyLinesAtTheStartOfBlocks: true +LambdaBodyIndentation: Signature +# MacroBlockBegin: +# MacroBlockEnd: +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +# NamespaceMacros: +PenaltyBreakAssignment: 250 +PenaltyBreakBeforeFirstCallParameter: 300 +# PenaltyBreakComment: 300 +# PenaltyBreakFirstLessLess: 120 +# PenaltyBreakString: 1000 +# PenaltyBreakTemplateDeclaration: 10 +# PenaltyExcessCharacter: 1000000 +# PenaltyIndentedWhitespace: +PenaltyReturnTypeOnItsOwnLine: 100 +PointerAlignment: Right +ReflowComments: true +SortIncludes: CaseInsensitive +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +StatementAttributeLikeMacros: [emit] +# StatementMacros: [Q_UNUSED, QT_REQUIRE_VERSION] +TabWidth: 4 +# TypenameMacros: ['STACK_OF', 'LIST'] +UseCRLF: false +UseTab: Never +# WhitespaceSensitiveMacros: ['STRINGIZE', 'PP_STRINGIZE'] +... diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..f35d899 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,51 @@ +# -*- mode: conf; fill-column: 100; -*- +# Written for clang-tidy 13. + +--- +Checks: '*, + -cppcoreguidelines-non-private-member-variables-in-classes, + -fuchsia-default-arguments-calls, + -fuchsia-default-arguments-declarations, + -fuchsia-default-arguments, + -llvm-include-order, + -llvm-header-guard, + -misc-non-private-member-variables-in-classes, + -fuchsia-overloaded-operator, + -cppcoreguidelines-avoid-magic-numbers, + -readability-magic-numbers, + -cppcoreguidelines-pro-bounds-array-to-pointer-decay, + -hicpp-no-array-decay, + -modernize-avoid-c-arrays, + -cppcoreguidelines-avoid-c-arrays, + -hicpp-avoid-c-arrays, + -google-build-using-namespace, + -readability-named-parameter, + -google-runtime-references, + -hicpp-avoid-goto, + -hicpp-vararg, + -fuchsia-statically-constructed-objects, + -google-readability-todo, + -modernize-use-trailing-return-type, + -fuchsia-multiple-inheritance, + -llvmlibc*, + -cppcoreguidelines-avoid-non-const-global-variables, + -cert-*-c, + -abseil-string-find-*, + -altera-unroll-loops, + -altera-id-dependent-backward-branch, + -altera-struct-pack-align' +FormatStyle: file # Use .clang-format. +CheckOptions: # ↓ Clashes with static private member prefix. (static int _var;) ↓ + - { key: readability-identifier-naming.VariableCase, value: lower_case } + - { key: readability-identifier-naming.MemberCase, value: lower_case } + - { key: readability-identifier-naming.PrivateMemberCase, value: lower_case } + - { key: readability-identifier-naming.PrivateMemberPrefix, value: _ } + - { key: readability-identifier-naming.ProtectedMemberCase, value: lower_case } + - { key: readability-identifier-naming.ProtectedMemberPrefix, value: _ } + + - { key: readability-identifier-naming.ClassCase, value: lower_case } + - { key: readability-identifier-naming.StructCase, value: lower_case } + - { key: readability-identifier-naming.EnumCase, value: lower_case } + - { key: readability-identifier-naming.FunctionCase, value: lower_case } + - { key: readability-identifier-naming.ParameterCase, value: lower_case } +... diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ad5e59f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# Configuration file for EditorConfig. +# More information is available under . + +root = true + +[*] +indent_style = space +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +max_line_length = 80 + +[*.?pp] +indent_size = 4 +tab_width = 4 + +[{CMakeLists.txt,*.cmake}] +indent_size = 2 +tab_width = 2 diff --git a/CMakeLists.txt b/CMakeLists.txt index 1fe99a8..6e72547 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,16 +1,20 @@ -cmake_minimum_required(VERSION 3.10...3.20) +cmake_minimum_required(VERSION 3.12...3.20) + +# Global build options. +set(CMAKE_BUILD_TYPE "Release" CACHE STRING "The type of build.") +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + project(libravatarserv - VERSION 0.7.5 + VERSION 0.8.0 DESCRIPTION "A simple libravatar server." + HOMEPAGE_URL "https://schlomp.space/tastytea/libravatarserv" LANGUAGES CXX) list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") -include(GNUInstallDirs) - -set(CMAKE_CXX_STANDARD 14) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) +include(cmake/debug_flags.cmake) find_package(PkgConfig REQUIRED) pkg_check_modules(Magick++ REQUIRED IMPORTED_TARGET Magick++) @@ -19,30 +23,9 @@ if(NOT libcryptopp_FOUND) # Debian stretch package installs libcrypto++.pc pkg_check_modules(libcryptopp REQUIRED IMPORTED_TARGET libcrypto++) endif() -pkg_check_modules(libxdg-basedir REQUIRED IMPORTED_TARGET libxdg-basedir) find_package(identiconpp REQUIRED CONFIG) find_package(Filesystem REQUIRED COMPONENTS Final Experimental) -include(cmake/debug_flags.cmake) - -include_directories(${PROJECT_BINARY_DIR}) - -# Write version in header -configure_file( - "src/version.hpp.in" - "version.hpp") - -# Include or . -configure_file("src/fs-compat.hpp.in" "fs-compat.hpp" @ONLY) - -file(GLOB sources src/*.cpp) -add_executable(${PROJECT_NAME} ${sources}) -target_include_directories(${PROJECT_NAME} - PRIVATE ${PROJECT_BINARY_DIR}) -target_link_libraries(${PROJECT_NAME} - PRIVATE PkgConfig::Magick++ PkgConfig::libcryptopp PkgConfig::libxdg-basedir - std::filesystem identiconpp::identiconpp) -install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR}) -install(FILES README.md DESTINATION ${CMAKE_INSTALL_DOCDIR}) +add_subdirectory(src) include(cmake/packages.cmake) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..41af38c --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,37 @@ +{ + "version": 2, + "cmakeMinimumRequired": { + "major": 3, + "minor": 20, + "patch": 0 + }, + "configurePresets": [ + { + "name": "common", + "hidden": true, + "generator": "Unix Makefiles", + "binaryDir": "build", + "cacheVariables": { + "CMAKE_EXPORT_COMPILE_COMMANDS": true + } + }, + { + "name": "dev", + "displayName": "Developer config", + "description": "Build with debug symbols", + "inherits": "common", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "release", + "displayName": "Release config", + "description": "Build without debug symbols or tests", + "inherits": "common", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + } + ] +} diff --git a/cmake/debug_flags.cmake b/cmake/debug_flags.cmake index 7390512..9f1434d 100644 --- a/cmake/debug_flags.cmake +++ b/cmake/debug_flags.cmake @@ -24,9 +24,13 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang" "-Wdouble-promotion" "-Wformat=2" "-ftrapv" - "-fsanitize=undefined" "-Og" "-fno-omit-frame-pointer") + if(WITH_SANITIZERS) + list(APPEND tmp_CXXFLAGS + "-fsanitize=undefined" + "-fsanitize=address") + endif() if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") list(APPEND tmp_CXXFLAGS "-Wlogical-op" @@ -44,8 +48,11 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang" endif() add_compile_options("$<$:${tmp_CXXFLAGS}>") - list(APPEND tmp_LDFLAGS - "-fsanitize=undefined") + if(WITH_SANITIZERS) + list(APPEND tmp_LDFLAGS + "-fsanitize=undefined" + "-fsanitize=address") + endif() # add_link_options was introduced in version 3.13. if(${CMAKE_VERSION} VERSION_LESS 3.13) set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${tmp_LDFLAGS}") diff --git a/cmake/packages.cmake b/cmake/packages.cmake index 8a9983c..f8b5e3a 100644 --- a/cmake/packages.cmake +++ b/cmake/packages.cmake @@ -1,44 +1,57 @@ -# Packages -set(CPACK_PACKAGE_NAME ${CMAKE_PROJECT_NAME}) -set(CPACK_PACKAGE_VERSION_MAJOR ${${CMAKE_PROJECT_NAME}_VERSION_MAJOR}) -set(CPACK_PACKAGE_VERSION_MINOR ${${CMAKE_PROJECT_NAME}_VERSION_MINOR}) -set(CPACK_PACKAGE_VERSION_PATCH ${${CMAKE_PROJECT_NAME}_VERSION_PATCH}) -set(CPACK_PACKAGE_VERSION ${${CMAKE_PROJECT_NAME}_VERSION}) -set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Simple libravatar server.") -set(CPACK_PACKAGE_CONTACT "tastytea ") -set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") -set(CPACK_RESOURCE_FILE_README "${PROJECT_SOURCE_DIR}/README.md") -list(APPEND CPACK_SOURCE_IGNORE_FILES "/\\\\.git" - "/.gitignore" - "/build/" - "\\\\.sublime-" - "/.drone.yml") -execute_process(COMMAND uname -m - OUTPUT_VARIABLE CPACK_PACKAGE_ARCHITECTURE - OUTPUT_STRIP_TRAILING_WHITESPACE) -set(CPACK_PACKAGE_FILE_NAME - "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}_${CPACK_PACKAGE_ARCHITECTURE}") -set(CPACK_GENERATOR "TGZ") -set(CPACK_SOURCE_GENERATOR "TGZ") +include(GNUInstallDirs) -if (WITH_DEB) - set(CPACK_GENERATOR "DEB") - set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://schlomp.space/tastytea/${CMAKE_PROJECT_NAME}") - set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) - execute_process(COMMAND dpkg --print-architecture - OUTPUT_VARIABLE CPACK_DEBIAN_PACKAGE_ARCHITECTURE - OUTPUT_STRIP_TRAILING_WHITESPACE) - set(CPACK_PACKAGE_FILE_NAME - "${CPACK_PACKAGE_NAME}_${CPACK_PACKAGE_VERSION}-0_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}") +set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") +set(CPACK_PACKAGE_CONTACT "tastytea ") + +# Should be set automatically, but they are not. +set(CPACK_PACKAGE_NAME "${PROJECT_NAME}") +set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${CMAKE_PROJECT_DESCRIPTION}") + +# DEB +# Figure out dependencies automatically. +set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) + +# Should be set automatically, but it is not. +execute_process(COMMAND dpkg --print-architecture + OUTPUT_VARIABLE CPACK_DEBIAN_PACKAGE_ARCHITECTURE + OUTPUT_STRIP_TRAILING_WHITESPACE) + +execute_process(COMMAND lsb_release --codename --short + OUTPUT_VARIABLE DEBIAN_CODENAME + OUTPUT_STRIP_TRAILING_WHITESPACE) +if ("${DEBIAN_CODENAME}" STREQUAL "n/a") + set(DEBIAN_CODENAME "unknown") endif() -if (WITH_RPM) - set(CPACK_GENERATOR "RPM") - set(CPACK_RPM_PACKAGE_LICENSE "GPL-3") - set(CPACK_RPM_PACKAGE_URL "https://schlomp.space/tastytea/${CMAKE_PROJECT_NAME}") - set(CPACK_RPM_PACKAGE_REQUIRES "cryptopp++, libMagick++, libxdg-basedir >= 1.2.0") - set(CPACK_PACKAGE_FILE_NAME - "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-0.${CPACK_PACKAGE_ARCHITECTURE}") +# The default does not produce valid Debian package names. +set(CPACK_DEBIAN_FILE_NAME + "${CPACK_PACKAGE_NAME}_${CPACK_PACKAGE_VERSION}-0_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}_${DEBIAN_CODENAME}.deb") + +# RPM +set(CPACK_RPM_PACKAGE_LICENSE "GPL-3") + +# Figure out dependencies automatically. +set(CPACK_RPM_PACKAGE_AUTOREQ ON) + +# Should be set automatically, but it is not. +execute_process(COMMAND uname -m + OUTPUT_VARIABLE CPACK_RPM_PACKAGE_ARCHITECTURE + OUTPUT_STRIP_TRAILING_WHITESPACE) + +set(CPACK_PACKAGE_FILE_NAME + "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-0.${CPACK_RPM_PACKAGE_ARCHITECTURE}") + +execute_process(COMMAND lsb_release --id --short + OUTPUT_VARIABLE OS + OUTPUT_STRIP_TRAILING_WHITESPACE) + +if("${OS}" STREQUAL "openSUSE") + execute_process(COMMAND lsb_release --release --short + OUTPUT_VARIABLE OS_RELEASE + OUTPUT_STRIP_TRAILING_WHITESPACE) + set(CPACK_PACKAGE_FILE_NAME + "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-0.${CPACK_RPM_PACKAGE_ARCHITECTURE}.opensuse-${OS_RELEASE}") endif() include(CPack) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..09284cd --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,20 @@ +include(GNUInstallDirs) + +configure_file("version.hpp.in" "version.hpp" @ONLY) +configure_file("fs-compat.hpp.in" "fs-compat.hpp" @ONLY) + +file(GLOB sources_src CONFIGURE_DEPENDS *.cpp) +file(GLOB headers_src CONFIGURE_DEPENDS *.hpp) + +add_executable(${PROJECT_NAME}) +target_sources(${PROJECT_NAME} PRIVATE "${sources_src}" "${headers_src}") +target_include_directories(${PROJECT_NAME} + PRIVATE "$") +target_link_libraries(${PROJECT_NAME} + PRIVATE + PkgConfig::Magick++ + PkgConfig::libcryptopp + std::filesystem + identiconpp::identiconpp) + +install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/src/config.cpp b/src/config.cpp new file mode 100644 index 0000000..da1e847 --- /dev/null +++ b/src/config.cpp @@ -0,0 +1,33 @@ +/* This file is part of libravatarserv. + * Copyright © 2022 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 "config.hpp" + +#include "fs-compat.hpp" +#include "helpers.hpp" + +namespace libravatarserv::config { + +config read() { + config cfg; + cfg.avatar_dir = read_env("LIBRAVATARSERV_DIR", fs::current_path().c_str()); + cfg.default_fallback = read_env("LIBRAVATARSERV_DEFAULT_FALLBACK", "404"); + cfg.redirect = (read_env("LIBRAVATARSERV_REDIRECT") == "1"); + + return cfg; +} + +} // namespace libravatarserv::config diff --git a/src/config.hpp b/src/config.hpp new file mode 100644 index 0000000..fc43b24 --- /dev/null +++ b/src/config.hpp @@ -0,0 +1,36 @@ +/* This file is part of libravatarserv. + * Copyright © 2022 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 . + */ + +#ifndef LIBRAVATARSERV_CONFIG_HPP +#define LIBRAVATARSERV_CONFIG_HPP + +#include "fs-compat.hpp" + +#include + +namespace libravatarserv::config { + +struct config { + fs::path avatar_dir; + std::string default_fallback; + bool redirect; +}; + +[[nodiscard]] config read(); + +} // namespace libravatarserv::config + +#endif // LIBRAVATARSERV_CONFIG_HPP diff --git a/src/hash.cpp b/src/hash.cpp deleted file mode 100644 index aed9aa7..0000000 --- a/src/hash.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/* This file is part of libravatarserv. - * Copyright © 2018, 2020 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 "libravatarserv.hpp" - -#define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1 -#include -#include -#include -#include - -#include -#include - -using std::string; - -using namespace libravatarserv; -using namespace libravatarserv::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(settings::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; -} - -bool hash::is_valid(const string &digest) -{ - if (digest.length() != 64 && digest.length() != 32) - { - return false; - } - if (std::any_of(digest.begin(), digest.end(), not_hex)) - { - return false; - } - - return true; -} - -bool hash::not_hex(const char &c) -{ - if (c >= 0x61 && c <= 0x66) - { // a-f - return false; - } - if (c >= 0x30 && c <= 0x39) - { // 0-9 - return false; - } - - return true; -} diff --git a/src/helpers.cpp b/src/helpers.cpp new file mode 100644 index 0000000..07b477f --- /dev/null +++ b/src/helpers.cpp @@ -0,0 +1,34 @@ +/* This file is part of libravatarserv. + * Copyright © 2022 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 "helpers.hpp" + +#include + +namespace libravatarserv { + +// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) +std::string_view read_env(const std::string_view name, + const std::string_view default_value) { + // NOLINTNEXTLINE(concurrency-mt-unsave) + const char *env{std::getenv(name.data())}; + if (env != nullptr) { + return env; + } + return default_value; +} + +} // namespace libravatarserv diff --git a/src/helpers.hpp b/src/helpers.hpp new file mode 100644 index 0000000..04dde0e --- /dev/null +++ b/src/helpers.hpp @@ -0,0 +1,28 @@ +/* This file is part of libravatarserv. + * Copyright © 2022 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 . + */ + +#ifndef LIBRAVATARSERV_HELPERS_HPP +#define LIBRAVATARSERV_HELPERS_HPP + +#include +namespace libravatarserv { + +[[nodiscard]] std::string_view read_env(std::string_view name, + std::string_view default_value = ""); + +} // namespace libravatarserv + +#endif // LIBRAVATARSERV_HELPERS_HPP diff --git a/src/http.cpp b/src/http.cpp deleted file mode 100644 index 59668e1..0000000 --- a/src/http.cpp +++ /dev/null @@ -1,145 +0,0 @@ -/* This file is part of libravatarserv. - * Copyright © 2018,2019,2020 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 "libravatarserv.hpp" - -#include -#include -#include - -using std::cerr; -using std::cout; -using std::endl; -using namespace libravatarserv; -using namespace libravatarserv::http; - -const Request http::parse_request(const string &request) -{ - if (request.substr(0, 8) == "/favicon") - { - cout << "Status: 404 Not Found\n\n"; - std::exit(1); - } - if (request.substr(0, 8) != "/avatar/" - || request.find("..", 8) != std::string::npos) - { - cout << "Status: 400 Bad Request\n\n"; - cerr << "Error: Invalid URL.\n"; - std::exit(1); - } - - int16_t size = 80; - string fallback; - string digest = request.substr(8); - std::transform(digest.begin(), digest.end(), digest.begin(), ::tolower); - std::size_t pos_digest = digest.find('?'); - - if (pos_digest != std::string::npos) - { - string answer; - { - answer = get_parameter(request, "s"); - if (!answer.empty()) - { - try - { - size = static_cast(std::stoul(answer)); - } - catch (const std::exception &) - {} - } - else - { - answer = get_parameter(request, "size"); - if (!answer.empty()) - { - try - { - size = static_cast(std::stoul(answer)); - } - catch (const std::exception &) - {} - } - } - if (size > 512) - { - size = 512; - } - else if (size <= 0) - { - size = 80; - } - } - { - answer = get_parameter(request, "d"); - if (!answer.empty()) - { - fallback = answer; - } - else - { - answer = get_parameter(request, "default"); - if (!answer.empty()) - { - fallback = answer; - } - } - } - } - - digest = digest.substr(0, pos_digest); - pos_digest = digest.find('.'); - if (pos_digest != std::string::npos) - { - digest = digest.substr(0, pos_digest); - } - - return {digest, static_cast(size), fallback}; -} - -const string http::get_parameter(const string &request, const string ¶meter) -{ - std::size_t pos = request.find("&" + parameter + "="); - if (pos == std::string::npos) - { - pos = request.find("?" + parameter + "="); - } - if (pos != std::string::npos) - { - pos += (2 + parameter.length()); - return request.substr(pos, request.find('&', pos)); - } - - return ""; -} - -void http::send_redirect(const Request &request) -{ - const char *env = std::getenv("HTTPS"); - string baseurl; - if (env != nullptr && env[1] == 'n') - { // "on" - baseurl = "https://seccdn.libravatar.org"; - } - else - { - baseurl = "http://cdn.libravatar.org"; - } - cout << "Status: 307 Temporary Redirect\n"; - cout << "Location: " << baseurl << "/avatar/" << request.digest - << "?s=" << request.size << "&d=" << request.fallback << endl - << endl; -} diff --git a/src/image.cpp b/src/image.cpp deleted file mode 100644 index 6a23657..0000000 --- a/src/image.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/* This file is part of libravatarserv. - * Copyright © 2018, 2020 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 "libravatarserv.hpp" - -#include -#include -#include -#include -#include -#include -#include - -using std::cerr; -using std::cout; -using std::endl; -using namespace libravatarserv; -using namespace libravatarserv::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 = settings::avatar_dir / "default"; - if (!fs::is_regular_file(filename)) - { - cerr << "Warning: User not found and no default image set.\n"; - return {2, img}; - } - } - else - { - filename = settings::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}; -} - -void image::write(Image &image) -{ - string magick = image.image.magick(); - std::transform(magick.begin(), magick.end(), magick.begin(), ::tolower); - - Magick::Blob res_buffer; - image.image.magick(magick); // force the same format - image.image.write(&res_buffer); - - cout << "Content-Type: image/" << magick << endl; - cout << "Content-Length: " << res_buffer.length() << endl << endl; - cout.write(static_cast(res_buffer.data()), - res_buffer.length()); -} diff --git a/src/libravatarserv.cpp b/src/libravatarserv.cpp deleted file mode 100644 index 74d3511..0000000 --- a/src/libravatarserv.cpp +++ /dev/null @@ -1,161 +0,0 @@ -/* This file is part of libravatarserv. - * Copyright © 2018, 2019, 2020, 2021 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 "libravatarserv.hpp" - -#include "version.hpp" - -#include -#include - -#include - -using namespace libravatarserv; -using std::cerr; -using std::cout; -using std::endl; - -// Global variables -std::map hash::table; -fs::path settings::avatar_dir; -settings::Settings settings::settings; - -int main() -{ - cout << "Server: libravatarserv/" << global::version << endl; - - const char *request = std::getenv("REQUEST_URI"); - if (request == nullptr) - { - cout << "Status: 400 Bad Request\n\n"; - cerr << "Error: ${REQUEST_URI} is empty.\n"; - return 1; - } - http::Request avatar = http::parse_request(request); - if (!hash::is_valid(avatar.digest)) - { - cout << "Status: 400 Bad Request\n\n"; - cerr << "Error: Hash is invalid\n"; - return 1; - } - - if (!settings::find_avatar_dir()) - { - cout << "Status: 503 Service Unavailable\n\n"; - cerr << "Error: No avatars found.\n"; - return 3; - } - hash::fill_table(); - settings::read_settings(); - - image::Image image = image::get(avatar.digest, avatar.size); - if (image.error == 0) - { - image::write(image); - } - else - { - cerr << "Warning " << std::to_string(image.error) - << ": Could not open file.\n"; - if (settings::settings.redirect_nouser) - { - http::send_redirect(avatar); - return 0; - } - if (avatar.fallback.empty()) - { - avatar.fallback = settings::settings.default_fallback; - } - if (avatar.fallback.substr(0, 3) == "404") - { - cout << "Status: 404 Not Found\n\n"; - } - else if (avatar.fallback[0] == '/') - { - cout << "Status: 307 Temporary Redirect\n"; - cout << "Location: " << avatar.fallback << endl << endl; - } - else if (avatar.fallback.substr(0, 2) == "mp" - || avatar.fallback.substr(0, 2) == "mm") - { - // MD5 hash of 'mp' - image = image::get("1f2dfa567dcf95833eddf7aec167fec7", avatar.size); - if (image.error == 0) - { - image::write(image); - } - else - { - cerr << "Error: Mystery person not found.\n"; - // Jump to last else - goto not_implemented; - } - } - else if (avatar.fallback.substr(0, 9) == "identicon" - || avatar.fallback.substr(0, 5) == "retro") - { - try - { - auto padwidth = static_cast(avatar.size / 10); - if (avatar.size < 60) - { - padwidth = 0; - } - else if (padwidth < 10) - { - padwidth = 10; - } - - Identiconpp identicon(5, 5, Identiconpp::algorithm::sigil, - "fefefeff", - { - // The same colors ivatar uses. - "2d4fffff", // Blue - "feb42cff", // Yellow - "e279eaff", // Bright pink - "1eb3fdff", // Cyan - "E84D41ff", // Red - "31CB73ff", // Green - "8D45AAff" // Dark pink - }, - {padwidth, padwidth}); - image.image = identicon.generate(avatar.digest, avatar.size); - image.image.magick("PNG"); - image::write(image); - } - catch (const std::exception &e) - { - cout << "Status: 500 Internal Server Error\n\n"; - cerr << "Error: Couldn't generate identicon (" << e.what() - << ").\n"; - } - } - else - { -not_implemented: - if (settings::settings.redirect_nofallback) - { - http::send_redirect(avatar); - } - else - { - cout << "Status: 501 Not Implemented\n\n"; - } - } - } - - return 0; -} diff --git a/src/libravatarserv.hpp b/src/libravatarserv.hpp deleted file mode 100644 index 167f936..0000000 --- a/src/libravatarserv.hpp +++ /dev/null @@ -1,90 +0,0 @@ -/* This file is part of libravatarserv. - * Copyright © 2018, 2020 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 . - */ - -#ifndef LIBRAVATARSERV_HPP -#define LIBRAVATARSERV_HPP - -#include "fs-compat.hpp" - -#include -#include -#include - -namespace libravatarserv -{ - -using std::int8_t; -using std::string; -using std::uint16_t; -using std::uint8_t; - -int main(); - -namespace settings -{ // settings.cpp -extern fs::path avatar_dir; -extern struct Settings -{ - string default_fallback = "404"; - bool redirect_nofallback = false; - bool redirect_nouser = false; -} settings; - -bool find_avatar_dir(); -void read_settings(); -} // namespace settings - -namespace http // http.cpp -{ -struct Request -{ - const string digest; - const uint16_t size = 0; - string fallback; -}; - -const Request parse_request(const string &request); -const string get_parameter(const string &request, const string ¶meter); -void send_redirect(const Request &request); -} // namespace http - -namespace hash // hash.cpp -{ -extern std::map table; - -const string md5(const string &text); -const string sha256(const string &text); -bool fill_table(); -bool is_valid(const string &digest); -bool not_hex(const char &c); -} // namespace hash - -namespace image // image.cpp -{ -struct Image -{ - uint8_t error = 0; - Magick::Image image; -}; - -const Image get(const string &digest, uint16_t size); -void write(Image &image); -Image identicon(const string &digest); -} // namespace image - -} // namespace libravatarserv - -#endif // LIBRAVATARSERV_HPP diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..6c668f4 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,39 @@ +/* This file is part of libravatarserv. + * Copyright © 2022 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 "config.hpp" +#include "helpers.hpp" +#include "version.hpp" + +#include +#include + +int main(int /*argc*/, char * /*argv*/[]) { + using namespace libravatarserv; + + std::cout << "Server: libravatarserv/" << version << '\n'; + + const std::string uri{read_env("REQUEST_URI")}; + if (uri.empty()) { + std::cout << "Status: 400 Bad Request\n\n"; + std::cerr << "ERROR: REQUEST_URI is empty.\n"; + return 1; + } + + auto cfg{config::read()}; + + return 0; +} diff --git a/src/settings.cpp b/src/settings.cpp deleted file mode 100644 index 2d82e1c..0000000 --- a/src/settings.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/* This file is part of libravatarserv. - * Copyright © 2018, 2020 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 "libravatarserv.hpp" - -#include -#include - -using namespace libravatarserv; -using namespace libravatarserv::settings; - -bool settings::find_avatar_dir() -{ - const char *envdir = std::getenv("LIBRAVATARSERV_DIR"); - if (envdir == nullptr) - { - // TODO: Remove AVATAR_DIR in 1.0.0 - // DEPRECATED because it is potentially used by another program - envdir = std::getenv("AVATAR_DIR"); - } - if (envdir != nullptr && fs::is_directory(envdir)) - { - avatar_dir = envdir; - } - 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; -} - -void settings::read_settings() -{ - const char *env = std::getenv("LIBRAVATARSERV_DEFAULT_FALLBACK"); - if (env != nullptr) - { - settings.default_fallback = env; - } - - env = std::getenv("LIBRAVATARSERV_REDIRECT_NOFALLBACK"); - if (env != nullptr && env[0] == '1') - { - settings.redirect_nofallback = true; - } - - env = std::getenv("LIBRAVATARSERV_REDIRECT_NOUSER"); - if (env != nullptr && env[0] == '1') - { - settings.redirect_nouser = true; - } -} diff --git a/src/version.hpp.in b/src/version.hpp.in index cc23a3f..04e6242 100644 --- a/src/version.hpp.in +++ b/src/version.hpp.in @@ -1,9 +1,19 @@ -#ifndef VERSION_HPP -#define VERSION_HPP +/*! + * @file + * + * @brief Version variables. + */ -namespace global -{ -static constexpr char version[] = "@PROJECT_VERSION@"; -} // namespace global +#ifndef LIBRAVATARSERV_VERSION_HPP +#define LIBRAVATARSERV_VERSION_HPP -#endif // VERSION_HPP +#include + +namespace libravatarserv { + +//! The version of the program. +inline constexpr std::string_view version{"@PROJECT_VERSION@"}; + +} // namespace libravatarserv + +#endif // LIBRAVATARSERV_VERSION_HPP