Compare commits

...

64 Commits
0.6.8 ... main

Author SHA1 Message Date
tastytea 8a91017272
Explain supported image formats
continuous-integration/drone/push Build is passing Details
2022-06-26 16:05:26 +02:00
tastytea 849762eba6
fix nginx example URL and dependency versions in readme
continuous-integration/drone/push Build is passing Details
2022-06-25 13:31:10 +02:00
tastytea 58791fce82
Version bump 0.9.0
continuous-integration/drone/push Build is passing Details
2022-06-11 09:06:04 +02:00
tastytea ed9b0bfcfb
add note about default branch name change
continuous-integration/drone/push Build was killed Details
2022-06-11 09:04:17 +02:00
tastytea 89edcb4de7
Fix install in CI
continuous-integration/drone/push Build is passing Details
2022-06-11 08:58:37 +02:00
tastytea a1f2309dc7
typo 2022-06-11 08:43:56 +02:00
tastytea 7328f93b6e
Add git to CI
continuous-integration/drone/push Build is passing Details
2022-06-11 08:42:42 +02:00
tastytea 528f4480aa
Use FetchContent for identiconpp
continuous-integration/drone/push Build is failing Details
2022-06-11 08:33:58 +02:00
tastytea 80ccc2c7a1
CI: fix identiconpp discovery?
continuous-integration/drone/push Build is failing Details
2022-06-11 07:55:41 +02:00
tastytea d927ef6ca0
remove libxdg-basedir from docs and CI 2022-06-11 07:52:25 +02:00
tastytea dc5d843c08
drone: get submodules
continuous-integration/drone/push Build is failing Details
2022-06-11 07:48:26 +02:00
tastytea 65004ce1f8
Update drone recipe and docs
continuous-integration/drone/push Build is failing Details
2022-06-11 07:42:56 +02:00
tastytea 3039d16919
Bundle identiconpp 2022-06-11 07:35:29 +02:00
tastytea 2b9575c51a
actually pass file type to image::write() 2022-06-11 06:40:00 +02:00
tastytea e4b246bc75
remove debug statement 2022-06-11 06:27:26 +02:00
tastytea 78a7184250
allow requesting file type, try to guess right quality 2022-06-11 06:16:12 +02:00
tastytea 6aa3b40faa
Read images from file, allow forcing magick 2022-06-11 05:28:55 +02:00
tastytea 17205298cd
implement fallbacks 2022-06-11 04:46:54 +02:00
tastytea cb354e666d
move types to types.hpp, match hash to email 2022-06-11 04:29:05 +02:00
tastytea cbd86c7000
Generate hashtable 2022-06-09 20:53:26 +02:00
tastytea 03ae11a842
Parse REQUEST_URI 2022-06-09 04:28:50 +02:00
tastytea 7316fe90e3
start of rewrite
- update CMake files
- add QA and formatting configs
2022-06-09 02:31:30 +02:00
tastytea 41abb50563
Version bump 0.7.5
continuous-integration/drone/push Build is passing Details
Also bumped the maximum supported CMake version while I was in the file.
2021-11-26 04:51:31 +01:00
tastytea 3433f88dcc
Don't allow redirections to external websites.
continuous-integration/drone/push Build is passing Details
See <https://cwe.mitre.org/data/definitions/601.html>.
2021-11-26 04:45:59 +01:00
tastytea 8341793768
Version bump 0.7.4.
continuous-integration/drone/push Build is passing Details
2020-10-24 12:11:36 +02:00
tastytea 6549dcf8e6
Update readme.
continuous-integration/drone/push Build is passing Details
2020-10-24 12:09:54 +02:00
tastytea e8c1dcdf2b
Add CGI caching to nginx example. 2020-10-24 12:00:54 +02:00
tastytea e70e07c579
Remove copyright from readme. 2020-10-24 11:40:49 +02:00
tastytea d523342f6a
Re-enable CI.
continuous-integration/drone/push Build is passing Details
2020-10-24 11:35:50 +02:00
tastytea 891d5d3ca3
Update formatting, add namespace and fix a few warnings.
continuous-integration/drone/push Build is passing Details
2020-10-24 11:25:15 +02:00
tastytea 846e85aecf
CI: Disable Debian buster test.
The stretch package of identiconpp can not be installed on buster.
2020-10-24 11:16:57 +02:00
tastytea 05c6276d43
Fix crypto++ lookup. 2020-10-24 11:14:26 +02:00
tastytea 5b71abecf2
Configure fs-compat.hpp. 2020-10-24 11:14:10 +02:00
tastytea 83dbd5b6f4
CI: Disable GCC 6 test.
continuous-integration/drone/push Build is failing Details
I don't know how to fix this error:

