From ddb786c332e3dc6c8e0d6815bad6dc597fc0885e Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Sat, 27 Jul 2019 09:24:39 -0400 Subject: [PATCH] Persist unknown JSON properties, expose to API, safety improvements. --- platform-independent/c/cli/src/mpw-cli.c | 13 +- .../c/cli/src/mpw-tests-util.c | 17 +- .../c/core/src/mpw-marshal-util.c | 32 +- .../c/core/src/mpw-marshal-util.h | 29 +- platform-independent/c/core/src/mpw-marshal.c | 317 +++++++++++++----- platform-independent/c/core/src/mpw-marshal.h | 14 +- platform-independent/c/core/src/mpw-types.c | 28 +- 7 files changed, 317 insertions(+), 133 deletions(-) diff --git a/platform-independent/c/cli/src/mpw-cli.c b/platform-independent/c/cli/src/mpw-cli.c index 486ca33d..6472135f 100644 --- a/platform-independent/c/cli/src/mpw-cli.c +++ b/platform-independent/c/cli/src/mpw-cli.c @@ -489,7 +489,7 @@ void cli_user(Arguments *args, Operation *operation) { mpw_free_string( &operation->sitesPath ); mpw_marshal_free( &operation->file ); operation->file = mpw_marshal_file( mpw_marshal_user( - operation->fullName, cli_masterKeyProvider_op( operation ), MPAlgorithmVersionCurrent ) ); + operation->fullName, cli_masterKeyProvider_op( operation ), MPAlgorithmVersionCurrent ), NULL ); } else { @@ -550,7 +550,7 @@ void cli_site(Arguments *args, Operation *operation) { // Load the site object from mpsites. MPMarshalledUser *user = operation->file->user; for (size_t s = 0; !operation->site && s < user->sites_count; ++s) - if (strcmp( operation->siteName, (&user->sites[s])->siteName ) == 0) + if (strcmp( operation->siteName, (&user->sites[s])->siteName ) == OK) operation->site = &user->sites[s]; // If no site from mpsites, create a new one. @@ -571,8 +571,11 @@ void cli_question(Arguments *args, Operation *operation) { break; case MPKeyPurposeRecovery: for (size_t q = 0; !operation->question && q < operation->site->questions_count; ++q) - if ((!operation->keyContext && !strlen( (&operation->site->questions[q])->keyword )) || - (operation->keyContext && strcmp( (&operation->site->questions[q])->keyword, operation->keyContext ) == 0)) + if (operation->keyContext == (&operation->site->questions[q])->keyword || + (!operation->keyContext && !strlen( (&operation->site->questions[q])->keyword )) || + (!(&operation->site->questions[q])->keyword && !strlen( operation->keyContext )) || + ((operation->keyContext && (&operation->site->questions[q])->keyword) && + strcmp( (&operation->site->questions[q])->keyword, operation->keyContext ) == OK)) operation->question = &operation->site->questions[q]; // If no question from mpsites, create a new one. @@ -700,7 +703,7 @@ void cli_algorithmVersion(Arguments *args, Operation *operation) { void cli_sitesRedacted(Arguments *args, Operation *operation) { if (args->sitesRedacted) - operation->file->user->redacted = strcmp( args->sitesRedacted, "1" ) == 0; + operation->file->user->redacted = strcmp( args->sitesRedacted, "1" ) == OK; else if (!operation->file->user->redacted) wrn( "Sites configuration is not redacted. Use -R 1 to change this." ); diff --git a/platform-independent/c/cli/src/mpw-tests-util.c b/platform-independent/c/cli/src/mpw-tests-util.c index 724e8c2e..57c72dab 100644 --- a/platform-independent/c/cli/src/mpw-tests-util.c +++ b/platform-independent/c/cli/src/mpw-tests-util.c @@ -45,18 +45,21 @@ static xmlChar const *mpw_xmlPath(xmlNodePtr context) { xmlNodePtr mpw_xmlTestCaseNode(xmlNodePtr testCaseNode, const char *nodeName) { + if (!nodeName) + return NULL; + // Try to find an attribute node. for (xmlAttrPtr child = testCaseNode->properties; child; child = child->next) - if (xmlStrcmp( child->name, BAD_CAST nodeName ) == 0) + if (xmlStrcmp( child->name, BAD_CAST nodeName ) == OK) return (xmlNodePtr)child; // Try to find an element node. for (xmlNodePtr child = testCaseNode->children; child; child = child->next) - if (xmlStrcmp( child->name, BAD_CAST nodeName ) == 0) + if (xmlStrcmp( child->name, BAD_CAST nodeName ) == OK) return child; // Missing content, try to find parent case. - if (strcmp( nodeName, "parent" ) == 0) + if (strcmp( nodeName, "parent" ) == OK) // Was just searching for testCaseNode's parent, none found. return NULL; xmlChar *parentId = mpw_xmlTestCaseString( testCaseNode, "parent" ); @@ -66,7 +69,7 @@ xmlNodePtr mpw_xmlTestCaseNode(xmlNodePtr testCaseNode, const char *nodeName) { for (xmlNodePtr otherTestCaseNode = testCaseNode->parent->children; otherTestCaseNode; otherTestCaseNode = otherTestCaseNode->next) { xmlChar *id = mpw_xmlTestCaseString( otherTestCaseNode, "id" ); - int foundParent = id && xmlStrcmp( id, parentId ) == 0; + int foundParent = id && xmlStrcmp( id, parentId ) == OK; xmlFree( id ); if (foundParent) { @@ -81,12 +84,18 @@ xmlNodePtr mpw_xmlTestCaseNode(xmlNodePtr testCaseNode, const char *nodeName) { xmlChar *mpw_xmlTestCaseString(xmlNodePtr context, const char *nodeName) { + if (!nodeName) + return NULL; + xmlNodePtr child = mpw_xmlTestCaseNode( context, nodeName ); return child? xmlNodeGetContent( child ): NULL; } uint32_t mpw_xmlTestCaseInteger(xmlNodePtr context, const char *nodeName) { + if (!nodeName) + return 0; + xmlChar *string = mpw_xmlTestCaseString( context, nodeName ); uint32_t integer = string? (uint32_t)atol( (char *)string ): 0; xmlFree( string ); diff --git a/platform-independent/c/core/src/mpw-marshal-util.c b/platform-independent/c/core/src/mpw-marshal-util.c index 3df86cd6..ce94e529 100644 --- a/platform-independent/c/core/src/mpw-marshal-util.c +++ b/platform-independent/c/core/src/mpw-marshal-util.c @@ -58,26 +58,27 @@ time_t mpw_timegm(const char *time) { } #if MPW_JSON -json_object *mpw_get_json_section( - json_object *obj, const char *section) { - json_object *json_value = obj; - char *sectionTokenizer = mpw_strdup( section ), *sectionToken = sectionTokenizer; - for (sectionToken = strtok( sectionToken, "." ); sectionToken; sectionToken = strtok( NULL, "." )) - if (!json_object_object_get_ex( json_value, sectionToken, &json_value ) || !json_value) { - trc( "While resolving: %s: Missing value for: %s", section, sectionToken ); +json_object *mpw_get_json_object( + json_object *obj, const char *key, bool create) { + + if (!obj) + return NULL; + + json_object *json_value = NULL; + if (!json_object_object_get_ex( obj, key, &json_value ) || !json_value) + if (!create || json_object_object_add( obj, key, json_value = json_object_new_object() ) != OK) { + trc( "Missing value for: %s", key ); json_value = NULL; - break; } - free( sectionTokenizer ); return json_value; } const char *mpw_get_json_string( - json_object *obj, const char *section, const char *defaultValue) { + json_object *obj, const char *key, const char *defaultValue) { - json_object *json_value = mpw_get_json_section( obj, section ); + json_object *json_value = mpw_get_json_object( obj, key, false ); if (!json_value) return defaultValue; @@ -85,9 +86,9 @@ const char *mpw_get_json_string( } int64_t mpw_get_json_int( - json_object *obj, const char *section, int64_t defaultValue) { + json_object *obj, const char *key, int64_t defaultValue) { - json_object *json_value = mpw_get_json_section( obj, section ); + json_object *json_value = mpw_get_json_object( obj, key, false ); if (!json_value) return defaultValue; @@ -95,14 +96,15 @@ int64_t mpw_get_json_int( } bool mpw_get_json_boolean( - json_object *obj, const char *section, bool defaultValue) { + json_object *obj, const char *key, bool defaultValue) { - json_object *json_value = mpw_get_json_section( obj, section ); + json_object *json_value = mpw_get_json_object( obj, key, false ); if (!json_value) return defaultValue; return json_object_get_boolean( json_value ) == true; } + #endif bool mpw_update_master_key(MPMasterKey *masterKey, MPAlgorithmVersion *masterKeyAlgorithm, MPAlgorithmVersion targetKeyAlgorithm, diff --git a/platform-independent/c/core/src/mpw-marshal-util.h b/platform-independent/c/core/src/mpw-marshal-util.h index 533c1e01..4f35d9d8 100644 --- a/platform-independent/c/core/src/mpw-marshal-util.h +++ b/platform-independent/c/core/src/mpw-marshal-util.h @@ -43,26 +43,27 @@ time_t mpw_timegm( /// JSON parsing. #if MPW_JSON -/** Search for a JSON child object in a JSON object tree. - * @param section A dot-delimited list of JSON object keys to walk toward the child object. - * @return A JSON object (shared) or NULL if one of the section's object keys was not found in the source object's tree. */ -json_object *mpw_get_json_section( - json_object *obj, const char *section); +/** Search for an object in a JSON object tree. + * @param key A JSON object key for the child in this object. + * @param create If true, create and insert new objects for any missing path components. + * @return An object (shared) or a new object (shared) installed in the tree if the path's object path was not found. */ +json_object *mpw_get_json_object( + json_object *obj, const char *key, bool create); /** Search for a string in a JSON object tree. - * @param section A dot-delimited list of JSON object keys to walk toward the child object. - * @return A string (shared) or defaultValue if one of the section's object keys was not found in the source object's tree. */ + * @param key A dot-delimited list of JSON object keys to walk toward the child object. + * @return A string (shared) or defaultValue if one of the path's object keys was not found in the source object's tree. */ const char *mpw_get_json_string( - json_object *obj, const char *section, const char *defaultValue); + json_object *obj, const char *key, const char *defaultValue); /** Search for an integer in a JSON object tree. - * @param section A dot-delimited list of JSON object keys to walk toward the child object. - * @return The integer value or defaultValue if one of the section's object keys was not found in the source object's tree. */ + * @param key A dot-delimited list of JSON object keys to walk toward the child object. + * @return The integer value or defaultValue if one of the path's object keys was not found in the source object's tree. */ int64_t mpw_get_json_int( - json_object *obj, const char *section, int64_t defaultValue); + json_object *obj, const char *key, int64_t defaultValue); /** Search for a boolean in a JSON object tree. - * @param section A dot-delimited list of JSON object keys to walk toward the child object. - * @return The boolean value or defaultValue if one of the section's object keys was not found in the source object's tree. */ + * @param key A dot-delimited list of JSON object keys to walk toward the child object. + * @return The boolean value or defaultValue if one of the path's object keys was not found in the source object's tree. */ bool mpw_get_json_boolean( - json_object *obj, const char *section, bool defaultValue); + json_object *obj, const char *key, bool defaultValue); #endif /// mpw. diff --git a/platform-independent/c/core/src/mpw-marshal.c b/platform-independent/c/core/src/mpw-marshal.c index 521d759e..e5551215 100644 --- a/platform-independent/c/core/src/mpw-marshal.c +++ b/platform-independent/c/core/src/mpw-marshal.c @@ -25,6 +25,7 @@ MP_LIBS_BEGIN #include #include #include +#include MP_LIBS_END MPMarshalledUser *mpw_marshal_user( @@ -56,8 +57,12 @@ MPMarshalledSite *mpw_marshal_site( MPMarshalledUser *user, const char *siteName, const MPResultType resultType, const MPCounterValue siteCounter, const MPAlgorithmVersion algorithmVersion) { - if (!siteName || !mpw_realloc( &user->sites, NULL, sizeof( MPMarshalledSite ) * ++user->sites_count )) + if (!siteName) return NULL; + if (!mpw_realloc( &user->sites, NULL, sizeof( MPMarshalledSite ) * ++user->sites_count )) { + user->sites_count--; + return NULL; + } MPMarshalledSite *site = &user->sites[user->sites_count - 1]; *site = (MPMarshalledSite){ @@ -84,8 +89,10 @@ MPMarshalledSite *mpw_marshal_site( MPMarshalledQuestion *mpw_marshal_question( MPMarshalledSite *site, const char *keyword) { - if (!mpw_realloc( &site->questions, NULL, sizeof( MPMarshalledQuestion ) * ++site->questions_count )) + if (!mpw_realloc( &site->questions, NULL, sizeof( MPMarshalledQuestion ) * ++site->questions_count )) { + site->questions_count--; return NULL; + } if (!keyword) keyword = ""; @@ -151,16 +158,20 @@ static bool mpw_marshal_user_free( return success; } -static bool mpw_marshal_data_free( - MPMarshalledData **data) { +static bool mpw_marshal_data_null( + MPMarshalledData *data) { - if (!data || !*data) + if (!data) return true; - bool success = mpw_free_strings( &(*data)->key, &(*data)->str_value, NULL ); - for (unsigned int c = 0; c < (*data)->obj_children_count; ++c) - success &= mpw_marshal_data_free( &(*data)->obj_children[c] ); - success &= mpw_free( data, sizeof( MPMarshalledData ) ); + bool success = mpw_free_strings( &data->key, &data->str_value, NULL ); + for (unsigned int c = 0; c < data->children_count; ++c) + success &= mpw_marshal_data_null( &data->children[c] ); + success &= mpw_free( &data->children, sizeof( MPMarshalledData ) * data->children_count ); + data->children_count = 0; + data->num_value = NAN; + data->is_bool = false; + data->is_null = true; return success; } @@ -175,7 +186,8 @@ bool mpw_marshal_free( success &= mpw_marshal_info_free( &(*file)->info ); success &= mpw_marshal_user_free( &(*file)->user ); - success &= mpw_marshal_data_free( &(*file)->data ); + success &= mpw_marshal_data_null( (*file)->data ); + success &= mpw_free( &(*file)->data, sizeof( MPMarshalledData ) ); success &= mpw_free( file, sizeof( MPMarshalledFile ) ); return success; @@ -272,6 +284,37 @@ static const char *mpw_marshal_write_flat( #if MPW_JSON +static json_object *mpw_get_json_data( + MPMarshalledData *data) { + + if (!data || data->is_null) + return NULL; + if (data->is_bool) + return json_object_new_boolean( data->num_value != false ); + if (!isnan( data->num_value )) + return json_object_new_double_s( data->num_value, data->str_value ); + if (data->str_value) + return json_object_new_string( data->str_value ); + + json_object *obj = NULL; + for (size_t index = 0; index < data->children_count; ++index) { + MPMarshalledData *child = &data->children[index]; + if (!obj) { + if (child->key) + obj = json_object_new_object(); + else + obj = json_object_new_array(); + } + + if (child->key) + json_object_object_add( obj, child->key, mpw_get_json_data( child ) ); + else + json_object_array_add( obj, mpw_get_json_data( child ) ); + } + + return obj; +} + static const char *mpw_marshal_write_json( const MPMarshalledFile *file, MPMarshalError *error) { @@ -290,9 +333,10 @@ static const char *mpw_marshal_write_json( masterKey = user->masterKeyProvider( user->algorithm, user->fullName ); // Section: "export" - json_object *json_file = json_object_new_object(); - json_object *json_export = json_object_new_object(); - json_object_object_add( json_file, "export", json_export ); + json_object *json_file = mpw_get_json_data( file->data ); + if (!json_file) + json_file = json_object_new_object(); + json_object *json_export = mpw_get_json_object( json_file, "export", true ); json_object_object_add( json_export, "format", json_object_new_int( 1 ) ); json_object_object_add( json_export, "redacted", json_object_new_boolean( user->redacted ) ); @@ -302,8 +346,7 @@ static const char *mpw_marshal_write_json( json_object_object_add( json_export, "date", json_object_new_string( dateString ) ); // Section: "user" - json_object *json_user = json_object_new_object(); - json_object_object_add( json_file, "user", json_user ); + json_object *json_user = mpw_get_json_object( json_file, "user", true ); json_object_object_add( json_user, "avatar", json_object_new_int( (int32_t)user->avatar ) ); json_object_object_add( json_user, "full_name", json_object_new_string( user->fullName ) ); @@ -318,8 +361,7 @@ static const char *mpw_marshal_write_json( json_object_object_add( json_user, "default_type", json_object_new_int( (int32_t)user->defaultType ) ); // Section "sites" - json_object *json_sites = json_object_new_object(); - json_object_object_add( json_file, "sites", json_sites ); + json_object *json_sites = mpw_get_json_object( json_file, "sites", true ); for (size_t s = 0; s < user->sites_count; ++s) { MPMarshalledSite *site = &user->sites[s]; if (!site->siteName || !strlen( site->siteName )) @@ -348,8 +390,7 @@ static const char *mpw_marshal_write_json( loginState = mpw_strdup( site->loginState ); } - json_object *json_site = json_object_new_object(); - json_object_object_add( json_sites, site->siteName, json_site ); + json_object *json_site = mpw_get_json_object( json_sites, site->siteName, true ); json_object_object_add( json_site, "type", json_object_new_int( (int32_t)site->resultType ) ); json_object_object_add( json_site, "counter", json_object_new_int( (int32_t)site->counter ) ); json_object_object_add( json_site, "algorithm", json_object_new_int( (int32_t)site->algorithm ) ); @@ -364,15 +405,13 @@ static const char *mpw_marshal_write_json( json_object_object_add( json_site, "last_used", json_object_new_string( dateString ) ); if (site->questions_count) { - json_object *json_site_questions = json_object_new_object(); - json_object_object_add( json_site, "questions", json_site_questions ); + json_object *json_site_questions = mpw_get_json_object( json_site, "questions", true ); for (size_t q = 0; q < site->questions_count; ++q) { MPMarshalledQuestion *question = &site->questions[q]; if (!question->keyword) continue; - json_object *json_site_question = json_object_new_object(); - json_object_object_add( json_site_questions, question->keyword, json_site_question ); + json_object *json_site_question = mpw_get_json_object( json_site_questions, question->keyword, true ); json_object_object_add( json_site_question, "type", json_object_new_int( (int32_t)question->type ) ); if (!user->redacted) { @@ -389,11 +428,11 @@ static const char *mpw_marshal_write_json( } } - json_object *json_site_mpw = json_object_new_object(); + json_object *json_site_mpw = mpw_get_json_object( json_site, "_ext_mpw", true ); if (site->url) json_object_object_add( json_site_mpw, "url", json_object_new_string( site->url ) ); - if (json_object_object_length( json_site_mpw )) - json_object_object_add( json_site, "_ext_mpw", json_site_mpw ); + if (!json_object_object_length( json_site_mpw )) + json_object_object_del( json_site, "_ext_mpw" ); mpw_free_strings( &resultState, &loginState, NULL ); } @@ -414,22 +453,31 @@ static const char *mpw_marshal_write_json( #endif const char *mpw_marshal_write( - const MPMarshalFormat outFormat, const MPMarshalledFile *file, MPMarshalError *error) { + const MPMarshalFormat outFormat, MPMarshalledFile *file, MPMarshalError *error) { + const char *out = NULL; switch (outFormat) { case MPMarshalFormatNone: *error = (MPMarshalError){ .type = MPMarshalSuccess }; - return NULL; + break; case MPMarshalFormatFlat: - return mpw_marshal_write_flat( file, error ); + out = mpw_marshal_write_flat( file, error ); + break; #if MPW_JSON case MPMarshalFormatJSON: - return mpw_marshal_write_json( file, error ); + out = mpw_marshal_write_json( file, error ); + break; #endif default: *error = (MPMarshalError){ MPMarshalErrorFormat, mpw_str( "Unsupported output format: %u", outFormat ) }; - return NULL; + break; } + if (file) { + mpw_marshal_info_free( &file->info ); + file->info = mpw_marshal_read_info( out ); + } + + return out; } static void mpw_marshal_read_flat_info( @@ -462,19 +510,19 @@ static void mpw_marshal_read_flat_info( if (!headerName || !headerValue) continue; - if (strcmp( headerName, "Date" ) == 0) + if (strcmp( headerName, "Date" ) == OK) info->exportDate = info->lastUsed = mpw_timegm( headerValue ); - if (strcmp( headerName, "Passwords" ) == 0) - info->redacted = strcmp( headerValue, "VISIBLE" ) != 0; - if (strcmp( headerName, "Algorithm" ) == 0) + if (strcmp( headerName, "Passwords" ) == OK) + info->redacted = strcmp( headerValue, "VISIBLE" ) != OK; + if (strcmp( headerName, "Algorithm" ) == OK) info->algorithm = (MPAlgorithmVersion)atoi( headerValue ); - if (strcmp( headerName, "Avatar" ) == 0) + if (strcmp( headerName, "Avatar" ) == OK) info->avatar = (unsigned int)atoi( headerValue ); - if (strcmp( headerName, "Full Name" ) == 0 || strcmp( headerName, "User Name" ) == 0) + if (strcmp( headerName, "Full Name" ) == OK || strcmp( headerName, "User Name" ) == OK) info->fullName = mpw_strdup( headerValue ); - if (strcmp( headerName, "Identicon" ) == 0) + if (strcmp( headerName, "Identicon" ) == OK) info->identicon = mpw_identicon_encoded( headerValue ); - if (strcmp( headerName, "Key ID" ) == 0) + if (strcmp( headerName, "Key ID" ) == OK) info->keyID = mpw_strdup( headerValue ); mpw_free_strings( &headerName, &headerValue, NULL ); @@ -569,13 +617,13 @@ static MPMarshalledFile *mpw_marshal_read_flat( return NULL; } - if (strcmp( headerName, "Format" ) == 0) + if (strcmp( headerName, "Format" ) == OK) format = (unsigned int)atoi( headerValue ); - if (strcmp( headerName, "Date" ) == 0) + if (strcmp( headerName, "Date" ) == OK) exportDate = mpw_timegm( headerValue ); - if (strcmp( headerName, "Passwords" ) == 0) - importRedacted = strcmp( headerValue, "VISIBLE" ) != 0; - if (strcmp( headerName, "Algorithm" ) == 0) { + if (strcmp( headerName, "Passwords" ) == OK) + importRedacted = strcmp( headerValue, "VISIBLE" ) != OK; + if (strcmp( headerName, "Algorithm" ) == OK) { int value = atoi( headerValue ); if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) { *error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user algorithm version: %s", headerValue ) }; @@ -587,15 +635,15 @@ static MPMarshalledFile *mpw_marshal_read_flat( } algorithm = (MPAlgorithmVersion)value; } - if (strcmp( headerName, "Avatar" ) == 0) + if (strcmp( headerName, "Avatar" ) == OK) avatar = (unsigned int)atoi( headerValue ); - if (strcmp( headerName, "Full Name" ) == 0 || strcmp( headerName, "User Name" ) == 0) + if (strcmp( headerName, "Full Name" ) == OK || strcmp( headerName, "User Name" ) == OK) fullName = mpw_strdup( headerValue ); - if (strcmp( headerName, "Identicon" ) == 0) + if (strcmp( headerName, "Identicon" ) == OK) identicon = mpw_identicon_encoded( headerValue ); - if (strcmp( headerName, "Key ID" ) == 0) + if (strcmp( headerName, "Key ID" ) == OK) keyID = mpw_strdup( headerValue ); - if (strcmp( headerName, "Default Type" ) == 0) { + if (strcmp( headerName, "Default Type" ) == OK) { int value = atoi( headerValue ); if (!mpw_type_short_name( (MPResultType)value )) { *error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user default type: %s", headerValue ) }; @@ -783,6 +831,110 @@ static MPMarshalledFile *mpw_marshal_read_flat( #if MPW_JSON +static void mpw_set_json_data( + MPMarshalledData *data, json_object *obj) { + + json_type type = json_object_get_type( obj ); + data->is_null = type == json_type_null; + data->is_bool = type == json_type_boolean; + + if (type == json_type_boolean) + data->num_value = json_object_get_boolean( obj ); + else if (type == json_type_double) + data->num_value = json_object_get_double( obj ); + else if (type == json_type_int) + data->num_value = json_object_get_int64( obj ); + else + data->num_value = NAN; + + const char *str = NULL; + if (type == json_type_string || !isnan( data->num_value )) + str = json_object_get_string( obj ); + if (!str || !data->str_value || strcmp( str, data->str_value ) != OK) { + mpw_free_string( &data->str_value ); + data->str_value = mpw_strdup( str ); + } + + // Clean up children + MPMarshalledData *newChildren = NULL; + size_t newChildrenCount = 0; + for (size_t c = 0; c < data->children_count; ++c) { + MPMarshalledData *child = &data->children[c]; + if ((type != json_type_object && type != json_type_array) || + (child->key && type != json_type_object) || (!isnan( child->index ) && type != json_type_array)) { + mpw_marshal_data_null( child ); + if (!newChildren) { + newChildren = malloc( sizeof( MPMarshalledData ) * newChildrenCount ); + if (newChildren) + memcpy( newChildren, data->children, sizeof( MPMarshalledData ) * newChildrenCount ); + } + } + else { + ++newChildrenCount; + if (newChildren) { + if (!mpw_realloc( &newChildren, NULL, sizeof( MPMarshalledData ) * newChildrenCount )) { + --newChildrenCount; + continue; + } + child->index = newChildrenCount - 1; + newChildren[child->index] = *child; + } + } + } + if (newChildren) { + mpw_free( &data->children, sizeof( MPMarshalledData ) * data->children_count ); + data->children = newChildren; + data->children_count = newChildrenCount; + } + + // Object + if (type == json_type_object) { + json_object_iter entry; + json_object_object_foreachC( obj, entry ) { + MPMarshalledData *child = NULL; + + // Find existing child. + for (size_t c = 0; c < data->children_count; ++c) + if (data->children[c].key == entry.key || + (data->children[c].key && entry.key && strcmp( data->children[c].key, entry.key )) == OK) { + child = &data->children[c]; + break; + } + + // Create new child. + if (!child) { + if (!mpw_realloc( &data->children, NULL, sizeof( MPMarshalledData ) * ++data->children_count )) { + --data->children_count; + continue; + } + *(child = &data->children[data->children_count - 1]) = (MPMarshalledData){ .key = mpw_strdup( entry.key ) }; + } + + mpw_set_json_data( child, entry.val ); + } + } + + // Array + if (type == json_type_array) { + for (size_t index = 0; index < json_object_array_length( obj ); ++index) { + MPMarshalledData *child = NULL; + + if (index < data->children_count) + child = &data->children[index]; + + else { + if (!mpw_realloc( &data->children, NULL, sizeof( MPMarshalledData ) * ++data->children_count )) { + --data->children_count; + continue; + } + *(child = &data->children[data->children_count - 1]) = (MPMarshalledData){ .index = index }; + } + + mpw_set_json_data( child, json_object_array_get_idx( obj, index ) ); + } + } +} + static void mpw_marshal_read_json_info( const char *in, MPMarshalInfo *info) { @@ -793,19 +945,21 @@ static void mpw_marshal_read_json_info( return; // Section: "export" - int64_t fileFormat = mpw_get_json_int( json_file, "export.format", 0 ); + json_object *json_export = mpw_get_json_object( json_file, "export", false ); + int64_t fileFormat = mpw_get_json_int( json_export, "format", 0 ); if (fileFormat < 1) return; - info->exportDate = mpw_timegm( mpw_get_json_string( json_file, "export.date", NULL ) ); - info->redacted = mpw_get_json_boolean( json_file, "export.redacted", true ); + info->exportDate = mpw_timegm( mpw_get_json_string( json_export, "date", NULL ) ); + info->redacted = mpw_get_json_boolean( json_export, "redacted", true ); // Section: "user" - info->algorithm = (MPAlgorithmVersion)mpw_get_json_int( json_file, "user.algorithm", MPAlgorithmVersionCurrent ); - info->avatar = (unsigned int)mpw_get_json_int( json_file, "user.avatar", 0 ); - info->fullName = mpw_strdup( mpw_get_json_string( json_file, "user.full_name", NULL ) ); - info->identicon = mpw_identicon_encoded( mpw_get_json_string( json_file, "user.identicon", NULL ) ); - info->keyID = mpw_strdup( mpw_get_json_string( json_file, "user.key_id", NULL ) ); - info->lastUsed = mpw_timegm( mpw_get_json_string( json_file, "user.last_used", NULL ) ); + json_object *json_user = mpw_get_json_object( json_file, "user", false ); + info->algorithm = (MPAlgorithmVersion)mpw_get_json_int( json_user, "algorithm", MPAlgorithmVersionCurrent ); + info->avatar = (unsigned int)mpw_get_json_int( json_user, "avatar", 0 ); + info->fullName = mpw_strdup( mpw_get_json_string( json_user, "full_name", NULL ) ); + info->identicon = mpw_identicon_encoded( mpw_get_json_string( json_user, "identicon", NULL ) ); + info->keyID = mpw_strdup( mpw_get_json_string( json_user, "key_id", NULL ) ); + info->lastUsed = mpw_timegm( mpw_get_json_string( json_user, "last_used", NULL ) ); json_object_put( json_file ); } @@ -830,38 +984,40 @@ static MPMarshalledFile *mpw_marshal_read_json( } // Section: "export" - int64_t fileFormat = mpw_get_json_int( json_file, "export.format", 0 ); + json_object *json_export = mpw_get_json_object( json_file, "export", false ); + int64_t fileFormat = mpw_get_json_int( json_export, "format", 0 ); if (fileFormat < 1) { *error = (MPMarshalError){ MPMarshalErrorFormat, mpw_str( "Unsupported format: %u", fileFormat ) }; json_object_put( json_file ); return NULL; } - bool fileRedacted = mpw_get_json_boolean( json_file, "export.redacted", true ); + bool fileRedacted = mpw_get_json_boolean( json_export, "redacted", true ); // Section: "user" - int64_t value = mpw_get_json_int( json_file, "user.algorithm", MPAlgorithmVersionCurrent ); + json_object *json_user = mpw_get_json_object( json_file, "user", false ); + int64_t value = mpw_get_json_int( json_user, "algorithm", MPAlgorithmVersionCurrent ); if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) { *error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user algorithm version: %u", value ) }; json_object_put( json_file ); return NULL; } MPAlgorithmVersion algorithm = (MPAlgorithmVersion)value; - unsigned int avatar = (unsigned int)mpw_get_json_int( json_file, "user.avatar", 0 ); - const char *fullName = mpw_get_json_string( json_file, "user.full_name", NULL ); + unsigned int avatar = (unsigned int)mpw_get_json_int( json_user, "avatar", 0 ); + const char *fullName = mpw_get_json_string( json_user, "full_name", NULL ); if (!fullName || !strlen( fullName )) { *error = (MPMarshalError){ MPMarshalErrorMissing, "Missing value for full name." }; json_object_put( json_file ); return NULL; } - MPIdenticon identicon = mpw_identicon_encoded( mpw_get_json_string( json_file, "user.identicon", NULL ) ); - const char *keyID = mpw_get_json_string( json_file, "user.key_id", NULL ); - MPResultType defaultType = (MPResultType)mpw_get_json_int( json_file, "user.default_type", MPResultTypeDefault ); + MPIdenticon identicon = mpw_identicon_encoded( mpw_get_json_string( json_user, "identicon", NULL ) ); + const char *keyID = mpw_get_json_string( json_user, "key_id", NULL ); + MPResultType defaultType = (MPResultType)mpw_get_json_int( json_user, "default_type", MPResultTypeDefault ); if (!mpw_type_short_name( defaultType )) { *error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user default type: %u", defaultType ) }; json_object_put( json_file ); return NULL; } - const char *str_lastUsed = mpw_get_json_string( json_file, "user.last_used", NULL ); + const char *str_lastUsed = mpw_get_json_string( json_user, "last_used", NULL ); time_t lastUsed = mpw_timegm( str_lastUsed ); if (!lastUsed) { *error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user last used: %s", str_lastUsed ) }; @@ -901,7 +1057,7 @@ static MPMarshalledFile *mpw_marshal_read_json( // Section "sites" json_object_iter json_site; - json_object *json_sites = mpw_get_json_section( json_file, "sites" ); + json_object *json_sites = mpw_get_json_object( json_file, "sites", false ); json_object_object_foreachC( json_sites, json_site ) { const char *siteName = json_site.key; value = mpw_get_json_int( json_site.val, "algorithm", (int32_t)user->algorithm ); @@ -951,7 +1107,7 @@ static MPMarshalledFile *mpw_marshal_read_json( return NULL; } - json_object *json_site_mpw = mpw_get_json_section( json_site.val, "_ext_mpw" ); + json_object *json_site_mpw = mpw_get_json_object( json_site.val, "_ext_mpw", false ); const char *siteURL = mpw_get_json_string( json_site_mpw, "url", NULL ); MPMarshalledSite *site = mpw_marshal_site( user, siteName, siteType, siteCounter, siteAlgorithm ); @@ -993,7 +1149,7 @@ static MPMarshalledFile *mpw_marshal_read_json( site->loginState = mpw_strdup( siteLoginState ); } - json_object *json_site_questions = mpw_get_json_section( json_site.val, "questions" ); + json_object *json_site_questions = mpw_get_json_object( json_site.val, "questions", false ); if (json_site_questions) { json_object_iter json_site_question; json_object_object_foreachC( json_site_questions, json_site_question ) { @@ -1016,9 +1172,15 @@ static MPMarshalledFile *mpw_marshal_read_json( } } mpw_free( &masterKey, MPMasterKeySize ); + + MPMarshalledData *data = malloc( sizeof( MPMarshalledData ) ); + if (data) { + *data = (MPMarshalledData){}; + mpw_set_json_data( data, json_file ); + } json_object_put( json_file ); - MPMarshalledFile *file = mpw_marshal_file( user, NULL ); + MPMarshalledFile *file = mpw_marshal_file( user, data ); if (!file) { *error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't allocate a new marshal file." }; mpw_marshal_user_free( &user ); @@ -1064,6 +1226,9 @@ MPMarshalledFile *mpw_marshal_read( const char *in, MPMasterKeyProvider masterKeyProvider, MPMarshalError *error) { MPMarshalInfo *info = mpw_marshal_read_info( in ); + if (!info) + return NULL; + MPMarshalledFile *file = NULL; switch (info->format) { case MPMarshalFormatNone: @@ -1081,8 +1246,10 @@ MPMarshalledFile *mpw_marshal_read( *error = (MPMarshalError){ MPMarshalErrorFormat, mpw_str( "Unsupported input format: %u", info->format ) }; break; } - if (file) + if (file) { + mpw_marshal_info_free( &(file->info) ); file->info = info; + } return file; } @@ -1093,11 +1260,11 @@ const MPMarshalFormat mpw_format_named( if (!formatName || !strlen( formatName )) return MPMarshalFormatNone; - if (mpw_strncasecmp( mpw_format_name( MPMarshalFormatNone ), formatName, strlen( formatName ) ) == 0) + if (mpw_strncasecmp( mpw_format_name( MPMarshalFormatNone ), formatName, strlen( formatName ) ) == OK) return MPMarshalFormatNone; - if (mpw_strncasecmp( mpw_format_name( MPMarshalFormatFlat ), formatName, strlen( formatName ) ) == 0) + if (mpw_strncasecmp( mpw_format_name( MPMarshalFormatFlat ), formatName, strlen( formatName ) ) == OK) return MPMarshalFormatFlat; - if (mpw_strncasecmp( mpw_format_name( MPMarshalFormatJSON ), formatName, strlen( formatName ) ) == 0) + if (mpw_strncasecmp( mpw_format_name( MPMarshalFormatJSON ), formatName, strlen( formatName ) ) == OK) return MPMarshalFormatJSON; dbg( "Not a format name: %s", formatName ); diff --git a/platform-independent/c/core/src/mpw-marshal.h b/platform-independent/c/core/src/mpw-marshal.h index 794edaaf..4c4d40d1 100644 --- a/platform-independent/c/core/src/mpw-marshal.h +++ b/platform-independent/c/core/src/mpw-marshal.h @@ -126,18 +126,20 @@ typedef struct MPMarshalledData { // If data is held in a parent object. const char *key; // If data is held in a parent array. - unsigned int index; + size_t index; + // If data is a null. + bool is_null; + // If data is a boolean. + bool is_bool; // If data is a string. const char *str_value; - // If data is a boolean. - bool bool_value; // If data is a number. double num_value; // If data is an object or array. - struct MPMarshalledData **obj_children; - size_t obj_children_count; + size_t children_count; + struct MPMarshalledData *children; } MPMarshalledData; typedef struct MPMarshalledFile { @@ -151,7 +153,7 @@ typedef struct MPMarshalledFile { /** Write the user and all associated data out using the given marshalling format. * @return A string (allocated), or NULL if the format is unrecognized, does not support marshalling or a format error occurred. */ const char *mpw_marshal_write( - const MPMarshalFormat outFormat, const MPMarshalledFile *file, MPMarshalError *error); + const MPMarshalFormat outFormat, MPMarshalledFile *file, MPMarshalError *error); /** Best effort parse of metadata on the sites in the input buffer. Fields that could not be parsed remain at their type's initial value. * @return A metadata object (allocated); NULL if the object could not be allocated or the format was not understood. */ MPMarshalInfo *mpw_marshal_read_info( diff --git a/platform-independent/c/core/src/mpw-types.c b/platform-independent/c/core/src/mpw-types.c index c2247e7f..006d9491 100644 --- a/platform-independent/c/core/src/mpw-types.c +++ b/platform-independent/c/core/src/mpw-types.c @@ -63,27 +63,27 @@ const MPResultType mpw_type_named(const char *typeName) { } // Find what password type is represented by the type name. - if (mpw_strncasecmp( mpw_type_short_name( MPResultTypeTemplateMaximum ), typeName, strlen( typeName ) ) == 0) + if (mpw_strncasecmp( mpw_type_short_name( MPResultTypeTemplateMaximum ), typeName, strlen( typeName ) ) == OK) return MPResultTypeTemplateMaximum; - if (mpw_strncasecmp( mpw_type_short_name( MPResultTypeTemplateLong ), typeName, strlen( typeName ) ) == 0) + if (mpw_strncasecmp( mpw_type_short_name( MPResultTypeTemplateLong ), typeName, strlen( typeName ) ) == OK) return MPResultTypeTemplateLong; - if (mpw_strncasecmp( mpw_type_short_name( MPResultTypeTemplateMedium ), typeName, strlen( typeName ) ) == 0) + if (mpw_strncasecmp( mpw_type_short_name( MPResultTypeTemplateMedium ), typeName, strlen( typeName ) ) == OK) return MPResultTypeTemplateMedium; - if (mpw_strncasecmp( mpw_type_short_name( MPResultTypeTemplateBasic ), typeName, strlen( typeName ) ) == 0) + if (mpw_strncasecmp( mpw_type_short_name( MPResultTypeTemplateBasic ), typeName, strlen( typeName ) ) == OK) return MPResultTypeTemplateBasic; - if (mpw_strncasecmp( mpw_type_short_name( MPResultTypeTemplateShort ), typeName, strlen( typeName ) ) == 0) + if (mpw_strncasecmp( mpw_type_short_name( MPResultTypeTemplateShort ), typeName, strlen( typeName ) ) == OK) return MPResultTypeTemplateShort; - if (mpw_strncasecmp( mpw_type_short_name( MPResultTypeTemplatePIN ), typeName, strlen( typeName ) ) == 0) + if (mpw_strncasecmp( mpw_type_short_name( MPResultTypeTemplatePIN ), typeName, strlen( typeName ) ) == OK) return MPResultTypeTemplatePIN; - if (mpw_strncasecmp( mpw_type_short_name( MPResultTypeTemplateName ), typeName, strlen( typeName ) ) == 0) + if (mpw_strncasecmp( mpw_type_short_name( MPResultTypeTemplateName ), typeName, strlen( typeName ) ) == OK) return MPResultTypeTemplateName; - if (mpw_strncasecmp( mpw_type_short_name( MPResultTypeTemplatePhrase ), typeName, strlen( typeName ) ) == 0) + if (mpw_strncasecmp( mpw_type_short_name( MPResultTypeTemplatePhrase ), typeName, strlen( typeName ) ) == OK) return MPResultTypeTemplatePhrase; - if (mpw_strncasecmp( mpw_type_short_name( MPResultTypeStatefulPersonal ), typeName, strlen( typeName ) ) == 0) + if (mpw_strncasecmp( mpw_type_short_name( MPResultTypeStatefulPersonal ), typeName, strlen( typeName ) ) == OK) return MPResultTypeStatefulPersonal; - if (mpw_strncasecmp( mpw_type_short_name( MPResultTypeStatefulDevice ), typeName, strlen( typeName ) ) == 0) + if (mpw_strncasecmp( mpw_type_short_name( MPResultTypeStatefulDevice ), typeName, strlen( typeName ) ) == OK) return MPResultTypeStatefulDevice; - if (mpw_strncasecmp( mpw_type_short_name( MPResultTypeDeriveKey ), typeName, strlen( typeName ) ) == 0) + if (mpw_strncasecmp( mpw_type_short_name( MPResultTypeDeriveKey ), typeName, strlen( typeName ) ) == OK) return MPResultTypeDeriveKey; dbg( "Not a generated type name: %s", typeName ); @@ -243,11 +243,11 @@ const char *mpw_type_template(MPResultType type, uint8_t templateIndex) { const MPKeyPurpose mpw_purpose_named(const char *purposeName) { - if (mpw_strncasecmp( mpw_purpose_name( MPKeyPurposeAuthentication ), purposeName, strlen( purposeName ) ) == 0) + if (mpw_strncasecmp( mpw_purpose_name( MPKeyPurposeAuthentication ), purposeName, strlen( purposeName ) ) == OK) return MPKeyPurposeAuthentication; - if (mpw_strncasecmp( mpw_purpose_name( MPKeyPurposeIdentification ), purposeName, strlen( purposeName ) ) == 0) + if (mpw_strncasecmp( mpw_purpose_name( MPKeyPurposeIdentification ), purposeName, strlen( purposeName ) ) == OK) return MPKeyPurposeIdentification; - if (mpw_strncasecmp( mpw_purpose_name( MPKeyPurposeRecovery ), purposeName, strlen( purposeName ) ) == 0) + if (mpw_strncasecmp( mpw_purpose_name( MPKeyPurposeRecovery ), purposeName, strlen( purposeName ) ) == OK) return MPKeyPurposeRecovery; dbg( "Not a purpose name: %s", purposeName );