1*8a2d737cSLawrence Tang /** 2*8a2d737cSLawrence Tang * A very basic, non-complete implementation of a validator for the JSON Schema specification, 3*8a2d737cSLawrence Tang * for validating CPER-JSON. 4*8a2d737cSLawrence Tang * 5*8a2d737cSLawrence Tang * Author: Lawrence.Tang@arm.com 6*8a2d737cSLawrence Tang **/ 7*8a2d737cSLawrence Tang 8*8a2d737cSLawrence Tang #include <stdio.h> 9*8a2d737cSLawrence Tang #include <string.h> 10*8a2d737cSLawrence Tang #include <unistd.h> 11*8a2d737cSLawrence Tang #include <libgen.h> 12*8a2d737cSLawrence Tang #include <limits.h> 13*8a2d737cSLawrence Tang #include "json.h" 14*8a2d737cSLawrence Tang #include "json-schema.h" 15*8a2d737cSLawrence Tang #include "edk/BaseTypes.h" 16*8a2d737cSLawrence Tang 17*8a2d737cSLawrence Tang //Private pre-definitions. 18*8a2d737cSLawrence Tang int validate_field(const char* name, json_object* schema, json_object* object, char* error_message); 19*8a2d737cSLawrence Tang int validate_integer(const char* field_name, json_object* schema, json_object* object, char* error_message); 20*8a2d737cSLawrence Tang int validate_string(const char* field_name, json_object* schema, json_object* object, char* error_message); 21*8a2d737cSLawrence Tang int validate_object(const char* field_name, json_object* schema, json_object* object, char* error_message); 22*8a2d737cSLawrence Tang int validate_array(const char* field_name, json_object* schema, json_object* object, char* error_message); 23*8a2d737cSLawrence Tang 24*8a2d737cSLawrence Tang //Validates a single JSON object against a provided schema file, returning 1 on success and 0 on failure to validate. 25*8a2d737cSLawrence Tang //Error message space must be allocated prior to call. 26*8a2d737cSLawrence Tang int validate_schema_from_file(const char* schema_file, json_object* object, char* error_message) 27*8a2d737cSLawrence Tang { 28*8a2d737cSLawrence Tang //Load schema IR from file. 29*8a2d737cSLawrence Tang json_object* schema_ir = json_object_from_file(schema_file); 30*8a2d737cSLawrence Tang if (schema_ir == NULL) 31*8a2d737cSLawrence Tang { 32*8a2d737cSLawrence Tang sprintf(error_message, "Failed to load schema from file '%s'.", schema_file); 33*8a2d737cSLawrence Tang return 0; 34*8a2d737cSLawrence Tang } 35*8a2d737cSLawrence Tang 36*8a2d737cSLawrence Tang //Get the directory of the file. 37*8a2d737cSLawrence Tang char* schema_file_copy = malloc(strlen(schema_file) + 1); 38*8a2d737cSLawrence Tang strcpy(schema_file_copy, schema_file); 39*8a2d737cSLawrence Tang char* schema_dir = dirname(schema_file_copy); 40*8a2d737cSLawrence Tang 41*8a2d737cSLawrence Tang int result = validate_schema(schema_ir, schema_dir, object, error_message); 42*8a2d737cSLawrence Tang 43*8a2d737cSLawrence Tang //Free memory from directory call. 44*8a2d737cSLawrence Tang free(schema_file_copy); 45*8a2d737cSLawrence Tang 46*8a2d737cSLawrence Tang return result; 47*8a2d737cSLawrence Tang } 48*8a2d737cSLawrence Tang 49*8a2d737cSLawrence Tang //Validates a single JSON object against a provided schema, returning 1 on success and 0 on failure to validate. 50*8a2d737cSLawrence Tang //Error message space must be allocated prior to call. 51*8a2d737cSLawrence Tang //If the schema does not include any other sub-schemas using "$ref", then leaving schema_directory as NULL is valid. 52*8a2d737cSLawrence Tang int validate_schema(json_object* schema, char* schema_directory, json_object* object, char* error_message) 53*8a2d737cSLawrence Tang { 54*8a2d737cSLawrence Tang //Check that the schema version is the same as this validator. 55*8a2d737cSLawrence Tang json_object* schema_ver = json_object_object_get(schema, "$schema"); 56*8a2d737cSLawrence Tang if (schema_ver == NULL || strcmp(json_object_get_string(schema_ver), JSON_SCHEMA_VERSION)) 57*8a2d737cSLawrence Tang { 58*8a2d737cSLawrence Tang sprintf(error_message, "Provided schema is not of the same version that is referenced by this validator, or is not a schema."); 59*8a2d737cSLawrence Tang return 0; 60*8a2d737cSLawrence Tang } 61*8a2d737cSLawrence Tang 62*8a2d737cSLawrence Tang //Change current directory into the schema directory. 63*8a2d737cSLawrence Tang char* original_cwd = malloc(PATH_MAX); 64*8a2d737cSLawrence Tang if (getcwd(original_cwd, PATH_MAX) == NULL) 65*8a2d737cSLawrence Tang { 66*8a2d737cSLawrence Tang sprintf(error_message, "Failed fetching the current directory."); 67*8a2d737cSLawrence Tang return 0; 68*8a2d737cSLawrence Tang } 69*8a2d737cSLawrence Tang if (chdir(schema_directory)) 70*8a2d737cSLawrence Tang { 71*8a2d737cSLawrence Tang sprintf(error_message, "Failed to chdir into schema directory."); 72*8a2d737cSLawrence Tang return 0; 73*8a2d737cSLawrence Tang } 74*8a2d737cSLawrence Tang 75*8a2d737cSLawrence Tang //Parse the top level structure appropriately. 76*8a2d737cSLawrence Tang int result = validate_field("parent", schema, object, error_message); 77*8a2d737cSLawrence Tang 78*8a2d737cSLawrence Tang //Change back to original CWD. 79*8a2d737cSLawrence Tang chdir(original_cwd); 80*8a2d737cSLawrence Tang free(original_cwd); 81*8a2d737cSLawrence Tang 82*8a2d737cSLawrence Tang return result; 83*8a2d737cSLawrence Tang } 84*8a2d737cSLawrence Tang 85*8a2d737cSLawrence Tang //Validates a single JSON field given a schema/object. 86*8a2d737cSLawrence Tang int validate_field(const char* field_name, json_object* schema, json_object* object, char* error_message) 87*8a2d737cSLawrence Tang { 88*8a2d737cSLawrence Tang //If there is a "$ref" field, attempt to load the referenced schema. 89*8a2d737cSLawrence Tang json_object* ref_schema = json_object_object_get(schema, "$ref"); 90*8a2d737cSLawrence Tang if (ref_schema != NULL && json_object_get_type(ref_schema) == json_type_string) 91*8a2d737cSLawrence Tang { 92*8a2d737cSLawrence Tang //Attempt to load. If loading fails, report error. 93*8a2d737cSLawrence Tang const char* ref_path = json_object_get_string(ref_schema); 94*8a2d737cSLawrence Tang schema = json_object_from_file(ref_path); 95*8a2d737cSLawrence Tang if (schema == NULL) 96*8a2d737cSLawrence Tang { 97*8a2d737cSLawrence Tang sprintf(error_message, "Failed to open referenced schema file '%s'.", ref_path); 98*8a2d737cSLawrence Tang return 0; 99*8a2d737cSLawrence Tang } 100*8a2d737cSLawrence Tang } 101*8a2d737cSLawrence Tang 102*8a2d737cSLawrence Tang //Get the schema field type. 103*8a2d737cSLawrence Tang json_object* desired_field_type = json_object_object_get(schema, "type"); 104*8a2d737cSLawrence Tang if (desired_field_type == NULL || !json_object_is_type(desired_field_type, json_type_string)) 105*8a2d737cSLawrence Tang { 106*8a2d737cSLawrence Tang sprintf(error_message, "Desired field type not provided within schema/is not a string for field '%s' (schema violation).", field_name); 107*8a2d737cSLawrence Tang return 0; 108*8a2d737cSLawrence Tang } 109*8a2d737cSLawrence Tang 110*8a2d737cSLawrence Tang //Check the field types are actually equal. 111*8a2d737cSLawrence Tang const char* desired_field_type_str = json_object_get_string(desired_field_type); 112*8a2d737cSLawrence Tang if (!( 113*8a2d737cSLawrence Tang (!strcmp(desired_field_type_str, "object") && json_object_is_type(object, json_type_object)) 114*8a2d737cSLawrence Tang || (!strcmp(desired_field_type_str, "array") && json_object_is_type(object, json_type_array)) 115*8a2d737cSLawrence Tang || (!strcmp(desired_field_type_str, "integer") && json_object_is_type(object, json_type_int)) 116*8a2d737cSLawrence Tang || (!strcmp(desired_field_type_str, "string") && json_object_is_type(object, json_type_string)) 117*8a2d737cSLawrence Tang || (!strcmp(desired_field_type_str, "boolean") && json_object_is_type(object, json_type_boolean)) 118*8a2d737cSLawrence Tang || (!strcmp(desired_field_type_str, "double") && json_object_is_type(object, json_type_double)) 119*8a2d737cSLawrence Tang )) 120*8a2d737cSLawrence Tang { 121*8a2d737cSLawrence Tang sprintf(error_message, "Field type match failed for field '%s'.", field_name); 122*8a2d737cSLawrence Tang return 0; 123*8a2d737cSLawrence Tang } 124*8a2d737cSLawrence Tang 125*8a2d737cSLawrence Tang //Switch and validate each type in turn. 126*8a2d737cSLawrence Tang switch (json_object_get_type(object)) 127*8a2d737cSLawrence Tang { 128*8a2d737cSLawrence Tang case json_type_int: 129*8a2d737cSLawrence Tang return validate_integer(field_name, schema, object, error_message); 130*8a2d737cSLawrence Tang case json_type_string: 131*8a2d737cSLawrence Tang return validate_string(field_name, schema, object, error_message); 132*8a2d737cSLawrence Tang case json_type_object: 133*8a2d737cSLawrence Tang return validate_object(field_name, schema, object, error_message); 134*8a2d737cSLawrence Tang case json_type_array: 135*8a2d737cSLawrence Tang return validate_object(field_name, schema, object, error_message); 136*8a2d737cSLawrence Tang 137*8a2d737cSLawrence Tang //We don't perform extra validation on this type. 138*8a2d737cSLawrence Tang default: 139*8a2d737cSLawrence Tang return 1; 140*8a2d737cSLawrence Tang } 141*8a2d737cSLawrence Tang } 142*8a2d737cSLawrence Tang 143*8a2d737cSLawrence Tang //Validates a single integer value according to the given specification. 144*8a2d737cSLawrence Tang int validate_integer(const char* field_name, json_object* schema, json_object* object, char* error_message) 145*8a2d737cSLawrence Tang { 146*8a2d737cSLawrence Tang //Is there a minimum/maximum specified? If so, check those. 147*8a2d737cSLawrence Tang //Validate minimum. 148*8a2d737cSLawrence Tang json_object* min_value = json_object_object_get(schema, "minimum"); 149*8a2d737cSLawrence Tang if (min_value != NULL && json_object_is_type(min_value, json_type_int)) 150*8a2d737cSLawrence Tang { 151*8a2d737cSLawrence Tang int min_value_int = json_object_get_int(min_value); 152*8a2d737cSLawrence Tang if (json_object_get_uint64(object) < min_value_int) 153*8a2d737cSLawrence Tang { 154*8a2d737cSLawrence Tang sprintf(error_message, "Failed to validate integer field '%s'. Value was below minimum of %d.", field_name, min_value_int); 155*8a2d737cSLawrence Tang return 0; 156*8a2d737cSLawrence Tang } 157*8a2d737cSLawrence Tang } 158*8a2d737cSLawrence Tang 159*8a2d737cSLawrence Tang //Validate maximum. 160*8a2d737cSLawrence Tang json_object* max_value = json_object_object_get(schema, "maximum"); 161*8a2d737cSLawrence Tang if (max_value != NULL && json_object_is_type(max_value, json_type_int)) 162*8a2d737cSLawrence Tang { 163*8a2d737cSLawrence Tang int max_value_int = json_object_get_int(max_value); 164*8a2d737cSLawrence Tang if (json_object_get_uint64(object) > max_value_int) 165*8a2d737cSLawrence Tang { 166*8a2d737cSLawrence Tang sprintf(error_message, "Failed to validate integer field '%s'. Value was above maximum of %d.", field_name, max_value_int); 167*8a2d737cSLawrence Tang return 0; 168*8a2d737cSLawrence Tang } 169*8a2d737cSLawrence Tang } 170*8a2d737cSLawrence Tang 171*8a2d737cSLawrence Tang return 1; 172*8a2d737cSLawrence Tang } 173*8a2d737cSLawrence Tang 174*8a2d737cSLawrence Tang //Validates a single string value according to the given specification. 175*8a2d737cSLawrence Tang int validate_string(const char* field_name, json_object* schema, json_object* object, char* error_message) 176*8a2d737cSLawrence Tang { 177*8a2d737cSLawrence Tang //todo: if there is a "pattern" field, verify the string with RegEx. 178*8a2d737cSLawrence Tang return 1; 179*8a2d737cSLawrence Tang } 180*8a2d737cSLawrence Tang 181*8a2d737cSLawrence Tang //Validates a single object value according to the given specification. 182*8a2d737cSLawrence Tang int validate_object(const char* field_name, json_object* schema, json_object* object, char* error_message) 183*8a2d737cSLawrence Tang { 184*8a2d737cSLawrence Tang //Are there a set of "required" fields? If so, check they all exist. 185*8a2d737cSLawrence Tang json_object* required_fields = json_object_object_get(schema, "required"); 186*8a2d737cSLawrence Tang if (required_fields != NULL && json_object_get_type(required_fields) == json_type_array) 187*8a2d737cSLawrence Tang { 188*8a2d737cSLawrence Tang int len = json_object_array_length(required_fields); 189*8a2d737cSLawrence Tang for (int i=0; i<len; i++) 190*8a2d737cSLawrence Tang { 191*8a2d737cSLawrence Tang //Get the required field from schema. 192*8a2d737cSLawrence Tang json_object* required_field = json_object_array_get_idx(required_fields, i); 193*8a2d737cSLawrence Tang if (json_object_get_type(required_field) != json_type_string) 194*8a2d737cSLawrence Tang { 195*8a2d737cSLawrence Tang sprintf(error_message, "Required field for object '%s' is not a string (schema violation).", field_name); 196*8a2d737cSLawrence Tang return 0; 197*8a2d737cSLawrence Tang } 198*8a2d737cSLawrence Tang 199*8a2d737cSLawrence Tang //Does it exist in the object? 200*8a2d737cSLawrence Tang const char* required_field_str = json_object_get_string(required_field); 201*8a2d737cSLawrence Tang if (json_object_object_get(object, required_field_str) == NULL) 202*8a2d737cSLawrence Tang { 203*8a2d737cSLawrence Tang sprintf(error_message, "Required field '%s' was not present in object '%s'.", required_field_str, field_name); 204*8a2d737cSLawrence Tang return 0; 205*8a2d737cSLawrence Tang } 206*8a2d737cSLawrence Tang } 207*8a2d737cSLawrence Tang } 208*8a2d737cSLawrence Tang 209*8a2d737cSLawrence Tang //If the boolean field "additionalProperties" exists and is set to false, ensure there are no 210*8a2d737cSLawrence Tang //extra properties apart from those required in the object. 211*8a2d737cSLawrence Tang //... todo 212*8a2d737cSLawrence Tang 213*8a2d737cSLawrence Tang //Run through the "properties" object and validate each of those in turn. 214*8a2d737cSLawrence Tang json_object* properties = json_object_object_get(schema, "properties"); 215*8a2d737cSLawrence Tang if (properties != NULL && json_object_get_type(properties) == json_type_object) 216*8a2d737cSLawrence Tang { 217*8a2d737cSLawrence Tang json_object_object_foreach(properties, key, value) { 218*8a2d737cSLawrence Tang 219*8a2d737cSLawrence Tang //If the given property name does not exist on the target object, ignore and continue next. 220*8a2d737cSLawrence Tang json_object* object_prop = json_object_object_get(object, key); 221*8a2d737cSLawrence Tang if (object_prop == NULL) 222*8a2d737cSLawrence Tang continue; 223*8a2d737cSLawrence Tang 224*8a2d737cSLawrence Tang //Validate against the schema. 225*8a2d737cSLawrence Tang if (!validate_field(key, value, object_prop, error_message)) 226*8a2d737cSLawrence Tang return 0; 227*8a2d737cSLawrence Tang } 228*8a2d737cSLawrence Tang } 229*8a2d737cSLawrence Tang 230*8a2d737cSLawrence Tang return 1; 231*8a2d737cSLawrence Tang } 232*8a2d737cSLawrence Tang 233*8a2d737cSLawrence Tang //Validates a single array value according to the given specification. 234*8a2d737cSLawrence Tang int validate_array(const char* field_name, json_object* schema, json_object* object, char* error_message) 235*8a2d737cSLawrence Tang { 236*8a2d737cSLawrence Tang return 1; 237*8a2d737cSLawrence Tang }