source: branches/nameserver/CORE/ConfigMapping.cxx

Last change on this file was 18125, checked in by westram, 5 years ago
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 9.9 KB
Line 
1// ========================================================= //
2//                                                           //
3//   File      : ConfigMapping.cxx                           //
4//   Purpose   : config <-> string mapping                   //
5//                                                           //
6//   Coded by Ralf Westram (coder@reallysoft.de) in Mar 19   //
7//   http://www.arb-home.de/                                 //
8//                                                           //
9// ========================================================= //
10
11#include "ConfigMapping.h"
12#include <arb_msg.h>
13
14
15using namespace std;
16
17GB_ERROR ConfigMapping::decode_escapes(string& s) {
18    string::iterator f = s.begin();
19    string::iterator t = s.begin();
20
21    for (; f != s.end(); ++f, ++t) {
22        if (*f == '\\') {
23            ++f;
24            if (f == s.end()) return GBS_global_string("Trailing \\ in '%s'", s.c_str());
25            switch (*f) {
26                case 'n':
27                    *t = '\n';
28                    break;
29                case 'r':
30                    *t = '\r';
31                    break;
32                case 't':
33                    *t = '\t';
34                    break;
35                default:
36                    *t = *f;
37                    break;
38            }
39        }
40        else {
41            *t = *f;
42        }
43    }
44
45    s.erase(t, f);
46
47    return NULp;
48}
49
50void ConfigMapping::encode_escapes(string& s, const char *to_escape) {
51    string neu;
52    neu.reserve(s.length()*2+1);
53
54    for (string::iterator p = s.begin(); p != s.end(); ++p) {
55        if (*p == '\\' || strchr(to_escape, *p)) {
56            neu = neu+'\\'+*p;
57        }
58        else if (*p == '\n') { neu = neu+"\\n"; }
59        else if (*p == '\r') { neu = neu+"\\r"; }
60        else if (*p == '\t') { neu = neu+"\\t"; }
61        else { neu = neu+*p; }
62    }
63    s = neu;
64}
65
66GB_ERROR ConfigMapping::parseFrom(const std::string& configString) {
67    // parse string in format "key1='value1';key2='value2'"..
68    // and put values into a map.
69    //
70    // assumes that keys are unique
71
72    size_t   pos         = 0;
73    GB_ERROR parse_error = NULp;
74
75    while (!parse_error) {
76        size_t equal = configString.find('=', pos);
77        if (equal == string::npos) break;
78
79        if (configString[equal+1] != '\'') {
80            parse_error = "expected quote \"'\"";
81            break;
82        }
83        size_t start = equal+2;
84        size_t end   = configString.find('\'', start);
85        while (end != string::npos) {
86            if (configString[end-1] != '\\') break; // unescaped quote (=end of value)
87            if (configString[end-2] == '\\') { // multiple escapes -> count
88                int    escCount = 2;
89                size_t bwd      = end-3;
90                while (bwd>=start && configString[bwd] == '\\') {
91                    escCount++;
92                    bwd--;
93                }
94                if ((escCount%2) == 0) break; // even number of escapes => unescaped quote (=end of value)
95                // otherwise the quote belongs to the value
96            }
97            end = configString.find('\'', end+1);
98        }
99        if (end == string::npos) {
100            parse_error = "could not find matching quote \"'\"";
101            break;
102        }
103
104        string config_name = configString.substr(pos, equal-pos);
105        string value       = configString.substr(start, end-start);
106
107        parse_error = decode_escapes(value);
108        if (!parse_error) {
109            set_entry(config_name, value);
110        }
111
112        pos = end+2; // skip ';'
113    }
114    return parse_error;
115}
116
117// --------------------------------------------------------------------------------
118
119#ifdef UNIT_TESTS
120#ifndef TEST_UNIT_H
121#include <test_unit.h>
122#include <arb_defs.h>
123#endif
124
125#define TEST_ESCAPE_ENCODING(plain,expected) do{                        \
126        string encoded = plain;                                         \
127        ConfigMapping::encode_escapes(encoded, "'");                    \
128        TEST_EXPECT_EQUAL(encoded, expected);                           \
129        string decoded = encoded;                                       \
130        TEST_EXPECT_NO_ERROR(ConfigMapping::decode_escapes(decoded));   \
131        TEST_EXPECT_EQUAL(decoded, plain);                              \
132    }while(0)
133
134void TEST_configValueEscaping() {
135    TEST_ESCAPE_ENCODING("hello", "hello"); // plain text
136
137    TEST_ESCAPE_ENCODING("'hello'",   "\\'hello\\'"); // quoted text
138    TEST_ESCAPE_ENCODING("\"hello\"", "\"hello\"");   // double-quoted text
139
140    // special characters (LF, CR + TAB shall not be saved to config, esp. if saving to file via AWT_config_manager)
141    TEST_ESCAPE_ENCODING("LINE\nNEXT", "LINE\\nNEXT");
142    TEST_ESCAPE_ENCODING("1\t2\r3",    "1\\t2\\r3");
143
144    // simple escape-handling
145    TEST_ESCAPE_ENCODING("hel\\lo", "hel\\\\lo");
146    TEST_ESCAPE_ENCODING("\\hello", "\\\\hello");
147    TEST_ESCAPE_ENCODING("hello\\", "hello\\\\");
148
149    TEST_ESCAPE_ENCODING("hello\\'",    "hello\\\\\\'");
150    TEST_ESCAPE_ENCODING("\\'hello\\'", "\\\\\\'hello\\\\\\'");
151
152    string invalidEncoding = "xyz\\";
153    TEST_EXPECT_ERROR_CONTAINS(ConfigMapping::decode_escapes(invalidEncoding), "Trailing \\ in 'xyz\\'");
154}
155
156struct TestedConfig {
157    const char *configString;
158    int         entryCount;
159    const char *entryList;
160};
161struct ReinterpretedConfig {
162    const char *configString;
163    const char *reinterpreted;
164};
165struct FailingConfig {
166    const char *configString;
167    GB_ERROR    error;
168};
169
170static TestedConfig testedCfg[] = {
171    { "",                      0, ""},               // empty config
172    { "tag='value'",           1, "tag" },
173    { "321='1st';abc='2nd'",   2, "321/abc" },
174    { "t-ha t='2';t;hi/s='1'", 2, "t-ha t,t;hi/s" }, // tags can contain anything but '='
175    { "t'ag'='value'",         1, "t'ag'" },         // even quotes are possible in tags
176};
177static FailingConfig failedCfg[] = {
178    { "nix=;was='ia'", "expected quote \"'\"" },          // no value
179    { "tag=value",     "expected quote \"'\"" },          // unquoted value
180    { "hasto='match",  "could not find matching quote" }, // unmatched quote
181};
182static ReinterpretedConfig reinterpretCfg[] = {
183    { "laksjd",            "" },                  // is reinterpreted as "" (empty config)
184    { "this='1';that='2'", "that='2';this='1'" }, // sorted by tagname
185};
186
187inline bool anyStringContains(const CharPtrArray& strings, char c) {
188    for (int i = 0; strings[i]; ++i) {
189        if (strchr(strings[i], c)) {
190            return true;
191        }
192    }
193    return false;
194}
195static char autodetectSeparator(const CharPtrArray& strings) {
196    const char *sep = "/;,:|#*";
197    for (int i = 0; sep[i]; ++i) {
198        if (!anyStringContains(strings, sep[i])) {
199            return sep[i];
200        }
201    }
202    TEST_REJECT(true); // add more sep
203    return 0;
204}
205
206void TEST_ConfigMapping() {
207    for (size_t c = 0; c<ARRAY_ELEMS(testedCfg); ++c) {
208        const TestedConfig& CFG = testedCfg[c];
209        TEST_ANNOTATE(CFG.configString);
210
211        ConfigMapping config;
212        TEST_EXPECT_NO_ERROR(config.parseFrom(CFG.configString));
213
214        // convert to string + compare with loaded config string:
215        TEST_EXPECT_EQUAL(config.config_string(), CFG.configString);
216
217        // test entries:
218        ConstStrArray entries;
219        config.get_entries(entries);
220        TEST_EXPECT_EQUAL(entries.size(), CFG.entryCount);
221
222        TEST_EXPECT_EQUAL_STRINGCOPY__NOERROREXPORTED(GBT_join_strings(entries, autodetectSeparator(entries)), CFG.entryList);
223    }
224
225    // test nested quoting:
226    {
227        TEST_ANNOTATE(NULp);
228        const char    *stored = "tag='val';'qtag'='\\'qval\\''";
229        ConfigMapping  config;
230        TEST_EXPECT_NO_ERROR(config.parseFrom(stored));
231
232        TEST_EXPECT_EQUAL(config.get_entry("tag"),    "val");
233        TEST_EXPECT_EQUAL(config.get_entry("'qtag'"), "'qval'");
234
235        TEST_EXPECT(config.has_entry("'qtag'"));
236        TEST_REJECT(config.has_entry("qtag"));
237
238        const char    *storedAsValue = "stored='tag=\\'val\\';\\'qtag\\'=\\'\\\\\\'qval\\\\\\'\\''";
239        ConfigMapping  wrapped;
240        wrapped.set_entry("stored", stored);
241
242        TEST_EXPECT_EQUAL(wrapped.config_string(), storedAsValue);
243
244        ConfigMapping unwrapped;
245        TEST_EXPECT_NO_ERROR(unwrapped.parseFrom(storedAsValue));
246        TEST_EXPECT_EQUAL(unwrapped.get_entry("stored"), stored);
247
248        // test delete entry
249        unwrapped.delete_entry("stored");
250        TEST_EXPECT_EQUAL(unwrapped.config_string(), "");
251
252        config.delete_entry("'qtag'");
253        TEST_EXPECT_EQUAL(config.config_string(), "tag='val'");
254
255        config.set_entry("tag", "lav"); // test overwriting a value
256        TEST_EXPECT_EQUAL(config.config_string(), "tag='lav'");
257
258        const char *SLASHED = "slashed\\";
259        config.set_entry("key", SLASHED);
260        TEST_EXPECT_EQUAL(config.config_string(), "key='slashed\\\\';tag='lav'");
261
262        {
263            ConfigMapping slashed;
264            string        cfgStr = config.config_string();
265
266            TEST_EXPECT_NO_ERROR(slashed.parseFrom(cfgStr));
267            TEST_EXPECT_EQUAL(slashed.get_entry("key"), SLASHED);
268            TEST_EXPECT_EQUAL(slashed.config_string(), cfgStr);
269        }
270    }
271
272    // test reinterpretation of configs:
273    for (size_t c = 0; c<ARRAY_ELEMS(reinterpretCfg); ++c) {
274        const ReinterpretedConfig& CFG = reinterpretCfg[c];
275        TEST_ANNOTATE(CFG.configString);
276        ConfigMapping config;
277        TEST_EXPECT_NO_ERROR(config.parseFrom(CFG.configString));
278
279        // convert to string + compare with expected reinterpretation:
280        TEST_EXPECT_EQUAL(config.config_string(), CFG.reinterpreted);
281    }
282
283    // test error-config:
284    for (size_t c = 0; c<ARRAY_ELEMS(failedCfg); ++c) {
285        const FailingConfig& CFG = failedCfg[c];
286        TEST_ANNOTATE(CFG.configString);
287        ConfigMapping config;
288        TEST_EXPECT_ERROR_CONTAINS(config.parseFrom(CFG.configString), CFG.error);
289    }
290}
291
292#endif // UNIT_TESTS
293
294// --------------------------------------------------------------------------------
295
Note: See TracBrowser for help on using the repository browser.