|
|
@ -533,128 +533,148 @@ static cJSON_bool insert_item_in_array(cJSON *array, size_t which, cJSON *newite |
|
|
|
|
|
|
|
enum patch_operation { INVALID, ADD, REMOVE, REPLACE, MOVE, COPY, TEST }; |
|
|
|
|
|
|
|
static int cJSONUtils_ApplyPatch(cJSON *object, cJSON *patch) |
|
|
|
static enum patch_operation decode_patch_operation(const cJSON * const patch) |
|
|
|
{ |
|
|
|
cJSON *op = NULL; |
|
|
|
cJSON *path = NULL; |
|
|
|
cJSON *value = NULL; |
|
|
|
cJSON *parent = NULL; |
|
|
|
enum patch_operation opcode = INVALID; |
|
|
|
unsigned char *parentptr = NULL; |
|
|
|
unsigned char *childptr = NULL; |
|
|
|
|
|
|
|
op = cJSON_GetObjectItem(patch, "op"); |
|
|
|
path = cJSON_GetObjectItem(patch, "path"); |
|
|
|
if (!cJSON_IsString(op) || !cJSON_IsString(path)) |
|
|
|
cJSON *operation = cJSON_GetObjectItem(patch, "op"); |
|
|
|
if (!cJSON_IsString(operation)) |
|
|
|
{ |
|
|
|
/* malformed patch. */ |
|
|
|
return 2; |
|
|
|
return INVALID; |
|
|
|
} |
|
|
|
|
|
|
|
/* decode operation */ |
|
|
|
if (!strcmp(op->valuestring, "add")) |
|
|
|
if (strcmp(operation->valuestring, "add") == 0) |
|
|
|
{ |
|
|
|
opcode = ADD; |
|
|
|
return ADD; |
|
|
|
} |
|
|
|
else if (!strcmp(op->valuestring, "remove")) |
|
|
|
|
|
|
|
if (strcmp(operation->valuestring, "remove") == 0) |
|
|
|
{ |
|
|
|
opcode = REMOVE; |
|
|
|
return REMOVE; |
|
|
|
} |
|
|
|
else if (!strcmp(op->valuestring, "replace")) |
|
|
|
|
|
|
|
if (strcmp(operation->valuestring, "replace") == 0) |
|
|
|
{ |
|
|
|
opcode = REPLACE; |
|
|
|
return REPLACE; |
|
|
|
} |
|
|
|
else if (!strcmp(op->valuestring, "move")) |
|
|
|
|
|
|
|
if (strcmp(operation->valuestring, "move") == 0) |
|
|
|
{ |
|
|
|
opcode = MOVE; |
|
|
|
return MOVE; |
|
|
|
} |
|
|
|
else if (!strcmp(op->valuestring, "copy")) |
|
|
|
|
|
|
|
if (strcmp(operation->valuestring, "copy") == 0) |
|
|
|
{ |
|
|
|
opcode = COPY; |
|
|
|
return COPY; |
|
|
|
} |
|
|
|
else if (!strcmp(op->valuestring, "test")) |
|
|
|
|
|
|
|
if (strcmp(operation->valuestring, "test") == 0) |
|
|
|
{ |
|
|
|
/* compare value: {...} with the given path */ |
|
|
|
return cJSONUtils_Compare(cJSONUtils_GetPointer(object, path->valuestring), cJSON_GetObjectItem(patch, "value")); |
|
|
|
return TEST; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
/* unknown opcode. */ |
|
|
|
return 3; |
|
|
|
|
|
|
|
return INVALID; |
|
|
|
} |
|
|
|
|
|
|
|
/* special case for replacing the root */ |
|
|
|
if (path->valuestring[0] == '\0') |
|
|
|
{ |
|
|
|
if (opcode == REMOVE) |
|
|
|
/* overwrite and existing item with another one and free resources on the way */ |
|
|
|
static void overwrite_item(cJSON * const root, const cJSON replacement) |
|
|
|
{ |
|
|
|
/* remove possible children */ |
|
|
|
if (object->child != NULL) |
|
|
|
if (root == NULL) |
|
|
|
{ |
|
|
|
cJSON_Delete(object->child); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
/* remove other allocated resources */ |
|
|
|
if (object->string != NULL) |
|
|
|
if (root->string != NULL) |
|
|
|
{ |
|
|
|
cJSON_free(object->string); |
|
|
|
cJSON_free(root->string); |
|
|
|
} |
|
|
|
if (object->valuestring != NULL) |
|
|
|
if (root->valuestring != NULL) |
|
|
|
{ |
|
|
|
cJSON_free(object->valuestring); |
|
|
|
cJSON_free(root->valuestring); |
|
|
|
} |
|
|
|
if (root->child != NULL) |
|
|
|
{ |
|
|
|
cJSON_Delete(root->child); |
|
|
|
} |
|
|
|
|
|
|
|
/* make it invalid */ |
|
|
|
memset(object, '\0', sizeof(cJSON)); |
|
|
|
|
|
|
|
return 0; |
|
|
|
memcpy(root, &replacement, sizeof(cJSON)); |
|
|
|
} |
|
|
|
|
|
|
|
if ((opcode == REPLACE) || (opcode == ADD)) |
|
|
|
static int cJSONUtils_ApplyPatch(cJSON *object, const cJSON *patch) |
|
|
|
{ |
|
|
|
/* remove possible children */ |
|
|
|
if (object->child != NULL) |
|
|
|
cJSON *path = NULL; |
|
|
|
cJSON *value = NULL; |
|
|
|
cJSON *parent = NULL; |
|
|
|
enum patch_operation opcode = INVALID; |
|
|
|
unsigned char *parent_pointer = NULL; |
|
|
|
unsigned char *child_pointer = NULL; |
|
|
|
int status = 0; |
|
|
|
|
|
|
|
path = cJSON_GetObjectItem(patch, "path"); |
|
|
|
if (!cJSON_IsString(path)) |
|
|
|
{ |
|
|
|
cJSON_Delete(object->child); |
|
|
|
/* malformed patch. */ |
|
|
|
status = 2; |
|
|
|
goto cleanup; |
|
|
|
} |
|
|
|
|
|
|
|
/* remove other allocated resources */ |
|
|
|
if (object->string != NULL) |
|
|
|
opcode = decode_patch_operation(patch); |
|
|
|
if (opcode == INVALID) |
|
|
|
{ |
|
|
|
cJSON_free(object->string); |
|
|
|
status = 3; |
|
|
|
goto cleanup; |
|
|
|
} |
|
|
|
if (object->valuestring != NULL) |
|
|
|
else if (opcode == TEST) |
|
|
|
{ |
|
|
|
cJSON_free(object->valuestring); |
|
|
|
/* compare value: {...} with the given path */ |
|
|
|
status = cJSONUtils_Compare(cJSONUtils_GetPointer(object, path->valuestring), cJSON_GetObjectItem(patch, "value")); |
|
|
|
goto cleanup; |
|
|
|
} |
|
|
|
|
|
|
|
/* special case for replacing the root */ |
|
|
|
if (path->valuestring[0] == '\0') |
|
|
|
{ |
|
|
|
if (opcode == REMOVE) |
|
|
|
{ |
|
|
|
static const cJSON invalid = { NULL, NULL, NULL, cJSON_Invalid, NULL, 0, 0, NULL}; |
|
|
|
|
|
|
|
overwrite_item(object, invalid); |
|
|
|
|
|
|
|
status = 0; |
|
|
|
goto cleanup; |
|
|
|
} |
|
|
|
|
|
|
|
if ((opcode == REPLACE) || (opcode == ADD)) |
|
|
|
{ |
|
|
|
value = cJSON_GetObjectItem(patch, "value"); |
|
|
|
if (value == NULL) |
|
|
|
{ |
|
|
|
/* missing "value" for add/replace. */ |
|
|
|
return 7; |
|
|
|
status = 7; |
|
|
|
goto cleanup; |
|
|
|
} |
|
|
|
|
|
|
|
value = cJSON_Duplicate(value, 1); |
|
|
|
if (value == NULL) |
|
|
|
{ |
|
|
|
/* out of memory for add/replace. */ |
|
|
|
return 8; |
|
|
|
} |
|
|
|
/* the string "value" isn't needed */ |
|
|
|
if (value->string != NULL) |
|
|
|
{ |
|
|
|
cJSON_free(value->string); |
|
|
|
value->string = NULL; |
|
|
|
status = 8; |
|
|
|
goto cleanup; |
|
|
|
} |
|
|
|
|
|
|
|
/* copy over the value object */ |
|
|
|
memcpy(object, value, sizeof(cJSON)); |
|
|
|
overwrite_item(object, *value); |
|
|
|
|
|
|
|
/* delete the duplicated value */ |
|
|
|
cJSON_free(value); |
|
|
|
value = NULL; |
|
|
|
|
|
|
|
return 0; |
|
|
|
/* the string "value" isn't needed */ |
|
|
|
if (object->string != NULL) |
|
|
|
{ |
|
|
|
cJSON_free(object->string); |
|
|
|
object->string = NULL; |
|
|
|
} |
|
|
|
|
|
|
|
status = 0; |
|
|
|
goto cleanup; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
@ -664,13 +684,15 @@ static int cJSONUtils_ApplyPatch(cJSON *object, cJSON *patch) |
|
|
|
cJSON *old_item = cJSONUtils_PatchDetach(object, (unsigned char*)path->valuestring); |
|
|
|
if (old_item == NULL) |
|
|
|
{ |
|
|
|
return 13; |
|
|
|
status = 13; |
|
|
|
goto cleanup; |
|
|
|
} |
|
|
|
cJSON_Delete(old_item); |
|
|
|
if (opcode == REMOVE) |
|
|
|
{ |
|
|
|
/* For Remove, this job is done. */ |
|
|
|
return 0; |
|
|
|
status = 0; |
|
|
|
goto cleanup; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
@ -678,10 +700,11 @@ static int cJSONUtils_ApplyPatch(cJSON *object, cJSON *patch) |
|
|
|
if ((opcode == MOVE) || (opcode == COPY)) |
|
|
|
{ |
|
|
|
cJSON *from = cJSON_GetObjectItem(patch, "from"); |
|
|
|
if (!from) |
|
|
|
if (from == NULL) |
|
|
|
{ |
|
|
|
/* missing "from" for copy/move. */ |
|
|
|
return 4; |
|
|
|
status = 4; |
|
|
|
goto cleanup; |
|
|
|
} |
|
|
|
|
|
|
|
if (opcode == MOVE) |
|
|
@ -692,93 +715,103 @@ static int cJSONUtils_ApplyPatch(cJSON *object, cJSON *patch) |
|
|
|
{ |
|
|
|
value = cJSONUtils_GetPointer(object, from->valuestring); |
|
|
|
} |
|
|
|
if (!value) |
|
|
|
if (value == NULL) |
|
|
|
{ |
|
|
|
/* missing "from" for copy/move. */ |
|
|
|
return 5; |
|
|
|
status = 5; |
|
|
|
goto cleanup; |
|
|
|
} |
|
|
|
if (opcode == COPY) |
|
|
|
{ |
|
|
|
value = cJSON_Duplicate(value, 1); |
|
|
|
} |
|
|
|
if (!value) |
|
|
|
if (value == NULL) |
|
|
|
{ |
|
|
|
/* out of memory for copy/move. */ |
|
|
|
return 6; |
|
|
|
status = 6; |
|
|
|
goto cleanup; |
|
|
|
} |
|
|
|
} |
|
|
|
else /* Add/Replace uses "value". */ |
|
|
|
{ |
|
|
|
value = cJSON_GetObjectItem(patch, "value"); |
|
|
|
if (!value) |
|
|
|
if (value == NULL) |
|
|
|
{ |
|
|
|
/* missing "value" for add/replace. */ |
|
|
|
return 7; |
|
|
|
status = 7; |
|
|
|
goto cleanup; |
|
|
|
} |
|
|
|
value = cJSON_Duplicate(value, 1); |
|
|
|
if (!value) |
|
|
|
if (value == NULL) |
|
|
|
{ |
|
|
|
/* out of memory for add/replace. */ |
|
|
|
return 8; |
|
|
|
status = 8; |
|
|
|
goto cleanup; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/* Now, just add "value" to "path". */ |
|
|
|
|
|
|
|
/* split pointer in parent and child */ |
|
|
|
parentptr = cJSONUtils_strdup((unsigned char*)path->valuestring); |
|
|
|
childptr = (unsigned char*)strrchr((char*)parentptr, '/'); |
|
|
|
if (childptr) |
|
|
|
parent_pointer = cJSONUtils_strdup((unsigned char*)path->valuestring); |
|
|
|
child_pointer = (unsigned char*)strrchr((char*)parent_pointer, '/'); |
|
|
|
if (child_pointer != NULL) |
|
|
|
{ |
|
|
|
*childptr++ = '\0'; |
|
|
|
child_pointer[0] = '\0'; |
|
|
|
child_pointer++; |
|
|
|
} |
|
|
|
parent = cJSONUtils_GetPointer(object, (char*)parentptr); |
|
|
|
cJSONUtils_InplaceDecodePointerString(childptr); |
|
|
|
parent = cJSONUtils_GetPointer(object, (char*)parent_pointer); |
|
|
|
cJSONUtils_InplaceDecodePointerString(child_pointer); |
|
|
|
|
|
|
|
/* add, remove, replace, move, copy, test. */ |
|
|
|
if ((parent == NULL) || (childptr == NULL)) |
|
|
|
if ((parent == NULL) || (child_pointer == NULL)) |
|
|
|
{ |
|
|
|
/* Couldn't find object to add to. */ |
|
|
|
free(parentptr); |
|
|
|
cJSON_Delete(value); |
|
|
|
return 9; |
|
|
|
status = 9; |
|
|
|
goto cleanup; |
|
|
|
} |
|
|
|
else if (cJSON_IsArray(parent)) |
|
|
|
{ |
|
|
|
if (!strcmp((char*)childptr, "-")) |
|
|
|
if (strcmp((char*)child_pointer, "-") == 0) |
|
|
|
{ |
|
|
|
cJSON_AddItemToArray(parent, value); |
|
|
|
value = NULL; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
size_t index = 0; |
|
|
|
if (!decode_array_index_from_pointer(childptr, &index)) |
|
|
|
if (!decode_array_index_from_pointer(child_pointer, &index)) |
|
|
|
{ |
|
|
|
free(parentptr); |
|
|
|
cJSON_Delete(value); |
|
|
|
return 11; |
|
|
|
status = 11; |
|
|
|
goto cleanup; |
|
|
|
} |
|
|
|
|
|
|
|
if (!insert_item_in_array(parent, index, value)) |
|
|
|
{ |
|
|
|
free(parentptr); |
|
|
|
cJSON_Delete(value); |
|
|
|
return 10; |
|
|
|
status = 10; |
|
|
|
goto cleanup; |
|
|
|
} |
|
|
|
value = NULL; |
|
|
|
} |
|
|
|
} |
|
|
|
else if (cJSON_IsObject(parent)) |
|
|
|
{ |
|
|
|
cJSON_DeleteItemFromObject(parent, (char*)childptr); |
|
|
|
cJSON_AddItemToObject(parent, (char*)childptr, value); |
|
|
|
cJSON_DeleteItemFromObject(parent, (char*)child_pointer); |
|
|
|
cJSON_AddItemToObject(parent, (char*)child_pointer, value); |
|
|
|
value = NULL; |
|
|
|
} |
|
|
|
else |
|
|
|
|
|
|
|
cleanup: |
|
|
|
if (value != NULL) |
|
|
|
{ |
|
|
|
cJSON_Delete(value); |
|
|
|
} |
|
|
|
free(parentptr); |
|
|
|
if (parent_pointer != NULL) |
|
|
|
{ |
|
|
|
cJSON_free(parent_pointer); |
|
|
|
} |
|
|
|
|
|
|
|
return 0; |
|
|
|
return status; |
|
|
|
} |
|
|
|
|
|
|
|
CJSON_PUBLIC(int) cJSONUtils_ApplyPatches(cJSON *object, cJSON *patches) |
|
|
|