1 /** 2 * A very basic, non-complete implementation of a validator for the JSON Schema specification, 3 * for validating CPER-JSON. 4 * 5 * Author: Lawrence.Tang@arm.com 6 **/ 7 8 #include <stdio.h> 9 #include <string.h> 10 #include <unistd.h> 11 #include <libgen.h> 12 #include <limits.h> 13 #include <stdarg.h> 14 #include "json.h" 15 #include "json-schema.h" 16 #include "edk/BaseTypes.h" 17 18 //Field definitions. 19 int json_validator_debug = 0; 20 21 //Private pre-definitions. 22 int validate_field(const char *name, json_object *schema, json_object *object, 23 char *error_message); 24 int validate_integer(const char *field_name, json_object *schema, 25 json_object *object, char *error_message); 26 int validate_string(const char *field_name, json_object *schema, 27 json_object *object, char *error_message); 28 int validate_object(const char *field_name, json_object *schema, 29 json_object *object, char *error_message); 30 int validate_array(const char *field_name, json_object *schema, 31 json_object *object, char *error_message); 32 void log_validator_error(char *error_message, const char *format, ...); 33 void log_validator_debug(const char *format, ...); 34 void log_validator_msg(const char *format, va_list args); 35 36 //Validates a single JSON object against a provided schema file, returning 1 on success and 0 on failure to validate. 37 //Error message space must be allocated prior to call. 38 int validate_schema_from_file(const char *schema_file, json_object *object, 39 char *error_message) 40 { 41 //Load schema IR from file. 42 json_object *schema_ir = json_object_from_file(schema_file); 43 if (schema_ir == NULL) { 44 log_validator_error(error_message, 45 "Failed to load schema from file '%s'.", 46 schema_file); 47 return 0; 48 } 49 50 //Get the directory of the file. 51 char *schema_file_copy = malloc(strlen(schema_file) + 1); 52 strcpy(schema_file_copy, schema_file); 53 char *schema_dir = dirname(schema_file_copy); 54 55 int result = 56 validate_schema(schema_ir, schema_dir, object, error_message); 57 58 //Free memory from directory call. 59 free(schema_file_copy); 60 61 return result; 62 } 63 64 //Validates a single JSON object against a provided schema, returning 1 on success and 0 on failure to validate. 65 //Error message space must be allocated prior to call. 66 //If the schema does not include any other sub-schemas using "$ref", then leaving schema_directory as NULL is valid. 67 int validate_schema(json_object *schema, char *schema_directory, 68 json_object *object, char *error_message) 69 { 70 //Check that the schema version is the same as this validator. 71 json_object *schema_ver = json_object_object_get(schema, "$schema"); 72 if (schema_ver == NULL || 73 strcmp(json_object_get_string(schema_ver), JSON_SCHEMA_VERSION)) { 74 log_validator_error( 75 error_message, 76 "Provided schema is not of the same version that is referenced by this validator, or is not a schema."); 77 return 0; 78 } 79 80 //Change current directory into the schema directory. 81 char *original_cwd = malloc(PATH_MAX); 82 if (getcwd(original_cwd, PATH_MAX) == NULL) { 83 log_validator_error(error_message, 84 "Failed fetching the current directory."); 85 return 0; 86 } 87 if (chdir(schema_directory)) { 88 log_validator_error(error_message, 89 "Failed to chdir into schema directory."); 90 return 0; 91 } 92 93 //Parse the top level structure appropriately. 94 int result = validate_field("parent", schema, object, error_message); 95 96 //Change back to original CWD. 97 chdir(original_cwd); 98 free(original_cwd); 99 100 if (result) 101 log_validator_debug( 102 "Successfully validated the provided object against schema."); 103 return result; 104 } 105 106 //Validates a single JSON field given a schema/object. 107 //Returns -1 on fatal/error failure, 0 on validation failure, and 1 on validation. 108 int validate_field(const char *field_name, json_object *schema, 109 json_object *object, char *error_message) 110 { 111 log_validator_debug("Validating field '%s'...", field_name); 112 113 //If there is a "$ref" field, attempt to load the referenced schema. 114 json_object *ref_schema = json_object_object_get(schema, "$ref"); 115 if (ref_schema != NULL && 116 json_object_get_type(ref_schema) == json_type_string) { 117 log_validator_debug("$ref schema detected for field '%s'.", 118 field_name); 119 120 //Attempt to load. If loading fails, report error. 121 const char *ref_path = json_object_get_string(ref_schema); 122 schema = json_object_from_file(ref_path); 123 if (schema == NULL) { 124 log_validator_error( 125 error_message, 126 "Failed to open referenced schema file '%s'.", 127 ref_path); 128 return -1; 129 } 130 131 log_validator_debug("loaded schema path '%s' for field '%s'.", 132 ref_path, field_name); 133 } 134 135 //Get the schema field type. 136 json_object *desired_field_type = 137 json_object_object_get(schema, "type"); 138 if (desired_field_type == NULL || 139 !json_object_is_type(desired_field_type, json_type_string)) { 140 log_validator_error( 141 error_message, 142 "Desired field type not provided within schema/is not a string for field '%s' (schema violation).", 143 field_name); 144 return -1; 145 } 146 147 //Check the field types are actually equal. 148 const char *desired_field_type_str = 149 json_object_get_string(desired_field_type); 150 if (!((!strcmp(desired_field_type_str, "object") && 151 json_object_is_type(object, json_type_object)) || 152 (!strcmp(desired_field_type_str, "array") && 153 json_object_is_type(object, json_type_array)) || 154 (!strcmp(desired_field_type_str, "integer") && 155 json_object_is_type(object, json_type_int)) || 156 (!strcmp(desired_field_type_str, "string") && 157 json_object_is_type(object, json_type_string)) || 158 (!strcmp(desired_field_type_str, "boolean") && 159 json_object_is_type(object, json_type_boolean)) || 160 (!strcmp(desired_field_type_str, "double") && 161 json_object_is_type(object, json_type_double)))) { 162 log_validator_error(error_message, 163 "Field type match failed for field '%s'.", 164 field_name); 165 return 0; 166 } 167 168 //If the schema contains a "oneOf" array, we need to validate the field against each of the 169 //possible options in turn. 170 json_object *one_of = json_object_object_get(schema, "oneOf"); 171 if (one_of != NULL && json_object_get_type(one_of) == json_type_array) { 172 log_validator_debug("oneOf options detected for field '%s'.", 173 field_name); 174 175 int len = json_object_array_length(one_of); 176 int validated = 0; 177 for (int i = 0; i < len; i++) { 178 //If the "oneOf" member isn't an object, warn on schema violation. 179 json_object *one_of_option = 180 json_object_array_get_idx(one_of, i); 181 if (one_of_option == NULL || 182 json_object_get_type(one_of_option) != 183 json_type_object) { 184 log_validator_debug( 185 "Schema Warning: 'oneOf' member for field '%s' is not an object, schema violation.", 186 field_name); 187 continue; 188 } 189 190 //Validate field with schema. 191 validated = validate_field(field_name, one_of_option, 192 object, error_message); 193 if (validated == -1) 194 return -1; 195 if (validated) 196 break; 197 } 198 199 //Return if failed all checks. 200 if (!validated) { 201 log_validator_error( 202 error_message, 203 "No schema object structures matched provided object for field '%s'.", 204 field_name); 205 return 0; 206 } 207 } 208 209 //Switch and validate each type in turn. 210 switch (json_object_get_type(object)) { 211 case json_type_int: 212 return validate_integer(field_name, schema, object, 213 error_message); 214 case json_type_string: 215 return validate_string(field_name, schema, object, 216 error_message); 217 case json_type_object: 218 return validate_object(field_name, schema, object, 219 error_message); 220 case json_type_array: 221 return validate_array(field_name, schema, object, 222 error_message); 223 224 //We don't perform extra validation on this type. 225 default: 226 log_validator_debug( 227 "validation passed for '%s' (no extra validation).", 228 field_name); 229 return 1; 230 } 231 } 232 233 //Validates a single integer value according to the given specification. 234 int validate_integer(const char *field_name, json_object *schema, 235 json_object *object, char *error_message) 236 { 237 //Is there a minimum/maximum specified? If so, check those. 238 //Validate minimum. 239 json_object *min_value = json_object_object_get(schema, "minimum"); 240 if (min_value != NULL && 241 json_object_is_type(min_value, json_type_int)) { 242 int min_value_int = json_object_get_int(min_value); 243 if (json_object_get_uint64(object) < min_value_int) { 244 log_validator_error( 245 error_message, 246 "Failed to validate integer field '%s'. Value was below minimum of %d.", 247 field_name, min_value_int); 248 return 0; 249 } 250 } 251 252 //Validate maximum. 253 json_object *max_value = json_object_object_get(schema, "maximum"); 254 if (max_value != NULL && 255 json_object_is_type(max_value, json_type_int)) { 256 int max_value_int = json_object_get_int(max_value); 257 if (json_object_get_uint64(object) > max_value_int) { 258 log_validator_error( 259 error_message, 260 "Failed to validate integer field '%s'. Value was above maximum of %d.", 261 field_name, max_value_int); 262 return 0; 263 } 264 } 265 266 return 1; 267 } 268 269 //Validates a single string value according to the given specification. 270 int validate_string(const char *field_name, json_object *schema, 271 json_object *object, char *error_message) 272 { 273 //todo: if there is a "pattern" field, verify the string with RegEx. 274 return 1; 275 } 276 277 //Validates a single object value according to the given specification. 278 int validate_object(const char *field_name, json_object *schema, 279 json_object *object, char *error_message) 280 { 281 //Are there a set of "required" fields? If so, check they all exist. 282 json_object *required_fields = 283 json_object_object_get(schema, "required"); 284 if (required_fields != NULL && 285 json_object_get_type(required_fields) == json_type_array) { 286 log_validator_debug( 287 "Required fields found for '%s', matching...", 288 field_name); 289 290 int len = json_object_array_length(required_fields); 291 for (int i = 0; i < len; i++) { 292 //Get the required field from schema. 293 json_object *required_field = 294 json_object_array_get_idx(required_fields, i); 295 if (json_object_get_type(required_field) != 296 json_type_string) { 297 log_validator_error( 298 error_message, 299 "Required field for object '%s' is not a string (schema violation).", 300 field_name); 301 return 0; 302 } 303 304 //Does it exist in the object? 305 const char *required_field_str = 306 json_object_get_string(required_field); 307 if (json_object_object_get( 308 object, required_field_str) == NULL) { 309 log_validator_error( 310 error_message, 311 "Required field '%s' was not present in object '%s'.", 312 required_field_str, field_name); 313 return 0; 314 } 315 } 316 } 317 318 //Get additional properties value in advance. 319 json_object *additional_properties = 320 json_object_object_get(schema, "additionalProperties"); 321 int additional_properties_allowed = 0; 322 if (additional_properties != NULL && 323 json_object_get_type(additional_properties) == json_type_boolean) 324 additional_properties_allowed = 325 json_object_get_boolean(additional_properties); 326 327 //Run through the "properties" object and validate each of those in turn. 328 json_object *properties = json_object_object_get(schema, "properties"); 329 if (properties != NULL && 330 json_object_get_type(properties) == json_type_object) { 331 json_object_object_foreach(properties, key, value) 332 { 333 //If the given property name does not exist on the target object, ignore and continue next. 334 json_object *object_prop = 335 json_object_object_get(object, key); 336 if (object_prop == NULL) 337 continue; 338 339 //Validate against the schema. 340 if (!validate_field(key, value, object_prop, 341 error_message)) 342 return 0; 343 } 344 345 //If additional properties are banned, validate that no additional properties exist. 346 if (!additional_properties_allowed) { 347 json_object_object_foreach(object, key, value) 348 { 349 //If the given property name does not exist on the schema object, fail validation. 350 json_object *schema_prop = 351 json_object_object_get(properties, key); 352 if (schema_prop == NULL) { 353 log_validator_error( 354 error_message, 355 "Invalid additional property '%s' detected on field '%s'.", 356 key, field_name); 357 return 0; 358 } 359 } 360 } 361 } 362 363 return 1; 364 } 365 366 //Validates a single array value according to the given specification. 367 int validate_array(const char *field_name, json_object *schema, 368 json_object *object, char *error_message) 369 { 370 //Iterate all items in the array, and validate according to the "items" schema. 371 json_object *items_schema = json_object_object_get(schema, "items"); 372 if (items_schema != NULL && 373 json_object_get_type(items_schema) == json_type_object) { 374 int array_len = json_object_array_length(object); 375 for (int i = 0; i < array_len; i++) { 376 if (!validate_field(field_name, items_schema, 377 json_object_array_get_idx(object, 378 i), 379 error_message)) 380 return 0; 381 } 382 } 383 384 return 1; 385 } 386 387 //Enables/disables debugging globally for the JSON validator. 388 void validate_schema_debug_enable() 389 { 390 json_validator_debug = 1; 391 } 392 void validate_schema_debug_disable() 393 { 394 json_validator_debug = 0; 395 } 396 397 //Logs an error message to the given error message location and (optionally) provides debug output. 398 void log_validator_error(char *error_message, const char *format, ...) 399 { 400 va_list args; 401 402 //Log error to error out. 403 va_start(args, format); 404 vsnprintf(error_message, JSON_ERROR_MSG_MAX_LEN, format, args); 405 va_end(args); 406 407 //Debug message if necessary. 408 va_start(args, format); 409 log_validator_msg(format, args); 410 va_end(args); 411 } 412 413 //Logs a debug message to stdout, if validator debug is enabled. 414 void log_validator_debug(const char *format, ...) 415 { 416 va_list args; 417 va_start(args, format); 418 log_validator_msg(format, args); 419 va_end(args); 420 } 421 422 //Logs a single validator debug/error message. 423 void log_validator_msg(const char *format, va_list args) 424 { 425 //Print debug output if debug is on. 426 if (json_validator_debug) { 427 //Make new format string for error. 428 const char *header = "json_validator: "; 429 char *new_format = malloc(strlen(header) + strlen(format) + 2); 430 strcpy(new_format, header); 431 strcat(new_format, format); 432 strcat(new_format, "\n"); 433 434 //Print & free format. 435 vfprintf(stdout, new_format, args); 436 free(new_format); 437 } 438 }