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