Index: src/OFINICategory+Private.h ================================================================== --- src/OFINICategory+Private.h +++ src/OFINICategory+Private.h @@ -24,11 +24,12 @@ @class OFStream; OF_DIRECT_MEMBERS @interface OFINICategory () -- (instancetype)of_initWithName: (OFString *)name OF_METHOD_FAMILY(init); +- (instancetype)of_initWithName: (nullable OFString *)name + OF_METHOD_FAMILY(init); - (void)of_parseLine: (OFString *)line; - (bool)of_writeToStream: (OFStream *)stream encoding: (OFStringEncoding)encoding first: (bool)first; @end Index: src/OFINICategory.m ================================================================== --- src/OFINICategory.m +++ src/OFINICategory.m @@ -153,15 +153,19 @@ } - (void)of_parseLine: (OFString *)line { if (![line hasPrefix: @";"]) { - OFINICategoryPair *pair = - [[[OFINICategoryPair alloc] init] autorelease]; + OFINICategoryPair *pair; OFString *key, *value; size_t pos; + if (_name == nil) + @throw [OFInvalidFormatException exception]; + + pair = [[[OFINICategoryPair alloc] init] autorelease]; + if ((pos = [line rangeOfString: @"="].location) == OFNotFound) @throw [OFInvalidFormatException exception]; key = unescapeString([line substringToIndex: pos] .stringByDeletingEnclosingWhitespaces); @@ -477,14 +481,16 @@ first: (bool)first { if (_lines.count == 0) return false; - if (first) - [stream writeFormat: @"[%@]\r\n", _name]; - else - [stream writeFormat: @"\r\n[%@]\r\n", _name]; + if (_name != nil) { + if (first) + [stream writeFormat: @"[%@]\r\n", _name]; + else + [stream writeFormat: @"\r\n[%@]\r\n", _name]; + } for (id line in _lines) { if ([line isKindOfClass: [OFINICategoryComment class]]) { OFINICategoryComment *comment = line; [stream writeFormat: @"%@\r\n", comment->_comment]; Index: src/OFINIFile.h ================================================================== --- src/OFINIFile.h +++ src/OFINIFile.h @@ -32,10 +32,11 @@ * @brief A class for reading, creating and modifying INI files. */ OF_SUBCLASSING_RESTRICTED @interface OFINIFile: OFObject { + OFINICategory *_prologue; OFMutableArray OF_GENERIC(OFINICategory *) *_categories; } /** * @brief All categories in the INI file. Index: src/OFINIFile.m ================================================================== --- src/OFINIFile.m +++ src/OFINIFile.m @@ -77,10 +77,11 @@ - (instancetype)initWithIRI: (OFIRI *)IRI encoding: (OFStringEncoding)encoding { self = [super init]; @try { + _prologue = [[OFINICategory alloc] of_initWithName: nil]; _categories = [[OFMutableArray alloc] init]; [self of_parseIRI: IRI encoding: encoding]; } @catch (id e) { [self release]; @@ -90,10 +91,11 @@ return self; } - (void)dealloc { + [_prologue release]; [_categories release]; [super dealloc]; } @@ -116,11 +118,11 @@ - (void)of_parseIRI: (OFIRI *)IRI encoding: (OFStringEncoding)encoding { void *pool = objc_autoreleasePoolPush(); OFStream *file; - OFINICategory *category = nil; + OFINICategory *category = _prologue; OFString *line; if (encoding == OFStringEncodingAutodetect) encoding = OFStringEncodingUTF8; @@ -147,16 +149,12 @@ categoryName = [line substringWithRange: OFMakeRange(1, line.length - 2)]; category = [[[OFINICategory alloc] of_initWithName: categoryName] autorelease]; [_categories addObject: category]; - } else { - if (category == nil) - @throw [OFInvalidFormatException exception]; - + } else [category of_parseLine: line]; - } } objc_autoreleasePoolPop(pool); } @@ -168,10 +166,13 @@ - (void)writeToIRI: (OFIRI *)IRI encoding: (OFStringEncoding)encoding { void *pool = objc_autoreleasePoolPush(); OFStream *file = [OFIRIHandler openItemAtIRI: IRI mode: @"w"]; bool first = true; + + if ([_prologue of_writeToStream: file encoding: encoding first: true]) + first = false; for (OFINICategory *category in _categories) if ([category of_writeToStream: file encoding: encoding first: first]) Index: tests/OFINIFileTests.m ================================================================== --- tests/OFINIFileTests.m +++ tests/OFINIFileTests.m @@ -19,10 +19,12 @@ #include "config.h" #import "ObjFW.h" #import "ObjFWTest.h" + +#import "OFEmbeddedIRIHandler.h" @interface OFINIFileTests: OTTestCase { OFINIFile *_file; } @@ -107,11 +109,13 @@ [OFArray array]); } - (void)testWriteToIRIEncoding { - OFString *expectedOutput = @"[tests]\r\n" + OFString *expectedOutput = @"; Comment before categories\r\n" + @"\r\n" + @"[tests]\r\n" @"foo=baz\r\n" @"foobar=baz\r\n" @";comment\r\n" @"new=new\r\n" @"\r\n" @@ -170,6 +174,17 @@ } #else (void)expectedOutput; #endif } + +- (void)testValuePairOutsideOfCategoryRejected +{ + OFIRI *IRI = [OFIRI IRIWithString: @"embedded:testfile_broken.ini"]; + + OFRegisterEmbeddedFile(@"testfile_broken.ini", + (const uint8_t *)"; comment\r\na=b", 14); + + OTAssertThrowsSpecific([OFINIFile fileWithIRI: IRI], + OFInvalidFormatException); +} @end Index: tests/testfile.ini ================================================================== --- tests/testfile.ini +++ tests/testfile.ini @@ -1,5 +1,7 @@ +; Comment before categories + [tests] foo = bar foobar=baz ;comment