Index: src/OFINICategory.m ================================================================== --- src/OFINICategory.m +++ src/OFINICategory.m @@ -37,10 +37,70 @@ { @public OFString *_comment; } @end + +static OFString* +escapeString(OFString *string) +{ + OFMutableString *mutableString; + + /* FIXME: Optimize */ + if (![string hasPrefix: @" "] && ![string hasPrefix: @"\t"] && + ![string hasPrefix: @"\f"] && ![string hasSuffix: @" "] && + ![string hasSuffix: @"\t"] && ![string hasSuffix: @"\f"] && + ![string containsString: @"\""]) + return string; + + mutableString = [[string mutableCopy] autorelease]; + + [mutableString replaceOccurrencesOfString: @"\\" + withString: @"\\\\"]; + [mutableString replaceOccurrencesOfString: @"\f" + withString: @"\\f"]; + [mutableString replaceOccurrencesOfString: @"\r" + withString: @"\\r"]; + [mutableString replaceOccurrencesOfString: @"\n" + withString: @"\\n"]; + [mutableString replaceOccurrencesOfString: @"\"" + withString: @"\\\""]; + + [mutableString prependString: @"\""]; + [mutableString appendString: @"\""]; + + [mutableString makeImmutable]; + + return mutableString; +} + +static OFString* +unescapeString(OFString *string) +{ + OFMutableString *mutableString; + + if (![string hasPrefix: @"\""] || ![string hasSuffix: @"\""]) + return string; + + string = [string substringWithRange: of_range(1, [string length] - 2)]; + mutableString = [[string mutableCopy] autorelease]; + + [mutableString replaceOccurrencesOfString: @"\\f" + withString: @"\f"]; + [mutableString replaceOccurrencesOfString: @"\\r" + withString: @"\r"]; + [mutableString replaceOccurrencesOfString: @"\\n" + withString: @"\n"]; + [mutableString replaceOccurrencesOfString: @"\\\"" + withString: @"\""]; + [mutableString replaceOccurrencesOfString: @"\\\\" + withString: @"\\"]; + + [mutableString makeImmutable]; + + return mutableString; +} @implementation OFINICategory_Pair - (void)dealloc { [_key release]; @@ -110,14 +170,15 @@ key = [line substringWithRange: of_range(0, pos)]; value = [line substringWithRange: of_range(pos + 1, [line length] - pos - 1)]; - if ([key hasSuffix: @" "]) { - key = [key stringByDeletingEnclosingWhitespaces]; - value = [value stringByDeletingEnclosingWhitespaces]; - } + key = [key stringByDeletingEnclosingWhitespaces]; + value = [value stringByDeletingEnclosingWhitespaces]; + + key = unescapeString(key); + value = unescapeString(value); pair->_key = [key copy]; pair->_value = [value copy]; [_lines addObject: pair]; @@ -291,16 +352,12 @@ } - (void)setBool: (bool)bool_ forKey: (OFString*)key { - if (bool_) - [self setString: @"true" - forKey: key]; - else - [self setString: @"false" - forKey: key]; + [self setString: (bool_ ? @"true" : @"false") + forKey: key]; } - (void)setFloat: (float)float_ forKey: (OFString*)key { @@ -371,14 +428,16 @@ if ([line isKindOfClass: [OFINICategory_Comment class]]) { OFINICategory_Comment *comment = line; [stream writeLine: comment->_comment]; } else if ([line isKindOfClass: [OFINICategory_Pair class]]) { OFINICategory_Pair *pair = line; - [stream writeFormat: @"%@=%@\n", - pair->_key, pair->_value]; + OFString *key = escapeString(pair->_key); + OFString *value = escapeString(pair->_value); + + [stream writeFormat: @"%@=%@\n", key, value]; } else @throw [OFInvalidArgumentException exception]; } return true; } @end Index: src/OFINIFile.h ================================================================== --- src/OFINIFile.h +++ src/OFINIFile.h @@ -13,14 +13,14 @@ * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ #import "OFObject.h" +#import "OFINICategory.h" @class OFMutableArray; @class OFString; -@class OFINICategory; /*! * @class OFINIFile OFINIFile.h ObjFW/OFINIFile.h * * @brief A class for reading, creating and modifying INI files. Index: src/OFINIFile.m ================================================================== --- src/OFINIFile.m +++ src/OFINIFile.m @@ -20,10 +20,11 @@ #import "OFFile.h" #import "OFINICategory.h" #import "OFINICategory+Private.h" #import "OFInvalidFormatException.h" +#import "OFOpenFileFailedException.h" #import "autorelease.h" #import "macros.h" @interface OFINIFile (OF_PRIVATE_CATEGORY) @@ -113,14 +114,24 @@ } - (void)OF_parseFile: (OFString*)path { void *pool = objc_autoreleasePoolPush(); - OFFile *file = [OFFile fileWithPath: path - mode: @"r"]; + OFFile *file; OFINICategory *category = nil; OFString *line; + + @try { + file = [OFFile fileWithPath: path + mode: @"r"]; + } @catch (OFOpenFileFailedException *e) { + /* Handle missing file like an empty file */ + if ([e errNo] == ENOENT) + return; + + @throw e; + } while ((line = [file readLine]) != nil) { if (isWhitespaceLine(line)) continue; Index: src/ObjFW.h ================================================================== --- src/ObjFW.h +++ src/ObjFW.h @@ -48,11 +48,10 @@ #import "OFStdIOStream.h" #import "OFDeflateStream.h" #ifdef OF_HAVE_FILES # import "OFFile.h" # import "OFINIFile.h" -# import "OFINICategory.h" # import "OFZIPArchive.h" # import "OFZIPArchiveEntry.h" #endif #ifdef OF_HAVE_SOCKETS # import "OFStreamSocket.h" Index: tests/OFINIFileTests.m ================================================================== --- tests/OFINIFileTests.m +++ tests/OFINIFileTests.m @@ -42,11 +42,13 @@ @";comment" NL @"new=new" NL NL @"[foobar]" NL @";foobarcomment" NL - @"qux= asd" NL + @"qux=\" asd\"" NL + @"quxquxqux=\"hello\\\"world\"" NL + @"qux2=\"\\f\"" NL NL @"[types]" NL @"integer=16" NL @"bool=false" NL @"float=0.25" NL @@ -61,11 +63,15 @@ (category = [file categoryForName: @"tests"])) module = @"OFINICategory"; TEST(@"-[stringForKey:]", - [[category stringForKey: @"foo"] isEqual: @"bar"]) + [[category stringForKey: @"foo"] isEqual: @"bar"] && + (category = [file categoryForName: @"foobar"]) && + [[category stringForKey: @"quxquxqux"] isEqual: @"hello\"world"]) + + category = [file categoryForName: @"tests"]; TEST(@"-[setString:forKey:]", R([category setString: @"baz" forKey: @"foo"]) && R([category setString: @"new" @@ -102,11 +108,11 @@ forKey: @"double"])) category = [file categoryForName: @"foobar"]; TEST(@"-[removeValueForKey:]", - R([category removeValueForKey: @"quxqux"])) + R([category removeValueForKey: @"quxqux "])) module = @"OFINIFile"; /* FIXME: Find a way to write files on Nintendo DS */ #ifndef OF_NINTENDO_DS Index: tests/testfile.ini ================================================================== --- tests/testfile.ini +++ tests/testfile.ini @@ -3,13 +3,15 @@ foobar=baz ;comment [foobar] ;foobarcomment -qux= asd -quxqux =asd +qux=" asd" +"quxqux " = asd +quxquxqux="hello\"world" +qux2="\f" [types] integer = 0x20 bool = true float = 0.5 double = 0.25