| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "../libnetdata.h" |
| 4 | |
| 5 | // defined again in health/rrdvar.h — keep both in sync |
| 6 | #ifndef RRDVAR_MAX_LENGTH |
| 7 | #define RRDVAR_MAX_LENGTH 1024 |
| 8 | #endif |
| 9 | |
| 10 | // -------------------------------------------------------------------------------------------------------------------- |
| 11 | // RRD string sanitization (for units, title, family, context, plugin, module) |
| 12 | // |
| 13 | // This is more permissive than chart names - it preserves most printable ASCII. |
| 14 | // The only transformations are: |
| 15 | // - control characters → space (deduplicated by text_sanitize) |
| 16 | // - backslash → forward slash (to avoid escape sequence issues in protocols) |
| 17 | // - double quote → single quote (to avoid issues with quoted string parsing) |
| 18 | // |
| 19 | // UTF-8 is preserved by text_sanitize() when called with utf=true. |
| 20 | |
| 21 | unsigned char rrd_string_allowed_chars[256] = { |
| 22 | // Control characters (0-31) → space |
| 23 | [0] = '\0', // NUL stays NUL (string terminator) |
| 24 | [1] = ' ', [2] = ' ', [3] = ' ', [4] = ' ', [5] = ' ', [6] = ' ', [7] = ' ', [8] = ' ', |
| 25 | ['\t'] = ' ', ['\n'] = ' ', ['\v'] = ' ', ['\f'] = ' ', ['\r'] = ' ', |
| 26 | [14] = ' ', [15] = ' ', [16] = ' ', [17] = ' ', [18] = ' ', [19] = ' ', [20] = ' ', [21] = ' ', |
| 27 | [22] = ' ', [23] = ' ', [24] = ' ', [25] = ' ', [26] = ' ', [27] = ' ', [28] = ' ', [29] = ' ', |
| 28 | [30] = ' ', [31] = ' ', |
| 29 | |
| 30 | // Printable ASCII (32-126) - mostly pass through |
| 31 | [' '] = ' ', |
| 32 | ['!'] = '!', ['"'] = '\'', ['#'] = '#', ['$'] = '$', ['%'] = '%', ['&'] = '&', ['\''] = '\'', |
| 33 | ['('] = '(', [')'] = ')', ['*'] = '*', ['+'] = '+', [','] = ',', ['-'] = '-', ['.'] = '.', ['/'] = '/', |
| 34 | ['0'] = '0', ['1'] = '1', ['2'] = '2', ['3'] = '3', ['4'] = '4', ['5'] = '5', ['6'] = '6', ['7'] = '7', |
| 35 | ['8'] = '8', ['9'] = '9', |
| 36 | [':'] = ':', [';'] = ';', ['<'] = '<', ['='] = '=', ['>'] = '>', ['?'] = '?', ['@'] = '@', |
| 37 | ['A'] = 'A', ['B'] = 'B', ['C'] = 'C', ['D'] = 'D', ['E'] = 'E', ['F'] = 'F', ['G'] = 'G', ['H'] = 'H', |
| 38 | ['I'] = 'I', ['J'] = 'J', ['K'] = 'K', ['L'] = 'L', ['M'] = 'M', ['N'] = 'N', ['O'] = 'O', ['P'] = 'P', |
| 39 | ['Q'] = 'Q', ['R'] = 'R', ['S'] = 'S', ['T'] = 'T', ['U'] = 'U', ['V'] = 'V', ['W'] = 'W', ['X'] = 'X', |
| 40 | ['Y'] = 'Y', ['Z'] = 'Z', |
| 41 | ['['] = '[', ['\\'] = '/', [']'] = ']', ['^'] = '^', ['_'] = '_', ['`'] = '`', |
| 42 | ['a'] = 'a', ['b'] = 'b', ['c'] = 'c', ['d'] = 'd', ['e'] = 'e', ['f'] = 'f', ['g'] = 'g', ['h'] = 'h', |
| 43 | ['i'] = 'i', ['j'] = 'j', ['k'] = 'k', ['l'] = 'l', ['m'] = 'm', ['n'] = 'n', ['o'] = 'o', ['p'] = 'p', |
| 44 | ['q'] = 'q', ['r'] = 'r', ['s'] = 's', ['t'] = 't', ['u'] = 'u', ['v'] = 'v', ['w'] = 'w', ['x'] = 'x', |
| 45 | ['y'] = 'y', ['z'] = 'z', |
| 46 | ['{'] = '{', ['|'] = '|', ['}'] = '}', ['~'] = '~', |
| 47 | |
| 48 | // DEL and high bytes (127-255) - text_sanitize handles UTF-8, these are fallbacks for orphan bytes |
| 49 | [127] = ' ', |
| 50 | [128] = ' ', [129] = ' ', [130] = ' ', [131] = ' ', [132] = ' ', [133] = ' ', [134] = ' ', [135] = ' ', |
| 51 | [136] = ' ', [137] = ' ', [138] = ' ', [139] = ' ', [140] = ' ', [141] = ' ', [142] = ' ', [143] = ' ', |
| 52 | [144] = ' ', [145] = ' ', [146] = ' ', [147] = ' ', [148] = ' ', [149] = ' ', [150] = ' ', [151] = ' ', |
| 53 | [152] = ' ', [153] = ' ', [154] = ' ', [155] = ' ', [156] = ' ', [157] = ' ', [158] = ' ', [159] = ' ', |
| 54 | [160] = ' ', [161] = ' ', [162] = ' ', [163] = ' ', [164] = ' ', [165] = ' ', [166] = ' ', [167] = ' ', |
| 55 | [168] = ' ', [169] = ' ', [170] = ' ', [171] = ' ', [172] = ' ', [173] = ' ', [174] = ' ', [175] = ' ', |
| 56 | [176] = ' ', [177] = ' ', [178] = ' ', [179] = ' ', [180] = ' ', [181] = ' ', [182] = ' ', [183] = ' ', |
| 57 | [184] = ' ', [185] = ' ', [186] = ' ', [187] = ' ', [188] = ' ', [189] = ' ', [190] = ' ', [191] = ' ', |
| 58 | [192] = ' ', [193] = ' ', [194] = ' ', [195] = ' ', [196] = ' ', [197] = ' ', [198] = ' ', [199] = ' ', |
| 59 | [200] = ' ', [201] = ' ', [202] = ' ', [203] = ' ', [204] = ' ', [205] = ' ', [206] = ' ', [207] = ' ', |
| 60 | [208] = ' ', [209] = ' ', [210] = ' ', [211] = ' ', [212] = ' ', [213] = ' ', [214] = ' ', [215] = ' ', |
| 61 | [216] = ' ', [217] = ' ', [218] = ' ', [219] = ' ', [220] = ' ', [221] = ' ', [222] = ' ', [223] = ' ', |
| 62 | [224] = ' ', [225] = ' ', [226] = ' ', [227] = ' ', [228] = ' ', [229] = ' ', [230] = ' ', [231] = ' ', |
| 63 | [232] = ' ', [233] = ' ', [234] = ' ', [235] = ' ', [236] = ' ', [237] = ' ', [238] = ' ', [239] = ' ', |
| 64 | [240] = ' ', [241] = ' ', [242] = ' ', [243] = ' ', [244] = ' ', [245] = ' ', [246] = ' ', [247] = ' ', |
| 65 | [248] = ' ', [249] = ' ', [250] = ' ', [251] = ' ', [252] = ' ', [253] = ' ', [254] = ' ', [255] = ' ' |
| 66 | }; |
| 67 | |
| 68 | // -------------------------------------------------------------------------------------------------------------------- |
| 69 | |
| 70 | /* |
| 71 | * control characters become space, which are deduplicated. |
| 72 | * |
| 73 | * Character Name Sym To Why |
| 74 | * ---------------- --- --- ------------------------------------------------------------------------------------- |
| 75 | * space [ ] -> [_] |
| 76 | * exclamation mark [!] -> [_] (only when it is the first character) simple patterns negation |
| 77 | * double quotes ["] -> [_] needs escaping when parsing |
| 78 | * dollar [$] -> [_] health variables and security in alarm-notify.sh, cgroup-name.sh, etc. |
| 79 | * percent [%] -> [_] http GET encoded characters |
| 80 | * ampersand [&] -> [_] http GET fields separator |
| 81 | * single quote ['] -> [_] needs escaping when parsing |
| 82 | * asterisk [*] -> [_] simple pattern wildcard |
| 83 | * plus [+] -> [_] http GET space |
| 84 | * comma [,] -> [.] list separator (probably not used today) |
| 85 | * equal [=] -> [_] plugins.d protocol separator |
| 86 | * question mark [?] -> [_] http GET query string separator |
| 87 | * at [@] -> [_] hostname separator (on the UI) |
| 88 | * apostrophe [`] -> [_] bash expansion (security in alarm-notify.sh and other shell scripts) |
| 89 | * pipe [|] -> [_] list separator (simple patterns and http GET) |
| 90 | * backslash [\] -> [/] to avoid interfering with escaping logic |
| 91 | */ |
| 92 | |
| 93 | unsigned char chart_names_allowed_chars[256] = { |
| 94 | [0] = '\0', [1] = ' ', [2] = ' ', [3] = ' ', [4] = ' ', [5] = ' ', [6] = ' ', [7] = ' ', [8] = ' ', |
| 95 | |
| 96 | // control characters to be treated as spaces |
| 97 | ['\t'] = ' ', ['\n'] = ' ', ['\v'] = ' ', ['\f'] = ' ', ['\r'] = ' ', |
| 98 | |
| 99 | [14] = ' ', [15] = ' ', [16] = ' ', [17] = ' ', [18] = ' ', [19] = ' ', [20] = ' ', [21] = ' ', |
| 100 | [22] = ' ', [23] = ' ', [24] = ' ', [25] = ' ', [26] = ' ', [27] = ' ', [28] = ' ', [29] = ' ', |
| 101 | [30] = ' ', [31] = ' ', |
| 102 | |
| 103 | // symbols |
| 104 | [' '] = ' ', ['!'] = '!', ['"'] = '_', ['#'] = '#', ['$'] = '_', ['%'] = '_', ['&'] = '_', ['\''] = '_', |
| 105 | ['('] = '(', [')'] = ')', ['*'] = '_', ['+'] = '_', [','] = '.', ['-'] = '-', ['.'] = '.', ['/'] = '/', |
| 106 | |
| 107 | // numbers |
| 108 | ['0'] = '0', ['1'] = '1', ['2'] = '2', ['3'] = '3', ['4'] = '4', ['5'] = '5', ['6'] = '6', ['7'] = '7', |
| 109 | ['8'] = '8', ['9'] = '9', |
| 110 | |
| 111 | // symbols |
| 112 | [':'] = ':', [';'] = ';', ['<'] = '<', ['='] = '_', ['>'] = '>', ['?'] = '_', ['@'] = '_', |
| 113 | |
| 114 | // capitals |
| 115 | ['A'] = 'A', ['B'] = 'B', ['C'] = 'C', ['D'] = 'D', ['E'] = 'E', ['F'] = 'F', ['G'] = 'G', ['H'] = 'H', |
| 116 | ['I'] = 'I', ['J'] = 'J', ['K'] = 'K', ['L'] = 'L', ['M'] = 'M', ['N'] = 'N', ['O'] = 'O', ['P'] = 'P', |
| 117 | ['Q'] = 'Q', ['R'] = 'R', ['S'] = 'S', ['T'] = 'T', ['U'] = 'U', ['V'] = 'V', ['W'] = 'W', ['X'] = 'X', |
| 118 | ['Y'] = 'Y', ['Z'] = 'Z', |
| 119 | |
| 120 | // symbols |
| 121 | ['['] = '[', ['\\'] = '/', [']'] = ']', ['^'] = '_', ['_'] = '_', ['`'] = '_', |
| 122 | |
| 123 | // lower |
| 124 | ['a'] = 'a', ['b'] = 'b', ['c'] = 'c', ['d'] = 'd', ['e'] = 'e', ['f'] = 'f', ['g'] = 'g', ['h'] = 'h', |
| 125 | ['i'] = 'i', ['j'] = 'j', ['k'] = 'k', ['l'] = 'l', ['m'] = 'm', ['n'] = 'n', ['o'] = 'o', ['p'] = 'p', |
| 126 | ['q'] = 'q', ['r'] = 'r', ['s'] = 's', ['t'] = 't', ['u'] = 'u', ['v'] = 'v', ['w'] = 'w', ['x'] = 'x', |
| 127 | ['y'] = 'y', ['z'] = 'z', |
| 128 | |
| 129 | // symbols |
| 130 | ['{'] = '{', ['|'] = '_', ['}'] = '}', ['~'] = '~', |
| 131 | |
| 132 | // rest |
| 133 | [127] = ' ', [128] = ' ', [129] = ' ', [130] = ' ', [131] = ' ', [132] = ' ', [133] = ' ', [134] = ' ', |
| 134 | [135] = ' ', [136] = ' ', [137] = ' ', [138] = ' ', [139] = ' ', [140] = ' ', [141] = ' ', [142] = ' ', |
| 135 | [143] = ' ', [144] = ' ', [145] = ' ', [146] = ' ', [147] = ' ', [148] = ' ', [149] = ' ', [150] = ' ', |
| 136 | [151] = ' ', [152] = ' ', [153] = ' ', [154] = ' ', [155] = ' ', [156] = ' ', [157] = ' ', [158] = ' ', |
| 137 | [159] = ' ', [160] = ' ', [161] = ' ', [162] = ' ', [163] = ' ', [164] = ' ', [165] = ' ', [166] = ' ', |
| 138 | [167] = ' ', [168] = ' ', [169] = ' ', [170] = ' ', [171] = ' ', [172] = ' ', [173] = ' ', [174] = ' ', |
| 139 | [175] = ' ', [176] = ' ', [177] = ' ', [178] = ' ', [179] = ' ', [180] = ' ', [181] = ' ', [182] = ' ', |
| 140 | [183] = ' ', [184] = ' ', [185] = ' ', [186] = ' ', [187] = ' ', [188] = ' ', [189] = ' ', [190] = ' ', |
| 141 | [191] = ' ', [192] = ' ', [193] = ' ', [194] = ' ', [195] = ' ', [196] = ' ', [197] = ' ', [198] = ' ', |
| 142 | [199] = ' ', [200] = ' ', [201] = ' ', [202] = ' ', [203] = ' ', [204] = ' ', [205] = ' ', [206] = ' ', |
| 143 | [207] = ' ', [208] = ' ', [209] = ' ', [210] = ' ', [211] = ' ', [212] = ' ', [213] = ' ', [214] = ' ', |
| 144 | [215] = ' ', [216] = ' ', [217] = ' ', [218] = ' ', [219] = ' ', [220] = ' ', [221] = ' ', [222] = ' ', |
| 145 | [223] = ' ', [224] = ' ', [225] = ' ', [226] = ' ', [227] = ' ', [228] = ' ', [229] = ' ', [230] = ' ', |
| 146 | [231] = ' ', [232] = ' ', [233] = ' ', [234] = ' ', [235] = ' ', [236] = ' ', [237] = ' ', [238] = ' ', |
| 147 | [239] = ' ', [240] = ' ', [241] = ' ', [242] = ' ', [243] = ' ', [244] = ' ', [245] = ' ', [246] = ' ', |
| 148 | [247] = ' ', [248] = ' ', [249] = ' ', [250] = ' ', [251] = ' ', [252] = ' ', [253] = ' ', [254] = ' ', |
| 149 | [255] = ' ' |
| 150 | }; |
| 151 | |
| 152 | static inline void sanitize_chart_name(char *dst, const char *src, size_t dst_size) { |
| 153 | // text_sanitize deduplicates spaces |
| 154 | text_sanitize((unsigned char *)dst, (const unsigned char *)src, dst_size, |
| 155 | chart_names_allowed_chars, true, "", NULL); |
| 156 | |
| 157 | char *d = dst; |
| 158 | |
| 159 | // do not accept ! as the first character |
| 160 | if(*d == '!') *d = '_'; |
| 161 | |
| 162 | // convert remaining spaces to underscores |
| 163 | while(*d) { |
| 164 | if(*d == ' ') *d = '_'; |
| 165 | d++; |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | // make sure the supplied string |
| 170 | // is good for a netdata chart/dimension ID/NAME |
| 171 | void netdata_fix_chart_name(char *s) { |
| 172 | sanitize_chart_name(s, s, strlen(s) + 1); |
| 173 | } |
| 174 | |
| 175 | void netdata_fix_chart_id(char *s) { |
| 176 | sanitize_chart_name(s, s, strlen(s) + 1); |
| 177 | // size_t len = strlen(s); |
| 178 | // char buf[len + 1]; |
| 179 | // |
| 180 | // text_sanitize((unsigned char *)buf, (const unsigned char *)s, sizeof(buf), |
| 181 | // chart_names_allowed_chars, true, "", NULL); |
| 182 | // |
| 183 | // if(memcmp(s, buf, sizeof(buf)) == 0) |
| 184 | // // they are the same |
| 185 | // return; |
| 186 | // |
| 187 | // // they differ |
| 188 | // XXH128_hash_t hash = XXH3_128bits(s, len); |
| 189 | // ND_UUID *uuid = (ND_UUID *)&hash; |
| 190 | // internal_fatal(sizeof(hash) != sizeof(ND_UUID), "XXH128 and ND_UUID do not have the same size"); |
| 191 | // buf[0] = 'x'; |
| 192 | // buf[1] = 'x'; |
| 193 | // buf[2] = 'h'; |
| 194 | // buf[3] = '_'; |
| 195 | // uuid_unparse_lower_compact(uuid->uuid, &buf[4]); |
| 196 | } |
| 197 | |
| 198 | char *rrdset_strncpyz_name(char *dst, const char *src, size_t dst_size_minus_1) { |
| 199 | // src starts with "type." |
| 200 | sanitize_chart_name(dst, src, dst_size_minus_1 + 1); |
| 201 | return dst; |
| 202 | } |
| 203 | |
| 204 | bool rrdvar_fix_name(char *variable) { |
| 205 | size_t len = strlen(variable); |
| 206 | |
| 207 | char buf[RRDVAR_MAX_LENGTH + 1]; |
| 208 | if(unlikely(len >= sizeof(buf))) |
| 209 | len = sizeof(buf) - 1; |
| 210 | |
| 211 | memcpy(buf, variable, len + 1); |
| 212 | sanitize_chart_name(variable, variable, len + 1); |
| 213 | return memcmp(buf, variable, len + 1) != 0; |
| 214 | } |