Directory: | cvmfs/ |
---|---|
File: | cvmfs/json_document.cc |
Date: | 2025-06-01 02:36:00 |
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 | 52 | JsonDocument *JsonDocument::Create(const string &text) { | |
19 |
3/6✓ Branch 1 taken 52 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 52 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 52 times.
✗ Branch 8 not taken.
|
52 | UniquePtr<JsonDocument> json(new JsonDocument()); |
20 |
1/2✓ Branch 2 taken 52 times.
✗ Branch 3 not taken.
|
52 | bool retval = json->Parse(text); |
21 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 47 times.
|
52 | if (!retval) |
22 | 5 | return NULL; | |
23 | 47 | return json.Release(); | |
24 | 52 | } | |
25 | |||
26 | 14 | string JsonDocument::EscapeString(const string &input) { | |
27 | 14 | string escaped; | |
28 |
1/2✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
|
14 | escaped.reserve(input.length()); |
29 | |||
30 |
2/2✓ Branch 1 taken 102 times.
✓ Branch 2 taken 14 times.
|
116 | for (unsigned i = 0, s = input.length(); i < s; ++i) { |
31 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 102 times.
|
102 | if (input[i] == '\\') { |
32 | ✗ | escaped.push_back('\\'); | |
33 | ✗ | escaped.push_back('\\'); | |
34 |
2/2✓ Branch 1 taken 2 times.
✓ Branch 2 taken 100 times.
|
102 | } else if (input[i] == '"') { |
35 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
2 | escaped.push_back('\\'); |
36 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
2 | escaped.push_back('"'); |
37 | } else { | ||
38 |
1/2✓ Branch 2 taken 100 times.
✗ Branch 3 not taken.
|
100 | escaped.push_back(input[i]); |
39 | } | ||
40 | } | ||
41 | 14 | return escaped; | |
42 | } | ||
43 | |||
44 | 52 | JsonDocument::JsonDocument() | |
45 | 52 | : allocator_(kDefaultBlockSize), root_(NULL), raw_text_(NULL) { } | |
46 | |||
47 | 52 | JsonDocument::~JsonDocument() { | |
48 |
1/2✓ Branch 0 taken 52 times.
✗ Branch 1 not taken.
|
52 | if (raw_text_) |
49 | 52 | free(raw_text_); | |
50 | 52 | } | |
51 | |||
52 | /** | ||
53 | * Parses a JSON string in text. | ||
54 | * | ||
55 | * @return true if parsing was successful | ||
56 | */ | ||
57 | 52 | bool JsonDocument::Parse(const string &text) { | |
58 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 52 times.
|
52 | 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 | 52 | raw_text_ = strdup(text.c_str()); | |
64 | |||
65 | 52 | char *error_pos = 0; | |
66 | 52 | char *error_desc = 0; | |
67 | 52 | int error_line = 0; | |
68 |
1/2✓ Branch 1 taken 52 times.
✗ Branch 2 not taken.
|
52 | 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 5 times.
✓ Branch 1 taken 47 times.
|
52 | if (!root) { |
73 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | LogCvmfs(kLogUtility, kLogDebug, |
74 | "Failed to parse json string. Error at line %d: %s (%s)", | ||
75 | error_line, error_desc, error_pos); | ||
76 | 5 | return false; | |
77 | } | ||
78 | |||
79 | 47 | root_ = root; | |
80 | 47 | return true; | |
81 | } | ||
82 | |||
83 | 3 | string JsonDocument::PrintArray(JSON *first_child, PrintOptions print_options) { | |
84 |
1/2✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | string result = "["; |
85 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (print_options.with_whitespace) { |
86 | ✗ | result += "\n"; | |
87 | ✗ | print_options.num_indent += 2; | |
88 | } | ||
89 | 3 | JSON *value = first_child; | |
90 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
|
3 | if (value != NULL) { |
91 |
2/4✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
|
2 | result += PrintValue(value, print_options); |
92 | 2 | value = value->next_sibling; | |
93 | } | ||
94 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 3 times.
|
13 | while (value != NULL) { |
95 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
✓ Branch 3 taken 10 times.
✗ Branch 4 not taken.
|
10 | result += print_options.with_whitespace ? ",\n" : ","; |
96 |
2/4✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
|
10 | result += PrintValue(value, print_options); |
97 | 10 | value = value->next_sibling; | |
98 | } | ||
99 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | 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 3 times.
✗ Branch 2 not taken.
|
6 | return result + "]"; |
105 | 3 | } | |
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 | 3 | string JsonDocument::PrintCanonical() { | |
115 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (!root_) |
116 | ✗ | return ""; | |
117 | 3 | PrintOptions print_options; | |
118 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | return PrintObject(root_->first_child, print_options); |
119 | } | ||
120 | |||
121 | 7 | string JsonDocument::PrintObject(JSON *first_child, | |
122 | PrintOptions print_options) { | ||
123 |
1/2✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | string result = "{"; |
124 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
|
7 | if (print_options.with_whitespace) { |
125 | ✗ | result += "\n"; | |
126 | ✗ | print_options.num_indent += 2; | |
127 | } | ||
128 | 7 | JSON *value = first_child; | |
129 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 3 times.
|
7 | if (value != NULL) { |
130 |
2/4✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
|
4 | result += PrintValue(value, print_options); |
131 | 4 | value = value->next_sibling; | |
132 | } | ||
133 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 7 times.
|
16 | while (value != NULL) { |
134 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
✓ Branch 3 taken 9 times.
✗ Branch 4 not taken.
|
9 | result += print_options.with_whitespace ? ",\n" : ","; |
135 |
2/4✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 9 times.
✗ Branch 5 not taken.
|
9 | result += PrintValue(value, print_options); |
136 | 9 | value = value->next_sibling; | |
137 | } | ||
138 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
|
7 | 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 7 times.
✗ Branch 2 not taken.
|
14 | return result + "}"; |
144 | 7 | } | |
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 | 25 | std::string JsonDocument::PrintValue(JSON *value, PrintOptions print_options) { | |
158 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 25 times.
|
25 | assert(value); |
159 | |||
160 | 25 | string result; | |
161 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 25 times.
|
25 | for (unsigned i = 0; i < print_options.num_indent; ++i) |
162 | ✗ | result.push_back(' '); | |
163 |
2/2✓ Branch 0 taken 11 times.
✓ Branch 1 taken 14 times.
|
25 | if (value->name) { |
164 |
5/10✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 11 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 11 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 11 times.
✗ Branch 12 not taken.
✓ Branch 14 taken 11 times.
✗ Branch 15 not taken.
|
11 | result += "\"" + EscapeString(value->name) + "\":"; |
165 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
|
11 | if (print_options.with_whitespace) |
166 | ✗ | result += " "; | |
167 | } | ||
168 |
7/8✓ Branch 0 taken 2 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 8 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
|
25 | switch (value->type) { |
169 | 2 | case JSON_NULL: | |
170 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
2 | result += "null"; |
171 | 2 | break; | |
172 | 4 | case JSON_OBJECT: | |
173 |
2/4✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
|
4 | result += PrintObject(value->first_child, print_options); |
174 | 4 | break; | |
175 | 3 | case JSON_ARRAY: | |
176 |
2/4✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
|
3 | result += PrintArray(value->first_child, print_options); |
177 | 3 | break; | |
178 | 3 | case JSON_STRING: | |
179 |
5/10✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 3 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 3 times.
✗ Branch 12 not taken.
✓ Branch 14 taken 3 times.
✗ Branch 15 not taken.
|
3 | result += "\"" + EscapeString(value->string_value) + "\""; |
180 | 3 | break; | |
181 | 8 | case JSON_INT: | |
182 |
2/4✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
|
8 | result += StringifyInt(value->int_value); |
183 | 8 | break; | |
184 | 2 | case JSON_FLOAT: | |
185 |
2/4✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
|
2 | result += StringifyDouble(value->float_value); |
186 | 2 | break; | |
187 | 3 | case JSON_BOOL: | |
188 |
3/4✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
|
3 | result += value->int_value ? "true" : "false"; |
189 | 3 | break; | |
190 | ✗ | default: | |
191 | ✗ | PANIC(NULL); | |
192 | } | ||
193 | 25 | return result; | |
194 | } | ||
195 | |||
196 | 119 | JSON *JsonDocument::SearchInObject(const JSON *json_object, const string &name, | |
197 | const json_type type) { | ||
198 |
4/4✓ Branch 0 taken 118 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 117 times.
|
119 | if (!json_object || (json_object->type != JSON_OBJECT)) |
199 | 2 | return NULL; | |
200 | |||
201 | 117 | JSON *walker = json_object->first_child; | |
202 |
2/2✓ Branch 0 taken 225 times.
✓ Branch 1 taken 14 times.
|
239 | while (walker != NULL) { |
203 |
3/6✓ Branch 2 taken 225 times.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 103 times.
✓ Branch 8 taken 122 times.
|
225 | if (string(walker->name) == name) { |
204 |
2/2✓ Branch 0 taken 101 times.
✓ Branch 1 taken 2 times.
|
103 | return (walker->type == type) ? walker : NULL; |
205 | } | ||
206 | 122 | walker = walker->next_sibling; | |
207 | } | ||
208 | 14 | return NULL; | |
209 | } | ||
210 | |||
211 | template<> | ||
212 | 6 | bool GetFromJSON<std::string>(const JSON *object, const std::string &name, | |
213 | std::string *value) { | ||
214 | 6 | const JSON *o = JsonDocument::SearchInObject(object, name, JSON_STRING); | |
215 | |||
216 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 5 times.
|
6 | if (o == NULL) { |
217 | 1 | return false; | |
218 | } | ||
219 | |||
220 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | if (value) { |
221 | 5 | *value = o->string_value; | |
222 | } | ||
223 | |||
224 | 5 | return true; | |
225 | } | ||
226 | |||
227 | template<> | ||
228 | 2 | bool GetFromJSON<int>(const JSON *object, const std::string &name, int *value) { | |
229 | 2 | const JSON *o = JsonDocument::SearchInObject(object, name, JSON_INT); | |
230 | |||
231 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
|
2 | if (o == NULL || value == NULL) { |
232 | ✗ | return false; | |
233 | } | ||
234 | |||
235 | 2 | *value = o->int_value; | |
236 | |||
237 | 2 | 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 |