Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief filter plugin for the Base64 encoding
5 : *
6 : * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
7 : *
8 : */
9 :
10 : #include "base64.h"
11 : #include <kdb.h>
12 : #include <kdberrors.h>
13 : #include <stdbool.h>
14 : #include <string.h>
15 :
16 : /**
17 : * @brief Unescape a key value starting with two `ELEKTRA_PLUGIN_BASE64_ESCAPE` characters (`@@`).
18 : *
19 : * @pre The type of the key value must be string.
20 : *
21 : * @param key This parameter contains the key value this function escapes.
22 : * @param parent The function stores information about errors in this parameter.
23 : *
24 : * @retval -1 if the function was unable to unescape the value of `key`
25 : * @retval 0 if the given key was not modified
26 : * @retval 1 if the function successfully unescaped `key`
27 : */
28 56 : static int unescape (Key * key, Key * parent)
29 : {
30 56 : const char * strVal = keyString (key);
31 56 : const char escapedPrefix[] = ELEKTRA_PLUGIN_BASE64_ESCAPE ELEKTRA_PLUGIN_BASE64_ESCAPE;
32 :
33 56 : if (strlen (strVal) < 2 || strncmp (strVal, escapedPrefix, 2) != 0) return 0;
34 :
35 : // Discard the first escape character
36 10 : char * unescaped = elektraStrDup (&strVal[1]);
37 10 : if (!unescaped)
38 : {
39 0 : ELEKTRA_SET_OUT_OF_MEMORY_ERROR (parent, "Memory allocation failed");
40 0 : return -1;
41 : }
42 10 : keySetString (key, unescaped);
43 10 : elektraFree (unescaped);
44 10 : return 1;
45 : }
46 :
47 : /**
48 : * @brief Check if the given key should be decoded by the plugin.
49 : *
50 : * @pre The type of the key value must be string.
51 : *
52 : * @param key This parameter specifies the key for which this function decides if it should be encoded or not.
53 : * @param metaMode This parameter specifies if the plugin uses meta mode or not.
54 : *
55 : * @retval true if the plugin should decode the given key
56 : * @retval false otherwise
57 : */
58 395 : static bool shouldDecode (Key * key, bool metaMode)
59 : {
60 395 : if (metaMode) return keyGetMeta (key, "type") && strcmp (keyValue (keyGetMeta (key, "type")), "binary") == 0;
61 :
62 84 : const char * strVal = keyString (key);
63 160 : return strlen (strVal) >= ELEKTRA_PLUGIN_BASE64_PREFIX_LENGTH &&
64 76 : strncmp (strVal, ELEKTRA_PLUGIN_BASE64_PREFIX, ELEKTRA_PLUGIN_BASE64_PREFIX_LENGTH) == 0;
65 : }
66 :
67 : /**
68 : * @brief Decode a base64 encoded key value and save the result as binary data in the key.
69 : *
70 : * The conversion only happens if the value of the key has type `string` and
71 : *
72 : * 1. in escaping mode: the key value starts with `ELEKTRA_PLUGIN_BASE64_PREFIX` (`"@BASE64"`),
73 : * 2. in meta mode: the key contains the metakey `type` with the value `binary`
74 : *
75 : * . If the key value starts with two prefix characters (`@@`) in **escaping mode**, then the function unescapes the value by removing one
76 : * of the prefix characters.
77 : *
78 : * @param key This parameter specifies the key that this function decodes.
79 : * @param parent The function stores information about errors/warnings in this parameter.
80 : * @param metaMode This parameter specifies if the plugin uses meta mode or not.
81 : *
82 : * @retval -1 if the function was unable to convert or unescape the value of `key`
83 : * @retval 0 if the given key was not modified
84 : * @retval 1 if the function successfully modified `key`
85 : */
86 547 : static int decode (Key * key, Key * parent, bool metaMode)
87 : {
88 547 : if (!keyIsString (key)) return 0;
89 :
90 395 : if (!shouldDecode (key, metaMode))
91 : {
92 360 : if (metaMode) return 0;
93 56 : return unescape (key, parent);
94 : }
95 :
96 : ELEKTRA_LOG_DEBUG ("Decode binary value");
97 :
98 : kdb_octet_t * buffer;
99 : size_t bufferLen;
100 35 : const char * strVal = keyString (key);
101 35 : int result = base64Decode (strVal + (metaMode ? 0 : ELEKTRA_PLUGIN_BASE64_PREFIX_LENGTH), &buffer, &bufferLen);
102 35 : if (result == 1)
103 : {
104 : // Success
105 33 : keySetBinary (key, buffer, bufferLen);
106 : }
107 2 : else if (result == -1)
108 : {
109 : // Decoding error
110 2 : ELEKTRA_ADD_VALIDATION_SYNTACTIC_WARNINGF (parent, "Key %s was not Base64 encoded: %s", keyName (key), strVal);
111 : }
112 0 : else if (result == -2)
113 : {
114 : // Memory error
115 0 : ELEKTRA_SET_OUT_OF_MEMORY_ERROR (parent, "Memory allocation failed");
116 0 : return -1;
117 : }
118 :
119 35 : elektraFree (buffer);
120 35 : return 1;
121 : }
122 :
123 : /**
124 : * @brief Encode a binary key value using base64 encoding and save the result as textual data in the key.
125 : *
126 : * @param key This parameter specifies the key that this function encodes.
127 : * @param parent The function stores information about errors in this parameter.
128 : * @param metaMode This parameter specifies if the plugin uses meta mode or not.
129 : *
130 : * @retval -1 if the function was unable to convert the value of `key`
131 : * @retval 0 if no conversion has taken place
132 : * @retval 1 if the function successfully converted the value of `key`
133 : */
134 195 : static int encode (Key * key, Key * parent, bool metaMode)
135 : {
136 195 : if (!keyIsBinary (key) || (keyGetValueSize (key) == 0 && metaMode)) return 0;
137 :
138 12 : char * base64 = base64Encode (keyValue (key), (size_t) keyGetValueSize (key));
139 12 : if (!base64)
140 : {
141 0 : ELEKTRA_SET_OUT_OF_MEMORY_ERROR (parent, "Memory allocation failed");
142 0 : return -1;
143 : }
144 :
145 12 : if (metaMode)
146 : {
147 1 : keySetString (key, base64);
148 : }
149 : else
150 : {
151 11 : const size_t newValLen = strlen (base64) + ELEKTRA_PLUGIN_BASE64_PREFIX_LENGTH + 1;
152 11 : char * newVal = elektraMalloc (newValLen);
153 11 : if (!newVal)
154 : {
155 0 : ELEKTRA_SET_OUT_OF_MEMORY_ERROR (parent, "Memory allocation failed");
156 0 : elektraFree (base64);
157 0 : return -1;
158 : }
159 11 : snprintf (newVal, newValLen, "%s%s", ELEKTRA_PLUGIN_BASE64_PREFIX, base64); //! OCLint (constant conditional operator)
160 11 : keySetString (key, newVal);
161 11 : elektraFree (newVal);
162 : }
163 :
164 12 : elektraFree (base64);
165 :
166 12 : return 1;
167 : }
168 :
169 : /**
170 : * @brief Escape the prefix character `ELEKTRA_PLUGIN_BASE64_PREFIX` (`@`) in a key value by prefixing it with another `@`.
171 : *
172 : * This function only inserts another prefix character if the type of `key` is string.
173 : *
174 : * @param key This parameter specifies the key value that this function escapes.
175 : * @param parent The function stores information about errors in this parameter.
176 : *
177 : * @retval -1 if the function was unable to escape the key value
178 : * @retval 0 if the function did not change the key value
179 : * @retval 1 if the function successfully escaped the value of `key`
180 : */
181 32 : static int escape (Key * key, Key * parent)
182 : {
183 32 : if (keyIsString (key) == 0) return 0;
184 :
185 : // escape the prefix character
186 21 : const char * strVal = keyString (key);
187 21 : const size_t strValLen = strlen (strVal);
188 21 : if (strValLen <= 0 || strVal[0] != ELEKTRA_PLUGIN_BASE64_ESCAPE_CHAR) return 0;
189 :
190 : // + 1 for the additional escape character
191 : // + 1 for the NULL terminator
192 4 : char * escapedVal = elektraMalloc (strValLen + 2);
193 4 : if (!escapedVal)
194 : {
195 0 : ELEKTRA_SET_OUT_OF_MEMORY_ERROR (parent, "Memory allocation failed");
196 0 : return -1;
197 : }
198 :
199 : // add the escape character in front of the original value
200 4 : escapedVal[0] = ELEKTRA_PLUGIN_BASE64_ESCAPE_CHAR;
201 4 : strncpy (&escapedVal[1], strVal, strValLen + 1); //! OCLint (constant conditional operator)
202 4 : keySetString (key, escapedVal);
203 4 : elektraFree (escapedVal);
204 4 : return 1;
205 : }
206 :
207 : /**
208 : * @brief Check if the plugin should use meta mode for the base64 conversion.
209 : *
210 : * @param handle This parameter stores the configuration of the plugin.
211 : *
212 : * @retval true if the plugin should use meta mode
213 : * @retval false if the plugin should use escaping mode
214 : */
215 230 : static bool useMetaMode (Plugin * handle)
216 : {
217 230 : KeySet * config = elektraPluginGetConfig (handle);
218 230 : Key * metaMode = ksLookupByName (config, "/binary/meta", 0);
219 :
220 : ELEKTRA_LOG ("Using %s mode", metaMode ? "meta" : "escaping");
221 230 : return metaMode ? true : false;
222 : }
223 :
224 : /**
225 : * @brief Establish the Elektra plugin contract and decode all Base64 encoded values back to their original binary form.
226 : *
227 : * @param handle This parameter stores the configuration of the plugin.
228 : * @param keySet This parameter specifies the key set that this function updates.
229 : * @param parent The function stores information about errors/warnings in this parameter.
230 : *
231 : * @retval 1 if any keys were updated
232 : * @retval 0 if `keyset` was not modified
233 : * @retval -1 on failure
234 : */
235 560 : int PLUGIN_FUNCTION (get) (Plugin * handle, KeySet * keySet, Key * parentKey)
236 : {
237 : // Publish module configuration to Elektra (establish the contract)
238 560 : if (!strcmp (keyName (parentKey), "system/elektra/modules/" ELEKTRA_PLUGIN_NAME))
239 : {
240 394 : KeySet * moduleConfig = ksNew (30,
241 : #include "contract.h"
242 : KS_END);
243 394 : ksAppend (keySet, moduleConfig);
244 394 : ksDel (moduleConfig);
245 394 : return 1;
246 : }
247 :
248 166 : bool metaMode = useMetaMode (handle);
249 :
250 : // base64 decoding
251 : Key * key;
252 166 : ksRewind (keySet);
253 166 : int status = 0;
254 879 : while (status >= 0 && (key = ksNext (keySet)))
255 : {
256 547 : status |= decode (key, parentKey, metaMode);
257 : }
258 : return status;
259 : }
260 :
261 : /**
262 : * @brief Encode all binary values using the Base64 encoding scheme.
263 : *
264 : * @param handle This parameter stores the configuration of the plugin.
265 : * @param keySet This parameter specifies the key set that this function updates.
266 : * @param parent The function stores information about errors in this parameter.
267 : *
268 : * @retval 1 if any keys were updated
269 : * @retval 0 if `keyset` was not modified
270 : * @retval -1 on failure
271 : */
272 64 : int PLUGIN_FUNCTION (set) (Plugin * handle, KeySet * keySet, Key * parentKey)
273 : {
274 : Key * key;
275 :
276 64 : ksRewind (keySet);
277 :
278 64 : bool metaMode = useMetaMode (handle);
279 :
280 64 : int status = 0;
281 323 : while (status >= 0 && (key = ksNext (keySet)))
282 : {
283 195 : if (!metaMode)
284 : {
285 32 : status |= escape (key, parentKey);
286 32 : if (status < 0) break;
287 : }
288 195 : status |= encode (key, parentKey, metaMode);
289 : }
290 64 : return status;
291 : }
292 :
293 1175 : Plugin * ELEKTRA_PLUGIN_EXPORT
294 : {
295 1175 : return elektraPluginExport (ELEKTRA_PLUGIN_NAME, ELEKTRA_PLUGIN_GET, &PLUGIN_FUNCTION (get), ELEKTRA_PLUGIN_SET,
296 : &PLUGIN_FUNCTION (set), ELEKTRA_PLUGIN_END);
297 : }
|