chiark / gitweb /
98b4bdf18c37d49976292a54112eb50d3557aa63
[elogind.git] / src / basic / escape.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3   This file is part of systemd.
4
5   Copyright 2010 Lennart Poettering
6 ***/
7
8 #include <errno.h>
9 #include <stdlib.h>
10 #include <string.h>
11
12 #include "alloc-util.h"
13 #include "escape.h"
14 #include "hexdecoct.h"
15 #include "macro.h"
16 #include "utf8.h"
17
18 int cescape_char(char c, char *buf) {
19         char *buf_old = buf;
20
21         /* Needs space for 4 characters in the buffer */
22
23         switch (c) {
24
25                 case '\a':
26                         *(buf++) = '\\';
27                         *(buf++) = 'a';
28                         break;
29                 case '\b':
30                         *(buf++) = '\\';
31                         *(buf++) = 'b';
32                         break;
33                 case '\f':
34                         *(buf++) = '\\';
35                         *(buf++) = 'f';
36                         break;
37                 case '\n':
38                         *(buf++) = '\\';
39                         *(buf++) = 'n';
40                         break;
41                 case '\r':
42                         *(buf++) = '\\';
43                         *(buf++) = 'r';
44                         break;
45                 case '\t':
46                         *(buf++) = '\\';
47                         *(buf++) = 't';
48                         break;
49                 case '\v':
50                         *(buf++) = '\\';
51                         *(buf++) = 'v';
52                         break;
53                 case '\\':
54                         *(buf++) = '\\';
55                         *(buf++) = '\\';
56                         break;
57                 case '"':
58                         *(buf++) = '\\';
59                         *(buf++) = '"';
60                         break;
61                 case '\'':
62                         *(buf++) = '\\';
63                         *(buf++) = '\'';
64                         break;
65
66                 default:
67                         /* For special chars we prefer octal over
68                          * hexadecimal encoding, simply because glib's
69                          * g_strescape() does the same */
70                         if ((c < ' ') || (c >= 127)) {
71                                 *(buf++) = '\\';
72                                 *(buf++) = octchar((unsigned char) c >> 6);
73                                 *(buf++) = octchar((unsigned char) c >> 3);
74                                 *(buf++) = octchar((unsigned char) c);
75                         } else
76                                 *(buf++) = c;
77                         break;
78         }
79
80         return buf - buf_old;
81 }
82
83 char *cescape_length(const char *s, size_t n) {
84         const char *f;
85         char *r, *t;
86
87         assert(s || n == 0);
88
89         /* Does C style string escaping. May be reversed with
90          * cunescape(). */
91
92         r = new(char, n*4 + 1);
93         if (!r)
94                 return NULL;
95
96         for (f = s, t = r; f < s + n; f++)
97                 t += cescape_char(*f, t);
98
99         *t = 0;
100
101         return r;
102 }
103
104 char *cescape(const char *s) {
105         assert(s);
106
107         return cescape_length(s, strlen(s));
108 }
109
110 int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit) {
111         int r = 1;
112
113         assert(p);
114         assert(*p);
115         assert(ret);
116
117         /* Unescapes C style. Returns the unescaped character in ret.
118          * Sets *eight_bit to true if the escaped sequence either fits in
119          * one byte in UTF-8 or is a non-unicode literal byte and should
120          * instead be copied directly.
121          */
122
123         if (length != (size_t) -1 && length < 1)
124                 return -EINVAL;
125
126         switch (p[0]) {
127
128         case 'a':
129                 *ret = '\a';
130                 break;
131         case 'b':
132                 *ret = '\b';
133                 break;
134         case 'f':
135                 *ret = '\f';
136                 break;
137         case 'n':
138                 *ret = '\n';
139                 break;
140         case 'r':
141                 *ret = '\r';
142                 break;
143         case 't':
144                 *ret = '\t';
145                 break;
146         case 'v':
147                 *ret = '\v';
148                 break;
149         case '\\':
150                 *ret = '\\';
151                 break;
152         case '"':
153                 *ret = '"';
154                 break;
155         case '\'':
156                 *ret = '\'';
157                 break;
158
159         case 's':
160                 /* This is an extension of the XDG syntax files */
161                 *ret = ' ';
162                 break;
163
164         case 'x': {
165                 /* hexadecimal encoding */
166                 int a, b;
167
168                 if (length != (size_t) -1 && length < 3)
169                         return -EINVAL;
170
171                 a = unhexchar(p[1]);
172                 if (a < 0)
173                         return -EINVAL;
174
175                 b = unhexchar(p[2]);
176                 if (b < 0)
177                         return -EINVAL;
178
179                 /* Don't allow NUL bytes */
180                 if (a == 0 && b == 0)
181                         return -EINVAL;
182
183                 *ret = (a << 4U) | b;
184                 *eight_bit = true;
185                 r = 3;
186                 break;
187         }
188
189         case 'u': {
190                 /* C++11 style 16bit unicode */
191
192                 int a[4];
193                 size_t i;
194                 uint32_t c;
195
196                 if (length != (size_t) -1 && length < 5)
197                         return -EINVAL;
198
199                 for (i = 0; i < 4; i++) {
200                         a[i] = unhexchar(p[1 + i]);
201                         if (a[i] < 0)
202                                 return a[i];
203                 }
204
205                 c = ((uint32_t) a[0] << 12U) | ((uint32_t) a[1] << 8U) | ((uint32_t) a[2] << 4U) | (uint32_t) a[3];
206
207                 /* Don't allow 0 chars */
208                 if (c == 0)
209                         return -EINVAL;
210
211                 *ret = c;
212                 r = 5;
213                 break;
214         }
215
216         case 'U': {
217                 /* C++11 style 32bit unicode */
218
219                 int a[8];
220                 size_t i;
221                 char32_t c;
222
223                 if (length != (size_t) -1 && length < 9)
224                         return -EINVAL;
225
226                 for (i = 0; i < 8; i++) {
227                         a[i] = unhexchar(p[1 + i]);
228                         if (a[i] < 0)
229                                 return a[i];
230                 }
231
232                 c = ((uint32_t) a[0] << 28U) | ((uint32_t) a[1] << 24U) | ((uint32_t) a[2] << 20U) | ((uint32_t) a[3] << 16U) |
233                     ((uint32_t) a[4] << 12U) | ((uint32_t) a[5] <<  8U) | ((uint32_t) a[6] <<  4U) |  (uint32_t) a[7];
234
235                 /* Don't allow 0 chars */
236                 if (c == 0)
237                         return -EINVAL;
238
239                 /* Don't allow invalid code points */
240                 if (!unichar_is_valid(c))
241                         return -EINVAL;
242
243                 *ret = c;
244                 r = 9;
245                 break;
246         }
247
248         case '0':
249         case '1':
250         case '2':
251         case '3':
252         case '4':
253         case '5':
254         case '6':
255         case '7': {
256                 /* octal encoding */
257                 int a, b, c;
258                 char32_t m;
259
260                 if (length != (size_t) -1 && length < 3)
261                         return -EINVAL;
262
263                 a = unoctchar(p[0]);
264                 if (a < 0)
265                         return -EINVAL;
266
267                 b = unoctchar(p[1]);
268                 if (b < 0)
269                         return -EINVAL;
270
271                 c = unoctchar(p[2]);
272                 if (c < 0)
273                         return -EINVAL;
274
275                 /* don't allow NUL bytes */
276                 if (a == 0 && b == 0 && c == 0)
277                         return -EINVAL;
278
279                 /* Don't allow bytes above 255 */
280                 m = ((uint32_t) a << 6U) | ((uint32_t) b << 3U) | (uint32_t) c;
281                 if (m > 255)
282                         return -EINVAL;
283
284                 *ret = m;
285                 *eight_bit = true;
286                 r = 3;
287                 break;
288         }
289
290         default:
291                 return -EINVAL;
292         }
293
294         return r;
295 }
296
297 int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret) {
298         char *r, *t;
299         const char *f;
300         size_t pl;
301
302         assert(s);
303         assert(ret);
304
305         /* Undoes C style string escaping, and optionally prefixes it. */
306
307         pl = strlen_ptr(prefix);
308
309         r = new(char, pl+length+1);
310         if (!r)
311                 return -ENOMEM;
312
313         if (prefix)
314                 memcpy(r, prefix, pl);
315
316         for (f = s, t = r + pl; f < s + length; f++) {
317                 size_t remaining;
318                 bool eight_bit = false;
319                 char32_t u;
320                 int k;
321
322                 remaining = s + length - f;
323                 assert(remaining > 0);
324
325                 if (*f != '\\') {
326                         /* A literal, copy verbatim */
327                         *(t++) = *f;
328                         continue;
329                 }
330
331                 if (remaining == 1) {
332                         if (flags & UNESCAPE_RELAX) {
333                                 /* A trailing backslash, copy verbatim */
334                                 *(t++) = *f;
335                                 continue;
336                         }
337
338                         free(r);
339                         return -EINVAL;
340                 }
341
342                 k = cunescape_one(f + 1, remaining - 1, &u, &eight_bit);
343                 if (k < 0) {
344                         if (flags & UNESCAPE_RELAX) {
345                                 /* Invalid escape code, let's take it literal then */
346                                 *(t++) = '\\';
347                                 continue;
348                         }
349
350                         free(r);
351                         return k;
352                 }
353
354                 f += k;
355                 if (eight_bit)
356                         /* One byte? Set directly as specified */
357                         *(t++) = u;
358                 else
359                         /* Otherwise encode as multi-byte UTF-8 */
360                         t += utf8_encode_unichar(t, u);
361         }
362
363         *t = 0;
364
365         *ret = r;
366         return t - r;
367 }
368
369 int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret) {
370         return cunescape_length_with_prefix(s, length, NULL, flags, ret);
371 }
372
373 int cunescape(const char *s, UnescapeFlags flags, char **ret) {
374         return cunescape_length(s, strlen(s), flags, ret);
375 }
376
377 char *xescape(const char *s, const char *bad) {
378         char *r, *t;
379         const char *f;
380
381         /* Escapes all chars in bad, in addition to \ and all special
382          * chars, in \xFF style escaping. May be reversed with
383          * cunescape(). */
384
385         r = new(char, strlen(s) * 4 + 1);
386         if (!r)
387                 return NULL;
388
389         for (f = s, t = r; *f; f++) {
390
391                 if ((*f < ' ') || (*f >= 127) ||
392                     (*f == '\\') || strchr(bad, *f)) {
393                         *(t++) = '\\';
394                         *(t++) = 'x';
395                         *(t++) = hexchar(*f >> 4);
396                         *(t++) = hexchar(*f);
397                 } else
398                         *(t++) = *f;
399         }
400
401         *t = 0;
402
403         return r;
404 }
405
406 #if 0 /// UNNEEDED by elogind
407 char *octescape(const char *s, size_t len) {
408         char *r, *t;
409         const char *f;
410
411         /* Escapes all chars in bad, in addition to \ and " chars,
412          * in \nnn style escaping. */
413
414         r = new(char, len * 4 + 1);
415         if (!r)
416                 return NULL;
417
418         for (f = s, t = r; f < s + len; f++) {
419
420                 if (*f < ' ' || *f >= 127 || IN_SET(*f, '\\', '"')) {
421                         *(t++) = '\\';
422                         *(t++) = '0' + (*f >> 6);
423                         *(t++) = '0' + ((*f >> 3) & 8);
424                         *(t++) = '0' + (*f & 8);
425                 } else
426                         *(t++) = *f;
427         }
428
429         *t = 0;
430
431         return r;
432
433 }
434
435 static char *strcpy_backslash_escaped(char *t, const char *s, const char *bad, bool escape_tab_nl) {
436         assert(bad);
437
438         for (; *s; s++) {
439                 if (escape_tab_nl && IN_SET(*s, '\n', '\t')) {
440                         *(t++) = '\\';
441                         *(t++) = *s == '\n' ? 'n' : 't';
442                         continue;
443                 }
444
445                 if (*s == '\\' || strchr(bad, *s))
446                         *(t++) = '\\';
447
448                 *(t++) = *s;
449         }
450
451         return t;
452 }
453
454 char *shell_escape(const char *s, const char *bad) {
455         char *r, *t;
456
457         r = new(char, strlen(s)*2+1);
458         if (!r)
459                 return NULL;
460
461         t = strcpy_backslash_escaped(r, s, bad, false);
462         *t = 0;
463
464         return r;
465 }
466
467 char* shell_maybe_quote(const char *s, EscapeStyle style) {
468         const char *p;
469         char *r, *t;
470
471         assert(s);
472
473         /* Encloses a string in quotes if necessary to make it OK as a shell
474          * string. Note that we treat benign UTF-8 characters as needing
475          * escaping too, but that should be OK. */
476
477         for (p = s; *p; p++)
478                 if (*p <= ' ' ||
479                     *p >= 127 ||
480                     strchr(SHELL_NEED_QUOTES, *p))
481                         break;
482
483         if (!*p)
484                 return strdup(s);
485
486         r = new(char, (style == ESCAPE_POSIX) + 1 + strlen(s)*2 + 1 + 1);
487         if (!r)
488                 return NULL;
489
490         t = r;
491         if (style == ESCAPE_BACKSLASH)
492                 *(t++) = '"';
493         else if (style == ESCAPE_POSIX) {
494                 *(t++) = '$';
495                 *(t++) = '\'';
496         } else
497                 assert_not_reached("Bad EscapeStyle");
498
499         t = mempcpy(t, s, p - s);
500
501         if (style == ESCAPE_BACKSLASH)
502                 t = strcpy_backslash_escaped(t, p, SHELL_NEED_ESCAPE, false);
503         else
504                 t = strcpy_backslash_escaped(t, p, SHELL_NEED_ESCAPE_POSIX, true);
505
506         if (style == ESCAPE_BACKSLASH)
507                 *(t++) = '"';
508         else
509                 *(t++) = '\'';
510         *t = 0;
511
512         return r;
513 }
514 #endif // 0