+ apt-get install -qq -t stretch-backports cmake
E: Unable to correct problems, you have held broken packages.
2020-10-24 11:12:06 +02:00
tastytea a0db3380b1
Detect which file-system-header to include more reliably. 2020-10-24 10:55:35 +02:00
tastytea b8bfbbeccd
CI: use CMake from stretch-backports.
continuous-integration/drone/push Build is failing Details
stretch has 3.7, we need 3.10.
2020-10-24 10:54:03 +02:00
tastytea ee4451af6b
Update Drone CI recipe.
continuous-integration/drone/push Build is failing Details
New format, reduce tests, no auto-generated packages.
2020-10-24 10:31:25 +02:00
tastytea e7423c92ca
Update / modernize CMake files.
* CMake 3.10 required from now on.
* Use CMakeConfig and IMPORTED_TARGET for pkg-config.
* More debug flags.
2020-10-24 09:55:52 +02:00
tastytea 53466aadef Merge pull request 'Do not reopen stdout while image resizing' (#7) from mymedia2/libravatarserv:patch-1 into master
Reviewed-on: #7
2020-10-24 08:41:13 +02:00
Nicholas Guriev a7c89b0e3f
Do not reopen stdout while image resizing
On Linux, some web-servers (Apache in particular) close file with fd = 1 and
offer something else as stdout. Notable difference can be seen with a pipe and
an immediate redirection. Consider:

  $ REQUEST_URI=/avatar/68b329da9893e34099c7d8ad5cb9c940 libravatarserv | cat > /tmp/response1.out
  $ REQUEST_URI=/avatar/68b329da9893e34099c7d8ad5cb9c940 libravatarserv > /tmp/response2.out
  $ diff -s /tmp/response{1,2}.out
  Binary files /tmp/response1.out and /tmp/response2.out differ

Solution.

The output should be identical in both cases. So do not let ImageMagick to open
/dev/stdout by itself, instead write the resulted image to an in-memory buffer
and then to a file associated with the "cout" global object.
2020-10-23 21:13:47 +03:00
tastytea de7e61a2fe
Update copyright years.
the build was successful Details
2019-02-28 19:49:46 +01:00
tastytea 139e5b24be
Bumped version to 0.7.3.
the build was successful Details
2019-02-28 19:23:45 +01:00
tastytea 1a4ebaf024
Fixed the bug introduced by latest bugfix. m(
the build was successful Details
We included ?/& in the search, but forgot to set the pos +1 char to
the right.
2019-02-28 19:05:27 +01:00
tastytea 79ed6d6432
Updated list of supported/unsupported fallbacks. 2019-02-28 18:57:13 +01:00
tastytea 3f32e4ff51
Bugfix: "forcedefault" would be treated as "default".
the build was successful Details
2019-02-28 18:54:50 +01:00
tastytea 00a29894e9
Fixed background color for identicons.
the build was successful Details
It was 2 characters too short. That resulted in an error.
2019-02-28 18:24:52 +01:00
tastytea 97099f0a8a
retro and identicon is now 5x5.
the build was successful Details
ivatar changed the size of retro to 5x5, mirroring that.
2019-02-28 17:53:52 +01:00
tastytea fba5c64486
Adjust identicons to match ivatar (10x10, padding, colors, retro = identicon).
the build was successful Details
2019-02-20 23:12:48 +01:00
tastytea abe643bb6c
Updated Readme. 2019-02-20 03:22:57 +01:00
tastytea 1475bdd3fc
Added extra warnings in debug builds. 2019-02-20 03:05:25 +01:00
tastytea 021e9cfc1d
Initialize variables in Request and Image. 2019-02-20 03:04:23 +01:00
tastytea ee6a113e1a
Re-use Image object for identicons. 2019-02-20 03:03:09 +01:00
tastytea 19896e78e0
Updated the dependency-list to require identiconpp-0.3.4 or above. 2018-12-28 02:25:34 +01:00
tastytea e43e3681e9
Updated the dependency-list to require identiconpp-0.3.3 or above.
the build was successful Details
2018-12-27 22:55:53 +01:00
tastytea 2491cb47e0
Updated the dependency-list to require identiconpp-0-3-2 or above.
the build was successful Details
2018-12-27 22:13:10 +01:00
tastytea 5b618eaf55
CI: Use identiconpp-0.3.1
the build was successful Details
2018-12-27 19:44:13 +01:00
tastytea 7e41468176
Set identicons to png
the build was successful Details
2018-12-27 06:55:16 +01:00
tastytea 68ac890eab
Switched identicon algorithm to sigil.
the build was successful Details
2018-12-27 00:18:24 +01:00
tastytea ec8cd3aa10
Added identiconpp-dependency to drone file.
the build was successful Details
2018-12-26 19:19:30 +01:00
tastytea a909b0405c
Deleted old identicon code. 2018-12-26 05:55:18 +01:00
tastytea 41299b9529
Transformed some errors into warnings. 2018-12-26 05:47:06 +01:00
tastytea 1dd89882b1
Switched to identiconpp for identicon-generation. 2018-12-26 05:45:31 +01:00
tastytea 519f1e8fd6
Added -fno-omit-frame-pointer to debug builds 2018-12-19 15:01:44 +01:00
tastytea 5475a60495
Removed cache-mention, because the header is not set anymore.
the build was successful Details
2018-12-10 21:12:15 +01:00
32 changed files with 1432 additions and 1011 deletions

119
.clang-format Normal file
View File

@ -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']
...

51
.clang-tidy Normal file
View File

@ -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 }
...

View File

@ -1,197 +1,50 @@
pipeline:
gcc5:
image: debian:stretch-slim
pull: true
when:
event: [push, pull_request]
environment:
- LANG=C.utf8
- CXX=g++-5
- CXXFLAGS=-pipe -O2
commands:
- rm /etc/apt/apt.conf.d/docker-clean
- apt-get update -q
- echo "APT::Default-Release \"stretch\";" >> /etc/apt/apt.conf.d/00default_release
- echo "deb http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu xenial main" >> /etc/apt/sources.list.d/ubuntu-toolchain-r.list
- apt-get install -qy gnupg
- gpg --keyserver hkp://keyserver.ubuntu.com --recv-keys 0x60c317803a41ba51845e371a1e9377a2ba9ef27f
- gpg --armor --export 0x60c317803a41ba51845e371a1e9377a2ba9ef27f | apt-key add -
- apt-get update -q
- apt-get install -qy -t xenial g++-5
- apt-get install -qy cmake pkg-config
- apt-get install -qy libcrypto++-dev libmagick++-dev libxdg-basedir-dev
- rm -rf build && mkdir -p build && cd build
- cmake -DCMAKE_INSTALL_PREFIX=/usr ..
- make VERBOSE=1
volumes:
- /var/cache/debian-package-cache:/var/cache/apt/archives
# -*- fill-column: 1000 -*-
kind: pipeline
name: build x86_64
gcc6:
image: debian:stretch-slim
pull: true
environment:
- LANG=C.utf8
- CXX=g++-6
- CXXFLAGS=-pipe -O2
commands:
- rm /etc/apt/apt.conf.d/docker-clean
- apt-get update -q
- apt-get install -qy g++-6 cmake pkg-config
- apt-get install -qy libcrypto++-dev libmagick++-dev libxdg-basedir-dev
- apt-get install -qy dpkg-dev d-shlibs rpm file wget
- gpg --no-tty --import /var/autosign_gpg.key
- rm -rf build && mkdir -p build && cd build
- cmake -DCMAKE_INSTALL_PREFIX=/usr ..
- make VERBOSE=1
- make install DESTDIR=install
- make package
- cmake -DWITH_DEB=ON ..
- make package
- cmake -DWITH_DEB=OFF -DWITH_RPM=ON ..
- make package
- gpg --verbose --detach-sign *.tar.gz
- gpg --verbose --detach-sign *.deb
- gpg --verbose --detach-sign *.rpm
volumes:
- /var/cache/debian-package-cache:/var/cache/apt/archives
- /home/tastytea/misc/autosign_gpg.key:/var/autosign_gpg.key
volumes:
- name: debian-package-cache
host:
path: /var/cache/debian-package-cache
gcc7:
image: debian:stretch-slim
pull: true
when:
event: [push, pull_request]
environment:
- LANG=C.utf8
- CXX=g++-7
- CXXFLAGS=-pipe -O2
commands:
- rm /etc/apt/apt.conf.d/docker-clean
- apt-get update -q
- echo "APT::Default-Release \"stretch\";" >> /etc/apt/apt.conf.d/00default_release
- echo "deb http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu xenial main" >> /etc/apt/sources.list.d/ubuntu-toolchain-r.list
- apt-get install -qy gnupg
- gpg --keyserver hkp://keyserver.ubuntu.com --recv-keys 0x60c317803a41ba51845e371a1e9377a2ba9ef27f
- gpg --armor --export 0x60c317803a41ba51845e371a1e9377a2ba9ef27f | apt-key add -
- apt-get update -q
- apt-get install -qy -t xenial g++-7
- apt-get install -qy cmake pkg-config
- apt-get install -qy libcrypto++-dev libmagick++-dev libxdg-basedir-dev
- rm -rf build && mkdir -p build && cd build
- cmake -DCMAKE_INSTALL_PREFIX=/usr ..
- make VERBOSE=1
volumes:
- /var/cache/debian-package-cache:/var/cache/apt/archives
trigger:
event:
exclude:
- tag
gcc8:
image: debian:stretch-slim
pull: true
when:
event: [push, pull_request]
environment:
- LANG=C.utf8
- CXX=g++-8
- CXXFLAGS=-pipe -O2
commands:
- rm /etc/apt/apt.conf.d/docker-clean
- apt-get update -q
- echo "APT::Default-Release \"stretch\";" >> /etc/apt/apt.conf.d/00default_release
- echo "deb http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu xenial main" >> /etc/apt/sources.list.d/ubuntu-toolchain-r.list
- apt-get install -qy gnupg
- gpg --keyserver hkp://keyserver.ubuntu.com --recv-keys 0x60c317803a41ba51845e371a1e9377a2ba9ef27f
- gpg --armor --export 0x60c317803a41ba51845e371a1e9377a2ba9ef27f | apt-key add -
- apt-get update -q
- apt-get install -qy -t xenial g++-8
- apt-get install -qy cmake pkg-config
- apt-get install -qy libcrypto++-dev libmagick++-dev libxdg-basedir-dev
- rm -rf build && mkdir -p build && cd build
- cmake -DCMAKE_INSTALL_PREFIX=/usr ..
- make VERBOSE=1
volumes:
- /var/cache/debian-package-cache:/var/cache/apt/archives
steps:
- name: GCC 8 / clang 7
image: debian:buster-slim
pull: always
environment:
CXX: g++-8
CXXFLAGS: -pipe -O2
DEBIAN_FRONTEND: noninteractive
LANG: C.utf8
commands:
- rm /etc/apt/apt.conf.d/docker-clean
- apt-get update -q
- apt-get install -qq build-essential clang pkg-config cmake git libcrypto++-dev libmagick++-dev
- rm -rf build
- cmake -S . -B build -DLIBRAVATAR_BUNDLED_IDENTICONPP=YES
- cmake --build build
- rm -rf build
- CXX="clang++" cmake -S . -B build -DLIBRAVATAR_BUNDLED_IDENTICONPP=YES
- cmake --build build
- cd build && make install
volumes:
- name: debian-package-cache
path: /var/cache/apt/archives
clang5:
image: debian:stretch-slim
pull: true
when:
event: [push, pull_request]
environment:
- LANG=C.utf8
- CXX=clang++-5.0
- CXXFLAGS=-pipe -O2
commands:
- rm /etc/apt/apt.conf.d/docker-clean
- echo "APT::Default-Release \"stretch\";" >> /etc/apt/apt.conf.d/00default_release
- echo "deb http://deb.debian.org/debian stretch-backports main" >> /etc/apt/sources.list.d/stretch.list
- apt-get update -q
- apt-get install -qy -t stretch-backports clang-5.0
- apt-get install -qy cmake pkg-config
- apt-get install -qy libcrypto++-dev libmagick++-dev libxdg-basedir-dev
- rm -rf build && mkdir -p build && cd build
- cmake -DCMAKE_INSTALL_PREFIX=/usr ..
- make VERBOSE=1
volumes:
- /var/cache/debian-package-cache:/var/cache/apt/archives
clang6:
image: debian:stretch-slim
pull: true
when:
event: [push, pull_request]
environment:
- LANG=C.utf8
- CXX=clang++-6.0
- CXXFLAGS=-pipe -O2
commands:
- rm /etc/apt/apt.conf.d/docker-clean
- echo "APT::Default-Release \"stretch\";" >> /etc/apt/apt.conf.d/00default_release
- echo "deb http://deb.debian.org/debian stretch-backports main" >> /etc/apt/sources.list.d/stretch.list
- apt-get update -q
- apt-get install -qy -t stretch-backports clang-6.0
- apt-get install -qy cmake pkg-config
- apt-get install -qy libcrypto++-dev libmagick++-dev libxdg-basedir-dev
- rm -rf build && mkdir -p build && cd build
- cmake -DCMAKE_INSTALL_PREFIX=/usr ..
- make VERBOSE=1
volumes:
- /var/cache/debian-package-cache:/var/cache/apt/archives
prepare_release:
image: ubuntu:xenial
pull: true
when:
event: tag
commands:
- cp -v build/libravatarserv-${DRONE_TAG}_x86_64.tar.gz .
- cp -v build/libravatarserv-${DRONE_TAG}_x86_64.tar.gz.sig .
- cp -v build/libravatarserv_${DRONE_TAG}-0_amd64.deb .
- cp -v build/libravatarserv_${DRONE_TAG}-0_amd64.deb.sig .
- cp -v build/libravatarserv-${DRONE_TAG}-0.x86_64.rpm .
- cp -v build/libravatarserv-${DRONE_TAG}-0.x86_64.rpm.sig .
gitea_release:
image: plugins/gitea-release
pull: true
when:
event: tag
base_url: https://schlomp.space
secrets: [ gitea_token ]
title: ${DRONE_TAG}
prerelease: true
files:
- libravatarserv-${DRONE_TAG}_x86_64.tar.gz
- libravatarserv-${DRONE_TAG}_x86_64.tar.gz.sig
- libravatarserv_${DRONE_TAG}-0_amd64.deb
- libravatarserv_${DRONE_TAG}-0_amd64.deb.sig
- libravatarserv-${DRONE_TAG}-0.x86_64.rpm
- libravatarserv-${DRONE_TAG}-0.x86_64.rpm.sig
checksum:
- sha512
notify:
image: drillster/drone-email
pull: true
host: cryptoparty-celle.de
secrets: [ email_username, email_password ]
- name: notification
image: drillster/drone-email
pull: always
settings:
host: tzend.de
from: drone@tzend.de
when:
status: [ changed, failure ]
username:
from_secret: email_username
password:
from_secret: email_password
when:
status: [ changed, failure ]

20
.editorconfig Normal file
View File

@ -0,0 +1,20 @@
# Configuration file for EditorConfig.
# More information is available under <https://editorconfig.org/>.
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

0
.gitmodules vendored Normal file
View File

View File

@ -1,49 +1,54 @@
cmake_minimum_required (VERSION 3.2)
project(libravatarserv
VERSION 0.6.8
LANGUAGES CXX
)
cmake_minimum_required(VERSION 3.12...3.20)
include(GNUInstallDirs)
find_package(PkgConfig REQUIRED)
pkg_check_modules(MAGICPP REQUIRED Magick++)
pkg_check_modules(LIBCRYPTOPP libcryptopp)
if(NOT LIBCRYPTOPP_FOUND)
# Debian stretch package installs libcrypto++.pc
pkg_check_modules(LIBCRYPTOPP REQUIRED libcrypto++)
endif()
pkg_check_modules(LIBXDG_BASEDIR REQUIRED libxdg-basedir)
set(CMAKE_CXX_STANDARD 14)
# 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)
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")
# project build option
option(LIBRAVATAR_BUNDLED_IDENTICONPP "use bundled identiconpp" NO)
include_directories(${PROJECT_BINARY_DIR})
include_directories(${MAGICPP_INCLUDE_DIRS})
include_directories(${LIBCRYPTOPP_INCLUDE_DIRS})
include_directories(${LIBXDG_BASEDIR_INCLUDE_DIRS})
project(libravatarserv
VERSION 0.9.0
DESCRIPTION "A simple libravatar server."
HOMEPAGE_URL "https://schlomp.space/tastytea/libravatarserv"
LANGUAGES CXX)
link_directories(${MAGICPP_LIBRARY_DIRS})
link_directories(${LIBCRYPTOPP_LIBRARY_DIRS})
link_directories(${LIBXDG_BASEDIR_LIBRARY_DIRS})
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
# Write version in header
configure_file(
"${PROJECT_SOURCE_DIR}/src/version.hpp.in"
"${PROJECT_BINARY_DIR}/version.hpp"
)
include(cmake/debug_flags.cmake)
file(GLOB sources src/*.cpp)
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})
install(FILES README.md DESTINATION ${CMAKE_INSTALL_DOCDIR})
find_package(PkgConfig REQUIRED)
pkg_check_modules(Magick++ REQUIRED IMPORTED_TARGET Magick++)
pkg_check_modules(libcryptopp IMPORTED_TARGET libcryptopp)
if(NOT libcryptopp_FOUND)
# Debian stretch package installs libcrypto++.pc
pkg_check_modules(libcryptopp REQUIRED IMPORTED_TARGET libcrypto++)
endif()
if (LIBRAVATAR_BUNDLED_IDENTICONPP)
include(FetchContent)
FetchContent_Declare(identiconpp
GIT_REPOSITORY https://schlomp.space/tastytea/identiconpp.git
GIT_TAG 0.7.3
)
if (NOT CMAKE_VERSION VERSION_LESS 3.14)
FetchContent_MakeAvailable(identiconpp)
else()
FetchContent_GetProperties(identiconpp)
if(NOT identiconpp_POPULATED)
FetchContent_Populate(identiconpp)
add_subdirectory(${identiconpp_SOURCE_DIR} ${identiconpp_BINARY_DIR})
endif()
endif()
include(packages.CMakeLists.txt)
add_library(identiconpp::identiconpp ALIAS identiconpp)
else()
find_package(identiconpp REQUIRED CONFIG)
endif()
find_package(Filesystem REQUIRED COMPONENTS Final Experimental)
add_subdirectory(src)
include(cmake/packages.cmake)

37
CMakePresets.json Normal file
View File

@ -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"
}
}
]
}

129
README.md
View File

@ -5,6 +5,8 @@ It is intended to be used as a
[Libravatar](https://www.libravatar.org/) is a free service and an open
specification for hosting profile images tied to email or OpenID addresses.
**The default branch changed from `master` to `main` on 2022-06-11.**
## Features
* Avatar delivery based on email addresses
@ -12,10 +14,9 @@ specification for hosting profile images tied to email or OpenID addresses.
* MD5 hashes
* SHA256 hashes
* Variable image size (`s` or `size`)
* Default fallbacks (`d` or `default`): 404, URL, mp/mm, identicon
Clients are asked to cache results for up to 1 day to reduce load. This can
apply to negative results as well.
* Default fallbacks (`d` or `default`): 404, mp/mm, identicon (=retro), retro
* Requesting file type by appending `.jpg`, `.png`, `.gif` or `.webp` to the
hash
The API is explained in greater detail at the
[Libravar wiki](https://wiki.libravatar.org/api/).
@ -24,29 +25,34 @@ The API is explained in greater detail at the
* OpenID
* Because it isn't possible to store filenames with '/' in it on most filesystems.
* The default fallbacks monsterid, wavatar and retro
* Because I don't think anyone uses them?
* The default fallbacks monsterid, wavatar, robohash and pagan
* Patches welcome
* forcedefault
* Fallback URLs for to external sites (due to [CWE-601](https://cwe.mitre.org/data/definitions/601.html))
* We have a server setting (`LIBRAVATARSERV_REDIRECT`) to redirect to
libravatar.org.
## Usage
Install nginx and
[fcgiwrap](https://www.nginx.com/resources/wiki/start/topics/examples/fcgiwrap/),
copy the [example config](https://schlomp.space/tastytea/libravatarserv/src/branch/master/doc/nginx-example.conf) to the nginx configuration directory and edit it
according to your needs. Other webservers and cgi spawners will also work, of
course.
copy the [example config](https://schlomp.space/tastytea/libravatarserv/src/branch/main/doc/nginx-example.conf)
to the nginx configuration directory and edit it according to your needs. Other
webservers and cgi spawners will also work, of course.
Add the following DNS records to your nameserver:
```PLAIN
``` plain
_avatars._tcp.example.com. IN SRV 0 0 80 avatars.example.com
_avatars-sec._tcp.example.com. IN SRV 0 0 443 avatars.example.com
```
`_avatars._tcp.example.com` is for HTTP, `_avatars-sec._tcp.example.com` is for
HTTPS.
libravatarserv looks in each of `${XDG_DATA_DIRS}` for a directory named
`libravatarserv`. You can force a different directory by setting the environment
variable `LIBRAVATARSERV_DIR` (until 0.3.0 `AVATAR_DIR`).
libravatarserv looks in the current working directory for images. You can force
a different directory by setting the environment variable
`LIBRAVATARSERV_DIR`. Any file format supported by ImageMagick is supported, but
it should be PNG, JPEG or GIF for compatibility.
The image files are named like your email address, no file extension. The
default behaviour for unknown users is to return a 404 error. You can change
@ -56,17 +62,18 @@ unknown email addresses, name it `default`. The default image overrides the
specified fallback in the URL and in the environment variable.
If you want to support "Mystery persons" (mp/mm) as default avatars, place a
file named `mp` in the avatar dir. You can use the [default libravatar mystery
person](https://seccdn.libravatar.org/mm/512.png)
([SVG](https://git.linux-kernel.at/oliver/ivatar/raw/master/ivatar/static/img/mm.svg)), for example.
file named `mp` in the avatar dir. You can use the
[default libravatar mystery person](https://git.linux-kernel.at/oliver/ivatar/raw/master/ivatar/static/img/mm.png)
([SVG](https://git.linux-kernel.at/oliver/ivatar/raw/master/ivatar/static/img/mm.svg)),
for example.
Test your setup on https://www.libravatar.org/tools/check/.
Test your setup on <https://www.libravatar.org/tools/check/>.
### Example
The avatar directory could look like this:
```PLAIN
/usr/share/libravatarserv
``` plain
/var/db/libravatarserv
├── [ 32K] default
├── [ 759] user@example.com
└── [ 16] user+newsletter@example.com -> user@example.com
@ -80,7 +87,7 @@ Configuration is done through environment variables.
The directory containing the avatars.
Default: empty
Default: current directory
**LIBRAVATARSERV_DEFAULT_FALLBACK**
@ -90,29 +97,18 @@ parameter.
Default: 404
**LIBRAVATARSERV_REDIRECT_NOFALLBACK**
Set to 1 to redirect to libravatar.org if the user is not found and the
requested fallback is not supported.
Default: 0
**LIBRAVATARSERV_REDIRECT_NOUSER**
**LIBRAVATARSERV_REDIRECT**
Set to 1 to redirect to libravatar.org if the user is not found.
Default: 0
Default: 1
### Things to keep in mind
libravatarserv resizes images on the fly and does not cache anything. It also
calculates both MD5 and SHA256 hashes for every user on every request. This
could seriously strain the ressources of your computer if many users use the
service.
The algorithm for identicon generation has not been thoroughly tested. The
generated images are maybe not individual enough. Please open a bug report if
that is the case.
libravatarserv resizes images on the fly and potentially calculates hashes for
every user on every request. This could seriously strain the ressources of your
computer if many users use the service. Make sure to set up caching if you
expect more than occasional traffic.
## Install
@ -121,53 +117,35 @@ that is the case.
Gentoo ebuilds are available via my
[repository](https://schlomp.space/tastytea/overlay).
### Automatically generated packages
Binary packages are generated automatically for each
[release](https://schlomp.space/tastytea/libravatarserv/releases) in the
formats:
* deb
* rpm
* tar.gz
They are generated on Debian Stretch 64 bit and signed with my
[automatic signing key](https://tastytea.de/tastytea_autosign.asc).
Up to and including 0.6.2, the packages were generated on Ubuntu 16.04 64 bit.
### From source
#### Dependencies
* C++ compiler (tested: [gcc](https://gcc.gnu.org/) 5/6/7/8,
[clang](https://llvm.org/) 5/6)
* [cmake](https://cmake.org/) (at least 3.2)
* [crypto++](https://cryptopp.com) (tested: 7.0 / 5.6)
* [imagemagick](https://www.imagemagick.org/) (tested: 7.0 / 6.7)
* [libxdg-basedir](http://repo.or.cz/w/libxdg-basedir.git) (tested: 1.2)
* C++ compiler (tested: [gcc](https://gcc.gnu.org/) 8/11,
[clang](https://llvm.org/) 7)
* [cmake](https://cmake.org/) (at least 3.12)
* [crypto++](https://cryptopp.com) (tested: 8.6 / 5.6)
* [imagemagick](https://www.imagemagick.org/) (tested: 7.1 / 6.7)
* [identiconpp](https://schlomp.space/tastytea/identiconpp) (at least: 0.7.1)
On a Debian system, install the packages: `build-essential cmake libcrypto++-dev
libmagick++-dev libxdg-basedir-dev`.
On a Debian system, install the packages: `build-essential pkg-config cmake
libcrypto++-dev libmagick++-dev` and
[identiconpp](https://schlomp.space/tastytea/identiconpp) (or use the bundled).
#### Compile
```SH
mkdir build
cd build
cmake ..
make
make install
``` shell
cmake -S . -B build
cmake --build build
```
##### cmake options
* `-DCMAKE_BUILD_TYPE=Debug` for a debug build
* One of:
* `-DWITH_DEB=YES` to generate a deb-package
* `-DWITH_RPM=YES` to generate an rpm-package
* `-DLIBRAVATAR_BUNDLED_IDENTICONPP=YES` to use the bundled identiconpp
To generate a binary package, execute `make package`
To install, run `sudo cmake --install build`. To create a linux distribution
package, run `cpack -G DEB` or `cpack -G RPM`.
## Contributing
@ -182,15 +160,8 @@ avatars. The tests were done with a 27KiB image, scaled down from 569px to
512px. It took 3,8s / 5-7s to transfer 50 unique avatars (about 3 KiB each) on
one page.
This test was done on an older version and may not be accurate any more.
## Contact
See https://tastytea.de/
## License & Copyright
```PLAIN
Copyright © 2018 tastytea <tastytea@tastytea.de>.
License GPLv3: GNU GPL version 3 <https://www.gnu.org/licenses/gpl-3.0.html>.
This program comes with ABSOLUTELY NO WARRANTY. This is free software,
and you are welcome to redistribute it under certain conditions.
```

232
cmake/FindFilesystem.cmake Normal file
View File

@ -0,0 +1,232 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
#[=======================================================================[.rst:
FindFilesystem
##############
This module supports the C++17 standard library's filesystem utilities. Use the
:imp-target:`std::filesystem` imported target to
Options
*******
The ``COMPONENTS`` argument to this module supports the following values:
.. find-component:: Experimental
:name: fs.Experimental
Allows the module to find the "experimental" Filesystem TS version of the
Filesystem library. This is the library that should be used with the
``std::experimental::filesystem`` namespace.
.. find-component:: Final
:name: fs.Final
Finds the final C++17 standard version of the filesystem library.
If no components are provided, behaves as if the
:find-component:`fs.Final` component was specified.
If both :find-component:`fs.Experimental` and :find-component:`fs.Final` are
provided, first looks for ``Final``, and falls back to ``Experimental`` in case
of failure. If ``Final`` is found, :imp-target:`std::filesystem` and all
:ref:`variables <fs.variables>` will refer to the ``Final`` version.
Imported Targets
****************
.. imp-target:: std::filesystem
The ``std::filesystem`` imported target is defined when any requested
version of the C++ filesystem library has been found, whether it is
*Experimental* or *Final*.
If no version of the filesystem library is available, this target will not
be defined.
.. note::
This target has ``cxx_std_17`` as an ``INTERFACE``
:ref:`compile language standard feature <req-lang-standards>`. Linking
to this target will automatically enable C++17 if no later standard
version is already required on the linking target.
.. _fs.variables:
Variables
*********
.. variable:: CXX_FILESYSTEM_IS_EXPERIMENTAL
Set to ``TRUE`` when the :find-component:`fs.Experimental` version of C++
filesystem library was found, otherwise ``FALSE``.
.. variable:: CXX_FILESYSTEM_HAVE_FS
Set to ``TRUE`` when a filesystem header was found.
.. variable:: CXX_FILESYSTEM_HEADER
Set to either ``filesystem`` or ``experimental/filesystem`` depending on
whether :find-component:`fs.Final` or :find-component:`fs.Experimental` was
found.
.. variable:: CXX_FILESYSTEM_NAMESPACE
Set to either ``std::filesystem`` or ``std::experimental::filesystem``
depending on whether :find-component:`fs.Final` or
:find-component:`fs.Experimental` was found.
Examples
********
Using `find_package(Filesystem)` with no component arguments:
.. code-block:: cmake
find_package(Filesystem REQUIRED)
add_executable(my-program main.cpp)
target_link_libraries(my-program PRIVATE std::filesystem)
#]=======================================================================]
if(TARGET std::filesystem)
# This module has already been processed. Don't do it again.
return()
endif()
cmake_minimum_required(VERSION 3.10)
include(CMakePushCheckState)
include(CheckIncludeFileCXX)
include(CheckCXXSourceCompiles)
cmake_push_check_state()
set(CMAKE_REQUIRED_QUIET ${Filesystem_FIND_QUIETLY})
# All of our tests required C++17 or later
set(CMAKE_CXX_STANDARD 17)
# Normalize and check the component list we were given
set(want_components ${Filesystem_FIND_COMPONENTS})
if(Filesystem_FIND_COMPONENTS STREQUAL "")
set(want_components Final)
endif()
# Warn on any unrecognized components
set(extra_components ${want_components})
list(REMOVE_ITEM extra_components Final Experimental)
foreach(component IN LISTS extra_components)
message(WARNING "Extraneous find_package component for Filesystem: ${component}")
endforeach()
# Detect which of Experimental and Final we should look for
set(find_experimental TRUE)
set(find_final TRUE)
if(NOT "Final" IN_LIST want_components)
set(find_final FALSE)
endif()
if(NOT "Experimental" IN_LIST want_components)
set(find_experimental FALSE)
endif()
if(find_final)
check_include_file_cxx("filesystem" _CXX_FILESYSTEM_HAVE_HEADER)
mark_as_advanced(_CXX_FILESYSTEM_HAVE_HEADER)
if(_CXX_FILESYSTEM_HAVE_HEADER)
# We found the non-experimental header. Don't bother looking for the
# experimental one.
set(find_experimental FALSE)
endif()
else()
set(_CXX_FILESYSTEM_HAVE_HEADER FALSE)
endif()
if(find_experimental)
check_include_file_cxx("experimental/filesystem" _CXX_FILESYSTEM_HAVE_EXPERIMENTAL_HEADER)
mark_as_advanced(_CXX_FILESYSTEM_HAVE_EXPERIMENTAL_HEADER)
else()
set(_CXX_FILESYSTEM_HAVE_EXPERIMENTAL_HEADER FALSE)
endif()
if(_CXX_FILESYSTEM_HAVE_HEADER)
set(_have_fs TRUE)
set(_fs_header filesystem)
set(_fs_namespace std::filesystem)
set(_is_experimental FALSE)
elseif(_CXX_FILESYSTEM_HAVE_EXPERIMENTAL_HEADER)
set(_have_fs TRUE)
set(_fs_header experimental/filesystem)
set(_fs_namespace std::experimental::filesystem)
set(_is_experimental TRUE)
else()
set(_have_fs FALSE)
endif()
set(CXX_FILESYSTEM_HAVE_FS ${_have_fs} CACHE BOOL "TRUE if we have the C++ filesystem headers")
set(CXX_FILESYSTEM_HEADER ${_fs_header} CACHE STRING "The header that should be included to obtain the filesystem APIs")
set(CXX_FILESYSTEM_NAMESPACE ${_fs_namespace} CACHE STRING "The C++ namespace that contains the filesystem APIs")
set(CXX_FILESYSTEM_IS_EXPERIMENTAL ${_is_experimental} CACHE BOOL "TRUE if the C++ filesystem library is the experimental version")
set(_found FALSE)
if(CXX_FILESYSTEM_HAVE_FS)
# We have some filesystem library available. Do link checks
string(CONFIGURE [[
#include <@CXX_FILESYSTEM_HEADER@>
int main() {
auto cwd = @CXX_FILESYSTEM_NAMESPACE@::current_path();
return static_cast<int>(cwd.string().size());
}
]] code @ONLY)
# Try to compile a simple filesystem program without any linker flags
check_cxx_source_compiles("${code}" CXX_FILESYSTEM_NO_LINK_NEEDED)
set(can_link ${CXX_FILESYSTEM_NO_LINK_NEEDED})
if(NOT CXX_FILESYSTEM_NO_LINK_NEEDED)
set(prev_libraries ${CMAKE_REQUIRED_LIBRARIES})
# Add the libstdc++ flag
set(CMAKE_REQUIRED_LIBRARIES ${prev_libraries} -lstdc++fs)
check_cxx_source_compiles("${code}" CXX_FILESYSTEM_STDCPPFS_NEEDED)
set(can_link ${CXX_FILESYSTEM_STDCPPFS_NEEDED})
if(NOT CXX_FILESYSTEM_STDCPPFS_NEEDED)
# Try the libc++ flag
set(CMAKE_REQUIRED_LIBRARIES ${prev_libraries} -lc++fs)
check_cxx_source_compiles("${code}" CXX_FILESYSTEM_CPPFS_NEEDED)
set(can_link ${CXX_FILESYSTEM_CPPFS_NEEDED})
endif()
endif()
if(can_link)
add_library(std::filesystem INTERFACE IMPORTED)
set_property(TARGET std::filesystem APPEND PROPERTY INTERFACE_COMPILE_FEATURES cxx_std_17)
set(_found TRUE)
if(CXX_FILESYSTEM_NO_LINK_NEEDED)
# Nothing to add...
elseif(CXX_FILESYSTEM_STDCPPFS_NEEDED)
set_property(TARGET std::filesystem APPEND PROPERTY INTERFACE_LINK_LIBRARIES -lstdc++fs)
elseif(CXX_FILESYSTEM_CPPFS_NEEDED)
set_property(TARGET std::filesystem APPEND PROPERTY INTERFACE_LINK_LIBRARIES -lc++fs)
endif()
endif()
endif()
cmake_pop_check_state()
set(Filesystem_FOUND ${_found} CACHE BOOL "TRUE if we can compile and link a program using std::filesystem" FORCE)
if(Filesystem_FIND_REQUIRED AND NOT Filesystem_FOUND)
message(FATAL_ERROR "Cannot Compile simple program using std::filesystem")
endif()

69
cmake/debug_flags.cmake Normal file
View File

@ -0,0 +1,69 @@
# Set compiler flags for Debug builds.
# Only has an effect on GCC/Clang >= 5.0.
set(tmp_CXXFLAGS "")
set(tmp_LDFLAGS "")
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang"
AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "5")
list(APPEND tmp_CXXFLAGS
"-Wall"
"-Wextra"
"-Wpedantic"
"-Wuninitialized"
"-Wshadow"
"-Wnon-virtual-dtor"
"-Wconversion"
"-Wsign-conversion"
"-Wold-style-cast"
"-Wzero-as-null-pointer-constant"
"-Wmissing-declarations"
"-Wcast-align"
"-Wunused"
"-Woverloaded-virtual"
"-Wdouble-promotion"
"-Wformat=2"
"-ftrapv"
"-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"
"-Wuseless-cast")
if(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "6")
list(APPEND tmp_CXXFLAGS
"-Wmisleading-indentation"
"-Wduplicated-cond"
"-Wnull-dereference")
if(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "7")
list(APPEND tmp_CXXFLAGS
"-Wduplicated-branches")
endif()
endif()
endif()
add_compile_options("$<$<CONFIG:Debug>:${tmp_CXXFLAGS}>")
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}")
else()
add_link_options("$<$<CONFIG:Debug>:${tmp_LDFLAGS}>")
endif()
else()
message(STATUS
"No additional compiler flags were set, "
"because your compiler was not anticipated.")
endif()
unset(tmp_CXXFLAGS)
unset(tmp_LDFLAGS)

57
cmake/packages.cmake Normal file
View File

@ -0,0 +1,57 @@
include(GNUInstallDirs)
set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
set(CPACK_PACKAGE_CONTACT "tastytea <tastytea@tastytea.de>")
# 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()
# 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)

View File

@ -1,47 +1,35 @@
# Use 100 MiB cache with a 1 MiB memory zone (enough for ~8,000 keys).
# Delete data that has not been accessed for 30 minutes.
fastcgi_cache_path /var/cache/nginx/avatar levels=1:2 keys_zone=avatar:1m
max_size=100m inactive=30m use_temp_path=off;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
add_header X-Cache $upstream_cache_status;
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name avatar.example.com;
# access_log /var/log/nginx/avatar main;
error_log /var/log/nginx/avatar warn;
error_log /var/log/nginx/avatar_log warn;
ssl_certificate /var/lib/dehydrated/certs/avatar.example.com/fullchain.pem;
ssl_certificate_key /var/lib/dehydrated/certs/avatar.example.com/privkey.pem;
# 86400 seconds = 1 day
expires 86400;
add_header Cache-Control "public, max-age=86400" always;
expires 6h;
location / {
include /etc/nginx/fastcgi_params;
fastcgi_pass unix:/var/run/cgi-fcgiwrap.socket-1;
fastcgi_pass unix:/var/run/cgi-fcgiwrap.socket-1;
fastcgi_param SCRIPT_FILENAME /usr/bin/libravatarserv;
# fastcgi_param LIBRAVATARSERV_DIR "/var/db/libravatarserv"
# fastcgi_param LIBRAVATARSERV_DEFAULT_FALLBACK 404;
# fastcgi_param LIBRAVATARSERV_REDIRECT_NOFALLBACK 0;
# fastcgi_param LIBRAVATARSERV_REDIRECT_NOUSER 0;
}
}
server {
listen 80;
listen [::]:80;
server_name avatar.example.com;
# access_log /var/log/nginx/avatar main;
error_log /var/log/nginx/avatar warn;
expires 86400;
add_header Cache-Control "public, max-age=86400" always;
location / {
include /etc/nginx/fastcgi_params;
fastcgi_pass unix:/var/run/cgi-fcgiwrap.socket-1;
fastcgi_param SCRIPT_FILENAME /usr/bin/libravatarserv;
# fastcgi_param LIBRAVATARSERV_DEFAULT_FALLBACK 404;
# fastcgi_param LIBRAVATARSERV_REDIRECT_NOFALLBACK 0;
# fastcgi_param LIBRAVATARSERV_REDIRECT_NOUSER 0;
# fastcgi_param LIBRAVATARSERV_REDIRECT 1;
fastcgi_cache avatar;
fastcgi_cache_valid 200 307 400 501 1h; # Cache answers for up to 1 hour
# Cache negative replies that may be temporary for 10 minutes
fastcgi_cache_valid 404 500 10m;
fastcgi_cache_lock on; # Relay only one identical request at a time.
}
}

View File

@ -1,44 +0,0 @@
# 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 <tastytea@tastytea.de>")
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")
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}")
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}")
endif()
include(CPack)

20
src/CMakeLists.txt Normal file
View File

@ -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 "$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>")
target_link_libraries(${PROJECT_NAME}
PRIVATE
PkgConfig::Magick++
PkgConfig::libcryptopp
std::filesystem
identiconpp::identiconpp)
install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})

33
src/config.cpp Normal file
View File

@ -0,0 +1,33 @@
/* This file is part of libravatarserv.
* Copyright © 2022 tastytea <tastytea@tastytea.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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

36
src/config.hpp Normal file
View File

@ -0,0 +1,36 @@
/* This file is part of libravatarserv.
* Copyright © 2022 tastytea <tastytea@tastytea.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef LIBRAVATARSERV_CONFIG_HPP
#define LIBRAVATARSERV_CONFIG_HPP
#include "fs-compat.hpp"
#include <string>
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

2
src/fs-compat.hpp.in Normal file
View File

@ -0,0 +1,2 @@
#include <@CXX_FILESYSTEM_HEADER@>
namespace fs = @CXX_FILESYSTEM_NAMESPACE@;

View File

@ -1,5 +1,5 @@
/* This file is part of libravatarserv.
* Copyright © 2018 tastytea <tastytea@tastytea.de>
* Copyright © 2022 tastytea <tastytea@tastytea.de>
*
* 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
@ -14,86 +14,74 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "hash.hpp"
#include "types.hpp"
#include <algorithm>
#define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1
#include <cryptopp/md5.h>
#include <cryptopp/sha.h>
#include <memory>
#include <string>
#include <string_view>
#define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1 // necessary for MD5
#include <cryptopp/cryptlib.h>
#include <cryptopp/filters.h>
#include <cryptopp/hex.h>
#include "libravatarserv.hpp"
#include <cryptopp/md5.h>
#include <cryptopp/sha.h>
using namespace hash;
namespace libravatarserv::hash {
const string hash::md5(const string &text)
{
std::string generate_hash(const algorithm_t algo, const std::string_view 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;
SHA256 sha256;
Weak::MD5 md5;
HashTransformation *algo_to_use{&sha256}; // HashFilter calls free()?
if (algo == algorithm_t::MD5) {
algo_to_use = &md5;
}
std::string hash;
StringSource s(text.data(), true,
new HashFilter(*algo_to_use,
new HexEncoder(new StringSink(hash))));
std::transform(hash.begin(), hash.end(), hash.begin(), ::tolower);
return hash;
}
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 });
std::string find_email(const request_t &req, const fs::path &directory) {
bool default_present{false};
for (const fs::path &path : fs::recursive_directory_iterator(directory)) {
if (fs::is_regular_file(path)) {
std::string email = path.filename();
std::transform(email.begin(), email.end(), email.begin(),
::tolower);
if (generate_hash(req.algo, email) == req.hash) {
return email;
}
if (email == "default") {
default_present = true;
}
}
}
return true;
return default_present ? "default" : "";
}
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;
}
bool is_valid(const std::string_view hash) {
auto 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;
}};
return true;
return !std::any_of(hash.begin(), hash.end(), not_hex);
}
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;
}
} // namespace libravatarserv::hash

41
src/hash.hpp Normal file
View File

@ -0,0 +1,41 @@
/* This file is part of libravatarserv.
* Copyright © 2022 tastytea <tastytea@tastytea.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef LIBRAVATARSERV_HASH_HPP
#define LIBRAVATARSERV_HASH_HPP
#include "fs-compat.hpp"
#include "types.hpp"
#include <string>
#include <string_view>
namespace libravatarserv::hash {
// generate hash from text
[[nodiscard]] std::string generate_hash(algorithm_t algo,
std::string_view text);
// Match hash to email
[[nodiscard]] std::string find_email(const request_t &req,
const fs::path &directory);
// return false if invalid characters are found in hash
[[nodiscard]] bool is_valid(std::string_view hash);
} // namespace libravatarserv::hash
#endif // LIBRAVATARSERV_HASH_HPP

34
src/helpers.cpp Normal file
View File

@ -0,0 +1,34 @@
/* This file is part of libravatarserv.
* Copyright © 2022 tastytea <tastytea@tastytea.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "helpers.hpp"
#include <cstdlib>
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

28
src/helpers.hpp Normal file
View File

@ -0,0 +1,28 @@
/* This file is part of libravatarserv.
* Copyright © 2022 tastytea <tastytea@tastytea.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef LIBRAVATARSERV_HELPERS_HPP
#define LIBRAVATARSERV_HELPERS_HPP
#include <string_view>
namespace libravatarserv {
[[nodiscard]] std::string_view read_env(std::string_view name,
std::string_view default_value = "");
} // namespace libravatarserv
#endif // LIBRAVATARSERV_HELPERS_HPP

View File

@ -1,135 +0,0 @@
/* This file is part of libravatarserv.
* Copyright © 2018 tastytea <tastytea@tastytea.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include "libravatarserv.hpp"
using std::cout;
using std::cerr;
using std::endl;
using namespace 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<int16_t>(std::stoul(answer));
} catch (const std::exception &) {}
}
else
{
answer = get_parameter(request, "size");
if (!answer.empty())
{
try
{
size = static_cast<int16_t>(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, size, fallback };
}
const string http::get_parameter(const string &request, const string &parameter)
{
std::size_t pos = request.find(parameter + "=");
if (pos != std::string::npos)
{
pos += (1 + 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;
}

View File

@ -1,5 +1,5 @@
/* This file is part of libravatarserv.
* Copyright © 2018 tastytea <tastytea@tastytea.de>
* Copyright © 2022 tastytea <tastytea@tastytea.de>
*
* 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
@ -14,124 +14,90 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <iostream>
#include <sstream>
#include <array>
#include <algorithm>
#include "image.hpp"
#include "types.hpp"
#include <identiconpp.hpp>
#include <Magick++/Blob.h>
#include <Magick++/Geometry.h>
#include <Magick++/Color.h>
#include <Magick++/Exception.h>
#include "libravatarserv.hpp"
#include <Magick++/Image.h>
using std::cout;
using std::cerr;
using std::endl;
using namespace image;
#include <algorithm>
#include <cstdint>
#include <ios>
#include <iostream>
#include <string>
#include <string_view>
const Image image::get(const string &digest, const uint16_t size)
{
uint8_t error = 0;
namespace libravatarserv::image {
Magick::Image get(const fs::path &filepath, const size_t size) {
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 << "Error: 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 };
img.read(filepath);
img.resize(Magick::Geometry(size, size));
return img;
}
void image::write(Image &image)
{
string magick = image.image.magick();
Magick::Image generate_identicon(const request_t &req) {
const auto padwidth{[&req]() -> std::uint8_t {
if (req.size < 60) {
return 0;
}
return static_cast<std::uint8_t>(std::max(req.size / 10, 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});
Magick::Image img{
identicon.generate(req.hash, static_cast<std::uint8_t>(req.size))};
img.magick("PNG");
return img;
}
void write(Magick::Image &img,
const std::optional<std::string_view> magick_force) {
std::string magick{magick_force.value_or(img.magick())};
std::transform(magick.begin(), magick.end(), magick.begin(), ::tolower);
cout << "Content-Type: image/" << magick << endl << endl;
cout.flush(); // We need to flush before we use /dev/stdout directly.
image.image.write("/dev/stdout");
}
Image image::identicon(const string &digest)
{
const string sha256 = hash::sha256(digest);
Magick::Image img(Magick::Geometry(4, 4), Magick::Color("white"));
// The 16 named colors specified in HTML 4.01 minus white, silver and gray.
const std::array<Magick::Color, 13> colors =
{
Magick::Color("black"),
Magick::Color("red"),
Magick::Color("maroon"),
Magick::Color("yellow"),
Magick::Color("olive"),
Magick::Color("lime"),
Magick::Color("green"),
Magick::Color("aqua"),
Magick::Color("teal"),
Magick::Color("blue"),
Magick::Color("navy"),
Magick::Color("fuchsia"),
Magick::Color("purple")
};
try
{
std::uint64_t random = 0xffffffffffffffff;
for (uint64_t chunk = 0; chunk < 4; ++chunk)
{
std::stringstream ss;
ss << std::hex << sha256.substr(chunk * 16, 16);
random = (random / 2) + (static_cast<std::uint64_t>(ss.get()) / 2);
const auto quality{[&img, &magick]() -> size_t {
if (img.quality() != 0) {
return img.quality(); // same as source
}
Magick::Color dotcolor = colors[random % 13];
for (uint8_t row = 0; row < 4; ++row)
{
for (uint8_t column = 0; column < 4; ++column)
{
std::stringstream ss;
uint16_t px;
ss << std::hex << sha256.substr(row * 4 + column, 4);
ss >> px;
if (px > 0x8000)
{
img.pixelColor(column, row, dotcolor);
}
if (magick == "png") {
return 100; // highest compression
}
if (magick == "webp") {
if (img.magick() == "WEBP" || img.magick() == "PNG"
|| img.magick() == "GIF" || img.magick() == "SVG") {
return 100; // lossless
}
}
}
catch (const std::exception &e)
{
cerr << "Error: " << e.what() << endl;
return { 5, img };
}
return 0; // default
}()};
img.magick("png");
return { 0, img };
img.magick(magick);
img.quality(quality);
Magick::Blob buffer;
img.write(&buffer);
std::cout << "Content-Type: image/" << magick << '\n';
std::cout << "Content-Length: " << buffer.length() << "\n\n";
std::cout.write(static_cast<const char *>(buffer.data()),
static_cast<std::streamsize>(buffer.length()));
}
} // namespace libravatarserv::image

41
src/image.hpp Normal file
View File

@ -0,0 +1,41 @@
/* This file is part of libravatarserv.
* Copyright © 2022 tastytea <tastytea@tastytea.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef LIBRAVATARSERV_IMAGE_HPP
#define LIBRAVATARSERV_IMAGE_HPP
#include "fs-compat.hpp"
#include "types.hpp"
#include <Magick++/Image.h>
#include <optional>
namespace libravatarserv::image {
// read image from file
[[nodiscard]] Magick::Image get(const fs::path &filepath, size_t size);
// Generate identicon from hash
[[nodiscard]] Magick::Image generate_identicon(const request_t &req);
// write image with headers to stdout
void write(Magick::Image &img,
std::optional<std::string_view> magick_force = {});
} // namespace libravatarserv::image
#endif // LIBRAVATARSERV_IMAGE_HPP

View File

@ -1,132 +0,0 @@
/* This file is part of libravatarserv.
* Copyright © 2018 tastytea <tastytea@tastytea.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <iostream>
#include <Magick++/Geometry.h>
#include "version.hpp"
#include "libravatarserv.hpp"
using std::cout;
using std::cerr;
using std::endl;
// Global variables
std::map<const string, const string> 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 << "Error " << 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.substr(0, 4) == "http")
{
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")
{
image = image::identicon(avatar.digest);
if (image.error == 0)
{
image.image.scale(Magick::Geometry(avatar.size, avatar.size));
image::write(image);
}
else
{
cout << "Status: 500 Internal Server Error\n\n";
cerr << "Error: Couldn't generate identicon.\n";
}
}
else
{
not_implemented:
if (settings::settings.redirect_nofallback)
{
http::send_redirect(avatar);
}
else
{
cout << "Status: 501 Not Implemented\n\n";
}
}
}
return 0;
}

View File

@ -1,93 +0,0 @@
/* This file is part of libravatarserv.
* Copyright © 2018 tastytea <tastytea@tastytea.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef LIBRAVATARSERV_HPP
#define LIBRAVATARSERV_HPP
#if __cplusplus >= 201703L
#include <filesystem>
#else
#include <experimental/filesystem>
#endif
#include <string>
#include <map>
#include <Magick++/Image.h>
#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;
using std::int8_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 http // http.cpp
{
struct Request
{
const string digest;
const int16_t size;
string fallback;
};
const Request parse_request(const string &request);
const string get_parameter(const string &request, const string &parameter);
void send_redirect(const Request &request);
}
namespace hash // hash.cpp
{
extern std::map<const string, const string> 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 image // image.cpp
{
struct Image
{
uint8_t error;
Magick::Image image;
};
const Image get(const string &digest, const uint16_t size);
void write(Image &image);
Image identicon(const string &digest);
}
#endif // LIBRAVATARSERV_HPP

108
src/main.cpp Normal file
View File

@ -0,0 +1,108 @@
/* This file is part of libravatarserv.
* Copyright © 2022 tastytea <tastytea@tastytea.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "config.hpp"
#include "fs-compat.hpp"
#include "hash.hpp"
#include "helpers.hpp"
#include "image.hpp"
#include "request.hpp"
#include "version.hpp"
#include <Magick++/Exception.h>
#include <Magick++/Image.h>
#include <cstring>
#include <iostream>
#include <stdexcept>
#include <string>
int main(int /*argc*/, char * /*argv*/[]) {
using namespace libravatarserv;
std::cout << "Server: libravatarserv/" << version << '\n';
try {
const std::string uri{read_env("REQUEST_URI")};
if (uri.empty()) {
std::cout << "Status: 400 Bad Request\n\n";
throw std::runtime_error{"REQUEST_URI is empty"};
}
auto cfg{config::read()};
auto req{request::parse(uri)};
std::string email{hash::find_email(req, cfg.avatar_dir)};
Magick::Image img;
if (!email.empty()) {
try {
img = image::get(cfg.avatar_dir / email,
static_cast<size_t>(req.size));
} catch (const Magick::Exception &e) {
std::cout << "Status: 500 Internal Server Error\n\n";
std::cerr << "ERROR: " << e.what() << '\n';
return 1;
}
} else {
if (cfg.redirect) {
std::cout << "Status: 307 Temporary Redirect\n";
std::cout << "Location: https://seccdn.libravatar.org/avatar/"
<< req.hash << "?s=" << req.size
<< "&d=" << req.fallback << "\n\n";
return 0;
}
if (req.fallback.empty()) {
req.fallback = cfg.default_fallback;
}
if (req.fallback == "404") {
std::cout << "Status: 404 Not Found\n\n";
return 0;
}
if (req.fallback == "mp" || req.fallback == "mm") {
if (!fs::exists(cfg.avatar_dir / "mp")) {
std::cout << "Status: 404 Not Found\n\n";
return 0;
}
email = "mp";
} else if (req.fallback == "identicon" || req.fallback == "retro") {
try {
img = image::generate_identicon(req);
} catch (const std::exception &e) {
std::cout << "Status: 500 Internal Server Error\n\n";
std::cerr
<< "ERROR: could not generate identicon: " << e.what()
<< "\n";
return 1;
}
} else {
std::cout << "Status: 501 Not Implemented\n\n";
}
}
image::write(img, req.magick);
} catch (const std::runtime_error &e) {
if (std::strlen(e.what()) != 0) {
std::cerr << "ERROR: " << e.what() << '\n';
}
return 1;
}
return 0;
}

122
src/request.cpp Normal file
View File

@ -0,0 +1,122 @@
/* This file is part of libravatarserv.
* Copyright © 2022 tastytea <tastytea@tastytea.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "request.hpp"
#include "hash.hpp"
#include "types.hpp"
#include <algorithm>
#include <array>
#include <cstdint>
#include <iostream>
#include <stdexcept>
#include <string>
#include <string_view>
namespace libravatarserv::request {
request_t parse(const std::string_view uri) {
constexpr std::uint8_t offset{8};
if (uri.substr(0, offset) != "/avatar/"
|| uri.find("..", offset) != std::string_view::npos) {
std::cout << "Status: 400 Bad Request\n\n";
throw std::runtime_error{"Invalid URL"};
}
request_t req;
req.size = 80;
size_t pos{uri.find('?')};
if (pos == std::string_view::npos) {
pos = uri.size();
}
req.hash.resize(pos - offset);
std::transform(uri.begin() + offset, uri.begin() + pos, req.hash.begin(),
::tolower);
if (const auto pos_dot{req.hash.find('.')}; pos_dot != std::string::npos) {
std::string magick{req.hash.substr(pos_dot + 1)};
req.hash = req.hash.substr(0, pos_dot);
if (magick == "jpg") {
magick = "jpeg";
}
constexpr std::array<std::string_view, 4> allowed{
{"jpeg", "png", "gif", "webp"}
};
if (std::any_of(allowed.begin(), allowed.end(),
[magick](const std::string_view supplied) {
return magick == supplied;
})) {
req.magick = magick;
}
}
if (req.hash.size() == 64 && hash::is_valid(req.hash)) {
req.algo = algorithm_t::SHA256;
} else if (req.hash.size() == 32 && hash::is_valid(req.hash)) {
req.algo = algorithm_t::MD5;
} else {
std::cout << "Status: 400 Bad Request\n\n";
throw std::runtime_error{"Invalid hash"};
}
if (pos != uri.size()) {
std::string uri_rest;
uri_rest.resize(uri.size() - pos);
std::transform(uri.begin() + pos, uri.end(), uri_rest.begin(),
::tolower);
auto strsize{get_parameter(uri_rest, "s")};
if (strsize.empty()) {
strsize = get_parameter(uri_rest, "size");
}
if (!strsize.empty()) {
try {
req.size = static_cast<int16_t>(std::stoul(strsize.data()));
if (req.size > 512) {
req.size = 512;
} else if (req.size < 1) {
req.size = 80;
}
} catch (...) {}
}
req.fallback = get_parameter(uri_rest, "d");
if (req.fallback.empty()) {
req.fallback = get_parameter(uri_rest, "default");
}
}
return req;
}
std::string_view get_parameter(const std::string_view uri,
const std::string &parameter) {
size_t pos{uri.find("?" + parameter + "=")};
if (pos == std::string_view::npos) {
pos = uri.find("&" + parameter + "=");
}
if (pos != std::string_view::npos) {
pos += (2 + parameter.length());
return uri.substr(pos, uri.find('&', pos));
}
return {};
}
} // namespace libravatarserv::request

36
src/request.hpp Normal file
View File

@ -0,0 +1,36 @@
/* This file is part of libravatarserv.
* Copyright © 2022 tastytea <tastytea@tastytea.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef LIBRAVATARSERV_REQUEST_HPP
#define LIBRAVATARSERV_REQUEST_HPP
#include "types.hpp"
#include <string>
#include <string_view>
namespace libravatarserv::request {
// parse request and return results
[[nodiscard]] request_t parse(std::string_view uri);
// get one parameter from an URI
[[nodiscard]] std::string_view get_parameter(std::string_view uri,
const std::string &parameter);
} // namespace libravatarserv::request
#endif // LIBRAVATARSERV_REQUEST_HPP

View File

@ -1,82 +0,0 @@
/* This file is part of libravatarserv.
* Copyright © 2018 tastytea <tastytea@tastytea.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <cstdlib>
#include <basedir.h>
#include "libravatarserv.hpp"
using namespace 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;
}
}

40
src/types.hpp Normal file
View File

@ -0,0 +1,40 @@
/* This file is part of libravatarserv.
* Copyright © 2022 tastytea <tastytea@tastytea.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef LIBRAVATARSERV_TYPES_HPP
#define LIBRAVATARSERV_TYPES_HPP
#include <cstdint>
#include <optional>
#include <string>
namespace libravatarserv {
enum class algorithm_t {
SHA256,
MD5
};
struct request_t {
std::string hash;
algorithm_t algo;
std::int16_t size;
std::string fallback;
std::optional<std::string> magick;
};
} // namespace libravatarserv
#endif // LIBRAVATARSERV_TYPES_HPP

View File

@ -1,9 +1,19 @@
#ifndef VERSION_HPP
#define VERSION_HPP
/*!
* @file
*
* @brief Version variables.
*/
namespace global
{
static constexpr char version[] = "@PROJECT_VERSION@";
}
#ifndef LIBRAVATARSERV_VERSION_HPP
#define LIBRAVATARSERV_VERSION_HPP
#endif // VERSION_HPP
#include <string_view>
namespace libravatarserv {
//! The version of the program.
inline constexpr std::string_view version{"@PROJECT_VERSION@"};
} // namespace libravatarserv
#endif // LIBRAVATARSERV_VERSION_HPP