| /** |
| * Copyright(c) 2011 Trusted Logic. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name Trusted Logic nor the names of its |
| * contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| #include "lib_manifest2.h" |
| #include <string.h> |
| |
| #define CHAR_CR (uint8_t)0x0D |
| #define CHAR_LF (uint8_t)0x0A |
| #define CHAR_TAB (uint8_t)0x09 |
| |
| #ifdef LIB_TOOL_IMPLEMENTATION |
| #include "exos_trace.h" |
| #define LOG_ERROR(pContext, msg, ...) log_error("%s - line %d: " msg, pContext->pManifestName, pContext->nLine, __VA_ARGS__) |
| static void log_error(const char* msg, ...) |
| { |
| va_list arg_list; |
| va_start(arg_list, msg); |
| exosTraceVPrintf("LIB_MANIFEST2", EXOS_TRACE_ORG_APPLI, K_PRINT_ERROR_LOG, msg, &arg_list); |
| va_end(arg_list); |
| } |
| #else |
| /* No error messages on the target */ |
| #ifdef __SYMBIAN32__ |
| #define LOG_ERROR(pContext...) |
| #else |
| #define LOG_ERROR(...) |
| #endif |
| #endif |
| |
| void libManifest2InitContext( |
| LIB_MANIFEST2_CONTEXT* pContext) |
| { |
| pContext->nOffset = 0; |
| pContext->nLine = 1; |
| pContext->nSectionStartOffset = 0; |
| } |
| |
| |
| #define CHARACTER_NAME_FIRST 1 |
| #define CHARACTER_NAME_SUBSEQUENT 2 |
| #define CHARACTER_SECTION_NAME 3 |
| |
| static bool static_checkCharacter(uint8_t x, uint32_t nType) |
| { |
| /* [A-Za-z0-9] is acceptable for everyone */ |
| if (x >= (uint8_t)'a' && x <= (uint8_t)'z') |
| { |
| return true; |
| } |
| if (x >=(uint8_t)'A' && x <= (uint8_t)'Z') |
| { |
| return true; |
| } |
| if (x >= (uint8_t)'0' && x <= (uint8_t)'9') |
| { |
| return true; |
| } |
| if (nType == CHARACTER_NAME_FIRST) |
| { |
| return false; |
| } |
| /* Subsequent property name or section name characters can be [_.-] */ |
| if (x == (uint8_t)'_' || x == (uint8_t)'.' || x == (uint8_t)'-') |
| { |
| return true; |
| } |
| if (nType == CHARACTER_NAME_SUBSEQUENT) |
| { |
| return false; |
| } |
| /* Space is also allowed in section names */ |
| if (x == (uint8_t)' ') |
| { |
| return true; |
| } |
| return false; |
| } |
| |
| static bool static_sectionNameEqualCaseInsensitive( |
| uint8_t* pName1, |
| uint32_t nName1Length, |
| uint8_t* pName2, |
| uint32_t nName2Length) |
| { |
| uint32_t i; |
| if (nName1Length != nName2Length) |
| { |
| return false; |
| } |
| for (i = 0; i < nName1Length; i++) |
| { |
| uint8_t x1 = pName1[i]; |
| uint8_t x2 = pName2[i]; |
| |
| /* This code assumes the characters have been checked before */ |
| |
| if ((x1 & ~0x20) != (x2 & ~0x20)) |
| { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| static S_RESULT static_libManifest2GetNextItemInternal( |
| LIB_MANIFEST2_CONTEXT* pContext, |
| OUT uint8_t** ppName, |
| OUT uint32_t* pNameLength, |
| OUT uint8_t** ppValue, |
| OUT uint32_t* pValueLength) |
| { |
| S_RESULT nResult = S_ERROR_BAD_FORMAT; |
| uint8_t* pCurrent = pContext->pManifestContent + pContext->nOffset; |
| uint8_t* pEnd = pContext->pManifestContent + pContext->nManifestLength; |
| uint8_t* pLastNonWhitespaceChar; |
| uint32_t nCurrentSequenceCount; |
| uint32_t nCurrentChar; |
| |
| if (pContext->nType != LIB_MANIFEST2_TYPE_COMPILED) |
| { |
| /* Skip leading BOM if we're at the start */ |
| if (pCurrent == pContext->pManifestContent) |
| { |
| /* We're at the start. Skip leading BOM if present */ |
| /* Note that the UTF-8 encoding of the BOM marker is EF BB BF */ |
| if (pContext->nManifestLength >= 3 |
| && pCurrent[0] == 0xEF |
| && pCurrent[1] == 0xBB |
| && pCurrent[2] == 0xBF) |
| { |
| pCurrent += 3; |
| } |
| } |
| /* Skip comments and newlines */ |
| while (pCurrent < pEnd) |
| { |
| if (*pCurrent == (uint8_t)'#') |
| { |
| /* This is the start of a comment. Skip until end of line or end of file */ |
| pCurrent++; |
| while (pCurrent < pEnd && *pCurrent != CHAR_LF && *pCurrent != CHAR_CR) |
| { |
| if (*pCurrent == 0) |
| { |
| LOG_ERROR(pContext, "NUL character forbidden"); |
| goto error; |
| } |
| pCurrent++; |
| } |
| } |
| else if (*pCurrent == CHAR_CR) |
| { |
| /* Check if a LF follows */ |
| pCurrent++; |
| if (pCurrent < pEnd && *pCurrent == CHAR_LF) |
| { |
| pCurrent++; |
| } |
| pContext->nLine++; |
| } |
| else if (*pCurrent == CHAR_LF) |
| { |
| pCurrent++; |
| pContext->nLine++; |
| } |
| else if (*pCurrent == ' ' || *pCurrent == '\t') |
| { |
| /* this is the start of a all-whitespace line */ |
| /* NOTE: this is not allowed by the current spec: spec update needed */ |
| pCurrent++; |
| while (pCurrent < pEnd) |
| { |
| if (*pCurrent == CHAR_LF || *pCurrent == CHAR_CR) |
| { |
| /* End-of-line reached */ |
| break; |
| } |
| if (! (*pCurrent == ' ' || *pCurrent == '\t')) |
| { |
| LOG_ERROR(pContext, "A line starting with whitespaces must contain only whitespaces. Illegal character: 0x%02X", *pCurrent); |
| goto error; |
| } |
| pCurrent++; |
| } |
| } |
| else |
| { |
| break; |
| } |
| } |
| } |
| |
| if (pCurrent >= pEnd) |
| { |
| /* No more properties */ |
| nResult = S_ERROR_ITEM_NOT_FOUND; |
| goto error; |
| } |
| |
| if (pContext->nType == LIB_MANIFEST2_TYPE_SOURCE_WITH_SECTIONS) |
| { |
| if (*pCurrent == '[') |
| { |
| /* This is a section descriptor */ |
| pCurrent++; |
| *ppName = pCurrent; |
| *ppValue = NULL; |
| *pValueLength = 0; |
| while (true) |
| { |
| if (pCurrent >= pEnd) |
| { |
| LOG_ERROR(pContext, "EOF reached within a section name"); |
| goto error; |
| } |
| if (*pCurrent == ']') |
| { |
| /* End of section name */ |
| *pNameLength = pCurrent - *ppName; |
| pCurrent++; |
| |
| /* Skip spaces and tabs. Note that this is a deviation from the current spec |
| (see SWIS). Spec must be updated */ |
| while (pCurrent < pEnd) |
| { |
| if (*pCurrent == ' ' || *pCurrent == '\t') |
| { |
| pCurrent++; |
| } |
| else if (*pCurrent == CHAR_CR || *pCurrent == CHAR_LF) |
| { |
| /* End of line */ |
| break; |
| } |
| else |
| { |
| LOG_ERROR(pContext, "Non-space character follows a sectino header: 0x02X", *pCurrent); |
| } |
| } |
| pContext->nOffset = pCurrent - pContext->pManifestContent; |
| pContext->nSectionStartOffset = pContext->nOffset; |
| return S_SUCCESS; |
| } |
| /* Check section name character */ |
| if (!static_checkCharacter(*pCurrent, CHARACTER_SECTION_NAME)) |
| { |
| LOG_ERROR(pContext, "Invalid character for a section name: 0x%02X", *pCurrent); |
| goto error; |
| } |
| pCurrent++; |
| } |
| } |
| |
| if (pContext->nSectionStartOffset == 0) |
| { |
| /* No section has been found yet. This is a bad format */ |
| LOG_ERROR(pContext, "Property found outside any section"); |
| goto error; |
| } |
| } |
| |
| *ppName = pCurrent; |
| |
| /* Check first character of name is in [A-Za-z0-9] */ |
| if (!static_checkCharacter(*pCurrent, CHARACTER_NAME_FIRST)) |
| { |
| LOG_ERROR(pContext, "Invalid first character for a property name: 0x%02X", *pCurrent); |
| goto error; |
| } |
| pCurrent++; |
| pLastNonWhitespaceChar = pCurrent; |
| while (true) |
| { |
| if (pCurrent == pEnd) |
| { |
| LOG_ERROR(pContext, "EOF reached within a property name"); |
| goto error; |
| } |
| if (*pCurrent == ':') |
| { |
| /* Colon reached */ |
| break; |
| } |
| if (pContext->nType != LIB_MANIFEST2_TYPE_COMPILED) |
| { |
| /* In source manifest, allow space characters before the colon. |
| This is a deviation from the spec. Spec must be updated */ |
| if (*pCurrent == ' ' || *pCurrent == '\t') |
| { |
| pCurrent++; |
| continue; |
| } |
| } |
| if (!static_checkCharacter(*pCurrent, CHARACTER_NAME_SUBSEQUENT)) |
| { |
| LOG_ERROR(pContext, "Invalid character for a property name: 0x%02X", *pCurrent); |
| goto error; |
| } |
| if (pContext->nType != LIB_MANIFEST2_TYPE_COMPILED) |
| { |
| /* Even in a source manifest, property name cannot contain spaces! */ |
| if (pCurrent != pLastNonWhitespaceChar) |
| { |
| LOG_ERROR(pContext, "Property name cannot contain spaces"); |
| goto error; |
| } |
| } |
| pCurrent++; |
| pLastNonWhitespaceChar = pCurrent; |
| } |
| *pNameLength = pLastNonWhitespaceChar - *ppName; |
| pCurrent++; |
| /* Skip spaces and tabs on the right of the colon */ |
| while (pCurrent < pEnd && (*pCurrent == ' ' || *pCurrent == '\t')) |
| { |
| pCurrent++; |
| } |
| *ppValue = pCurrent; |
| pLastNonWhitespaceChar = pCurrent-1; |
| |
| nCurrentSequenceCount = 0; |
| nCurrentChar = 0; |
| |
| while (pCurrent < pEnd) |
| { |
| uint32_t x; |
| x = *pCurrent; |
| if ((x & 0x80) == 0) |
| { |
| if (nCurrentSequenceCount != 0) |
| { |
| /* We were expecting a 10xxxxxx byte: ill-formed UTF-8 */ |
| LOG_ERROR(pContext, "Invalid UTF-8 sequence"); |
| goto error; |
| } |
| else if (x == 0) |
| { |
| /* The null character is forbidden */ |
| LOG_ERROR(pContext, "NUL character forbidden"); |
| goto error; |
| } |
| /* We have a well-formed Unicode character */ |
| nCurrentChar = x; |
| } |
| else if ((x & 0xC0) == 0xC0) |
| { |
| /* Start of a sequence */ |
| if (nCurrentSequenceCount != 0) |
| { |
| /* We were expecting a 10xxxxxx byte: ill-formed UTF-8 */ |
| LOG_ERROR(pContext, "Invalid UTF-8 sequence"); |
| goto error; |
| } |
| else if ((x & 0xE0) == 0xC0) |
| { |
| /* 1 byte follows */ |
| nCurrentChar = x & 0x1F; |
| nCurrentSequenceCount = 1; |
| if ((x & 0x1E) == 0) |
| { |
| /* Illegal UTF-8: overlong encoding of character in the [0x00-0x7F] range |
| (must use 1-byte encoding, not a 2-byte encoding) */ |
| LOG_ERROR(pContext, "Invalid UTF-8 sequence"); |
| goto error; |
| } |
| } |
| else if ((x & 0xF0) == 0xE0) |
| { |
| /* 2 bytes follow */ |
| nCurrentChar = x & 0x0F; |
| nCurrentSequenceCount = 2; |
| } |
| else if ((x & 0xF8) == 0xF0) |
| { |
| /* 3 bytes follow */ |
| nCurrentChar = x & 0x07; |
| nCurrentSequenceCount = 3; |
| } |
| else |
| { |
| /* Illegal start of sequence */ |
| LOG_ERROR(pContext, "Invalid UTF-8 sequence"); |
| goto error; |
| } |
| } |
| else if ((x & 0xC0) == 0x80) |
| { |
| /* Continuation byte */ |
| if (nCurrentSequenceCount == 0) |
| { |
| /* We were expecting a sequence start, not a continuation byte */ |
| LOG_ERROR(pContext, "Invalid UTF-8 sequence"); |
| goto error; |
| } |
| else |
| { |
| if (nCurrentSequenceCount == 2) |
| { |
| /* We're in a 3-byte sequence, check that we're not using an overlong sequence */ |
| if (nCurrentChar == 0 && (x & 0x20) == 0) |
| { |
| /* The character starts with at least 5 zero bits, so has fewer than 11 bits. It should |
| have used a 2-byte sequence, not a 3-byte sequence */ |
| LOG_ERROR(pContext, "Invalid UTF-8 sequence"); |
| goto error; |
| } |
| } |
| else if (nCurrentSequenceCount == 3) |
| { |
| if (nCurrentChar == 0 && (x & 0x30) == 0) |
| { |
| /* The character starts with at least 5 zero bits, so has fewer than 16 bits. It should |
| have used a 3-byte sequence, not a 4-byte sequence */ |
| LOG_ERROR(pContext, "Invalid UTF-8 sequence"); |
| goto error; |
| } |
| } |
| nCurrentSequenceCount--; |
| nCurrentChar = (nCurrentChar << 6) | (x & 0x3F); |
| } |
| } |
| else |
| { |
| /* Illegal byte */ |
| LOG_ERROR(pContext, "Invalid UTF-8 sequence"); |
| goto error; |
| } |
| if (nCurrentSequenceCount == 0) |
| { |
| /* nCurrentChar contains the current Unicode character */ |
| /* check character */ |
| if ((nCurrentChar >= 0xD800 && nCurrentChar < 0xE000) || nCurrentChar >= 0x110000) |
| { |
| /* Illegal code point */ |
| LOG_ERROR(pContext, "Invalid UTF-8 code point 0x%X", nCurrentChar); |
| goto error; |
| } |
| |
| if (*pCurrent == CHAR_CR) |
| { |
| if (pContext->nType == LIB_MANIFEST2_TYPE_COMPILED) |
| { |
| /* Check if a LF follows */ |
| pCurrent++; |
| if (pCurrent < pEnd && *pCurrent == CHAR_LF) |
| { |
| pCurrent++; |
| } |
| pContext->nLine++; |
| } |
| goto end; |
| } |
| else if (*pCurrent == CHAR_LF) |
| { |
| if (pContext->nType == LIB_MANIFEST2_TYPE_COMPILED) |
| { |
| pCurrent++; |
| pContext->nLine++; |
| } |
| goto end; |
| } |
| } |
| if (*pCurrent != ' ' && *pCurrent != CHAR_TAB) |
| { |
| /* It's a non-whitespace char */ |
| pLastNonWhitespaceChar = pCurrent; |
| } |
| pCurrent++; |
| } |
| |
| /* Hit the end of the manifest; Check that we're not in the middle of a sequence */ |
| if (nCurrentSequenceCount != 0) |
| { |
| LOG_ERROR(pContext, "File ends in the middle of an UTF-8 sequence"); |
| goto error; |
| } |
| |
| end: |
| |
| *pValueLength = pLastNonWhitespaceChar - *ppValue + 1; |
| pContext->nOffset = pCurrent - pContext->pManifestContent; |
| |
| return S_SUCCESS; |
| |
| error: |
| *ppName = NULL; |
| *pNameLength = 0; |
| *ppValue = NULL; |
| *pValueLength = 0; |
| return nResult; |
| } |
| |
| S_RESULT libManifest2GetNextItem( |
| LIB_MANIFEST2_CONTEXT* pContext, |
| OUT uint8_t** ppName, |
| OUT uint32_t* pNameLength, |
| OUT uint8_t** ppValue, |
| OUT uint32_t* pValueLength) |
| { |
| if (pContext->nType == LIB_MANIFEST2_TYPE_COMPILED) |
| { |
| /* Don't check for duplicates in binary manifests */ |
| return static_libManifest2GetNextItemInternal( |
| pContext, |
| ppName, |
| pNameLength, |
| ppValue, |
| pValueLength); |
| } |
| else |
| { |
| uint32_t nOriginalOffset = pContext->nOffset; |
| uint32_t nOffset; |
| uint32_t nLine; |
| uint32_t nSectionStartOffset; |
| S_RESULT nResult; |
| uint8_t* pDupName; |
| uint32_t nDupNameLength; |
| uint8_t* pDupValue; |
| uint32_t nDupValueLength; |
| |
| /* First get the item */ |
| nResult = static_libManifest2GetNextItemInternal( |
| pContext, |
| ppName, |
| pNameLength, |
| ppValue, |
| pValueLength); |
| if (nResult != S_SUCCESS) |
| { |
| return nResult; |
| } |
| /* Save the state of the parser */ |
| nOffset = pContext->nOffset; |
| nLine = pContext->nLine; |
| nSectionStartOffset = pContext->nSectionStartOffset; |
| if (pContext->nType == LIB_MANIFEST2_TYPE_SOURCE) |
| { |
| pContext->nOffset = 0; |
| } |
| else if (*ppValue == NULL) |
| { |
| /* The item was a section header. Iterate on all section headers and |
| check for duplicates */ |
| pContext->nOffset = 0; |
| } |
| else |
| { |
| if (nSectionStartOffset == 0) |
| { |
| LOG_ERROR(pContext, "Property definition outside any section"); |
| goto bad_format; |
| } |
| /* Iterate only on the properties in the section */ |
| pContext->nOffset = nSectionStartOffset; |
| } |
| while (pContext->nOffset < nOriginalOffset) |
| { |
| static_libManifest2GetNextItemInternal( |
| pContext, |
| &pDupName, |
| &nDupNameLength, |
| &pDupValue, |
| &nDupValueLength); |
| if (pContext->nType == LIB_MANIFEST2_TYPE_SOURCE_WITH_SECTIONS && *ppValue == NULL) |
| { |
| /* Check for duplicate section names */ |
| if (pDupValue == NULL |
| && |
| static_sectionNameEqualCaseInsensitive( |
| *ppName, |
| *pNameLength, |
| pDupName, |
| nDupNameLength)) |
| { |
| pContext->nOffset = nOffset; |
| pContext->nLine = nLine; |
| pContext->nSectionStartOffset = nSectionStartOffset; |
| LOG_ERROR(pContext, "Duplicate section %.*s", nDupNameLength, pDupName); |
| goto bad_format; |
| } |
| } |
| else |
| { |
| /* Check for duplicate property name */ |
| if (nDupNameLength == *pNameLength && |
| memcmp(pDupName, *ppName, nDupNameLength) == 0) |
| { |
| /* Duplicated property */ |
| pContext->nOffset = nOffset; |
| pContext->nLine = nLine; |
| pContext->nSectionStartOffset = nSectionStartOffset; |
| LOG_ERROR(pContext,"Duplicate property %.*s", nDupNameLength, pDupName); |
| goto bad_format; |
| } |
| } |
| } |
| /* Everything's fine. restore context and exit */ |
| /* Restore the context */ |
| pContext->nOffset = nOffset; |
| pContext->nLine = nLine; |
| pContext->nSectionStartOffset = nSectionStartOffset; |
| |
| return S_SUCCESS; |
| bad_format: |
| *ppName = NULL; |
| *pNameLength = 0; |
| *ppValue = NULL; |
| *pValueLength = 0; |
| return S_ERROR_BAD_FORMAT; |
| } |
| } |
| |
| |
| S_RESULT libManifest2CheckFormat( |
| LIB_MANIFEST2_CONTEXT* pContext, |
| uint32_t* pnItemCount) |
| { |
| uint32_t nPropertyCount = 0; |
| uint8_t* pName; |
| uint32_t nNameLength; |
| uint8_t* pValue; |
| uint32_t nValueLength; |
| S_RESULT nResult; |
| |
| pContext->nOffset = 0; |
| pContext->nLine = 1; |
| pContext->nSectionStartOffset = 0; |
| |
| while (true) |
| { |
| nResult = libManifest2GetNextItem( |
| pContext, |
| &pName, |
| &nNameLength, |
| &pValue, |
| &nValueLength); |
| if (nResult == S_ERROR_ITEM_NOT_FOUND) |
| { |
| if (pnItemCount != NULL) |
| { |
| *pnItemCount = nPropertyCount; |
| } |
| return S_SUCCESS; |
| } |
| if (nResult != S_SUCCESS) |
| { |
| return nResult; |
| } |
| nPropertyCount++; |
| } |
| } |