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