Compare commits

...

133 Commits
0.2.4 ... 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
tastytea ca0650a4cd
Fixed enginx example
the build was successful Details
2018-12-09 20:04:48 +01:00
tastytea d2d35d8ac5
If requested size is < 0, return 80px image.
the build was successful Details
2018-12-09 19:54:38 +01:00
tastytea f7d6bdc3e9
Moved Cache-Control header from program to nginx config. 2018-12-09 19:50:05 +01:00
tastytea 9aa8803029
Return 501 or redirect if "Mystery Person" is not found.
the build was successful Details
2018-12-06 22:03:59 +01:00
tastytea 18b0ed6359
Link to "Mystery Person" SVG. 2018-12-06 22:03:05 +01:00
tastytea b00dce398e
Removed header: "Connection: close"
the build was successful Details
I'm not sure what my motivation was to put it in there. It has no
place in a CGI program.
2018-12-03 22:07:51 +01:00
tastytea 5205395f7f
Typo
the build was successful Details
2018-12-03 21:40:18 +01:00
tastytea 3666be0872
Added "public" to Cache-Control header.
the build was successful Details
"max-age" already indicates that it can be cached, but it can't hurt
to allow it explicitly.
2018-12-03 21:27:26 +01:00
tastytea d065ed3bd2
Added section "Speed" to readme.
the build was successful Details
2018-12-03 21:17:13 +01:00
tastytea e15289097a
Reformat readme
the build was successful Details
2018-12-02 11:24:18 +01:00
tastytea 3bdcd7b120
Include only necessary Magick++-headers.
the build was successful Details
2018-12-01 11:33:32 +01:00
tastytea 785cd7a35f
Refactored settings-stuff into own file.
the build was successful Details
2018-11-30 07:00:37 +01:00
tastytea 06a1bfccd9
Exit with error code 1 if request is bad 2018-11-30 06:54:56 +01:00
tastytea 1aca78df88
Check if hash is valid 2018-11-30 06:48:45 +01:00
tastytea 513391f44b
Replaced "\n\n" with << endl << endl for consistency
the build was successful Details
2018-11-29 13:45:46 +01:00
tastytea 268ff7ab6f
If size=0, return standard size(80px) 2018-11-29 13:43:56 +01:00
tastytea ec140aad01
Bugfix: detect file type and send appropriate Content-Type. 2018-11-29 13:40:50 +01:00
tastytea b3593a95f0
Clarified readme.
the build was successful Details
2018-11-29 13:18:16 +01:00
tastytea b1347fc0f1
Made it clearer that the default image overrides fallbacks.
the build was successful Details
2018-11-29 13:09:42 +01:00
tastytea 60517a5f32
Add paragraph about identicons to "Things to keep in mind".
the build was successful Details
2018-11-29 09:38:14 +01:00
tastytea f670c1c8a1
Abort if favicon is requested.
the build was successful Details
2018-11-28 17:29:08 +01:00
tastytea 75680b3739
Switched package generation from Ubuntu 16.04 to Debian stretch
the build was successful Details
2018-11-28 16:48:17 +01:00
tastytea e9456be931
Added Debian package names for dependencies. 2018-11-28 16:21:26 +01:00
tastytea b0e267f13f
Refactored a tiny little bit.
the build was successful Details
2018-11-28 16:15:10 +01:00
tastytea 95741d99b8
Added warning about ressource usage with many users. 2018-11-28 16:10:12 +01:00
tastytea 4418251844
Initialize default fallback with "404"
the build was successful Details
2018-11-28 14:56:15 +01:00
tastytea 3e037d4936
Changed the HTTP Status responses to be more accurate
the build was successful Details
2018-11-28 10:31:20 +01:00
tastytea 1dad647059
Enhanced readme
the build was successful Details
2018-11-28 04:02:17 +01:00
tastytea 30020024bd
Install README.md
the build was successful Details
2018-11-27 17:57:55 +01:00
tastytea bcfbd228df
Beautified readme
the build was successful Details
2018-11-27 17:15:47 +01:00
tastytea a8c807717e
Made the documentation more coherent
the build was successful Details
2018-11-27 17:14:35 +01:00
tastytea fbf7437dd9
Added settings to redirect to libravatar.org if the user was not found
the build was successful Details
or the fallback is unknown.
2018-11-27 16:56:00 +01:00
tastytea 4d0033652d
Added setting to configure the default fallback.
the build was successful Details
2018-11-27 15:48:58 +01:00
tastytea f0406f7e3a
Fixed build script
the build was successful Details
2018-11-27 12:08:29 +01:00
tastytea 195d86c5c3
Added not supported features to readme 2018-11-27 12:06:34 +01:00
tastytea cf6b1f84aa
Initialize pseudorandom number with 2^64-1
the build was successful Details
2018-11-27 12:01:03 +01:00
tastytea 16eae60349
Switched to Ubuntu 16.04 for package generation.
the build failed Details
The builds on 14.04 were not usable on 14.04, 16.04 and 18.04.
2018-11-27 11:51:15 +01:00
tastytea a7cfffe6d9
Added link to automatic signing key.
the build was successful Details
2018-11-27 10:43:27 +01:00
tastytea 87bedbbf7b
typo
the build was successful Details
2018-11-27 10:14:44 +01:00
tastytea 645554e226
Added identicon support (closes #2)
the build failed Details
2018-11-27 10:05:35 +01:00
tastytea 3db99fbd03
Added documentation for cmake options
the build was successful Details
2018-11-27 06:24:43 +01:00
tastytea 3c185e7ae5
Added automatic package generation
the build was successful Details
2018-11-27 06:00:50 +01:00
tastytea 8cc6e73cb1
Bugfix: Return 404 if fallback is empty or unknown
the build was successful Details
2018-11-27 05:15:46 +01:00
tastytea 50d902bcc9
Removed debug statement
the build was successful Details
2018-11-27 05:00:19 +01:00
tastytea 9e6edf7179
Bugfix: Heed d=404
the build was successful Details
2018-11-27 04:54:20 +01:00
tastytea f793a86af2
Bugfix: Remove file extensions from digest with no parameters in URL
the build was successful Details
2018-11-27 04:50:03 +01:00
tastytea 43232d802b
Added support for mystery person (aka mystery man)
the build was successful Details
2018-11-27 04:40:17 +01:00
tastytea ed83a9c4a8
Remove file extensions from digests.
Gravatar supports file extensions, so we might come across them.
2018-11-27 04:22:21 +01:00
tastytea 13a71f0315
Changed environment variable AVATAR_DIR to LIBRAVATARSERV_DIR to avoid collisions.
AVATAR_DIR is still supported until 1.0.0 for backwards-compatibility.
2018-11-27 03:07:12 +01:00
tastytea 653499be95
Updated dependency list
the build was successful Details
2018-11-26 06:54:16 +01:00
tastytea e38b3191a8
Added CI
the build was successful Details
2018-11-26 06:27:31 +01:00
tastytea 8f68fc5f9e
Clarified the readme 2018-11-26 05:35:00 +01:00
tastytea df50807eeb
Catch non-numeric size and ignore it 2018-11-26 05:29:39 +01:00
tastytea 69579902e2
Made Request.size const 2018-11-26 05:22:49 +01:00
tastytea b18e630174
Updated readme 2018-11-26 05:20:46 +01:00
tastytea f611990708
Moved HTTP header Cache-Control and Connection to the top.
This way they are alwas sent, not just on success.
2018-11-26 05:11:27 +01:00
tastytea 4da9a2144e
Changed invalid URL detection to support default image URLs 2018-11-26 05:06:27 +01:00
tastytea 831b3c4ac0
Always send server info 2018-11-26 05:05:53 +01:00
tastytea d9fab6928b
Implemented default URL for missing images (#2) 2018-11-26 05:00:07 +01:00
tastytea a7d196ec38
Enhanced documentation 2018-11-26 04:37:28 +01:00
tastytea 9ff1cc0acf
Added algorithm include, fixed off-by-one error in http::get_parameter() 2018-11-26 04:27:11 +01:00
tastytea 75841d87a4
Restricted image size.
Fixes #1
2018-11-26 04:20:27 +01:00
tastytea b8ca3f6d13
Made a function for parameter parsing 2018-11-26 04:19:58 +01:00
tastytea c0c31f5093
Put the request parsing into a dedicated file. 2018-11-26 04:08:03 +01:00
tastytea 76f45a0dd8
Added unencrypted server block to nginx example. 2018-11-26 02:58:41 +01:00
tastytea ee2a118c1f
Added some headers.
Cache-Control: max-age=86400
Connection: close
Server: libravatarserv/version
2018-11-26 02:36:20 +01:00
tastytea a2254a9ae5
Fixed readme a bit. 2018-11-26 02:25:25 +01:00
tastytea fb003da2d6
Included algorithm, for std::transform 2018-11-26 02:20:33 +01:00
tastytea e893a4066f
Added support for size (not just s)
Fixes #3
2018-11-25 10:59:09 +01:00
29 changed files with 1520 additions and 368 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 }
...

50
.drone.yml Normal file
View File

@ -0,0 +1,50 @@
# -*- fill-column: 1000 -*-
kind: pipeline
name: build x86_64
volumes:
- name: debian-package-cache
host:
path: /var/cache/debian-package-cache
trigger:
event:
exclude:
- tag
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
- name: notification
image: drillster/drone-email
pull: always
settings:
host: tzend.de
from: drone@tzend.de
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,42 +1,54 @@
cmake_minimum_required (VERSION 3.2)
project(libravatarserv
VERSION 0.2.4
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 REQUIRED libcryptopp)
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})
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()
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"
}
}
]
}

