1/**
2 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include "json_parser.h"
17#include "logger.h"
18#include "utils.h"
19
20// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
21#define LOG_JSON(level) LOG(level, COMMON) << "JsonParser: " << std::string(log_recursion_level_, '\t')
22
23namespace panda {
24
25bool JsonObject::Parser::Parse(const std::string &text)
26{
27    std::istringstream iss(text);
28    istream_.rdbuf(iss.rdbuf());
29    return Parse();
30}
31
32bool JsonObject::Parser::Parse(std::streambuf *stream_buf)
33{
34    ASSERT(stream_buf != nullptr);
35    istream_.rdbuf(stream_buf);
36    return Parse();
37}
38
39bool JsonObject::Parser::Parse()
40{
41    ASSERT(current_obj_ != nullptr);
42    if (GetJsonObject(current_obj_) && TryGetSymbol('\0')) {
43        LOG_JSON(INFO) << "Successfully parsed JSON-object";
44        return true;
45    }
46    LOG_JSON(ERROR) << "Parsing failed";
47    return false;
48}
49
50bool JsonObject::Parser::GetJsonObject(JsonObject *empty_obj)
51{
52    LOG_JSON(DEBUG) << "Parsing object";
53    log_recursion_level_++;
54    ASSERT(empty_obj != nullptr);
55    ASSERT(empty_obj->values_map_.empty());
56    if (!TryGetSymbol('{')) {
57        return false;
58    }
59
60    if (TryGetSymbol('}')) {
61        empty_obj->is_valid_ = true;
62        return true;
63    }
64
65    while (true) {
66        if (!InsertKeyValuePairIn(empty_obj)) {
67            return false;
68        }
69        if (TryGetSymbol(',')) {
70            LOG_JSON(DEBUG) << "Got a comma-separator, getting a new \"key-value\" pair";
71            continue;
72        }
73        break;
74    }
75
76    log_recursion_level_--;
77    return (empty_obj->is_valid_ = TryGetSymbol('}'));
78}
79
80bool JsonObject::Parser::InsertKeyValuePairIn(JsonObject *obj)
81{
82    ASSERT(obj != nullptr);
83    // Get key:
84    if (!GetJsonString()) {
85        LOG_JSON(ERROR) << "Error while getting a key";
86        return false;
87    }
88    if (!TryGetSymbol(':')) {
89        LOG_JSON(ERROR) << "Expected ':' between key and value:";
90        return false;
91    }
92    ASSERT(parsed_temp_.Get<StringT>() != nullptr);
93    Key key(std::move(*parsed_temp_.Get<StringT>()));
94
95    if (!GetValue()) {
96        return false;
97    }
98
99    // Get value:
100    Value value(std::move(parsed_temp_));
101    ASSERT(obj != nullptr);
102
103    // Insert pair:
104    bool is_inserted = obj->values_map_.try_emplace(key, std::move(value)).second;
105    if (!is_inserted) {
106        LOG_JSON(ERROR) << "Key \"" << key << "\" must be unique";
107        return false;
108    }
109    // Save string representation as a "source" of scalar values. For non-scalar types, string_temp_ is "":
110    obj->string_map_.try_emplace(key, std::move(string_temp_));
111    obj->keys_.push_back(key);
112
113    LOG_JSON(DEBUG) << "Added entry with key \"" << key << "\"";
114    LOG_JSON(DEBUG) << "Parsed `key: value` pair:";
115    LOG_JSON(DEBUG) << "- key: \"" << key << '"';
116    ASSERT(obj->GetValueSourceString(key) != nullptr);
117    LOG_JSON(DEBUG) << "- value: \"" << *obj->GetValueSourceString(key) << '"';
118
119    return true;
120}
121
122bool JsonObject::Parser::GetJsonString()
123{
124    if (!TryGetSymbol('"')) {
125        LOG_JSON(ERROR) << "Expected '\"' at the start of the string";
126        return false;
127    }
128    return GetString('"');
129}
130
131static bool UnescapeStringChunk(std::string *result, const std::string &chunk, char delim, bool *finished)
132{
133    for (size_t start = 0; start < chunk.size();) {
134        size_t end = chunk.find('\\', start);
135        *result += chunk.substr(start, end - start);
136
137        if (end == std::string::npos) {
138            // No more escapes.
139            break;
140        }
141
142        if (end == chunk.size() - 1) {
143            // Chunk ends with an unfinished escape sequence.
144            *result += delim;
145            *finished = false;
146            return true;
147        }
148
149        ++end;
150        start = end + 1;
151
152        constexpr unsigned ULEN = 4;
153
154        switch (chunk[end]) {
155            case '"':
156            case '\\':
157            case '/':
158                *result += chunk[end];
159                break;
160            case 'b':
161                *result += '\b';
162                break;
163            case 'f':
164                *result += '\f';
165                break;
166            case 'n':
167                *result += '\n';
168                break;
169            case 'r':
170                *result += '\r';
171                break;
172            case 't':
173                *result += '\t';
174                break;
175            case 'u':
176                if (end + ULEN < chunk.size()) {
177                    // Char strings cannot include multibyte characters, ignore top byte.
178                    *result += static_cast<char>((HexValue(chunk[end + ULEN - 1]) << 4U) | HexValue(chunk[end + ULEN]));
179                    start += ULEN;
180                    break;
181                }
182                [[fallthrough]];
183            default:
184                // Invalid escape sequence.
185                return false;
186        }
187    }
188
189    *finished = true;
190    return true;
191}
192
193bool JsonObject::Parser::GetString(char delim)
194{
195    std::string string;
196
197    for (bool finished = false; !finished;) {
198        std::string chunk;
199        if (!std::getline(istream_, chunk, delim) || !UnescapeStringChunk(&string, chunk, delim, &finished)) {
200            LOG_JSON(ERROR) << "Error while reading a string";
201            return false;
202        }
203    }
204
205    LOG_JSON(DEBUG) << "Got a string: \"" << string << '"';
206    string_temp_ = string;
207    parsed_temp_.SetValue(std::move(string));
208
209    return true;
210}
211
212bool JsonObject::Parser::GetNum()
213{
214    NumT num = 0;
215    istream_ >> num;
216    if (istream_.fail()) {
217        LOG_JSON(ERROR) << "Failed to read a num";
218        return false;
219    }
220    parsed_temp_.SetValue(num);
221    LOG_JSON(DEBUG) << "Got an number: " << num;
222    return true;
223}
224
225bool JsonObject::Parser::GetBool()
226{
227    BoolT boolean {false};
228    istream_ >> std::boolalpha >> boolean;
229    if (istream_.fail()) {
230        LOG_JSON(ERROR) << "Failed to read a boolean";
231        return false;
232    }
233    parsed_temp_.SetValue(boolean);
234    LOG_JSON(DEBUG) << "Got a boolean: " << std::boolalpha << boolean;
235    return true;
236}
237
238bool JsonObject::Parser::GetValue()
239{
240    auto symbol = PeekSymbol();
241    size_t pos_start = static_cast<size_t>(istream_.tellg());
242    bool res = false;
243    switch (symbol) {
244        case 't':
245        case 'f':
246            res = GetBool();
247            break;
248
249        case '0':
250        case '1':
251        case '2':
252        case '3':
253        case '4':
254        case '5':
255        case '6':
256        case '7':
257        case '8':
258        case '9':
259        case '-':
260        case '+':
261        case '.':
262            res = GetNum();
263            break;
264
265        case '"':
266            return GetJsonString();
267        case '[':
268            string_temp_ = "";
269            return GetArray();
270        case '{': {
271            string_temp_ = "";
272            auto inner_obj_ptr = std::make_unique<JsonObject>();
273            if (!GetJsonObject(inner_obj_ptr.get())) {
274                return false;
275            }
276            LOG_JSON(DEBUG) << "Got an inner JSON-object";
277            parsed_temp_.SetValue(std::move(inner_obj_ptr));
278            return true;
279        }
280        default:
281            LOG_JSON(ERROR) << "Unexpected character when trying to get value: '" << PeekSymbol() << "'";
282            return false;
283    }
284
285    // Save source string of parsed value:
286    size_t pos_end = static_cast<size_t>(istream_.tellg());
287    if (pos_end == static_cast<size_t>(-1)) {
288        return false;
289    }
290    size_t size = pos_end - pos_start;
291    string_temp_.resize(size, '\0');
292    istream_.seekg(pos_start);
293    istream_.read(&string_temp_[0], size);
294    ASSERT(istream_);
295    return res;
296}
297
298bool JsonObject::Parser::GetArray()
299{
300    if (!TryGetSymbol('[')) {
301        LOG_JSON(ERROR) << "Expected '[' at the start of an array";
302        return false;
303    }
304
305    ArrayT temp;
306
307    if (TryGetSymbol(']')) {
308        parsed_temp_.SetValue(std::move(temp));
309        return true;
310    }
311
312    while (true) {
313        if (!GetValue()) {
314            return false;
315        }
316        temp.push_back(std::move(parsed_temp_));
317        if (TryGetSymbol(',')) {
318            LOG_JSON(DEBUG) << "Got a comma-separator, getting the next array element";
319            continue;
320        }
321        break;
322    }
323    parsed_temp_.SetValue(std::move(temp));
324    return TryGetSymbol(']');
325}
326
327char JsonObject::Parser::PeekSymbol()
328{
329    istream_ >> std::ws;
330    if (istream_.peek() == std::char_traits<char>::eof()) {
331        return '\0';
332    }
333    return static_cast<char>(istream_.peek());
334}
335
336char JsonObject::Parser::GetSymbol()
337{
338    if (!istream_) {
339        return '\0';
340    }
341    istream_ >> std::ws;
342    if (istream_.peek() == std::char_traits<char>::eof()) {
343        istream_.get();
344        return '\0';
345    }
346    return static_cast<char>(istream_.get());
347}
348
349bool JsonObject::Parser::TryGetSymbol(int symbol)
350{
351    ASSERT(!IsWhitespace(symbol));
352    if (static_cast<char>(symbol) != GetSymbol()) {
353        istream_.unget();
354        return false;
355    }
356    return true;
357}
358
359bool JsonObject::Parser::IsWhitespace(int symbol)
360{
361    return bool(std::isspace(static_cast<unsigned char>(symbol)));
362}
363}  // namespace panda
364
365#undef LOG_JSON
366