Directory: | cvmfs/ |
---|---|
File: | cvmfs/json_document.cc |
Date: | 2025-06-22 02:36:02 |
Exec | Total | Coverage | |
---|---|---|---|
Lines: | 118 | 148 | 79.7% |
Branches: | 101 | 202 | 50.0% |
Line | Branch | Exec | Source |
---|---|---|---|
1 | /** | ||
2 | * This file is part of the CernVM File System. | ||
3 | */ | ||
4 | |||
5 | #include "json_document.h" | ||
6 | |||
7 | #include <cassert> | ||
8 | #include <cstdlib> | ||
9 | #include <cstring> | ||
10 | |||
11 | #include "util/exception.h" | ||
12 | #include "util/logging.h" | ||
13 | #include "util/pointer.h" | ||
14 | #include "util/string.h" | ||
15 | |||
16 | using namespace std; // NOLINT | ||
17 | |||
18 | 1401 | JsonDocument *JsonDocument::Create(const string &text) { | |
19 |
3/6✓ Branch 1 taken 1401 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1401 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1401 times.
✗ Branch 8 not taken.
|
1401 | UniquePtr<JsonDocument> json(new JsonDocument()); |
20 |
1/2✓ Branch 2 taken 1401 times.
✗ Branch 3 not taken.
|
1401 | const bool retval = json->Parse(text); |
21 |
2/2✓ Branch 0 taken 192 times.
✓ Branch 1 taken 1209 times.
|
1401 | if (!retval) |
22 | 192 | return NULL; | |
23 | 1209 | return json.Release(); | |
24 | 1401 | } | |
25 | |||
26 | 196 | string JsonDocument::EscapeString(const string &input) { | |
27 | 196 | string escaped; | |
28 |
1/2✓ Branch 2 taken 196 times.
✗ Branch 3 not taken.
|
196 | escaped.reserve(input.length()); |
29 | |||
30 |
2/2✓ Branch 1 taken 1428 times.
✓ Branch 2 taken 196 times.
|
1624 | for (unsigned i = 0, s = input.length(); i < s; ++i) { |
31 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1428 times.
|
1428 | if (input[i] == '\\') { |
32 | ✗ | escaped.push_back('\\'); | |
33 | ✗ | escaped.push_back('\\'); | |
34 |
2/2✓ Branch 1 taken 28 times.
✓ Branch 2 taken 1400 times.
|
1428 | } else if (input[i] == '"') { |
35 |
1/2✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
|
28 | escaped.push_back('\\'); |
36 |
1/2✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
|
28 | escaped.push_back('"'); |
37 | } else { | ||
38 |
1/2✓ Branch 2 taken 1400 times.
✗ Branch 3 not taken.
|
1400 | escaped.push_back(input[i]); |
39 | } | ||
40 | } | ||
41 | 196 | return escaped; | |
42 | } | ||
43 | |||
44 | 1401 | JsonDocument::JsonDocument() | |
45 | 1401 | : allocator_(kDefaultBlockSize), root_(NULL), raw_text_(NULL) { } | |
46 | |||
47 | 1401 | JsonDocument::~JsonDocument() { | |
48 |
1/2✓ Branch 0 taken 1401 times.
✗ Branch 1 not taken.
|
1401 | if (raw_text_) |
49 | 1401 | free(raw_text_); | |
50 | 1401 | } | |
51 | |||
52 | /** | ||
53 | * Parses a JSON string in text. | ||
54 | * | ||
55 | * @return true if parsing was successful | ||
56 | */ | ||
57 | 1401 | bool JsonDocument::Parse(const string &text) { | |
58 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1401 times.
|
1401 | assert(root_ == NULL); |
59 | |||
60 | // The used JSON library 'vjson' is a destructive parser and therefore | ||
61 | // alters the content of the provided buffer. The buffer must persist as | ||
62 | // name and string values from JSON nodes just point into it. | ||
63 | 1401 | raw_text_ = strdup(text.c_str()); | |
64 | |||
65 | 1401 | char *error_pos = 0; | |
66 | 1401 | char *error_desc = 0; | |
67 | 1401 | int error_line = 0; | |
68 |
1/2✓ Branch 1 taken 1401 times.
✗ Branch 2 not taken.
|
1401 | JSON *root = json_parse(raw_text_, &error_pos, &error_desc, &error_line, |
69 | &allocator_); | ||
70 | |||
71 | // check if the json string was parsed successfully | ||
72 |
2/2✓ Branch 0 taken 192 times.
✓ Branch 1 taken 1209 times.
|
1401 | if (!root) { |
73 |
1/2✓ Branch 1 taken 192 times.
✗ Branch 2 not taken.
|
192 | LogCvmfs(kLogUtility, kLogDebug, |
74 | "Failed to parse json string. Error at line %d: %s (%s)", | ||
75 | error_line, error_desc, error_pos); | ||
76 | 192 | return false; | |
77 | } | ||
78 | |||
79 | 1209 | root_ = root; | |
80 | 1209 | return true; | |
81 | } | ||
82 | |||
83 | 42 | string JsonDocument::PrintArray(JSON *first_child, PrintOptions print_options) { | |
84 |
1/2✓ Branch 2 taken 42 times.
✗ Branch 3 not taken.
|
42 | string result = "["; |
85 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 42 times.
|
42 | if (print_options.with_whitespace) { |
86 | ✗ | result += "\n"; | |
87 | ✗ | print_options.num_indent += 2; | |
88 | } | ||
89 | 42 | JSON *value = first_child; | |
90 |
2/2✓ Branch 0 taken 28 times.
✓ Branch 1 taken 14 times.
|
42 | if (value != NULL) { |
91 |
2/4✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 28 times.
✗ Branch 5 not taken.
|
28 | result += PrintValue(value, print_options); |
92 | 28 | value = value->next_sibling; | |
93 | } | ||
94 |
2/2✓ Branch 0 taken 140 times.
✓ Branch 1 taken 42 times.
|
182 | while (value != NULL) { |
95 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 140 times.
✓ Branch 3 taken 140 times.
✗ Branch 4 not taken.
|
140 | result += print_options.with_whitespace ? ",\n" : ","; |
96 |
2/4✓ Branch 1 taken 140 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 140 times.
✗ Branch 5 not taken.
|
140 | result += PrintValue(value, print_options); |
97 | 140 | value = value->next_sibling; | |
98 | } | ||
99 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 42 times.
|
42 | if (print_options.with_whitespace) { |
100 | ✗ | result += "\n"; | |
101 | ✗ | for (unsigned i = 2; i < print_options.num_indent; ++i) | |
102 | ✗ | result.push_back(' '); | |
103 | } | ||
104 |
1/2✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
|
84 | return result + "]"; |
105 | 42 | } | |
106 | |||
107 | /** | ||
108 | * JSON string in a canonical format: | ||
109 | * - No whitespaces | ||
110 | * - Variable names and strings in quotes | ||
111 | * | ||
112 | * Can be used as a canonical representation to sign or encrypt a JSON text. | ||
113 | */ | ||
114 | 42 | string JsonDocument::PrintCanonical() { | |
115 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 42 times.
|
42 | if (!root_) |
116 | ✗ | return ""; | |
117 | 42 | const PrintOptions print_options; | |
118 |
1/2✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
|
42 | return PrintObject(root_->first_child, print_options); |
119 | } | ||
120 | |||
121 | 98 | string JsonDocument::PrintObject(JSON *first_child, | |
122 | PrintOptions print_options) { | ||
123 |
1/2✓ Branch 2 taken 98 times.
✗ Branch 3 not taken.
|
98 | string result = "{"; |
124 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 98 times.
|
98 | if (print_options.with_whitespace) { |
125 | ✗ | result += "\n"; | |
126 | ✗ | print_options.num_indent += 2; | |
127 | } | ||
128 | 98 | JSON *value = first_child; | |
129 |
2/2✓ Branch 0 taken 56 times.
✓ Branch 1 taken 42 times.
|
98 | if (value != NULL) { |
130 |
2/4✓ Branch 1 taken 56 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 56 times.
✗ Branch 5 not taken.
|
56 | result += PrintValue(value, print_options); |
131 | 56 | value = value->next_sibling; | |
132 | } | ||
133 |
2/2✓ Branch 0 taken 126 times.
✓ Branch 1 taken 98 times.
|
224 | while (value != NULL) { |
134 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 126 times.
✓ Branch 3 taken 126 times.
✗ Branch 4 not taken.
|
126 | result += print_options.with_whitespace ? ",\n" : ","; |
135 |
2/4✓ Branch 1 taken 126 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 126 times.
✗ Branch 5 not taken.
|
126 | result += PrintValue(value, print_options); |
136 | 126 | value = value->next_sibling; | |
137 | } | ||
138 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 98 times.
|
98 | if (print_options.with_whitespace) { |
139 | ✗ | result += "\n"; | |
140 | ✗ | for (unsigned i = 2; i < print_options.num_indent; ++i) | |
141 | ✗ | result.push_back(' '); | |
142 | } | ||
143 |
1/2✓ Branch 1 taken 98 times.
✗ Branch 2 not taken.
|
196 | return result + "}"; |
144 | 98 | } | |
145 | |||
146 | /** | ||
147 | * JSON string for humans. | ||
148 | */ | ||
149 | ✗ | string JsonDocument::PrintPretty() { | |
150 | ✗ | if (!root_) | |
151 | ✗ | return ""; | |
152 | ✗ | PrintOptions print_options; | |
153 | ✗ | print_options.with_whitespace = true; | |
154 | ✗ | return PrintObject(root_->first_child, print_options); | |
155 | } | ||
156 | |||
157 | 350 | std::string JsonDocument::PrintValue(JSON *value, PrintOptions print_options) { | |
158 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 350 times.
|
350 | assert(value); |
159 | |||
160 | 350 | string result; | |
161 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 350 times.
|
350 | for (unsigned i = 0; i < print_options.num_indent; ++i) |
162 | ✗ | result.push_back(' '); | |
163 |
2/2✓ Branch 0 taken 154 times.
✓ Branch 1 taken 196 times.
|
350 | if (value->name) { |
164 |
5/10✓ Branch 2 taken 154 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 154 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 154 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 154 times.
✗ Branch 12 not taken.
✓ Branch 14 taken 154 times.
✗ Branch 15 not taken.
|
154 | result += "\"" + EscapeString(value->name) + "\":"; |
165 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 154 times.
|
154 | if (print_options.with_whitespace) |
166 | ✗ | result += " "; | |
167 | } | ||
168 |
7/8✓ Branch 0 taken 28 times.
✓ Branch 1 taken 56 times.
✓ Branch 2 taken 42 times.
✓ Branch 3 taken 42 times.
✓ Branch 4 taken 112 times.
✓ Branch 5 taken 28 times.
✓ Branch 6 taken 42 times.
✗ Branch 7 not taken.
|
350 | switch (value->type) { |
169 | 28 | case JSON_NULL: | |
170 |
1/2✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
|
28 | result += "null"; |
171 | 28 | break; | |
172 | 56 | case JSON_OBJECT: | |
173 |
2/4✓ Branch 1 taken 56 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 56 times.
✗ Branch 5 not taken.
|
56 | result += PrintObject(value->first_child, print_options); |
174 | 56 | break; | |
175 | 42 | case JSON_ARRAY: | |
176 |
2/4✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 42 times.
✗ Branch 5 not taken.
|
42 | result += PrintArray(value->first_child, print_options); |
177 | 42 | break; | |
178 | 42 | case JSON_STRING: | |
179 |
5/10✓ Branch 2 taken 42 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 42 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 42 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 42 times.
✗ Branch 12 not taken.
✓ Branch 14 taken 42 times.
✗ Branch 15 not taken.
|
42 | result += "\"" + EscapeString(value->string_value) + "\""; |
180 | 42 | break; | |
181 | 112 | case JSON_INT: | |
182 |
2/4✓ Branch 1 taken 112 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 112 times.
✗ Branch 5 not taken.
|
112 | result += StringifyInt(value->int_value); |
183 | 112 | break; | |
184 | 28 | case JSON_FLOAT: | |
185 |
2/4✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 28 times.
✗ Branch 5 not taken.
|
28 | result += StringifyDouble(value->float_value); |
186 | 28 | break; | |
187 | 42 | case JSON_BOOL: | |
188 |
3/4✓ Branch 0 taken 28 times.
✓ Branch 1 taken 14 times.
✓ Branch 3 taken 42 times.
✗ Branch 4 not taken.
|
42 | result += value->int_value ? "true" : "false"; |
189 | 42 | break; | |
190 | ✗ | default: | |
191 | ✗ | PANIC(NULL); | |
192 | } | ||
193 | 350 | return result; | |
194 | } | ||
195 | |||
196 | 3632 | JSON *JsonDocument::SearchInObject(const JSON *json_object, const string &name, | |
197 | const json_type type) { | ||
198 |
4/4✓ Branch 0 taken 3618 times.
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 14 times.
✓ Branch 3 taken 3604 times.
|
3632 | if (!json_object || (json_object->type != JSON_OBJECT)) |
199 | 28 | return NULL; | |
200 | |||
201 | 3604 | JSON *walker = json_object->first_child; | |
202 |
2/2✓ Branch 0 taken 7058 times.
✓ Branch 1 taken 592 times.
|
7650 | while (walker != NULL) { |
203 |
3/6✓ Branch 2 taken 7058 times.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 3012 times.
✓ Branch 8 taken 4046 times.
|
7058 | if (string(walker->name) == name) { |
204 |
2/2✓ Branch 0 taken 2950 times.
✓ Branch 1 taken 62 times.
|
3012 | return (walker->type == type) ? walker : NULL; |
205 | } | ||
206 | 4046 | walker = walker->next_sibling; | |
207 | } | ||
208 | 592 | return NULL; | |
209 | } | ||
210 | |||
211 | template<> | ||
212 | 12 | bool GetFromJSON<std::string>(const JSON *object, const std::string &name, | |
213 | std::string *value) { | ||
214 | 12 | const JSON *o = JsonDocument::SearchInObject(object, name, JSON_STRING); | |
215 | |||
216 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 10 times.
|
12 | if (o == NULL) { |
217 | 2 | return false; | |
218 | } | ||
219 | |||
220 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | if (value) { |
221 | 10 | *value = o->string_value; | |
222 | } | ||
223 | |||
224 | 10 | return true; | |
225 | } | ||
226 | |||
227 | template<> | ||
228 | 4 | bool GetFromJSON<int>(const JSON *object, const std::string &name, int *value) { | |
229 | 4 | const JSON *o = JsonDocument::SearchInObject(object, name, JSON_INT); | |
230 | |||
231 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
|
4 | if (o == NULL || value == NULL) { |
232 | ✗ | return false; | |
233 | } | ||
234 | |||
235 | 4 | *value = o->int_value; | |
236 | |||
237 | 4 | return true; | |
238 | } | ||
239 | |||
240 | template<> | ||
241 | ✗ | bool GetFromJSON<float>(const JSON *object, const std::string &name, | |
242 | float *value) { | ||
243 | ✗ | const JSON *o = JsonDocument::SearchInObject(object, name, JSON_FLOAT); | |
244 | |||
245 | ✗ | if (o == NULL || value == NULL) { | |
246 | ✗ | return false; | |
247 | } | ||
248 | |||
249 | ✗ | *value = o->float_value; | |
250 | |||
251 | ✗ | return true; | |
252 | } | ||
253 |