160
README.md
View File

@ -1,9 +1,11 @@
**libravatarserv** is a simple [libravatar](https://www.libravatar.org/) server.
It is intended as a
**libravatarserv** is a simple libravatar server.
It is intended to be used as a
[CGI](https://en.wikipedia.org/wiki/Common_Gateway_Interface) program.
Libravatar is a free service and an open specification for hosting profile
images tied to email or OpenID addresses.
[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
@ -11,83 +13,155 @@ images tied to email or OpenID addresses.
* Default avatar for unknown addresses
* MD5 hashes
* SHA256 hashes
* Variable image size (`s`)
* Variable image size (`s` or `size`)
* 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/).
### Not supported
* Avatar delivery based on OpenID
* Default URL for missing images (`d`)
* OpenID
* Because it isn't possible to store filenames with '/' in it on most filesystems.
* 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 `${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. If you
want to deliver a default image for unknown email addresses, name it `default`.
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
that by setting the environment variable `LIBRAVATARSERV_DEFAULT_FALLBACK` to
any value accepted by `d` or `default`. If you want to force a default image for
unknown email addresses, name it `default`. The default image overrides the
specified fallback in the URL and in the environment variable.
Test your setup on https://www.libravatar.org/tools/check/.
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://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/>.
### Example
A directory could look like this:
```PLAIN
/usr/share/libravatarserv
The avatar directory could look like this:
``` plain
/var/db/libravatarserv
├── [ 32K] default
├── [ 759] user@example.com
└── [ 16] user+newsletter@example.com -> user@example.com
```
### Configuration
Configuration is done through environment variables.
**LIBRAVATARSERV_DIR**
The directory containing the avatars.
Default: current directory
**LIBRAVATARSERV_DEFAULT_FALLBACK**
Controls what happens if no fallback was requested.
Possible values: Anything that can be supplied with the `d` or `default`
parameter.
Default: 404
**LIBRAVATARSERV_REDIRECT**
Set to 1 to redirect to libravatar.org if the user is not found.
Default: 1
### Things to keep in mind
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
### Dependencies
### Gentoo
* C++ compiler (tested: [gcc](https://gcc.gnu.org/) 7,
[clang](https://llvm.org/) 6)
* [cmake](https://cmake.org/) (at least 3.2)
* [crypto++](https://cryptopp.com) (tested: 7.0)
* [imagemagick](https://www.imagemagick.org/) (tested: 7.0)
* [libxdg-basedir](http://repo.or.cz/w/libxdg-basedir.git) (tested: 1.2)
Gentoo ebuilds are available via my
[repository](https://schlomp.space/tastytea/overlay).
### Compile
### From source
```SH
mkdir build
cd build
cmake ..
make
make install
#### Dependencies
* 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 pkg-config cmake
libcrypto++-dev libmagick++-dev` and
[identiconpp](https://schlomp.space/tastytea/identiconpp) (or use the bundled).
#### Compile
``` shell
cmake -S . -B build
cmake --build build
```
##### cmake options
* `-DCMAKE_BUILD_TYPE=Debug` for a debug build
* `-DLIBRAVATAR_BUNDLED_IDENTICONPP=YES` to use the bundled identiconpp
To install, run `sudo cmake --install build`. To create a linux distribution
package, run `cpack -G DEB` or `cpack -G RPM`.
## Contributing
Contributions are always welcome. You can submit them as pull requests or via
email to `tastytea`@`tastytea.de`.
## Speed
Tests on a laptop with an x86_64 2GHz CPU with 2 cores showed that the average
response time is 140ms with 3 avatars in the "database", 180ms with 1003
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,17 +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;
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 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.
}
}

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,58 +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 global::avatar_dir;
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(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 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 !std::any_of(hash.begin(), hash.end(), not_hex);
}
} // 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,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,50 +14,90 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "image.hpp"
#include "types.hpp"
#include <identiconpp.hpp>
#include <Magick++/Blob.h>
#include <Magick++/Geometry.h>
#include <Magick++/Image.h>
#include <algorithm>
#include <cstdint>
#include <ios>
#include <iostream>
#include "libravatarserv.hpp"
#include <string>
#include <string_view>
using std::cerr;
using std::endl;
using global::avatar_dir;
using namespace image;
namespace libravatarserv::image {
const Image image::get(const string &digest, const uint16_t size)
{
uint8_t error = 0;
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 = 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 = 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;
}
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);
const auto quality{[&img, &magick]() -> size_t {
if (img.quality() != 0) {
return img.quality(); // same as source
}
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
}
}
return 0; // default
}()};
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,128 +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 <cstdlib> // getenv()
#include <basedir.h>
#include "libravatarserv.hpp"
using std::cout;
using std::cerr;
using std::endl;
using global::avatar_dir;
// Global variables
std::map<const string, const string> hash::table;
fs::path global::avatar_dir = "";
int main()
{
const char *tmp = std::getenv("REQUEST_URI");
if (tmp == nullptr)
{
cout << "Status: 404 Not Found\n\n";
cerr << "Error: ${REQUEST_URI} is empty.\n";
return 1;
}
const string request = tmp;
if (!find_avatar_dir())
{
cout << "Status: 404 Not Found\n\n";
cerr << "Error: No avatars found.\n";
return 3;
}
hash::fill_table();
if (request.substr(0, 8) != "/avatar/" ||
request.find('/', 8) != std::string::npos)
{
cout << "Status: 404 Not Found\n\n";
cerr << "Error: Invalid URL.\n";
return 1;
}
uint16_t size = 80;
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)
{
std::size_t pos_size = digest.find("s=", pos_digest);
if (pos_size != std::string::npos)
{
size = static_cast<uint16_t>(std::stoul(digest.substr(pos_size + 2)));
}
digest = digest.substr(0, pos_digest);
}
image::Image answer = image::get(digest, size);
if (answer.error == 0)
{
cout << "Content-type: image/png\n\n";
cout.flush(); // We need to flush before we use /dev/stdout directly.
// answer.image.write("test.png");
answer.image.write("/dev/stdout");
}
else
{
cout << "Status: 404 Not Found\n\n";
cerr << "Error " << std::to_string(answer.error) << ": Could not open file.\n";
}
return 0;
}
bool find_avatar_dir()
{
const char *envdir = std::getenv("AVATAR_DIR");
if (envdir != nullptr)
{
if (fs::is_directory(envdir))
{
avatar_dir = envdir;
}
else
{
return false;
}
}
else
{
xdgHandle xdg;
xdgInitHandle(&xdg);
auto data_dirs = xdgDataDirectories(&xdg);
const uint8_t size = sizeof(data_dirs) / sizeof(*data_dirs);
for (uint8_t index = 0; index <= size; ++index)
{
const string searchdir = data_dirs[index] + string("/libravatarserv");
if (fs::is_directory(searchdir))
{
avatar_dir = searchdir;
break;
}
}
xdgWipeHandle(&xdg);
if (avatar_dir.empty())
{
return false;
}
}
return true;
}

View File

@ -1,66 +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++.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;
int main();
bool find_avatar_dir();
namespace global
{
extern fs::path avatar_dir;
}
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();
}
namespace image //image.cpp
{
struct Image
{
const uint8_t error;
Magick::Image image;
};
const Image get(const string &digest, const uint16_t size);
}
#endif // LIBRAVATARSERV_HPP

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

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