403Webshell
Server IP : 189.126.111.107  /  Your IP : 216.73.217.69
Web Server : Apache
System : Linux www.gadotticar.com.br 5.4.0-135-generic #152-Ubuntu SMP Wed Nov 23 20:19:22 UTC 2022 x86_64
User : gadotticar ( 1000)
PHP Version : 8.2.27
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : ON  |  Sudo : ON  |  Pkexec : ON
Directory :  /usr/src/libks/src/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /usr/src/libks/src/ks_json_schema.cpp
/*
 * Copyright (c) 2025 SignalWire, Inc
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#ifdef HAVE_JSON_SCHEMA_VALIDATOR

#include <memory>
#include <string>
#include <sstream>
#include <nlohmann/json.hpp>
#include <nlohmann/json-schema.hpp>

#include "libks/ks.h"

static const char* JSON_META_SCHEMA_DRAFT_07 = R"({
    "$schema": "http://json-schema.org/draft-07/schema#",
    "$id": "http://json-schema.org/draft-07/schema#",
    "title": "Core schema meta-schema",
    "definitions": {
        "schemaArray": {
            "type": "array",
            "minItems": 1,
            "items": { "$ref": "#" }
        },
        "nonNegativeInteger": {
            "type": "integer",
            "minimum": 0
        },
        "nonNegativeIntegerDefault0": {
            "allOf": [
                { "$ref": "#/definitions/nonNegativeInteger" },
                { "default": 0 }
            ]
        },
        "simpleTypes": {
            "enum": [
                "array",
                "boolean",
                "integer",
                "null",
                "number",
                "object",
                "string"
            ]
        },
        "stringArray": {
            "type": "array",
            "items": { "type": "string" },
            "uniqueItems": true,
            "default": []
        }
    },
    "type": ["object", "boolean"],
    "properties": {
        "$id": {
            "type": "string",
            "format": "uri-reference"
        },
        "$schema": {
            "type": "string",
            "format": "uri"
        },
        "$ref": {
            "type": "string",
            "format": "uri-reference"
        },
        "$comment": {
            "type": "string"
        },
        "title": {
            "type": "string"
        },
        "description": {
            "type": "string"
        },
        "default": true,
        "readOnly": {
            "type": "boolean",
            "default": false
        },
        "writeOnly": {
            "type": "boolean",
            "default": false
        },
        "examples": {
            "type": "array",
            "items": true
        },
        "multipleOf": {
            "type": "number",
            "exclusiveMinimum": 0
        },
        "maximum": {
            "type": "number"
        },
        "exclusiveMaximum": {
            "type": "number"
        },
        "minimum": {
            "type": "number"
        },
        "exclusiveMinimum": {
            "type": "number"
        },
        "maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
        "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
        "pattern": {
            "type": "string",
            "format": "regex"
        },
        "additionalItems": { "$ref": "#" },
        "items": {
            "anyOf": [
                { "$ref": "#" },
                { "$ref": "#/definitions/schemaArray" }
            ],
            "default": true
        },
        "maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
        "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
        "uniqueItems": {
            "type": "boolean",
            "default": false
        },
        "contains": { "$ref": "#" },
        "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
        "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
        "required": { "$ref": "#/definitions/stringArray" },
        "additionalProperties": { "$ref": "#" },
        "definitions": {
            "type": "object",
            "additionalProperties": { "$ref": "#" },
            "default": {}
        },
        "properties": {
            "type": "object",
            "additionalProperties": { "$ref": "#" },
            "default": {}
        },
        "patternProperties": {
            "type": "object",
            "additionalProperties": { "$ref": "#" },
            "propertyNames": { "format": "regex" },
            "default": {}
        },
        "dependencies": {
            "type": "object",
            "additionalProperties": {
                "anyOf": [
                    { "$ref": "#" },
                    { "$ref": "#/definitions/stringArray" }
                ]
            }
        },
        "propertyNames": { "$ref": "#" },
        "const": true,
        "enum": {
            "type": "array",
            "items": true,
            "minItems": 1,
            "uniqueItems": true
        },
        "type": {
            "anyOf": [
                { "$ref": "#/definitions/simpleTypes" },
                {
                    "type": "array",
                    "items": { "$ref": "#/definitions/simpleTypes" },
                    "minItems": 1,
                    "uniqueItems": true
                }
            ]
        },
        "format": { "type": "string" },
        "contentMediaType": { "type": "string" },
        "contentEncoding": { "type": "string" },
        "if": { "$ref": "#" },
        "then": { "$ref": "#" },
        "else": { "$ref": "#" },
        "allOf": { "$ref": "#/definitions/schemaArray" },
        "anyOf": { "$ref": "#/definitions/schemaArray" },
        "oneOf": { "$ref": "#/definitions/schemaArray" },
        "not": { "$ref": "#" }
    },
    "default": true
})";

struct ks_json_schema {
	std::unique_ptr<nlohmann::json_schema::json_validator> validator;
};

// Global cached meta-schema validator
static ks_json_schema_t *g_meta_schema_validator = nullptr;
static bool g_json_schema_initialized = false;

// Forward declaration of internal function
static ks_json_schema_status_t ks_json_schema_create_internal(const char *schema_json, ks_json_schema_t **schema, ks_json_schema_error_t **errors, bool validate_meta_schema);

// URI-reference format checker based on RFC 3986 Section 4
static bool is_valid_uri_reference(const std::string &value)
{
	// URI-reference = URI / relative-ref
	// URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
	// relative-ref = relative-part [ "?" query ] [ "#" fragment ]

	if (value.empty()) {
		return true; // Empty string is a valid relative reference
	}

	// Basic character validation - URI-references should only contain:
	// - Unreserved characters: ALPHA / DIGIT / "-" / "." / "_" / "~"
	// - Reserved characters: ":" / "/" / "?" / "#" / "[" / "]" / "@" / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
	// - Percent-encoded characters: "%" HEXDIG HEXDIG

	for (size_t i = 0; i < value.length(); ++i) {
		char c = value[i];

		// Check for percent-encoded sequences
		if (c == '%') {
			if (i + 2 >= value.length()) {
				return false; // Incomplete percent encoding
			}
			char h1 = value[i + 1];
			char h2 = value[i + 2];
			if (!std::isxdigit(h1) || !std::isxdigit(h2)) {
				return false; // Invalid hex digits
			}
			i += 2; // Skip the hex digits
			continue;
		}

		// Check for valid URI characters
		if (std::isalnum(c) ||
			c == '-' || c == '.' || c == '_' || c == '~' || // unreserved
			c == ':' || c == '/' || c == '?' || c == '#' || c == '[' || c == ']' || c == '@' || // gen-delims
			c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' || c == ')' ||
			c == '*' || c == '+' || c == ',' || c == ';' || c == '=') { // sub-delims
			continue;
		}

		return false; // Invalid character
	}

	// Additional validation: check for proper structure
	// Look for scheme (if present): scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
	size_t colon_pos = value.find(':');
	if (colon_pos != std::string::npos) {
		// If there's a colon, validate the scheme part
		std::string scheme = value.substr(0, colon_pos);
		if (scheme.empty() || !std::isalpha(scheme[0])) {
			// Could be a relative reference with colon in path, which is valid
			// Only reject if it looks like an invalid scheme
			bool has_slash_before_colon = false;
			for (size_t j = 0; j < colon_pos; ++j) {
				if (value[j] == '/') {
					has_slash_before_colon = true;
					break;
				}
			}
			if (!has_slash_before_colon) {
				// This looks like a scheme, validate it
				for (char sc : scheme) {
					if (!std::isalnum(sc) && sc != '+' && sc != '-' && sc != '.') {
						return false;
					}
				}
			}
		}
	}

	return true;
}

static void schema_format_checker(const std::string &format, const std::string &value)
{
	if (format == "uri-reference") {
		if (!is_valid_uri_reference(value)) {
			throw std::invalid_argument("Invalid URI reference format");
		}
	} else {
		// Use default format checker for other formats
		nlohmann::json_schema::default_string_format_check(format, value);
	}
}

// Initialize JSON schema subsystem - internal function
static void ks_json_schema_init_internal(void)
{
	if (g_json_schema_initialized) {
		return; // Already initialized
	}

	// Create and cache the meta-schema validator
	ks_json_schema_error_t *errors = nullptr;
	ks_json_schema_status_t status = ks_json_schema_create_internal(JSON_META_SCHEMA_DRAFT_07, &g_meta_schema_validator, &errors, false);

	if (status != KS_JSON_SCHEMA_STATUS_SUCCESS) {
		// This should never happen with a valid meta-schema, but handle gracefully
		if (errors) {
			ks_json_schema_error_free(&errors);
		}
		g_meta_schema_validator = nullptr;
	}

	g_json_schema_initialized = true;
}

// Cleanup JSON schema subsystem - internal function
static void ks_json_schema_shutdown_internal(void)
{
	if (!g_json_schema_initialized) {
		return; // Not initialized
	}

	if (g_meta_schema_validator) {
		ks_json_schema_destroy(&g_meta_schema_validator);
		g_meta_schema_validator = nullptr;
	}

	g_json_schema_initialized = false;
}

// Internal function to create schema without meta-schema validation (to avoid recursion)
static ks_json_schema_status_t ks_json_schema_create_internal(const char *schema_json, ks_json_schema_t **schema, ks_json_schema_error_t **errors, bool validate_meta_schema)
{
	if (!schema_json || !schema) {
		return KS_JSON_SCHEMA_STATUS_INVALID_PARAM;
	}

	*schema = nullptr;
	if (errors) {
		*errors = nullptr;
	}

	try {
		// Parse the schema JSON
		nlohmann::json schema_json_obj = nlohmann::json::parse(schema_json);

		// Create validator with remote reference support
		auto ks_schema = std::make_unique<ks_json_schema>();
		ks_schema->validator = std::make_unique<nlohmann::json_schema::json_validator>(nullptr, schema_format_checker);

		// Set schema with remote reference loading enabled - this will validate the schema
		ks_schema->validator->set_root_schema(schema_json_obj);

		// Validate against meta-schema only if requested (to avoid recursion)
		if (validate_meta_schema) {
			// Check if JSON schema subsystem is properly initialized
			if (!g_json_schema_initialized || !g_meta_schema_validator) {
				// libks requires ks_init() to be called for proper operation
				return KS_JSON_SCHEMA_STATUS_UNAVAILABLE;
			}

			// Use cached meta-schema validator
			ks_json_schema_error_t *validation_errors = nullptr;
			ks_json_schema_status_t validation_status = ks_json_schema_validate_string(g_meta_schema_validator, schema_json, &validation_errors);

			if (validation_status != KS_JSON_SCHEMA_STATUS_SUCCESS) {
				if (errors) {
					// Prefix error messages to indicate they're schema validation errors
					ks_json_schema_error_t *current = validation_errors;
					while (current) {
						if (current->message) {
							std::string new_msg = "Schema validation error: " + std::string(current->message);
							free(current->message);
							current->message = strdup(new_msg.c_str());
						}
						current = current->next;
					}
					*errors = validation_errors;
				} else {
					ks_json_schema_error_free(&validation_errors);
				}
				return KS_JSON_SCHEMA_STATUS_INVALID_SCHEMA;
			}
		}

		*schema = ks_schema.release();
		return KS_JSON_SCHEMA_STATUS_SUCCESS;
	} catch (const nlohmann::json::parse_error& e) {
		if (errors) {
			auto error = static_cast<ks_json_schema_error_t *>(malloc(sizeof(ks_json_schema_error_t)));
			if (error) {
				std::string msg = "JSON parse error in schema: " + std::string(e.what());
				error->message = strdup(msg.c_str());
				error->path = strdup("");
				error->next = nullptr;
				*errors = error;
			}
		}
		return KS_JSON_SCHEMA_STATUS_INVALID_SCHEMA;
	} catch (const std::exception& e) {
		if (errors) {
			auto error = static_cast<ks_json_schema_error_t *>(malloc(sizeof(ks_json_schema_error_t)));
			if (error) {
				error->message = strdup(e.what());
				error->path = strdup("");
				error->next = nullptr;
				*errors = error;
			}
		}
		return KS_JSON_SCHEMA_STATUS_INVALID_SCHEMA;
	}
}

class ks_error_handler : public nlohmann::json_schema::basic_error_handler
{
public:
	struct validation_error {
		std::string message;
		std::string path;
	};

	std::vector<validation_error> errors;
	static const size_t MAX_ERRORS = 5;

	void error(const nlohmann::json::json_pointer &pointer, const nlohmann::json &instance, const std::string &message) override
	{
		// Stop collecting errors after reaching the limit
		if (errors.size() >= MAX_ERRORS) {
			return;
		}

		validation_error err;
		err.message = message;
		err.path = pointer.to_string();
		errors.push_back(err);

		// Add a truncation message if we hit the limit
		if (errors.size() == MAX_ERRORS) {
			validation_error truncated_err;
			truncated_err.message = "Maximum error limit reached. Additional errors may exist.";
			truncated_err.path = "";
			errors.push_back(truncated_err);
		}

		basic_error_handler::error(pointer, instance, message);
	}
};

#else

#include "libks/ks.h"

#endif

KS_BEGIN_EXTERN_C

KS_DECLARE(const char *) ks_json_schema_status_string(ks_json_schema_status_t status)
{
	switch (status) {
		case KS_JSON_SCHEMA_STATUS_SUCCESS:
			return "Success";
		case KS_JSON_SCHEMA_STATUS_UNAVAILABLE:
			return "JSON Schema validation not enabled";
		case KS_JSON_SCHEMA_STATUS_INVALID_SCHEMA:
			return "Invalid schema";
		case KS_JSON_SCHEMA_STATUS_INVALID_JSON:
			return "Invalid JSON";
		case KS_JSON_SCHEMA_STATUS_VALIDATION_FAILED:
			return "Validation failed";
		case KS_JSON_SCHEMA_STATUS_MEMORY_ERROR:
			return "Memory error";
		case KS_JSON_SCHEMA_STATUS_INVALID_PARAM:
			return "Invalid parameter";
		default:
			return "Unknown error";
	}
}

#ifdef HAVE_JSON_SCHEMA_VALIDATOR

KS_DECLARE(void) ks_json_schema_init(void)
{
	ks_json_schema_init_internal();
}

KS_DECLARE(void) ks_json_schema_shutdown(void)
{
	ks_json_schema_shutdown_internal();
}

KS_DECLARE(ks_json_schema_status_t) ks_json_schema_create(const char *schema_json, ks_json_schema_t **schema, ks_json_schema_error_t **errors)
{
	// Call internal function with meta-schema validation enabled
	return ks_json_schema_create_internal(schema_json, schema, errors, true);
}

KS_DECLARE(ks_json_schema_status_t) ks_json_schema_create_from_json(ks_json_t *schema_json, ks_json_schema_t **schema, ks_json_schema_error_t **errors)
{
	if (!schema_json || !schema) {
		return KS_JSON_SCHEMA_STATUS_INVALID_PARAM;
	}

	// Convert JSON object to string and delegate to ks_json_schema_create
	char *schema_string = ks_json_print_unformatted(schema_json);
	if (!schema_string) {
		if (errors) {
			auto error = static_cast<ks_json_schema_error_t *>(malloc(sizeof(ks_json_schema_error_t)));
			if (error) {
				error->message = strdup("Failed to serialize JSON object to string");
				error->path = strdup("");
				error->next = nullptr;
				*errors = error;
			}
		}
		return KS_JSON_SCHEMA_STATUS_INVALID_SCHEMA;
	}

	// Use the main schema creation function
	ks_json_schema_status_t result = ks_json_schema_create(schema_string, schema, errors);

	// Clean up the allocated string
	free(schema_string);

	return result;
}

KS_DECLARE(ks_json_schema_status_t) ks_json_schema_validate_string(ks_json_schema_t *schema, const char *json_string, ks_json_schema_error_t **errors)
{
	if (!schema || !json_string) {
		return KS_JSON_SCHEMA_STATUS_INVALID_PARAM;
	}

	if (errors) {
		*errors = nullptr;
	}

	try {
		// Parse the JSON string
		nlohmann::json json_doc = nlohmann::json::parse(json_string);

		// Create error handler to collect detailed validation errors
		ks_error_handler error_handler;

		// Validate using json-schema-validator with error handler
		schema->validator->validate(json_doc, error_handler);

		// Check if validation failed
		if (!error_handler.errors.empty()) {
			if (errors) {
				ks_json_schema_error_t *error_list = nullptr;
				ks_json_schema_error_t *last_error = nullptr;

				for (const auto& validation_error : error_handler.errors) {
					auto error = static_cast<ks_json_schema_error_t *>(malloc(sizeof(ks_json_schema_error_t)));
					if (!error) {
						ks_json_schema_error_free(&error_list);
						return KS_JSON_SCHEMA_STATUS_MEMORY_ERROR;
					}

					error->message = strdup(validation_error.message.c_str());
					error->path = strdup(validation_error.path.c_str());
					error->next = nullptr;

					if (!error->message || !error->path) {
						free(error->message);
						free(error->path);
						free(error);
						ks_json_schema_error_free(&error_list);
						return KS_JSON_SCHEMA_STATUS_MEMORY_ERROR;
					}

					if (last_error) {
						last_error->next = error;
					} else {
						error_list = error;
					}
					last_error = error;
				}

				*errors = error_list;
			}
			return KS_JSON_SCHEMA_STATUS_VALIDATION_FAILED;
		}

		return KS_JSON_SCHEMA_STATUS_SUCCESS;
	} catch (const nlohmann::json::parse_error& e) {
		if (errors) {
			auto error = static_cast<ks_json_schema_error_t *>(malloc(sizeof(ks_json_schema_error_t)));
			if (error) {
				std::string msg = "JSON parse error: " + std::string(e.what());
				error->message = strdup(msg.c_str());
				error->path = strdup("");
				error->next = nullptr;
				*errors = error;
			}
		}
		return KS_JSON_SCHEMA_STATUS_INVALID_JSON;
	} catch (const std::exception& e) {
		// Other validation errors
		if (errors) {
			auto error = static_cast<ks_json_schema_error_t *>(malloc(sizeof(ks_json_schema_error_t)));
			if (error) {
				error->message = strdup(e.what());
				error->path = strdup("");
				error->next = nullptr;
				*errors = error;
			}
		}
		return KS_JSON_SCHEMA_STATUS_VALIDATION_FAILED;
	}
}

KS_DECLARE(ks_json_schema_status_t) ks_json_schema_validate_json(ks_json_schema_t *schema, ks_json_t *json, ks_json_schema_error_t **errors)
{
	if (!schema || !json) {
		return KS_JSON_SCHEMA_STATUS_INVALID_PARAM;
	}

	// Convert JSON object to string and delegate to ks_json_schema_validate_string
	char *json_string = ks_json_print_unformatted(json);
	if (!json_string) {
		if (errors) {
			auto error = static_cast<ks_json_schema_error_t *>(malloc(sizeof(ks_json_schema_error_t)));
			if (error) {
				error->message = strdup("Failed to serialize JSON object to string");
				error->path = strdup("");
				error->next = nullptr;
				*errors = error;
			}
		}
		return KS_JSON_SCHEMA_STATUS_INVALID_JSON;
	}

	// Use the main validation function
	ks_json_schema_status_t result = ks_json_schema_validate_string(schema, json_string, errors);

	// Clean up the allocated string
	free(json_string);

	return result;
}

KS_DECLARE(void) ks_json_schema_destroy(ks_json_schema_t **schema)
{
	if (!schema || !*schema) {
		return;
	}

	delete *schema;
	*schema = nullptr;
}

#else

KS_DECLARE(void) ks_json_schema_init(void)
{
	// No-op when JSON schema validation is disabled
}

KS_DECLARE(void) ks_json_schema_shutdown(void)
{
	// No-op when JSON schema validation is disabled
}

KS_DECLARE(ks_json_schema_status_t) ks_json_schema_create(const char *schema_json, ks_json_schema_t **schema, ks_json_schema_error_t **errors)
{
	(void)schema_json;
	(void)schema;
	(void)errors;
	return KS_JSON_SCHEMA_STATUS_UNAVAILABLE;
}

KS_DECLARE(ks_json_schema_status_t) ks_json_schema_create_from_json(ks_json_t *schema_json, ks_json_schema_t **schema, ks_json_schema_error_t **errors)
{
	(void)schema_json;
	(void)schema;
	(void)errors;
	return KS_JSON_SCHEMA_STATUS_UNAVAILABLE;
}

KS_DECLARE(ks_json_schema_status_t) ks_json_schema_validate_string(ks_json_schema_t *schema, const char *json_string, ks_json_schema_error_t **errors)
{
	(void)schema;
	(void)json_string;
	(void)errors;
	return KS_JSON_SCHEMA_STATUS_UNAVAILABLE;
}

KS_DECLARE(ks_json_schema_status_t) ks_json_schema_validate_json(ks_json_schema_t *schema, ks_json_t *json, ks_json_schema_error_t **errors)
{
	(void)schema;
	(void)json;
	(void)errors;
	return KS_JSON_SCHEMA_STATUS_UNAVAILABLE;
}

KS_DECLARE(void) ks_json_schema_destroy(ks_json_schema_t **schema)
{
	(void)schema;
}

#endif

KS_DECLARE(void) ks_json_schema_error_free(ks_json_schema_error_t **errors)
{
	if (!errors || !*errors) {
		return;
	}

	ks_json_schema_error_t *current = *errors;
	while (current) {
		ks_json_schema_error_t *next = current->next;
		free(current->message);
		free(current->path);
		free(current);
		current = next;
	}

	*errors = nullptr;
}

KS_END_EXTERN_C

/* For Emacs:
 * Local Variables:
 * mode:c++
 * indent-tabs-mode:t
 * tab-width:4
 * c-basic-offset:4
 * End:
 * For VIM:
 * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
 */

Youez - 2016 - github.com/yon3zu
LinuXploit