blob: 88cc03c639e9366a7bfb63c26537d99bc2cfbbc2 [file] [log] [blame]
Lawrence Tang8a2d7372022-07-12 16:44:49 +01001/**
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>
Lawrence Tang8f793ac2022-07-13 10:17:09 +010013#include <stdarg.h>
Lawrence Tang8a2d7372022-07-12 16:44:49 +010014#include "json.h"
15#include "json-schema.h"
16#include "edk/BaseTypes.h"
17
Lawrence Tang8f793ac2022-07-13 10:17:09 +010018//Field definitions.
19int json_validator_debug = 0;
20
Lawrence Tang8a2d7372022-07-12 16:44:49 +010021//Private pre-definitions.
Lawrence Tange407b4c2022-07-21 13:54:01 +010022int validate_field(const char *name, json_object *schema, json_object *object,
23 char *error_message);
24int validate_integer(const char *field_name, json_object *schema,
25 json_object *object, char *error_message);
26int validate_string(const char *field_name, json_object *schema,
27 json_object *object, char *error_message);
28int validate_object(const char *field_name, json_object *schema,
29 json_object *object, char *error_message);
30int validate_array(const char *field_name, json_object *schema,
31 json_object *object, char *error_message);
32void log_validator_error(char *error_message, const char *format, ...);
33void log_validator_debug(const char *format, ...);
34void log_validator_msg(const char *format, va_list args);
Lawrence Tang8a2d7372022-07-12 16:44:49 +010035
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.
Lawrence Tange407b4c2022-07-21 13:54:01 +010038int validate_schema_from_file(const char *schema_file, json_object *object,
39 char *error_message)
Lawrence Tang8a2d7372022-07-12 16:44:49 +010040{
Lawrence Tange407b4c2022-07-21 13:54:01 +010041 //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 }
Lawrence Tang8a2d7372022-07-12 16:44:49 +010049
Lawrence Tange407b4c2022-07-21 13:54:01 +010050 //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);
Lawrence Tang8a2d7372022-07-12 16:44:49 +010054
Lawrence Tange407b4c2022-07-21 13:54:01 +010055 int result =
56 validate_schema(schema_ir, schema_dir, object, error_message);
Lawrence Tang8a2d7372022-07-12 16:44:49 +010057
Lawrence Tange407b4c2022-07-21 13:54:01 +010058 //Free memory from directory call.
59 free(schema_file_copy);
Lawrence Tang8a2d7372022-07-12 16:44:49 +010060
Lawrence Tange407b4c2022-07-21 13:54:01 +010061 return result;
Lawrence Tang8a2d7372022-07-12 16:44:49 +010062}
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.
Lawrence Tange407b4c2022-07-21 13:54:01 +010067int validate_schema(json_object *schema, char *schema_directory,
68 json_object *object, char *error_message)
Lawrence Tang8a2d7372022-07-12 16:44:49 +010069{
Lawrence Tange407b4c2022-07-21 13:54:01 +010070 //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 }
Lawrence Tang8a2d7372022-07-12 16:44:49 +010079
Lawrence Tange407b4c2022-07-21 13:54:01 +010080 //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 }
Lawrence Tang8a2d7372022-07-12 16:44:49 +010092
Lawrence Tange407b4c2022-07-21 13:54:01 +010093 //Parse the top level structure appropriately.
94 int result = validate_field("parent", schema, object, error_message);
Lawrence Tang8a2d7372022-07-12 16:44:49 +010095
Lawrence Tange407b4c2022-07-21 13:54:01 +010096 //Change back to original CWD.
97 chdir(original_cwd);
98 free(original_cwd);
Lawrence Tang8a2d7372022-07-12 16:44:49 +010099
Lawrence Tange407b4c2022-07-21 13:54:01 +0100100 if (result)
101 log_validator_debug(
102 "Successfully validated the provided object against schema.");
103 return result;
Lawrence Tang8a2d7372022-07-12 16:44:49 +0100104}
105
106//Validates a single JSON field given a schema/object.
Lawrence Tang8f793ac2022-07-13 10:17:09 +0100107//Returns -1 on fatal/error failure, 0 on validation failure, and 1 on validation.
Lawrence Tange407b4c2022-07-21 13:54:01 +0100108int validate_field(const char *field_name, json_object *schema,
109 json_object *object, char *error_message)
Lawrence Tang8a2d7372022-07-12 16:44:49 +0100110{
Lawrence Tange407b4c2022-07-21 13:54:01 +0100111 log_validator_debug("Validating field '%s'...", field_name);
Lawrence Tang8f793ac2022-07-13 10:17:09 +0100112
Lawrence Tange407b4c2022-07-21 13:54:01 +0100113 //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);
Lawrence Tang8f793ac2022-07-13 10:17:09 +0100119
Lawrence Tange407b4c2022-07-21 13:54:01 +0100120 //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 }
Lawrence Tang8f793ac2022-07-13 10:17:09 +0100130
Lawrence Tange407b4c2022-07-21 13:54:01 +0100131 log_validator_debug("loaded schema path '%s' for field '%s'.",
132 ref_path, field_name);
133 }
Lawrence Tang8a2d7372022-07-12 16:44:49 +0100134
Lawrence Tange407b4c2022-07-21 13:54:01 +0100135 //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 }
Lawrence Tang8a2d7372022-07-12 16:44:49 +0100146
Lawrence Tange407b4c2022-07-21 13:54:01 +0100147 //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 }
Lawrence Tang8a2d7372022-07-12 16:44:49 +0100167
Lawrence Tange407b4c2022-07-21 13:54:01 +0100168 //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);
Lawrence Tang8f793ac2022-07-13 10:17:09 +0100174
Lawrence Tange407b4c2022-07-21 13:54:01 +0100175 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 }
Lawrence Tang8f793ac2022-07-13 10:17:09 +0100189
Lawrence Tange407b4c2022-07-21 13:54:01 +0100190 //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 }
Lawrence Tang8f793ac2022-07-13 10:17:09 +0100198
Lawrence Tange407b4c2022-07-21 13:54:01 +0100199 //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 }
Lawrence Tang45e04b02022-07-12 16:54:01 +0100208
Lawrence Tange407b4c2022-07-21 13:54:01 +0100209 //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);
Lawrence Tang8a2d7372022-07-12 16:44:49 +0100223
Lawrence Tange407b4c2022-07-21 13:54:01 +0100224 //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 }
Lawrence Tang8a2d7372022-07-12 16:44:49 +0100231}
232
233//Validates a single integer value according to the given specification.
Lawrence Tange407b4c2022-07-21 13:54:01 +0100234int validate_integer(const char *field_name, json_object *schema,
235 json_object *object, char *error_message)
Lawrence Tang8a2d7372022-07-12 16:44:49 +0100236{
Lawrence Tange407b4c2022-07-21 13:54:01 +0100237 //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 }
Lawrence Tang8a2d7372022-07-12 16:44:49 +0100251
Lawrence Tange407b4c2022-07-21 13:54:01 +0100252 //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 }
Lawrence Tang8a2d7372022-07-12 16:44:49 +0100265
Lawrence Tange407b4c2022-07-21 13:54:01 +0100266 return 1;
Lawrence Tang8a2d7372022-07-12 16:44:49 +0100267}
268
269//Validates a single string value according to the given specification.
Lawrence Tange407b4c2022-07-21 13:54:01 +0100270int validate_string(const char *field_name, json_object *schema,
271 json_object *object, char *error_message)
Lawrence Tang8a2d7372022-07-12 16:44:49 +0100272{
Lawrence Tange407b4c2022-07-21 13:54:01 +0100273 //todo: if there is a "pattern" field, verify the string with RegEx.
274 return 1;
Lawrence Tang8a2d7372022-07-12 16:44:49 +0100275}
276
277//Validates a single object value according to the given specification.
Lawrence Tange407b4c2022-07-21 13:54:01 +0100278int validate_object(const char *field_name, json_object *schema,
279 json_object *object, char *error_message)
Lawrence Tang8a2d7372022-07-12 16:44:49 +0100280{
Lawrence Tange407b4c2022-07-21 13:54:01 +0100281 //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);
Lawrence Tang8f793ac2022-07-13 10:17:09 +0100289
Lawrence Tange407b4c2022-07-21 13:54:01 +0100290 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 }
Lawrence Tang8a2d7372022-07-12 16:44:49 +0100303
Lawrence Tange407b4c2022-07-21 13:54:01 +0100304 //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 }
Lawrence Tang8a2d7372022-07-12 16:44:49 +0100317
Lawrence Tange407b4c2022-07-21 13:54:01 +0100318 //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);
Lawrence Tang8a2d7372022-07-12 16:44:49 +0100326
Lawrence Tange407b4c2022-07-21 13:54:01 +0100327 //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;
Lawrence Tang8a2d7372022-07-12 16:44:49 +0100338
Lawrence Tange407b4c2022-07-21 13:54:01 +0100339 //Validate against the schema.
340 if (!validate_field(key, value, object_prop,
341 error_message))
342 return 0;
343 }
Lawrence Tang8a2d7372022-07-12 16:44:49 +0100344
Lawrence Tange407b4c2022-07-21 13:54:01 +0100345 //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 }
Lawrence Tangc4814592022-07-13 10:24:09 +0100362
Lawrence Tange407b4c2022-07-21 13:54:01 +0100363 return 1;
Lawrence Tang8a2d7372022-07-12 16:44:49 +0100364}
365
366//Validates a single array value according to the given specification.
Lawrence Tange407b4c2022-07-21 13:54:01 +0100367int validate_array(const char *field_name, json_object *schema,
368 json_object *object, char *error_message)
Lawrence Tang8a2d7372022-07-12 16:44:49 +0100369{
Lawrence Tange407b4c2022-07-21 13:54:01 +0100370 //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 }
Lawrence Tang45e04b02022-07-12 16:54:01 +0100383
Lawrence Tange407b4c2022-07-21 13:54:01 +0100384 return 1;
Lawrence Tang8f793ac2022-07-13 10:17:09 +0100385}
386
387//Enables/disables debugging globally for the JSON validator.
Lawrence Tange407b4c2022-07-21 13:54:01 +0100388void validate_schema_debug_enable()
389{
390 json_validator_debug = 1;
391}
392void validate_schema_debug_disable()
393{
394 json_validator_debug = 0;
395}
Lawrence Tang8f793ac2022-07-13 10:17:09 +0100396
Lawrence Tang7cd13902022-07-13 16:59:25 +0100397//Logs an error message to the given error message location and (optionally) provides debug output.
Lawrence Tange407b4c2022-07-21 13:54:01 +0100398void log_validator_error(char *error_message, const char *format, ...)
Lawrence Tang8f793ac2022-07-13 10:17:09 +0100399{
Lawrence Tange407b4c2022-07-21 13:54:01 +0100400 va_list args;
Lawrence Tang8f793ac2022-07-13 10:17:09 +0100401
Lawrence Tange407b4c2022-07-21 13:54:01 +0100402 //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);
Lawrence Tang8f793ac2022-07-13 10:17:09 +0100411}
412
Lawrence Tang7cd13902022-07-13 16:59:25 +0100413//Logs a debug message to stdout, if validator debug is enabled.
Lawrence Tange407b4c2022-07-21 13:54:01 +0100414void log_validator_debug(const char *format, ...)
Lawrence Tang8f793ac2022-07-13 10:17:09 +0100415{
Lawrence Tange407b4c2022-07-21 13:54:01 +0100416 va_list args;
417 va_start(args, format);
418 log_validator_msg(format, args);
419 va_end(args);
Lawrence Tang8f793ac2022-07-13 10:17:09 +0100420}
421
422//Logs a single validator debug/error message.
Lawrence Tange407b4c2022-07-21 13:54:01 +0100423void log_validator_msg(const char *format, va_list args)
Lawrence Tang8f793ac2022-07-13 10:17:09 +0100424{
Lawrence Tange407b4c2022-07-21 13:54:01 +0100425 //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");
Lawrence Tang8f793ac2022-07-13 10:17:09 +0100433
Lawrence Tange407b4c2022-07-21 13:54:01 +0100434 //Print & free format.
435 vfprintf(stdout, new_format, args);
436 free(new_format);
437 }
Lawrence Tang8a2d7372022-07-12 16:44:49 +0100438}