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