blob: a4322840b5d1e2196080be820bf65fd26ea326a5 [file] [log] [blame]
/*
* AWS IoT Common V1.0.0
* Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* 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.
*/
/**
* @file aws_iot_doc_parser.c
* @brief Implements the functions in aws_iot_doc_parser.h
*/
/* The config header is always included first. */
#include "iot_config.h"
/* Standard includes. */
#include <string.h>
/* JSON utilities include. */
#include "aws_iot_doc_parser.h"
#define IS_QUOTE( str, idx ) \
( ( str )[ ( idx ) ] == '"' && ( ( idx ) == 0 || ( str )[ ( idx ) - 1 ] != '\\' ) )
#define IS_WHITESPACE( str, idx ) \
( ( str )[ ( idx ) ] == ' ' || ( str )[ ( idx ) ] == '\n' || ( str )[ ( idx ) ] == '\r' || ( str )[ ( idx ) ] == '\t' )
/*-----------------------------------------------------------*/
bool AwsIotDocParser_FindValue( const char * pAwsIotJsonDocument,
size_t awsIotJsonDocumentLength,
const char * pAwsIotJsonKey,
size_t awsIotJsonKeyLength,
const char ** pAwsIotJsonValue,
size_t * pAwsIotJsonValueLength )
{
size_t i = 0;
size_t jsonValueLength = 0;
char openCharacter = '\0', closeCharacter = '\0';
int nestingLevel = 0;
bool isWithinQuotes = false;
/* Validate all the arguments.*/
if( ( pAwsIotJsonDocument == NULL ) || ( pAwsIotJsonKey == NULL ) ||
( awsIotJsonDocumentLength == 0 ) || ( awsIotJsonKeyLength == 0 ) )
{
return false;
}
/* Ensure the JSON document is long enough to contain the key/value pair. At
* the very least, a JSON key/value pair must contain the key and the 6
* characters {":""} */
if( awsIotJsonDocumentLength < awsIotJsonKeyLength + 6 )
{
return false;
}
/* Search the characters in the JSON document for the key. The end of the JSON
* document does not have to be searched once too few characters remain to hold a
* value. */
while( i < awsIotJsonDocumentLength - awsIotJsonKeyLength - 3 )
{
/* If the first character in the key is found and there's an unescaped double
* quote after the key length, do a string compare for the key. */
if( ( IS_QUOTE( pAwsIotJsonDocument, i ) ) &&
( IS_QUOTE( pAwsIotJsonDocument, i + 1 + awsIotJsonKeyLength ) ) &&
( pAwsIotJsonDocument[ i + 1 ] == pAwsIotJsonKey[ 0 ] ) &&
( strncmp( pAwsIotJsonDocument + i + 1,
pAwsIotJsonKey,
awsIotJsonKeyLength ) == 0 ) )
{
/* Key found; this is a potential match. */
/* Skip the characters in the JSON key and closing double quote. */
/* While loop guarantees that i < awsIotJsonDocumentLength - 1 */
i += awsIotJsonKeyLength + 2;
/* Skip all whitespace characters between the closing " and the : */
while( IS_WHITESPACE( pAwsIotJsonDocument, i ) )
{
i++;
/* If the end of the document is reached, this isn't a match. */
if( i >= awsIotJsonDocumentLength )
{
return false;
}
}
/* The character immediately following a key (and any whitespace) must be a :
* If it's another character, then this string is a JSON value; skip it. */
if( pAwsIotJsonDocument[ i ] != ':' )
{
continue;
}
else
{
/* Skip the : */
i++;
}
/* If the end of the document is reached, this isn't a match. */
if( i >= awsIotJsonDocumentLength )
{
return false;
}
/* Skip all whitespace characters between : and the first character in the value. */
while( IS_WHITESPACE( pAwsIotJsonDocument, i ) )
{
i++;
/* If the end of the document is reached, this isn't a match. */
if( i >= awsIotJsonDocumentLength )
{
return false;
}
}
/* Value found. Set the output parameter. */
if( pAwsIotJsonValue != NULL )
{
*pAwsIotJsonValue = pAwsIotJsonDocument + i;
}
/* Calculate the value's length. */
switch( pAwsIotJsonDocument[ i ] )
{
/* Calculate length of a JSON string. */
case '\"':
/* Include the length of the opening and closing double quotes. */
jsonValueLength = 2;
/* Skip the opening double quote. */
i++;
/* If the end of the document is reached, this isn't a match. */
if( i >= awsIotJsonDocumentLength )
{
return false;
}
/* Add the length of all characters in the JSON string. */
while( pAwsIotJsonDocument[ i ] != '\"' )
{
/* Ignore escaped double quotes. */
if( ( pAwsIotJsonDocument[ i ] == '\\' ) &&
( i + 1 < awsIotJsonDocumentLength ) &&
( pAwsIotJsonDocument[ i + 1 ] == '\"' ) )
{
/* Skip the characters \" */
i += 2;
jsonValueLength += 2;
}
else
{
/* Add the length of a single character. */
i++;
jsonValueLength++;
}
/* If the end of the document is reached, this isn't a match. */
if( i >= awsIotJsonDocumentLength )
{
return false;
}
}
break;
/* Set the matching opening and closing characters of a JSON object or array.
* The length calculation is performed below. */
case '{':
openCharacter = '{';
closeCharacter = '}';
break;
case '[':
openCharacter = '[';
closeCharacter = ']';
break;
/* Calculate the length of a JSON primitive. */
default:
/* Skip the characters in the JSON value. The JSON value ends with a , or } */
while( pAwsIotJsonDocument[ i ] != ',' &&
pAwsIotJsonDocument[ i ] != '}' )
{
/* Any whitespace before a , or } means the JSON document is invalid. */
if( IS_WHITESPACE( pAwsIotJsonDocument, i ) )
{
return false;
}
i++;
jsonValueLength++;
/* If the end of the document is reached, this isn't a match. */
if( i >= awsIotJsonDocumentLength )
{
return false;
}
}
break;
}
/* Calculate the length of a JSON object or array. */
if( ( openCharacter != '\0' ) && ( closeCharacter != '\0' ) )
{
/* Include the length of the opening and closing characters. */
jsonValueLength = 2;
/* Skip the opening character. */
i++;
/* If the end of the document is reached, this isn't a match. */
if( i >= awsIotJsonDocumentLength )
{
return false;
}
/* Add the length of all characters in the JSON object or array. This
* includes the length of nested objects. */
while( pAwsIotJsonDocument[ i ] != closeCharacter ||
( pAwsIotJsonDocument[ i ] == closeCharacter && ( nestingLevel != 0 || isWithinQuotes ) ) )
{
/* Check if its a quote so as to avoid considering the
* nested levels for opening and closing characters within
* quotes.
*/
if( IS_QUOTE( pAwsIotJsonDocument, i ) )
{
/*Toggle the flag*/
isWithinQuotes = !isWithinQuotes;
}
/* Calculate the nesting levels only if not in quotes. */
if( !isWithinQuotes )
{
/* An opening character starts a nested object. */
if( pAwsIotJsonDocument[ i ] == openCharacter )
{
nestingLevel++;
}
/* A closing character ends a nested object. */
else if( pAwsIotJsonDocument[ i ] == closeCharacter )
{
nestingLevel--;
}
}
i++;
jsonValueLength++;
/* If the end of the document is reached, this isn't a match. */
if( i >= awsIotJsonDocumentLength )
{
return false;
}
}
}
/* JSON value length calculated; set the output parameter. */
if( pAwsIotJsonValueLength != NULL )
{
*pAwsIotJsonValueLength = jsonValueLength;
}
return true;
}
i++;
}
return false;
}
/*-----------------------------------------------------------*/