/* Copyright (C) 2021 CZ.NIC, z.s.p.o. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include "libknot/yparser/ypschema.h" #include "libknot/yparser/yptrafo.h" #include "libknot/attribute.h" #include "libknot/errcode.h" static size_t schema_count( const yp_item_t *src) { size_t count = 0; for (const yp_item_t *item = src; item->name != NULL; item++) { count++; } return count; } /*! Initializes the referenced item. */ static int set_ref_item( yp_item_t *dst, const yp_item_t *schema) { if (schema == NULL) { return KNOT_EINVAL; } // Get referenced section. const yp_name_t *ref_name = dst->var.r.ref_name; const yp_item_t *ref = yp_schema_find(ref_name, NULL, schema); if (ref == NULL) { return KNOT_YP_EINVAL_ITEM; } dst->var.r.ref = ref; // Get referenced group if supported. const yp_name_t *grp_ref_name = dst->var.r.grp_ref_name; if (grp_ref_name != NULL) { const yp_item_t *grp_ref = yp_schema_find(grp_ref_name, NULL, schema); if (grp_ref == NULL) { return KNOT_YP_EINVAL_ITEM; } dst->var.r.grp_ref = grp_ref; } return KNOT_EOK; } /*! Copies the sub_items list and initializes pointer to the identifier item. */ static int set_grp_item( yp_item_t *dst, const yp_item_t *src, const yp_item_t *schema) { // Count subitems. size_t count = schema_count(src->var.g.sub_items); // Allocate space for subitems + terminal zero item. size_t memsize = (count + 1) * sizeof(yp_item_t); dst->sub_items = malloc(memsize); if (dst->sub_items == NULL) { return KNOT_ENOMEM; } memset(dst->sub_items, 0, memsize); // Copy subitems. for (size_t i = 0; i < count; i++) { // The first item is an identifier if multi group. if (i == 0 && (dst->flags & YP_FMULTI)) { dst->var.g.id = &dst->sub_items[0]; } // Copy sub-item. dst->sub_items[i] = src->var.g.sub_items[i]; // Initialize sub-item. int ret = KNOT_EOK; switch (dst->sub_items[i].type) { case YP_TREF: ret = set_ref_item(dst->sub_items + i, schema); break; case YP_TGRP: // Deeper hierarchy is not supported. ret = KNOT_ENOTSUP; break; default: break; } // Set the parent item. dst->sub_items[i].parent = dst; if (ret != KNOT_EOK) { free(dst->sub_items); dst->sub_items = NULL; return ret; } } if (src->flags & YP_FALLOC) { dst->var.g.sub_items = malloc(memsize); if (dst->var.g.sub_items == NULL) { free(dst->sub_items); dst->sub_items = NULL; return KNOT_ENOMEM; } memcpy((void *)dst->var.g.sub_items, src->var.g.sub_items, memsize); } return KNOT_EOK; } static int set_item( yp_item_t *dst, const yp_item_t *src, const yp_item_t *schema) { // Check maximal item name length. if ((uint8_t)src->name[0] > YP_MAX_ITEM_NAME_LEN) { return KNOT_ERANGE; } // Copy the static data. *dst = *src; // Copy item name into dynamic memory. if (src->flags & YP_FALLOC) { dst->name = malloc(src->name[0] + 2); if (dst->name == NULL) { return KNOT_ENOMEM; } memcpy((void *)dst->name, src->name, src->name[0] + 2); } int ret; // Item type specific preparation. switch (src->type) { case YP_TREF: ret = set_ref_item(dst, schema); break; case YP_TGRP: ret = set_grp_item(dst, src, schema); break; default: ret = KNOT_EOK; } if (ret != KNOT_EOK && src->flags & YP_FALLOC) { free((void *)dst->name); } return ret; } static void unset_item( yp_item_t *item) { if (item->flags & YP_FALLOC) { free((void *)item->name); } if (item->type & YP_TGRP) { free(item->sub_items); if (item->flags & YP_FALLOC) { free((void *)item->var.g.sub_items); } } memset(item, 0, sizeof(yp_item_t)); } static int schema_copy( yp_item_t *dst, const yp_item_t *src, const yp_item_t *schema) { // Copy the schema. for (int i = 0; src[i].name != NULL; i++) { int ret = set_item(&dst[i], &src[i], schema); if (ret != KNOT_EOK) { return ret; } } return KNOT_EOK; } _public_ int yp_schema_copy( yp_item_t **dst, const yp_item_t *src) { if (dst == NULL || src == NULL) { return KNOT_EINVAL; } // Allocate space for new schema (+ terminal NULL item). size_t size = (schema_count(src) + 1) * sizeof(yp_item_t); yp_item_t *out = malloc(size); if (out == NULL) { return KNOT_ENOMEM; } memset(out, 0, size); // Copy the schema. int ret = schema_copy(out, src, out); if (ret != KNOT_EOK) { free(out); return ret; } *dst = out; return KNOT_EOK; } _public_ int yp_schema_merge( yp_item_t **dst, const yp_item_t *src1, const yp_item_t *src2) { if (dst == NULL || src1 == NULL || src2 == NULL) { return KNOT_EINVAL; } size_t count1 = schema_count(src1); size_t count2 = schema_count(src2); // Allocate space for new schema (+ terminal NULL item). size_t size = (count1 + count2 + 1) * sizeof(yp_item_t); yp_item_t *out = malloc(size); if (out == NULL) { return KNOT_ENOMEM; } memset(out, 0, size); // Copy the first schema. int ret = schema_copy(out, src1, out); if (ret != KNOT_EOK) { free(out); return ret; } // Copy the second schema. ret = schema_copy(out + count1, src2, out); if (ret != KNOT_EOK) { free(out); return ret; } *dst = out; return KNOT_EOK; } _public_ void yp_schema_purge_dynamic( yp_item_t *schema) { if (schema == NULL) { return; } for (yp_item_t *item = schema; item->name != NULL; item++) { if (item->flags & YP_FALLOC) { unset_item(item); } } } _public_ void yp_schema_free( yp_item_t *schema) { if (schema == NULL) { return; } for (yp_item_t *item = schema; item->name != NULL; item++) { unset_item(item); } free(schema); } /*! Search the schema for an item with the given name. */ static const yp_item_t* find_item( const char *name, size_t name_len, const yp_item_t *schema) { if (name == NULL || schema == NULL) { return NULL; } for (const yp_item_t *item = schema; item->name != NULL; item++) { if (item->name[0] != name_len) { continue; } if (memcmp(item->name + 1, name, name_len) == 0) { return item; } } return NULL; } _public_ const yp_item_t* yp_schema_find( const yp_name_t *name, const yp_name_t *parent_name, const yp_item_t *schema) { if (name == NULL || schema == NULL) { return NULL; } if (parent_name == NULL) { return find_item(name + 1, name[0], schema); } else { const yp_item_t *parent = find_item(parent_name + 1, parent_name[0], schema); if (parent == NULL) { return NULL; } return find_item(name + 1, name[0], parent->sub_items); } } _public_ yp_check_ctx_t* yp_schema_check_init( yp_item_t **schema) { if (schema == NULL) { return NULL; } yp_check_ctx_t *ctx = malloc(sizeof(yp_check_ctx_t)); if (ctx == NULL) { return NULL; } memset(ctx, 0, sizeof(yp_check_ctx_t)); ctx->schema = schema; return ctx; } static void reset_ctx( yp_check_ctx_t *ctx, size_t index) { assert(index < YP_MAX_NODE_DEPTH); yp_node_t *node = &ctx->nodes[index]; node->parent = (index > 0) ? &ctx->nodes[index - 1] : NULL; node->item = NULL; node->id_len = 0; node->data_len = 0; ctx->current = index; } static int check_item( const char *key, size_t key_len, const char *data, size_t data_len, yp_check_ctx_t *ctx, bool allow_key1_without_id) { yp_node_t *node = &ctx->nodes[ctx->current]; yp_node_t *parent = node->parent; bool is_id = false; if (parent != NULL) { // Check for invalid indentation. if (parent->item == NULL) { return KNOT_YP_EINVAL_INDENT; } // Check if valid group parent. if (parent->item->type != YP_TGRP) { return KNOT_YP_EINVAL_ITEM; } // Check if valid subitem. node->item = find_item(key, key_len, parent->item->sub_items); } else { node->item = find_item(key, key_len, *ctx->schema); } if (node->item == NULL) { return KNOT_YP_EINVAL_ITEM; } // Check if the parent requires id specification. if (parent != NULL && parent->item->var.g.id != NULL) { // Check if id. if (node->item == parent->item->var.g.id) { is_id = true; // Move current to the parent. --(ctx->current); // Check for missing id. } else if (parent->id_len == 0 && !allow_key1_without_id) { return KNOT_YP_ENOID; } } // Return if no data provided. if (data == NULL) { return KNOT_EOK; } // Group cannot have data. if (data_len != 0 && node->item->type == YP_TGRP) { return KNOT_YP_ENOTSUP_DATA; } // Convert item data to binary format. const yp_item_t *item = (node->item->type != YP_TREF) ? node->item : node->item->var.r.ref->var.g.id; if (is_id) { // Textual id must not be empty. if (data_len == 0) { return KNOT_YP_ENODATA; } parent->id_len = sizeof(((yp_node_t *)NULL)->id); int ret = yp_item_to_bin(item, data, data_len, parent->id, &parent->id_len); // Binary id must not be empty. if (ret == KNOT_EOK && parent->id_len == 0) { return KNOT_YP_EINVAL_DATA; } return ret; } else { node->data_len = sizeof(((yp_node_t *)NULL)->data); int ret = yp_item_to_bin(item, data, data_len, node->data, &node->data_len); return ret; } } _public_ int yp_schema_check_parser( yp_check_ctx_t *ctx, const yp_parser_t *parser) { if (ctx == NULL || parser == NULL) { return KNOT_EINVAL; } int ret; switch (parser->event) { case YP_EKEY0: reset_ctx(ctx, 0); ret = check_item(parser->key, parser->key_len, parser->data, parser->data_len, ctx, false); break; case YP_EKEY1: reset_ctx(ctx, 1); ret = check_item(parser->key, parser->key_len, parser->data, parser->data_len, ctx, false); if (ret != KNOT_EOK) { break; } // Check for KEY1 event with id item. if (ctx->current != 1) { return KNOT_YP_ENOTSUP_ID; } break; case YP_EID: reset_ctx(ctx, 1); ret = check_item(parser->key, parser->key_len, parser->data, parser->data_len, ctx, false); if (ret != KNOT_EOK) { break; } // Check for ID event with nonid item. if (ctx->current != 0) { return KNOT_YP_EINVAL_ID; } break; default: ret = KNOT_EPARSEFAIL; break; } return ret; } _public_ int yp_schema_check_str( yp_check_ctx_t *ctx, const char *key0, const char *key1, const char *id, const char *data) { if (ctx == NULL) { return KNOT_EINVAL; } size_t key0_len = (key0 != NULL) ? strlen(key0) : 0; size_t key1_len = (key1 != NULL) ? strlen(key1) : 0; size_t id_len = (id != NULL) ? strlen(id) : 0; size_t data_len = (data != NULL) ? strlen(data) : 0; // Key0 must always be non-empty. if (key0_len == 0) { return KNOT_YP_EINVAL_ITEM; } // Process key0. reset_ctx(ctx, 0); if (key1_len == 0) { int ret = check_item(key0, key0_len, data, data_len, ctx, false); if (ret != KNOT_EOK) { return ret; } } else { int ret = check_item(key0, key0_len, NULL, 0, ctx, false); if (ret != KNOT_EOK) { return ret; } } // Process id. if (id_len != 0) { if (ctx->nodes[0].item->type != YP_TGRP || ctx->nodes[0].item->var.g.id == NULL) { return KNOT_YP_ENOTSUP_ID; } const yp_name_t *name = ctx->nodes[0].item->var.g.id->name; reset_ctx(ctx, 1); int ret = check_item(name + 1, name[0], id, id_len, ctx, true); if (ret != KNOT_EOK) { return ret; } // Check for non-id item (should not happen). assert(ctx->current == 0); // Check for group id with data. if (key1_len == 0 && data != NULL) { return KNOT_YP_ENOTSUP_DATA; } } // Process key1. if (key1_len != 0) { reset_ctx(ctx, 1); int ret = check_item(key1, key1_len, data, data_len, ctx, true); if (ret != KNOT_EOK) { return ret; } // Check for id in key1 with extra data. if (ctx->current != 1 && id_len != 0 && data != NULL) { return KNOT_YP_ENOTSUP_DATA; } } return KNOT_EOK; } _public_ void yp_schema_check_deinit( yp_check_ctx_t* ctx) { free(ctx); }