format.c (4343B)
1 // SPDX-License-Identifier: GPL-2.0-or-later 2 // Copyright The Music Player Daemon Project 3 4 #include "format.h" 5 6 #include <stdbool.h> 7 #include <stdio.h> 8 #include <string.h> 9 #include <stdlib.h> 10 11 /** 12 * Reallocate the given string and append the source string. 13 */ 14 gcc_malloc 15 static char * 16 string_append(char *dest, const char *src, size_t len) 17 { 18 size_t destlen = dest != NULL 19 ? strlen(dest) 20 : 0; 21 22 dest = realloc(dest, destlen + len + 1); 23 memcpy(dest + destlen, src, len); 24 dest[destlen + len] = '\0'; 25 26 return dest; 27 } 28 29 /** 30 * Skip the format string until the current group is closed by either 31 * '&', '|' or ']' (supports nesting). 32 */ 33 gcc_pure 34 static const char * 35 skip_format(const char *p) 36 { 37 unsigned stack = 0; 38 39 while (*p != '\0') { 40 if (*p == '[') 41 stack++; 42 else if (*p == '#' && p[1] != '\0') 43 /* skip escaped stuff */ 44 ++p; 45 else if (stack > 0) { 46 if (*p == ']') 47 --stack; 48 } else if (*p == '&' || *p == '|' || *p == ']') 49 break; 50 51 ++p; 52 } 53 54 return p; 55 } 56 57 static bool 58 is_name_char(char ch) 59 { 60 return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || 61 (ch >= '0' && ch <= '9') || ch == '_'; 62 } 63 64 static char * 65 format_object2(const char *format, const char **last, const void *object, 66 const char *(*getter)(const void *object, const char *name)) 67 { 68 char *ret = NULL; 69 const char *p; 70 bool found = false; 71 72 for (p = format; *p != '\0';) { 73 switch (p[0]) { 74 case '|': 75 ++p; 76 if (!found) { 77 /* nothing found yet: try the next 78 section */ 79 free(ret); 80 ret = NULL; 81 } else 82 /* already found a value: skip the 83 next section */ 84 p = skip_format(p); 85 break; 86 87 case '&': 88 ++p; 89 if (!found) 90 /* nothing found yet, so skip this 91 section */ 92 p = skip_format(p); 93 else 94 /* we found something yet, but it will 95 only be used if the next section 96 also found something, so reset the 97 flag */ 98 found = false; 99 break; 100 101 case '[': { 102 char *t = format_object2(p + 1, &p, object, getter); 103 if (t != NULL) { 104 ret = string_append(ret, t, strlen(t)); 105 free(t); 106 found = true; 107 } 108 } 109 break; 110 111 case ']': 112 if (last != NULL) 113 *last = p + 1; 114 if (!found) { 115 free(ret); 116 ret = NULL; 117 } 118 return ret; 119 120 case '\\': { 121 /* take care of escape sequences */ 122 char ltemp; 123 switch (p[1]) { 124 case 'a': 125 ltemp = '\a'; 126 break; 127 128 case 'b': 129 ltemp = '\b'; 130 break; 131 132 case 'e': 133 ltemp = '\033'; 134 break; 135 136 case 't': 137 ltemp = '\t'; 138 break; 139 140 case 'n': 141 ltemp = '\n'; 142 break; 143 144 case 'v': 145 ltemp = '\v'; 146 break; 147 148 case 'f': 149 ltemp = '\f'; 150 break; 151 152 case 'r': 153 ltemp = '\r'; 154 break; 155 156 case '[': 157 case ']': 158 ltemp = p[1]; 159 break; 160 161 default: 162 /* unknown escape: copy the 163 backslash */ 164 ltemp = p[0]; 165 --p; 166 break; 167 } 168 169 ret = string_append(ret, <emp, 1); 170 p += 2; 171 } 172 break; 173 174 case '%': { 175 /* find the extent of this format specifier 176 (stop at \0, ' ', or esc) */ 177 const char *end = p + 1; 178 while (is_name_char(*end)) 179 ++end; 180 181 const size_t length = end - p + 1; 182 183 if (*end != '%') { 184 ret = string_append(ret, p, length - 1); 185 p = end; 186 continue; 187 } 188 189 char name[32]; 190 if (length > (int)sizeof(name)) { 191 ret = string_append(ret, p, length); 192 p = end + 1; 193 continue; 194 } 195 196 memcpy(name, p + 1, length - 2); 197 name[length - 2] = 0; 198 199 const char *value = getter(object, name); 200 size_t value_length; 201 if (value != NULL) { 202 if (*value != 0) 203 found = true; 204 value_length = strlen(value); 205 } else { 206 /* unknown variable: copy verbatim 207 from format string */ 208 value = p; 209 value_length = length; 210 } 211 212 ret = string_append(ret, value, value_length); 213 214 /* advance past the specifier */ 215 p = end + 1; 216 } 217 break; 218 219 case '#': 220 /* let the escape character escape itself */ 221 if (p[1] != '\0') { 222 ret = string_append(ret, p + 1, 1); 223 p += 2; 224 break; 225 } 226 227 /* fall through */ 228 229 default: 230 /* pass-through non-escaped portions of the format string */ 231 ret = string_append(ret, p, 1); 232 ++p; 233 } 234 } 235 236 if (last != NULL) 237 *last = p; 238 return ret; 239 } 240 241 char * 242 format_object(const char *format, const void *object, 243 const char *(*getter)(const void *object, const char *name)) 244 { 245 return format_object2(format, NULL, object, getter); 246 }