Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -84,11 +84,11 @@ OFSortedList.m \ OFStdIOStream.m \ OFStream.m \ OFString.m \ OFString+CryptoHashing.m \ - OFString+JSONValue.m \ + OFString+JSONParsing.m \ OFString+PropertyListValue.m \ OFString+Serialization.m \ OFString+URLEncoding.m \ OFString+XMLEscaping.m \ OFString+XMLUnescaping.m \ Index: src/OFLocale.m ================================================================== --- src/OFLocale.m +++ src/OFLocale.m @@ -510,11 +510,12 @@ pool = objc_autoreleasePoolPush(); mapPath = [path stringByAppendingPathComponent: @"languages.json"]; @try { - map = [[OFString stringWithContentsOfFile: mapPath] JSONValue]; + map = [OFString stringWithContentsOfFile: mapPath] + .objectByParsingJSON; } @catch (OFOpenItemFailedException *e) { objc_autoreleasePoolPop(pool); return; } @@ -535,11 +536,12 @@ languageFile = [path stringByAppendingPathComponent: [languageFile stringByAppendingString: @".json"]]; [_localizedStrings addObject: - [[OFString stringWithContentsOfFile: languageFile] JSONValue]]; + [OFString stringWithContentsOfFile: languageFile] + .objectByParsingJSON]; objc_autoreleasePoolPop(pool); } #endif ADDED src/OFString+JSONParsing.h Index: src/OFString+JSONParsing.h ================================================================== --- src/OFString+JSONParsing.h +++ src/OFString+JSONParsing.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFString.h" + +OF_ASSUME_NONNULL_BEGIN + +#ifdef __cplusplus +extern "C" { +#endif +extern int _OFString_JSONParsing_reference; +#ifdef __cplusplus +} +#endif + +@interface OFString (JSONParsing) +/*! + * @brief The string interpreted as JSON and parsed as an object. + * + * @note This also allows parsing JSON5, an extension of JSON. See + * http://json5.org/ for more details. + * + * @warning Although not specified by the JSON specification, this can also + * return primitives like strings and numbers. The rationale behind + * this is that most JSON parsers allow JSON data just consisting of a + * single primitive, leading to real world JSON files sometimes only + * consisting of a single primitive. Therefore, you should not make any + * assumptions about the object returned by this method if you don't + * want your program to terminate due to a message not understood, but + * instead check the returned object using @ref isKindOfClass:. + */ +@property (readonly, nonatomic) id objectByParsingJSON; + +/*! + * @brief Creates an object from the JSON value of the string. + * + * @note This also allows parsing JSON5, an extension of JSON. See + * http://json5.org/ for more details. + * + * @warning Although not specified by the JSON specification, this can also + * return primitives like strings and numbers. The rationale behind + * this is that most JSON parsers allow JSON data just consisting of a + * single primitive, leading to real world JSON files sometimes only + * consisting of a single primitive. Therefore, you should not make any + * assumptions about the object returned by this method if you don't + * want your program to terminate due to a message not understood, but + * instead check the returned object using @ref isKindOfClass:. + * + * @param depthLimit The maximum depth the parser should accept (defaults to 32 + * if not specified, 0 means no limit (insecure!)) + * + * @return An object + */ +- (id)objectByParsingJSONWithDepthLimit: (size_t)depthLimit; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFString+JSONParsing.m Index: src/OFString+JSONParsing.m ================================================================== --- src/OFString+JSONParsing.m +++ src/OFString+JSONParsing.m @@ -0,0 +1,675 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include +#include + +#include + +#include + +#import "OFString+JSONParsing.h" +#import "OFArray.h" +#import "OFDictionary.h" +#import "OFNumber.h" +#import "OFNull.h" + +#import "OFInvalidJSONException.h" + +int _OFString_JSONParsing_reference; + +static id nextObject(const char **pointer, const char *stop, size_t *line, + size_t depthLimit); + +static void +skipWhitespaces(const char **pointer, const char *stop, size_t *line) +{ + while (*pointer < stop && (**pointer == ' ' || **pointer == '\t' || + **pointer == '\r' || **pointer == '\n')) { + if (**pointer == '\n') + (*line)++; + + (*pointer)++; + } +} + +static void +skipComment(const char **pointer, const char *stop, size_t *line) +{ + if (**pointer != '/') + return; + + if (*pointer + 1 >= stop) + return; + + (*pointer)++; + + if (**pointer == '*') { + bool lastIsAsterisk = false; + + (*pointer)++; + + while (*pointer < stop) { + if (lastIsAsterisk && **pointer == '/') { + (*pointer)++; + return; + } + + lastIsAsterisk = (**pointer == '*'); + + if (**pointer == '\n') + (*line)++; + + (*pointer)++; + } + } else if (**pointer == '/') { + (*pointer)++; + + while (*pointer < stop) { + if (**pointer == '\r' || **pointer == '\n') { + (*pointer)++; + (*line)++; + return; + } + + (*pointer)++; + } + } else + (*pointer)--; +} + +static void +skipWhitespacesAndComments(const char **pointer, const char *stop, size_t *line) +{ + const char *old = NULL; + + while (old != *pointer) { + old = *pointer; + + skipWhitespaces(pointer, stop, line); + skipComment(pointer, stop, line); + } +} + +static inline of_char16_t +parseUnicodeEscape(const char *pointer, const char *stop) +{ + of_char16_t ret = 0; + + if (pointer + 5 >= stop) + return 0xFFFF; + + if (pointer[0] != '\\' || pointer[1] != 'u') + return 0xFFFF; + + for (uint8_t i = 0; i < 4; i++) { + char c = pointer[i + 2]; + ret <<= 4; + + if (c >= '0' && c <= '9') + ret |= c - '0'; + else if (c >= 'a' && c <= 'f') + ret |= c + 10 - 'a'; + else if (c >= 'A' && c <= 'F') + ret |= c + 10 - 'A'; + else + return 0xFFFF; + } + + if (ret == 0) + return 0xFFFF; + + return ret; +} + +static inline OFString * +parseString(const char **pointer, const char *stop, size_t *line) +{ + char *buffer; + size_t i = 0; + char delimiter = **pointer; + + if (++(*pointer) + 1 >= stop) + return nil; + + if ((buffer = malloc(stop - *pointer)) == NULL) + return nil; + + while (*pointer < stop) { + /* Parse escape codes */ + if (**pointer == '\\') { + if (++(*pointer) >= stop) { + free(buffer); + return nil; + } + + switch (**pointer) { + case '"': + case '\\': + case '/': + buffer[i++] = **pointer; + (*pointer)++; + break; + case 'b': + buffer[i++] = '\b'; + (*pointer)++; + break; + case 'f': + buffer[i++] = '\f'; + (*pointer)++; + break; + case 'n': + buffer[i++] = '\n'; + (*pointer)++; + break; + case 'r': + buffer[i++] = '\r'; + (*pointer)++; + break; + case 't': + buffer[i++] = '\t'; + (*pointer)++; + break; + /* Parse Unicode escape sequence */ + case 'u':; + of_char16_t c1, c2; + of_unichar_t c; + size_t l; + + c1 = parseUnicodeEscape(*pointer - 1, stop); + if (c1 == 0xFFFF) { + free(buffer); + return nil; + } + + /* Low surrogate */ + if ((c1 & 0xFC00) == 0xDC00) { + free(buffer); + return nil; + } + + /* Normal character */ + if ((c1 & 0xFC00) != 0xD800) { + l = of_string_utf8_encode(c1, + buffer + i); + if (l == 0) { + free(buffer); + return nil; + } + + i += l; + *pointer += 5; + + break; + } + + /* + * If we are still here, we only got one UTF-16 + * surrogate and now need to get the other one + * in order to produce UTF-8 and not CESU-8. + */ + c2 = parseUnicodeEscape(*pointer + 5, stop); + if (c2 == 0xFFFF) { + free(buffer); + return nil; + } + + c = (((c1 & 0x3FF) << 10) | + (c2 & 0x3FF)) + 0x10000; + + l = of_string_utf8_encode(c, buffer + i); + if (l == 0) { + free(buffer); + return nil; + } + + i += l; + *pointer += 11; + + break; + case '\r': + (*pointer)++; + + if (*pointer < stop && **pointer == '\n') { + (*pointer)++; + (*line)++; + } + + break; + case '\n': + (*pointer)++; + (*line)++; + break; + default: + free(buffer); + return nil; + } + /* End of string found */ + } else if (**pointer == delimiter) { + OFString *ret; + + @try { + ret = [OFString stringWithUTF8String: buffer + length: i]; + } @finally { + free(buffer); + } + + (*pointer)++; + + return ret; + /* Newlines in strings are disallowed */ + } else if (**pointer == '\n' || **pointer == '\r') { + (*line)++; + free(buffer); + return nil; + } else { + buffer[i++] = **pointer; + (*pointer)++; + } + } + + free(buffer); + return nil; +} + +static inline OFString * +parseIdentifier(const char **pointer, const char *stop) +{ + char *buffer; + size_t i = 0; + + if ((buffer = malloc(stop - *pointer)) == NULL) + return nil; + + while (*pointer < stop) { + if ((**pointer >= 'a' && **pointer <= 'z') || + (**pointer >= 'A' && **pointer <= 'Z') || + (**pointer >= '0' && **pointer <= '9') || + **pointer == '_' || **pointer == '$' || + (**pointer & 0x80)) { + buffer[i++] = **pointer; + (*pointer)++; + } else if (**pointer == '\\') { + of_char16_t c1, c2; + of_unichar_t c; + size_t l; + + if (++(*pointer) >= stop || **pointer != 'u') { + free(buffer); + return nil; + } + + c1 = parseUnicodeEscape(*pointer - 1, stop); + if (c1 == 0xFFFF) { + free(buffer); + return nil; + } + + /* Low surrogate */ + if ((c1 & 0xFC00) == 0xDC00) { + free(buffer); + return nil; + } + + /* Normal character */ + if ((c1 & 0xFC00) != 0xD800) { + l = of_string_utf8_encode(c1, buffer + i); + if (l == 0) { + free(buffer); + return nil; + } + + i += l; + *pointer += 5; + + continue; + } + + /* + * If we are still here, we only got one UTF-16 + * surrogate and now need to get the other one in order + * to produce UTF-8 and not CESU-8. + */ + c2 = parseUnicodeEscape(*pointer + 5, stop); + if (c2 == 0xFFFF) { + free(buffer); + return nil; + } + + c = (((c1 & 0x3FF) << 10) | (c2 & 0x3FF)) + 0x10000; + + l = of_string_utf8_encode(c, buffer + i); + if (l == 0) { + free(buffer); + return nil; + } + + i += l; + *pointer += 11; + } else { + OFString *ret; + + if (i == 0 || (buffer[0] >= '0' && buffer[0] <= '9')) { + free(buffer); + return nil; + } + + @try { + ret = [OFString stringWithUTF8String: buffer + length: i]; + } @finally { + free(buffer); + } + + return ret; + } + } + + /* + * It is never possible to end with an identifier, thus we should never + * reach stop. + */ + return nil; +} + +static inline OFMutableArray * +parseArray(const char **pointer, const char *stop, size_t *line, + size_t depthLimit) +{ + OFMutableArray *array = [OFMutableArray array]; + + if (++(*pointer) >= stop) + return nil; + + if (--depthLimit == 0) + return nil; + + while (**pointer != ']') { + id object; + + skipWhitespacesAndComments(pointer, stop, line); + if (*pointer >= stop) + return nil; + + if (**pointer == ']') + break; + + if (**pointer == ',') { + (*pointer)++; + skipWhitespacesAndComments(pointer, stop, line); + + if (*pointer >= stop || **pointer != ']') + return nil; + + break; + } + + object = nextObject(pointer, stop, line, depthLimit); + if (object == nil) + return nil; + + [array addObject: object]; + + skipWhitespacesAndComments(pointer, stop, line); + if (*pointer >= stop) + return nil; + + if (**pointer == ',') { + (*pointer)++; + skipWhitespacesAndComments(pointer, stop, line); + + if (*pointer >= stop) + return nil; + } else if (**pointer != ']') + return nil; + } + + (*pointer)++; + + return array; +} + +static inline OFMutableDictionary * +parseDictionary(const char **pointer, const char *stop, size_t *line, + size_t depthLimit) +{ + OFMutableDictionary *dictionary = [OFMutableDictionary dictionary]; + + if (++(*pointer) >= stop) + return nil; + + if (--depthLimit == 0) + return nil; + + while (**pointer != '}') { + OFString *key; + id object; + + skipWhitespacesAndComments(pointer, stop, line); + if (*pointer >= stop) + return nil; + + if (**pointer == '}') + break; + + if (**pointer == ',') { + (*pointer)++; + skipWhitespacesAndComments(pointer, stop, line); + + if (*pointer >= stop || **pointer != '}') + return nil; + + break; + } + + skipWhitespacesAndComments(pointer, stop, line); + if (*pointer + 1 >= stop) + return nil; + + if ((**pointer >= 'a' && **pointer <= 'z') || + (**pointer >= 'A' && **pointer <= 'Z') || + **pointer == '_' || **pointer == '$' || **pointer == '\\') + key = parseIdentifier(pointer, stop); + else + key = nextObject(pointer, stop, line, depthLimit); + + if (![key isKindOfClass: [OFString class]]) + return nil; + + skipWhitespacesAndComments(pointer, stop, line); + if (*pointer + 1 >= stop || **pointer != ':') + return nil; + + (*pointer)++; + + object = nextObject(pointer, stop, line, depthLimit); + if (object == nil) + return nil; + + [dictionary setObject: object + forKey: key]; + + skipWhitespacesAndComments(pointer, stop, line); + if (*pointer >= stop) + return nil; + + if (**pointer == ',') { + (*pointer)++; + skipWhitespacesAndComments(pointer, stop, line); + + if (*pointer >= stop) + return nil; + } else if (**pointer != '}') + return nil; + } + + (*pointer)++; + + return dictionary; +} + +static inline OFNumber * +parseNumber(const char **pointer, const char *stop, size_t *line) +{ + bool isNegative = (*pointer < stop && (*pointer)[0] == '-'); + bool hasDecimal = false; + size_t i; + OFString *string; + OFNumber *number; + + for (i = 0; *pointer + i < stop; i++) { + if ((*pointer)[i] == '.') + hasDecimal = true; + + if ((*pointer)[i] == ' ' || (*pointer)[i] == '\t' || + (*pointer)[i] == '\r' || (*pointer)[i] == '\n' || + (*pointer)[i] == ',' || (*pointer)[i] == ']' || + (*pointer)[i] == '}') { + if ((*pointer)[i] == '\n') + (*line)++; + + break; + } + } + + string = [[OFString alloc] initWithUTF8String: *pointer + length: i]; + *pointer += i; + + @try { + if (hasDecimal) + number = [OFNumber numberWithDouble: + string.doubleValue]; + else if ([string isEqual: @"Infinity"]) + number = [OFNumber numberWithDouble: INFINITY]; + else if ([string isEqual: @"-Infinity"]) + number = [OFNumber numberWithDouble: -INFINITY]; + else if (isNegative) + number = [OFNumber numberWithLongLong: + [string longLongValueWithBase: 0]]; + else + number = [OFNumber numberWithUnsignedLongLong: + [string unsignedLongLongValueWithBase: 0]]; + } @finally { + [string release]; + } + + return number; +} + +static id +nextObject(const char **pointer, const char *stop, size_t *line, + size_t depthLimit) +{ + skipWhitespacesAndComments(pointer, stop, line); + + if (*pointer >= stop) + return nil; + + switch (**pointer) { + case '"': + case '\'': + return parseString(pointer, stop, line); + case '[': + return parseArray(pointer, stop, line, depthLimit); + case '{': + return parseDictionary(pointer, stop, line, depthLimit); + case 't': + if (*pointer + 3 >= stop) + return nil; + + if (memcmp(*pointer, "true", 4) != 0) + return nil; + + (*pointer) += 4; + + return [OFNumber numberWithBool: true]; + case 'f': + if (*pointer + 4 >= stop) + return nil; + + if (memcmp(*pointer, "false", 5) != 0) + return nil; + + (*pointer) += 5; + + return [OFNumber numberWithBool: false]; + case 'n': + if (*pointer + 3 >= stop) + return nil; + + if (memcmp(*pointer, "null", 4) != 0) + return nil; + + (*pointer) += 4; + + return [OFNull null]; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + case '.': + case 'I': + return parseNumber(pointer, stop, line); + default: + return nil; + } +} + +@implementation OFString (JSONParsing) +- (id)objectByParsingJSON +{ + return [self objectByParsingJSONWithDepthLimit: 32]; +} + +- (id)objectByParsingJSONWithDepthLimit: (size_t)depthLimit +{ + void *pool = objc_autoreleasePoolPush(); + const char *pointer = self.UTF8String; + const char *stop = pointer + self.UTF8StringLength; + id object; + size_t line = 1; + +#ifdef __clang_analyzer__ + assert(pointer != NULL); +#endif + + object = nextObject(&pointer, stop, &line, depthLimit); + skipWhitespacesAndComments(&pointer, stop, &line); + + if (pointer < stop || object == nil) + @throw [OFInvalidJSONException exceptionWithString: self + line: line]; + + [object retain]; + + objc_autoreleasePoolPop(pool); + + return [object autorelease]; +} +@end DELETED src/OFString+JSONValue.h Index: src/OFString+JSONValue.h ================================================================== --- src/OFString+JSONValue.h +++ src/OFString+JSONValue.h @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018, 2019, 2020 - * Jonathan Schleifer - * - * All rights reserved. - * - * This file is part of ObjFW. It may be distributed under the terms of the - * Q Public License 1.0, which can be found in the file LICENSE.QPL included in - * the packaging of this file. - * - * Alternatively, it may be distributed under the terms of the GNU General - * Public License, either version 2 or 3, which can be found in the file - * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this - * file. - */ - -#import "OFString.h" - -OF_ASSUME_NONNULL_BEGIN - -#ifdef __cplusplus -extern "C" { -#endif -extern int _OFString_JSONValue_reference; -#ifdef __cplusplus -} -#endif - -@interface OFString (JSONValue) -/*! - * @brief The string interpreted as JSON and parsed as an object. - * - * @note This also allows parsing JSON5, an extension of JSON. See - * http://json5.org/ for more details. - * - * @warning Although not specified by the JSON specification, this can also - * return primitives like strings and numbers. The rationale behind - * this is that most JSON parsers allow JSON data just consisting of a - * single primitive, leading to real world JSON files sometimes only - * consisting of a single primitive. Therefore, you should not make any - * assumptions about the object returned by this method if you don't - * want your program to terminate due to a message not understood, but - * instead check the returned object using @ref isKindOfClass:. - */ -@property (readonly, nonatomic) id JSONValue; - -/*! - * @brief Creates an object from the JSON value of the string. - * - * @note This also allows parsing JSON5, an extension of JSON. See - * http://json5.org/ for more details. - * - * @warning Although not specified by the JSON specification, this can also - * return primitives like strings and numbers. The rationale behind - * this is that most JSON parsers allow JSON data just consisting of a - * single primitive, leading to real world JSON files sometimes only - * consisting of a single primitive. Therefore, you should not make any - * assumptions about the object returned by this method if you don't - * want your program to terminate due to a message not understood, but - * instead check the returned object using @ref isKindOfClass:. - * - * @param depthLimit The maximum depth the parser should accept (defaults to 32 - * if not specified, 0 means no limit (insecure!)) - * - * @return An object - */ -- (id)JSONValueWithDepthLimit: (size_t)depthLimit; -@end - -OF_ASSUME_NONNULL_END DELETED src/OFString+JSONValue.m Index: src/OFString+JSONValue.m ================================================================== --- src/OFString+JSONValue.m +++ src/OFString+JSONValue.m @@ -1,675 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018, 2019, 2020 - * Jonathan Schleifer - * - * All rights reserved. - * - * This file is part of ObjFW. It may be distributed under the terms of the - * Q Public License 1.0, which can be found in the file LICENSE.QPL included in - * the packaging of this file. - * - * Alternatively, it may be distributed under the terms of the GNU General - * Public License, either version 2 or 3, which can be found in the file - * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this - * file. - */ - -#include "config.h" - -#include -#include - -#include - -#include - -#import "OFString+JSONValue.h" -#import "OFArray.h" -#import "OFDictionary.h" -#import "OFNumber.h" -#import "OFNull.h" - -#import "OFInvalidJSONException.h" - -int _OFString_JSONValue_reference; - -static id nextObject(const char **pointer, const char *stop, size_t *line, - size_t depthLimit); - -static void -skipWhitespaces(const char **pointer, const char *stop, size_t *line) -{ - while (*pointer < stop && (**pointer == ' ' || **pointer == '\t' || - **pointer == '\r' || **pointer == '\n')) { - if (**pointer == '\n') - (*line)++; - - (*pointer)++; - } -} - -static void -skipComment(const char **pointer, const char *stop, size_t *line) -{ - if (**pointer != '/') - return; - - if (*pointer + 1 >= stop) - return; - - (*pointer)++; - - if (**pointer == '*') { - bool lastIsAsterisk = false; - - (*pointer)++; - - while (*pointer < stop) { - if (lastIsAsterisk && **pointer == '/') { - (*pointer)++; - return; - } - - lastIsAsterisk = (**pointer == '*'); - - if (**pointer == '\n') - (*line)++; - - (*pointer)++; - } - } else if (**pointer == '/') { - (*pointer)++; - - while (*pointer < stop) { - if (**pointer == '\r' || **pointer == '\n') { - (*pointer)++; - (*line)++; - return; - } - - (*pointer)++; - } - } else - (*pointer)--; -} - -static void -skipWhitespacesAndComments(const char **pointer, const char *stop, size_t *line) -{ - const char *old = NULL; - - while (old != *pointer) { - old = *pointer; - - skipWhitespaces(pointer, stop, line); - skipComment(pointer, stop, line); - } -} - -static inline of_char16_t -parseUnicodeEscape(const char *pointer, const char *stop) -{ - of_char16_t ret = 0; - - if (pointer + 5 >= stop) - return 0xFFFF; - - if (pointer[0] != '\\' || pointer[1] != 'u') - return 0xFFFF; - - for (uint8_t i = 0; i < 4; i++) { - char c = pointer[i + 2]; - ret <<= 4; - - if (c >= '0' && c <= '9') - ret |= c - '0'; - else if (c >= 'a' && c <= 'f') - ret |= c + 10 - 'a'; - else if (c >= 'A' && c <= 'F') - ret |= c + 10 - 'A'; - else - return 0xFFFF; - } - - if (ret == 0) - return 0xFFFF; - - return ret; -} - -static inline OFString * -parseString(const char **pointer, const char *stop, size_t *line) -{ - char *buffer; - size_t i = 0; - char delimiter = **pointer; - - if (++(*pointer) + 1 >= stop) - return nil; - - if ((buffer = malloc(stop - *pointer)) == NULL) - return nil; - - while (*pointer < stop) { - /* Parse escape codes */ - if (**pointer == '\\') { - if (++(*pointer) >= stop) { - free(buffer); - return nil; - } - - switch (**pointer) { - case '"': - case '\\': - case '/': - buffer[i++] = **pointer; - (*pointer)++; - break; - case 'b': - buffer[i++] = '\b'; - (*pointer)++; - break; - case 'f': - buffer[i++] = '\f'; - (*pointer)++; - break; - case 'n': - buffer[i++] = '\n'; - (*pointer)++; - break; - case 'r': - buffer[i++] = '\r'; - (*pointer)++; - break; - case 't': - buffer[i++] = '\t'; - (*pointer)++; - break; - /* Parse Unicode escape sequence */ - case 'u':; - of_char16_t c1, c2; - of_unichar_t c; - size_t l; - - c1 = parseUnicodeEscape(*pointer - 1, stop); - if (c1 == 0xFFFF) { - free(buffer); - return nil; - } - - /* Low surrogate */ - if ((c1 & 0xFC00) == 0xDC00) { - free(buffer); - return nil; - } - - /* Normal character */ - if ((c1 & 0xFC00) != 0xD800) { - l = of_string_utf8_encode(c1, - buffer + i); - if (l == 0) { - free(buffer); - return nil; - } - - i += l; - *pointer += 5; - - break; - } - - /* - * If we are still here, we only got one UTF-16 - * surrogate and now need to get the other one - * in order to produce UTF-8 and not CESU-8. - */ - c2 = parseUnicodeEscape(*pointer + 5, stop); - if (c2 == 0xFFFF) { - free(buffer); - return nil; - } - - c = (((c1 & 0x3FF) << 10) | - (c2 & 0x3FF)) + 0x10000; - - l = of_string_utf8_encode(c, buffer + i); - if (l == 0) { - free(buffer); - return nil; - } - - i += l; - *pointer += 11; - - break; - case '\r': - (*pointer)++; - - if (*pointer < stop && **pointer == '\n') { - (*pointer)++; - (*line)++; - } - - break; - case '\n': - (*pointer)++; - (*line)++; - break; - default: - free(buffer); - return nil; - } - /* End of string found */ - } else if (**pointer == delimiter) { - OFString *ret; - - @try { - ret = [OFString stringWithUTF8String: buffer - length: i]; - } @finally { - free(buffer); - } - - (*pointer)++; - - return ret; - /* Newlines in strings are disallowed */ - } else if (**pointer == '\n' || **pointer == '\r') { - (*line)++; - free(buffer); - return nil; - } else { - buffer[i++] = **pointer; - (*pointer)++; - } - } - - free(buffer); - return nil; -} - -static inline OFString * -parseIdentifier(const char **pointer, const char *stop) -{ - char *buffer; - size_t i = 0; - - if ((buffer = malloc(stop - *pointer)) == NULL) - return nil; - - while (*pointer < stop) { - if ((**pointer >= 'a' && **pointer <= 'z') || - (**pointer >= 'A' && **pointer <= 'Z') || - (**pointer >= '0' && **pointer <= '9') || - **pointer == '_' || **pointer == '$' || - (**pointer & 0x80)) { - buffer[i++] = **pointer; - (*pointer)++; - } else if (**pointer == '\\') { - of_char16_t c1, c2; - of_unichar_t c; - size_t l; - - if (++(*pointer) >= stop || **pointer != 'u') { - free(buffer); - return nil; - } - - c1 = parseUnicodeEscape(*pointer - 1, stop); - if (c1 == 0xFFFF) { - free(buffer); - return nil; - } - - /* Low surrogate */ - if ((c1 & 0xFC00) == 0xDC00) { - free(buffer); - return nil; - } - - /* Normal character */ - if ((c1 & 0xFC00) != 0xD800) { - l = of_string_utf8_encode(c1, buffer + i); - if (l == 0) { - free(buffer); - return nil; - } - - i += l; - *pointer += 5; - - continue; - } - - /* - * If we are still here, we only got one UTF-16 - * surrogate and now need to get the other one in order - * to produce UTF-8 and not CESU-8. - */ - c2 = parseUnicodeEscape(*pointer + 5, stop); - if (c2 == 0xFFFF) { - free(buffer); - return nil; - } - - c = (((c1 & 0x3FF) << 10) | (c2 & 0x3FF)) + 0x10000; - - l = of_string_utf8_encode(c, buffer + i); - if (l == 0) { - free(buffer); - return nil; - } - - i += l; - *pointer += 11; - } else { - OFString *ret; - - if (i == 0 || (buffer[0] >= '0' && buffer[0] <= '9')) { - free(buffer); - return nil; - } - - @try { - ret = [OFString stringWithUTF8String: buffer - length: i]; - } @finally { - free(buffer); - } - - return ret; - } - } - - /* - * It is never possible to end with an identifier, thus we should never - * reach stop. - */ - return nil; -} - -static inline OFMutableArray * -parseArray(const char **pointer, const char *stop, size_t *line, - size_t depthLimit) -{ - OFMutableArray *array = [OFMutableArray array]; - - if (++(*pointer) >= stop) - return nil; - - if (--depthLimit == 0) - return nil; - - while (**pointer != ']') { - id object; - - skipWhitespacesAndComments(pointer, stop, line); - if (*pointer >= stop) - return nil; - - if (**pointer == ']') - break; - - if (**pointer == ',') { - (*pointer)++; - skipWhitespacesAndComments(pointer, stop, line); - - if (*pointer >= stop || **pointer != ']') - return nil; - - break; - } - - object = nextObject(pointer, stop, line, depthLimit); - if (object == nil) - return nil; - - [array addObject: object]; - - skipWhitespacesAndComments(pointer, stop, line); - if (*pointer >= stop) - return nil; - - if (**pointer == ',') { - (*pointer)++; - skipWhitespacesAndComments(pointer, stop, line); - - if (*pointer >= stop) - return nil; - } else if (**pointer != ']') - return nil; - } - - (*pointer)++; - - return array; -} - -static inline OFMutableDictionary * -parseDictionary(const char **pointer, const char *stop, size_t *line, - size_t depthLimit) -{ - OFMutableDictionary *dictionary = [OFMutableDictionary dictionary]; - - if (++(*pointer) >= stop) - return nil; - - if (--depthLimit == 0) - return nil; - - while (**pointer != '}') { - OFString *key; - id object; - - skipWhitespacesAndComments(pointer, stop, line); - if (*pointer >= stop) - return nil; - - if (**pointer == '}') - break; - - if (**pointer == ',') { - (*pointer)++; - skipWhitespacesAndComments(pointer, stop, line); - - if (*pointer >= stop || **pointer != '}') - return nil; - - break; - } - - skipWhitespacesAndComments(pointer, stop, line); - if (*pointer + 1 >= stop) - return nil; - - if ((**pointer >= 'a' && **pointer <= 'z') || - (**pointer >= 'A' && **pointer <= 'Z') || - **pointer == '_' || **pointer == '$' || **pointer == '\\') - key = parseIdentifier(pointer, stop); - else - key = nextObject(pointer, stop, line, depthLimit); - - if (![key isKindOfClass: [OFString class]]) - return nil; - - skipWhitespacesAndComments(pointer, stop, line); - if (*pointer + 1 >= stop || **pointer != ':') - return nil; - - (*pointer)++; - - object = nextObject(pointer, stop, line, depthLimit); - if (object == nil) - return nil; - - [dictionary setObject: object - forKey: key]; - - skipWhitespacesAndComments(pointer, stop, line); - if (*pointer >= stop) - return nil; - - if (**pointer == ',') { - (*pointer)++; - skipWhitespacesAndComments(pointer, stop, line); - - if (*pointer >= stop) - return nil; - } else if (**pointer != '}') - return nil; - } - - (*pointer)++; - - return dictionary; -} - -static inline OFNumber * -parseNumber(const char **pointer, const char *stop, size_t *line) -{ - bool isNegative = (*pointer < stop && (*pointer)[0] == '-'); - bool hasDecimal = false; - size_t i; - OFString *string; - OFNumber *number; - - for (i = 0; *pointer + i < stop; i++) { - if ((*pointer)[i] == '.') - hasDecimal = true; - - if ((*pointer)[i] == ' ' || (*pointer)[i] == '\t' || - (*pointer)[i] == '\r' || (*pointer)[i] == '\n' || - (*pointer)[i] == ',' || (*pointer)[i] == ']' || - (*pointer)[i] == '}') { - if ((*pointer)[i] == '\n') - (*line)++; - - break; - } - } - - string = [[OFString alloc] initWithUTF8String: *pointer - length: i]; - *pointer += i; - - @try { - if (hasDecimal) - number = [OFNumber numberWithDouble: - string.doubleValue]; - else if ([string isEqual: @"Infinity"]) - number = [OFNumber numberWithDouble: INFINITY]; - else if ([string isEqual: @"-Infinity"]) - number = [OFNumber numberWithDouble: -INFINITY]; - else if (isNegative) - number = [OFNumber numberWithLongLong: - [string longLongValueWithBase: 0]]; - else - number = [OFNumber numberWithUnsignedLongLong: - [string unsignedLongLongValueWithBase: 0]]; - } @finally { - [string release]; - } - - return number; -} - -static id -nextObject(const char **pointer, const char *stop, size_t *line, - size_t depthLimit) -{ - skipWhitespacesAndComments(pointer, stop, line); - - if (*pointer >= stop) - return nil; - - switch (**pointer) { - case '"': - case '\'': - return parseString(pointer, stop, line); - case '[': - return parseArray(pointer, stop, line, depthLimit); - case '{': - return parseDictionary(pointer, stop, line, depthLimit); - case 't': - if (*pointer + 3 >= stop) - return nil; - - if (memcmp(*pointer, "true", 4) != 0) - return nil; - - (*pointer) += 4; - - return [OFNumber numberWithBool: true]; - case 'f': - if (*pointer + 4 >= stop) - return nil; - - if (memcmp(*pointer, "false", 5) != 0) - return nil; - - (*pointer) += 5; - - return [OFNumber numberWithBool: false]; - case 'n': - if (*pointer + 3 >= stop) - return nil; - - if (memcmp(*pointer, "null", 4) != 0) - return nil; - - (*pointer) += 4; - - return [OFNull null]; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '+': - case '-': - case '.': - case 'I': - return parseNumber(pointer, stop, line); - default: - return nil; - } -} - -@implementation OFString (JSONValue) -- (id)JSONValue -{ - return [self JSONValueWithDepthLimit: 32]; -} - -- (id)JSONValueWithDepthLimit: (size_t)depthLimit -{ - void *pool = objc_autoreleasePoolPush(); - const char *pointer = self.UTF8String; - const char *stop = pointer + self.UTF8StringLength; - id object; - size_t line = 1; - -#ifdef __clang_analyzer__ - assert(pointer != NULL); -#endif - - object = nextObject(&pointer, stop, &line, depthLimit); - skipWhitespacesAndComments(&pointer, stop, &line); - - if (pointer < stop || object == nil) - @throw [OFInvalidJSONException exceptionWithString: self - line: line]; - - [object retain]; - - objc_autoreleasePoolPop(pool); - - return [object autorelease]; -} -@end Index: src/OFString.h ================================================================== --- src/OFString.h +++ src/OFString.h @@ -1278,11 +1278,11 @@ #include "OFConstantString.h" #include "OFMutableString.h" #ifdef __OBJC__ # import "OFString+CryptoHashing.h" -# import "OFString+JSONValue.h" +# import "OFString+JSONParsing.h" # ifdef OF_HAVE_FILES # import "OFString+PathAdditions.h" # endif # import "OFString+PropertyListValue.h" # import "OFString+Serialization.h" Index: src/OFString.m ================================================================== --- src/OFString.m +++ src/OFString.m @@ -128,11 +128,11 @@ /* References for static linking */ void _references_to_categories_of_OFString(void) { _OFString_CryptoHashing_reference = 1; - _OFString_JSONValue_reference = 1; + _OFString_JSONParsing_reference = 1; #ifdef OF_HAVE_FILES _OFString_PathAdditions_reference = 1; #endif _OFString_PropertyListValue_reference = 1; _OFString_Serialization_reference = 1; Index: tests/OFJSONTests.m ================================================================== --- tests/OFJSONTests.m +++ tests/OFJSONTests.m @@ -36,11 +36,11 @@ @"foo", [OFNumber numberWithBool: false], nil], nil]; - TEST(@"-[JSONValue] #1", [s.JSONValue isEqual: d]) + TEST(@"-[objectByParsingJSON] #1", [s.objectByParsingJSON isEqual: d]) TEST(@"-[JSONRepresentation]", [[d JSONRepresentation] isEqual: @"{\"x\":[0.5,15,null,\"foo\",false],\"foo\":\"b\\na\\r\"}"]) TEST(@"OF_JSON_REPRESENTATION_PRETTY", @@ -50,22 +50,22 @@ TEST(@"OF_JSON_REPRESENTATION_JSON5", [[d JSONRepresentationWithOptions: OF_JSON_REPRESENTATION_JSON5] isEqual: @"{x:[0.5,15,null,\"foo\",false],foo:\"b\\\na\\r\"}"]) - EXPECT_EXCEPTION(@"-[JSONValue] #2", OFInvalidJSONException, - [@"{" JSONValue]) - EXPECT_EXCEPTION(@"-[JSONValue] #3", OFInvalidJSONException, - [@"]" JSONValue]) - EXPECT_EXCEPTION(@"-[JSONValue] #4", OFInvalidJSONException, - [@"bar" JSONValue]) - EXPECT_EXCEPTION(@"-[JSONValue] #5", OFInvalidJSONException, - [@"[\"a\" \"b\"]" JSONValue]) - - TEST(@"-[JSONValue] #6", - [@"[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[{}]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" - .JSONValue isEqual: [OFArray arrayWithObject: + EXPECT_EXCEPTION(@"-[objectByParsingJSON] #2", OFInvalidJSONException, + [@"{" objectByParsingJSON]) + EXPECT_EXCEPTION(@"-[objectByParsingJSON] #3", OFInvalidJSONException, + [@"]" objectByParsingJSON]) + EXPECT_EXCEPTION(@"-[objectByParsingJSON] #4", OFInvalidJSONException, + [@"bar" objectByParsingJSON]) + EXPECT_EXCEPTION(@"-[objectByParsingJSON] #5", OFInvalidJSONException, + [@"[\"a\" \"b\"]" objectByParsingJSON]) + + TEST(@"-[objectByParsingJSON] #6", + [@"[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[{}]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" + .objectByParsingJSON isEqual: [OFArray arrayWithObject: [OFArray arrayWithObject: [OFArray arrayWithObject: [OFArray arrayWithObject: [OFArray arrayWithObject: [OFArray arrayWithObject: [OFArray arrayWithObject: [OFArray arrayWithObject: [OFArray arrayWithObject: [OFArray arrayWithObject: [OFArray arrayWithObject: @@ -79,12 +79,12 @@ [OFArray arrayWithObject: [OFArray arrayWithObject: [OFArray arrayWithObject: [OFArray arrayWithObject: [OFArray arrayWithObject: [OFDictionary dictionary]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]) - EXPECT_EXCEPTION(@"-[JSONValue] #7", OFInvalidJSONException, + EXPECT_EXCEPTION(@"-[objectByParsingJSON] #7", OFInvalidJSONException, [@"[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[{}]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" - JSONValue]) + objectByParsingJSON]) objc_autoreleasePoolPop(pool); } @end Index: utils/ofarc/LHAArchive.m ================================================================== --- utils/ofarc/LHAArchive.m +++ utils/ofarc/LHAArchive.m @@ -143,28 +143,28 @@ @"%04" PRIX16, entry.CRC16]; [of_stdout writeString: @"\t"]; [of_stdout writeLine: OF_LOCALIZED( @"list_compressed_size", - [@"[" - @" 'Compressed: '," - @" [" - @" {'size == 1': '1 byte'}," - @" {'': '%[size] bytes'}" - @" ]" - @"]" JSONValue], + @"[" + @" 'Compressed: '," + @" [" + @" {'size == 1': '1 byte'}," + @" {'': '%[size] bytes'}" + @" ]" + @"]".objectByParsingJSON, @"size", compressedSize)]; [of_stdout writeString: @"\t"]; [of_stdout writeLine: OF_LOCALIZED( @"list_uncompressed_size", - [@"[" - @" 'Uncompressed: '," - @" [" - @" {'size == 1': '1 byte'}," - @" {'': '%[size] bytes'}" - @" ]" - @"]" JSONValue], + @"[" + @" 'Uncompressed: '," + @" [" + @" {'size == 1': '1 byte'}," + @" {'': '%[size] bytes'}" + @" ]" + @"]".objectByParsingJSON, @"size", uncompressedSize)]; [of_stdout writeString: @"\t"]; [of_stdout writeLine: OF_LOCALIZED( @"list_compression_method", @"Compression method: %[method]", Index: utils/ofarc/TarArchive.m ================================================================== --- utils/ofarc/TarArchive.m +++ utils/ofarc/TarArchive.m @@ -124,17 +124,17 @@ OFString *GID = [OFString stringWithFormat: @"%u", entry.GID]; [of_stdout writeString: @"\t"]; [of_stdout writeLine: OF_LOCALIZED(@"list_size", - [@"[" - @" 'Size: '," - @" [" - @" {'size == 1': '1 byte'}," - @" {'': '%[size] bytes'}" - @" ]" - @"]" JSONValue], + @"[" + @" 'Size: '," + @" [" + @" {'size == 1': '1 byte'}," + @" {'': '%[size] bytes'}" + @" ]" + @"]".objectByParsingJSON, @"size", size)]; [of_stdout writeString: @"\t"]; [of_stdout writeLine: OF_LOCALIZED(@"list_mode", @"Mode: %[mode]", @"mode", mode)]; Index: utils/ofarc/ZIPArchive.m ================================================================== --- utils/ofarc/ZIPArchive.m +++ utils/ofarc/ZIPArchive.m @@ -136,28 +136,28 @@ localDateStringWithFormat: @"%Y-%m-%d %H:%M:%S"]; [of_stdout writeString: @"\t"]; [of_stdout writeLine: OF_LOCALIZED( @"list_compressed_size", - [@"[" - @" 'Compressed: '," - @" [" - @" {'size == 1': '1 byte'}," - @" {'': '%[size] bytes'}" - @" ]" - @"]" JSONValue], + @"[" + @" 'Compressed: '," + @" [" + @" {'size == 1': '1 byte'}," + @" {'': '%[size] bytes'}" + @" ]" + @"]".objectByParsingJSON, @"size", compressedSize)]; [of_stdout writeString: @"\t"]; [of_stdout writeLine: OF_LOCALIZED( @"list_uncompressed_size", - [@"[" - @" 'Uncompressed: '," - @" [" - @" {'size == 1': '1 byte'}," - @" {'': '%[size] bytes'}" - @" ]" - @"]" JSONValue], + @"[" + @" 'Uncompressed: '," + @" [" + @" {'size == 1': '1 byte'}," + @" {'': '%[size] bytes'}" + @" ]" + @"]".objectByParsingJSON, @"size", uncompressedSize)]; [of_stdout writeString: @"\t"]; [of_stdout writeLine: OF_LOCALIZED( @"list_compression_method", @"Compression method: %[method]", Index: utils/ofhttp/OFHTTP.m ================================================================== --- utils/ofhttp/OFHTTP.m +++ utils/ofhttp/OFHTTP.m @@ -811,16 +811,16 @@ @"num", lengthString); } else { lengthString = [OFString stringWithFormat: @"%jd", _resumedFrom + _length]; lengthString = OF_LOCALIZED(@"size_bytes", - [@"[" - @" [" - @" {'num == 1': '1 byte'}," - @" {'': '%[num] bytes'}" - @" ]" - @"]" JSONValue], + @"[" + @" [" + @" {'num == 1': '1 byte'}," + @" {'': '%[num] bytes'}" + @" ]" + @"]".objectByParsingJSON, @"num", lengthString); } } else lengthString = OF_LOCALIZED(@"size_unknown", @"unknown"); Index: utils/ofhttp/ProgressBar.m ================================================================== --- utils/ofhttp/ProgressBar.m +++ utils/ofhttp/ProgressBar.m @@ -202,16 +202,16 @@ @"num", num)]; } else { OFString *num = [OFString stringWithFormat: @"%jd", _resumedFrom + _received]; [of_stdout writeString: OF_LOCALIZED(@"progress_bytes", - [@"[" - @" [" - @" {'num == 1': '1 byte '}," - @" {'': '%[num] bytes'}" - @" ]" - @"]" JSONValue], + @"[" + @" [" + @" {'num == 1': '1 byte '}," + @" {'': '%[num] bytes'}" + @" ]" + @"]".objectByParsingJSON, @"num", num)]; } [of_stdout writeString: @" "];