|
1 | 1 | // SPDX-License-Identifier: Apache-2.0 |
2 | 2 |
|
3 | 3 | #include "SecretResolver.hpp" |
4 | | - |
5 | 4 | #include "backends/EnvBackend.hpp" |
6 | 5 | #include "backends/FileBackend.hpp" |
7 | 6 | #include "backends/StdinBackend.hpp" |
|
10 | 9 | #include <cstdlib> |
11 | 10 | #include <filesystem> |
12 | 11 | #include <format> |
| 12 | +#include <ranges> |
13 | 13 |
|
14 | 14 | namespace Lightweight::Secrets |
15 | 15 | { |
16 | 16 |
|
17 | 17 | namespace |
18 | 18 | { |
19 | 19 |
|
20 | | -/// Splits `secretRef` into `{prefix, key}` where the prefix is everything |
21 | | -/// before the first colon. Bare references (no colon) yield an empty prefix |
22 | | -/// and the whole string as the key. |
23 | | -struct SecretRefParts |
24 | | -{ |
25 | | - std::string_view prefix; |
26 | | - std::string_view key; |
27 | | -}; |
| 20 | + /// Splits `secretRef` into `{prefix, key}` where the prefix is everything |
| 21 | + /// before the first colon. Bare references (no colon) yield an empty prefix |
| 22 | + /// and the whole string as the key. |
| 23 | + struct SecretRefParts |
| 24 | + { |
| 25 | + std::string_view prefix; |
| 26 | + std::string_view key; |
| 27 | + }; |
28 | 28 |
|
29 | | -SecretRefParts SplitSecretRef(std::string_view secretRef) noexcept |
30 | | -{ |
31 | | - auto const colon = secretRef.find(':'); |
32 | | - if (colon == std::string_view::npos) |
33 | | - return { .prefix = {}, .key = secretRef }; |
34 | | - return { .prefix = secretRef.substr(0, colon), .key = secretRef.substr(colon + 1) }; |
35 | | -} |
| 29 | + SecretRefParts SplitSecretRef(std::string_view secretRef) noexcept |
| 30 | + { |
| 31 | + auto const colon = secretRef.find(':'); |
| 32 | + if (colon == std::string_view::npos) |
| 33 | + return { .prefix = {}, .key = secretRef }; |
| 34 | + return { .prefix = secretRef.substr(0, colon), .key = secretRef.substr(colon + 1) }; |
| 35 | + } |
36 | 36 |
|
37 | | -/// MSVC treats `std::getenv` as deprecated in favour of `_dupenv_s`; we wrap |
38 | | -/// it once here so the suppression is centralised rather than scattered at |
39 | | -/// every call site. The one-read-at-start-up use is safe enough — we never |
40 | | -/// interleave reads with `putenv` on the same variable. |
41 | | -char const* SafeGetenv(char const* name) noexcept |
42 | | -{ |
| 37 | + /// MSVC treats `std::getenv` as deprecated in favour of `_dupenv_s`; we wrap |
| 38 | + /// it once here so the suppression is centralised rather than scattered at |
| 39 | + /// every call site. The one-read-at-start-up use is safe enough — we never |
| 40 | + /// interleave reads with `putenv` on the same variable. |
| 41 | + char const* SafeGetenv(char const* name) noexcept |
| 42 | + { |
43 | 43 | #ifdef _WIN32 |
44 | 44 | #pragma warning(push) |
45 | 45 | #pragma warning(disable : 4996) |
46 | 46 | #endif |
47 | | - return std::getenv(name); |
| 47 | + return std::getenv(name); |
48 | 48 | #ifdef _WIN32 |
49 | 49 | #pragma warning(pop) |
50 | 50 | #endif |
51 | | -} |
| 51 | + } |
52 | 52 |
|
53 | | -/// Resolves `~` / `$HOME` in the default credentials path, matching the |
54 | | -/// behaviour users expect from shells and pg_hba.conf-style configs. |
55 | | -std::filesystem::path DefaultCredentialsPath() |
56 | | -{ |
| 53 | + /// Resolves `~` / `$HOME` in the default credentials path, matching the |
| 54 | + /// behaviour users expect from shells and pg_hba.conf-style configs. |
| 55 | + std::filesystem::path DefaultCredentialsPath() |
| 56 | + { |
57 | 57 | #ifdef _WIN32 |
58 | | - if (char const* appData = SafeGetenv("APPDATA"); appData && *appData) |
59 | | - return std::filesystem::path(appData) / "dbtool" / "credentials"; |
60 | | - return "dbtool-credentials"; |
| 58 | + if (char const* appData = SafeGetenv("APPDATA"); appData && *appData) |
| 59 | + return std::filesystem::path(appData) / "dbtool" / "credentials"; |
| 60 | + return "dbtool-credentials"; |
61 | 61 | #else |
62 | | - if (char const* xdg = SafeGetenv("XDG_CONFIG_HOME"); xdg && *xdg) |
63 | | - return std::filesystem::path(xdg) / "Lightweight" / "credentials"; |
64 | | - if (char const* home = SafeGetenv("HOME"); home && *home) |
65 | | - return std::filesystem::path(home) / ".config" / "Lightweight" / "credentials"; |
66 | | - return "Lightweight-credentials"; |
| 62 | + if (char const* xdg = SafeGetenv("XDG_CONFIG_HOME"); xdg && *xdg) |
| 63 | + return std::filesystem::path(xdg) / "Lightweight" / "credentials"; |
| 64 | + if (char const* home = SafeGetenv("HOME"); home && *home) |
| 65 | + return std::filesystem::path(home) / ".config" / "Lightweight" / "credentials"; |
| 66 | + return "Lightweight-credentials"; |
67 | 67 | #endif |
68 | | -} |
| 68 | + } |
69 | 69 |
|
70 | | -std::string JoinBackendNames(std::vector<std::string> const& names) |
71 | | -{ |
72 | | - std::string joined; |
73 | | - for (size_t i = 0; i < names.size(); ++i) |
| 70 | + std::string JoinBackendNames(std::vector<std::string> const& names) |
74 | 71 | { |
75 | | - if (i != 0) |
76 | | - joined += ", "; |
77 | | - joined += names[i]; |
| 72 | + std::string joined; |
| 73 | + bool first = true; |
| 74 | + for (auto const& name: names) |
| 75 | + { |
| 76 | + if (!first) |
| 77 | + joined += ", "; |
| 78 | + joined += name; |
| 79 | + first = false; |
| 80 | + } |
| 81 | + return joined; |
78 | 82 | } |
79 | | - return joined; |
80 | | -} |
81 | 83 |
|
82 | 84 | } // namespace |
83 | 85 |
|
@@ -114,7 +116,7 @@ std::expected<std::string, ResolveError> SecretResolver::ResolveExplicit(std::st |
114 | 116 | } |
115 | 117 |
|
116 | 118 | std::expected<std::string, ResolveError> SecretResolver::ResolveBare(std::string_view secretRef, |
117 | | - std::string_view profileName) const |
| 119 | + std::string_view profileName) const |
118 | 120 | { |
119 | 121 | // Bare ref: walk the registered chain in order. Each backend gets a key |
120 | 122 | // shaped to its own conventions so callers don't have to remember whether |
@@ -143,7 +145,7 @@ std::expected<std::string, ResolveError> SecretResolver::ResolveBare(std::string |
143 | 145 | } |
144 | 146 |
|
145 | 147 | std::expected<std::string, ResolveError> SecretResolver::Resolve(std::string_view secretRef, |
146 | | - std::string_view profileName) const |
| 148 | + std::string_view profileName) const |
147 | 149 | { |
148 | 150 | if (secretRef.empty()) |
149 | 151 | return std::unexpected(ResolveError { |
|
0 commit comments