Index: ChangeLog ================================================================== --- ChangeLog +++ ChangeLog @@ -1,9 +1,46 @@ Legend: * Changes of existing features or bugfixes. + New features. +ObjFW 0.5.1 -> ObjFW 0.5.2, 25.4.2011 + * Fix double-retain in OFList. + * Don't ignore the timeout in OFStreamObserver when using select(). + * Do -[OFURL copy] in a try block to prevent a leak when an exception occurs. + * Fix too big buffer in -[OFMutableString _applyTable:withSize:]. + * Call madvise() on the correct length variable so it covers the whole string. + * Fix a warning when sizeof(size_t) < sizeof(long long). + * Skip possible BOMs when appending strings. + +ObjFW 0.5 -> ObjFW 0.5.1, 21.04.2011 + * Work around a wrong warning produced by Apple GCC 4.0.1 which would cause + the build to fail due to -Werror. + * Call objc_thread_{add,remove} when using the GNU runtime to make sure the + runtime knows about our thread. + * Detach a thread before restarting if it was never joined. + * Release the old return value when restarting a thread. + +ObjFW 0.4-alpha1 -> 0.5, 09.04.2011 + + %@ is now allowed in format strings. + + Added of_log for easy logging. + * Exceptions have one header per exception now. + * Lots of exception improvements. + * Huge improvements in XML handling. + * Improvements in socket handling, including improved API. + * OFStreamObserver is now thread-safe and stops the current observe call when + the set of streams to observe is modified. + + New class OFURL. + + New class OFHTTPRequest. + + New class OFCondition. + * Improvements in objfw-compile. + + Blocks can be used together with Cocoa now. + + When linking ObjFW and Cocoa, OFAutoreleasePools are used by both now. + + Support for Base64. + + Use a real Xcode project instead of just calling make. + + Add Haiku to the list of supported platforms. + * Lots of small bugfixes and countless small changes. Read the commits! + ObjFW 0.3.1 -> 0.4-alpha1, 03.01.2011 * ObjFW is now available under the terms of the QPL, GPLv2 and GPLv3. + Support for blocks was added, including a blocks runtime. + Added support for the new GNU runtime, introduced in GCC 4.6. * Objects returned from collections are no longer retained and autoreleased. Index: Info.plist ================================================================== --- Info.plist +++ Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType FMWK CFBundleSignature OBJFW CFBundleVersion - 0.4-dev + 0.5.2 CFBundleShortVersionString - 0.4-dev + 0.5.2 Index: README ================================================================== --- README +++ README @@ -22,12 +22,12 @@ BUILDING AS A MAC OS X FRAMEWORK It is also possible to build ObjFW as a Mac OS X framework. To do so, - just execute xcodebuild in the root directory of ObjFW or open the - .xcodeproj in Xcode and choose Build -> Build from the menu. Copy the + just execute xcodebuild -target ObjFW in the root directory of ObjFW or open + the .xcodeproj in Xcode and choose Build -> Build from the menu. Copy the resulting ObjFW.framework to /Library/Frameworks and you are done. USING THE MAC OS X FRAMWORK IN XCODE Index: configure.ac ================================================================== --- configure.ac +++ configure.ac @@ -1,6 +1,6 @@ -AC_INIT(ObjFW, 0.4-dev, js@webkeks.org) +AC_INIT(ObjFW, 0.5.2, js@webkeks.org) AC_CONFIG_SRCDIR(src) AS_IF([test x"$host" = x"psp"], [ OBJCFLAGS="-G0 $OBJCFLAGS" LIBS="$LIBS -lpspdebug -lpspdisplay -lpspge -lpspctrl -lpspsdk -lc" Index: extra.mk.in ================================================================== --- extra.mk.in +++ extra.mk.in @@ -1,8 +1,8 @@ OBJFW_SHARED_LIB = @OBJFW_SHARED_LIB@ OBJFW_STATIC_LIB = @OBJFW_STATIC_LIB@ -OBJFW_LIB_MAJOR = 3 +OBJFW_LIB_MAJOR = 4 OBJFW_LIB_MINOR = 0 OBJFW_LIB_MAJOR_MINOR = ${OBJFW_LIB_MAJOR}.${OBJFW_LIB_MINOR} ASPRINTF_M = @ASPRINTF_M@ ATOMIC_H = @ATOMIC_H@ Index: src/OFHTTPRequest.m ================================================================== --- src/OFHTTPRequest.m +++ src/OFHTTPRequest.m @@ -165,11 +165,11 @@ OFMutableDictionary *s_headers; OFDataArray *data; OFEnumerator *enumerator; OFString *key; int status; - const char *t; + const char *t = NULL; [sock connectToHost: [URL host] onPort: [URL port]]; /* Index: src/OFList.m ================================================================== --- src/OFList.m +++ src/OFList.m @@ -79,12 +79,10 @@ firstListObject = o; count++; mutations++; - [obj retain]; - return o; } - (of_list_object_t*)prependObject: (id)obj { @@ -103,12 +101,10 @@ lastListObject = o; count++; mutations++; - [obj retain]; - return o; } - (of_list_object_t*)insertObject: (id)obj beforeListObject: (of_list_object_t*)listobj @@ -129,12 +125,10 @@ firstListObject = o; count++; mutations++; - [obj retain]; - return o; } - (of_list_object_t*)insertObject: (id)obj afterListObject: (of_list_object_t*)listobj @@ -155,12 +149,10 @@ lastListObject = o; count++; mutations++; - [obj retain]; - return o; } - (void)removeListObject: (of_list_object_t*)listobj { @@ -257,12 +249,10 @@ if (prev != NULL) prev->next = o; new->count++; - [o->object retain]; - prev = o; } } @catch (id e) { [new release]; @throw e; Index: src/OFMutableString.m ================================================================== --- src/OFMutableString.m +++ src/OFMutableString.m @@ -63,15 +63,14 @@ return; } ulen = [self length]; - ustr = [self allocMemoryForNItems: [self length] - withSize: ulen]; + ustr = [self allocMemoryForNItems: ulen + withSize: sizeof(of_unichar_t)]; - i = 0; - j = 0; + i = j = 0; nlen = 0; while (i < length) { clen = of_string_utf8_to_unicode(string + i, length - i, &c); @@ -136,10 +135,15 @@ size_t len; [self freeMemory: string]; len = strlen(str); + + if (len >= 3 && !memcmp(str, "\xEF\xBB\xBF", 3)) { + str += 3; + len -= 3; + } switch (of_string_check_utf8(str, len)) { case 0: isUTF8 = NO; break; @@ -162,10 +166,15 @@ - (void)appendCString: (const char*)str { size_t strlength; strlength = strlen(str); + + if (strlength >= 3 && !memcmp(str, "\xEF\xBB\xBF", 3)) { + str += 3; + strlength -= 3; + } switch (of_string_check_utf8(str, strlength)) { case 1: isUTF8 = YES; break; @@ -180,10 +189,15 @@ } - (void)appendCString: (const char*)str withLength: (size_t)len { + if (len >= 3 && !memcmp(str, "\xEF\xBB\xBF", 3)) { + str += 3; + len -= 3; + } + switch (of_string_check_utf8(str, len)) { case 1: isUTF8 = YES; break; case -1: @@ -266,21 +280,21 @@ - (void)reverse { size_t i, j, len = length / 2; - madvise(string, len, MADV_SEQUENTIAL); + madvise(string, length, MADV_SEQUENTIAL); /* We reverse all bytes and restore UTF-8 later, if necessary */ for (i = 0, j = length - 1; i < len; i++, j--) { string[i] ^= string[j]; string[j] ^= string[i]; string[i] ^= string[j]; } if (!isUTF8) { - madvise(string, len, MADV_NORMAL); + madvise(string, length, MADV_NORMAL); return; } for (i = 0; i < length; i++) { /* ASCII */ @@ -287,17 +301,17 @@ if (OF_LIKELY(!(string[i] & 0x80))) continue; /* A start byte can't happen first as we reversed everything */ if (OF_UNLIKELY(string[i] & 0x40)) { - madvise(string, len, MADV_NORMAL); + madvise(string, length, MADV_NORMAL); @throw [OFInvalidEncodingException newWithClass: isa]; } /* Next byte must not be ASCII */ if (OF_UNLIKELY(length < i + 1 || !(string[i + 1] & 0x80))) { - madvise(string, len, MADV_NORMAL); + madvise(string, length, MADV_NORMAL); @throw [OFInvalidEncodingException newWithClass: isa]; } /* Next byte is the start byte */ if (OF_LIKELY(string[i + 1] & 0x40)) { @@ -309,11 +323,11 @@ continue; } /* Second next byte must not be ASCII */ if (OF_UNLIKELY(length < i + 2 || !(string[i + 2] & 0x80))) { - madvise(string, len, MADV_NORMAL); + madvise(string, length, MADV_NORMAL); @throw [OFInvalidEncodingException newWithClass: isa]; } /* Second next byte is the start byte */ if (OF_LIKELY(string[i + 2] & 0x40)) { @@ -325,11 +339,11 @@ continue; } /* Third next byte must not be ASCII */ if (OF_UNLIKELY(length < i + 3 || !(string[i + 3] & 0x80))) { - madvise(string, len, MADV_NORMAL); + madvise(string, length, MADV_NORMAL); @throw [OFInvalidEncodingException newWithClass: isa]; } /* Third next byte is the start byte */ if (OF_LIKELY(string[i + 3] & 0x40)) { @@ -344,15 +358,15 @@ i += 3; continue; } /* UTF-8 does not allow more than 4 bytes per character */ - madvise(string, len, MADV_NORMAL); + madvise(string, length, MADV_NORMAL); @throw [OFInvalidEncodingException newWithClass: isa]; } - madvise(string, len, MADV_NORMAL); + madvise(string, length, MADV_NORMAL); } - (void)upper { [self _applyTable: of_unicode_upper_table Index: src/OFStreamObserver.m ================================================================== --- src/OFStreamObserver.m +++ src/OFStreamObserver.m @@ -487,10 +487,13 @@ readfds_ = readfds; writefds_ = writefds; exceptfds_ = exceptfds; # endif + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + if (select(nfds, &readfds_, &writefds_, &exceptfds_, (timeout != -1 ? &tv : NULL)) < 1) return NO; if (FD_ISSET(cancelFd[0], &readfds_)) { Index: src/OFString.m ================================================================== --- src/OFString.m +++ src/OFString.m @@ -664,17 +664,20 @@ if (stat([path cString], &s) == -1) @throw [OFOpenFileFailedException newWithClass: isa path: path mode: @"rb"]; + if (s.st_size > SIZE_MAX) + @throw [OFOutOfRangeException newWithClass: isa]; + file = [[OFFile alloc] initWithPath: path mode: @"rb"]; @try { - tmp = [self allocMemoryWithSize: s.st_size]; + tmp = [self allocMemoryWithSize: (size_t)s.st_size]; - [file readExactlyNBytes: s.st_size + [file readExactlyNBytes: (size_t)s.st_size intoBuffer: tmp]; } @finally { [file release]; } } @catch (id e) { @@ -682,11 +685,11 @@ @throw e; } self = [self initWithCString: tmp encoding: encoding - length: s.st_size]; + length: (size_t)s.st_size]; [self freeMemory: tmp]; return self; } Index: src/OFThread.m ================================================================== --- src/OFThread.m +++ src/OFThread.m @@ -20,10 +20,14 @@ # include # include #else # include #endif + +#if defined(OF_GNU_RUNTIME) || defined(OF_OLD_GNU_RUNTIME) +# import +#endif #import "OFThread.h" #import "OFList.h" #import "OFDate.h" #import "OFAutoreleasePool.h" @@ -49,10 +53,14 @@ static of_tlskey_t thread_self; static id call_main(id obj) { +#if defined(OF_GNU_RUNTIME) || defined(OF_OLD_GNU_RUNTIME) + objc_thread_add(); +#endif + if (!of_tlskey_set(thread_self, obj)) @throw [OFInitializationFailedException newWithClass: [obj class]]; /* @@ -67,10 +75,14 @@ [OFTLSKey callAllDestructors]; [OFAutoreleasePool releaseAll]; [obj release]; + +#if defined(OF_GNU_RUNTIME) || defined(OF_OLD_GNU_RUNTIME) + objc_thread_remove(); +#endif return 0; } @implementation OFThread @@ -206,10 +218,14 @@ [OFTLSKey callAllDestructors]; [OFAutoreleasePool releaseAll]; [thread release]; + +#if defined(OF_GNU_RUNTIME) || defined(OF_OLD_GNU_RUNTIME) + objc_thread_remove(); +#endif of_thread_exit(); } - initWithObject: (id)obj @@ -241,10 +257,15 @@ - (void)start { if (running == OF_THREAD_RUNNING) @throw [OFThreadStillRunningException newWithClass: isa thread: self]; + + if (running == OF_THREAD_WAITING_FOR_JOIN) { + of_thread_detach(thread); + [retval release]; + } [self retain]; if (!of_thread_new(&thread, call_main, self)) { [self release]; @@ -270,10 +291,17 @@ { if (running == OF_THREAD_RUNNING) @throw [OFThreadStillRunningException newWithClass: isa thread: self]; + /* + * We should not be running anymore, but call detach in order to free + * the resources. + */ + if (running == OF_THREAD_WAITING_FOR_JOIN) + of_thread_detach(thread); + [object release]; [retval release]; [super dealloc]; } Index: src/OFURL.m ================================================================== --- src/OFURL.m +++ src/OFURL.m @@ -360,19 +360,24 @@ - copy { OFURL *new = [[OFURL alloc] init]; - new->scheme = [scheme copy]; - new->host = [host copy]; - new->port = port; - new->user = [user copy]; - new->password = [password copy]; - new->path = [path copy]; - new->parameters = [parameters copy]; - new->query = [query copy]; - new->fragment = [fragment copy]; + @try { + new->scheme = [scheme copy]; + new->host = [host copy]; + new->port = port; + new->user = [user copy]; + new->password = [password copy]; + new->path = [path copy]; + new->parameters = [parameters copy]; + new->query = [query copy]; + new->fragment = [fragment copy]; + } @catch (id e) { + [new release]; + @throw e; + } return new; } - (OFString*)scheme Index: src/OFXMLParser.h ================================================================== --- src/OFXMLParser.h +++ src/OFXMLParser.h @@ -177,10 +177,11 @@ of_xml_parser_string_block_t CDATAHandler; of_xml_parser_string_block_t commentHandler; of_xml_parser_unknown_entity_block_t unknownEntityHandler; #endif size_t level; + BOOL acceptProlog; size_t lineNumber; BOOL lastCarriageReturn; BOOL finishedParsing; } Index: src/OFXMLParser.m ================================================================== --- src/OFXMLParser.m +++ src/OFXMLParser.m @@ -49,11 +49,11 @@ [cache replaceOccurrencesOfString: @"\r" withString: @"\n"]; return [cache stringByXMLUnescapingWithDelegate: delegate]; } -static OF_INLINE OFString* +static OFString* namespace_for_prefix(OFString *prefix, OFArray *namespaces) { OFDictionary **carray = [namespaces cArray]; ssize_t i; @@ -155,10 +155,11 @@ dict = [OFMutableDictionary dictionaryWithKeysAndObjects: @"xml", @"http://www.w3.org/XML/1998/namespace", @"xmlns", @"http://www.w3.org/2000/xmlns/", nil]; [namespaces addObject: dict]; + acceptProlog = YES; lineNumber = 1; [pool release]; } @catch (id e) { [self release]; @@ -265,11 +266,11 @@ } } /* * The following methods handle the different states of the parser. They are - * lookup up in +[initialize] and put in a lookup table to speed things up. + * looked up in +[initialize] and put in a lookup table to speed things up. * One dispatch for every character would be way too slow! */ /* Not in a tag */ - (void)_parseOutsideTagWithBuffer: (const char*)buf @@ -276,12 +277,13 @@ i: (size_t*)i last: (size_t*)last { size_t len; - if (finishedParsing && buf[*i] != ' ' && buf[*i] != '\t' && - buf[*i] != '\n' && buf[*i] != '\r' && buf[*i] != '<') + if ((finishedParsing || [previous count] < 1) && buf[*i] != ' ' && + buf[*i] != '\t' && buf[*i] != '\n' && buf[*i] != '\r' && + buf[*i] != '<') @throw [OFMalformedXMLException newWithClass: isa parser: self]; if (buf[*i] != '<') return; @@ -317,11 +319,11 @@ /* Tag was just opened */ - (void)_parseTagOpenedWithBuffer: (const char*)buf i: (size_t*)i last: (size_t*)last { - if (finishedParsing && buf[*i] != '!') + if (finishedParsing && buf[*i] != '!' && buf[*i] != '?') @throw [OFMalformedXMLException newWithClass: isa parser: self]; switch (buf[*i]) { case '?': @@ -330,23 +332,108 @@ level = 0; break; case '/': *last = *i + 1; state = OF_XMLPARSER_IN_CLOSE_TAG_NAME; + acceptProlog = NO; break; case '!': *last = *i + 1; state = OF_XMLPARSER_IN_EXCLAMATIONMARK; + acceptProlog = NO; break; default: state = OF_XMLPARSER_IN_TAG_NAME; + acceptProlog = NO; (*i)--; break; } } -/* Inside prolog */ +/* */ +- (BOOL)_parseXMLProcessingInstructions: (OFString*)pi +{ + const char *pi_c; + size_t i, last, pi_len; + int xstate = 0; + OFString *attr = nil; + OFString *val = nil; + char xdelim = 0; + + if (!acceptProlog) + return NO; + + acceptProlog = NO; + + pi = [pi substringFromIndex: 3 + toIndex: [pi length]]; + pi = [pi stringByDeletingLeadingAndTrailingWhitespaces]; + + pi_c = [pi cString]; + pi_len = [pi cStringLength]; + + for (i = last = 0; i < pi_len; i++) { + switch (xstate) { + case 0: + if (pi_c[i] == ' ' || pi_c[i] == '\t' || + pi_c[i] == '\r' || pi_c[i] == '\n') + continue; + + last = i; + xstate = 1; + i--; + + break; + case 1: + if (pi_c[i] != '=') + continue; + + attr = [OFString stringWithCString: pi_c + last + length: i - last]; + last = i + 1; + xstate = 2; + + break; + case 2: + if (pi_c[i] != '\'' && pi_c[i] != '"') + return NO; + + xdelim = pi_c[i]; + last = i + 1; + xstate = 3; + + break; + case 3: + if (pi_c[i] != xdelim) + continue; + + val = [OFString stringWithCString: pi_c + last + length: i - last]; + + if ([attr isEqual: @"version"]) + if (![val hasPrefix: @"1."]) + return NO; + + if ([attr isEqual: @"encoding"]) + if ([val caseInsensitiveCompare: @"utf-8"] != + OF_ORDERED_SAME) + return NO; + + last = i + 1; + xstate = 0; + + break; + } + } + + if (xstate != 0) + return NO; + + return YES; +} + +/* Inside processing instructions */ - (void)_parseInProcessingInstructionsWithBuffer: (const char*)buf i: (size_t*)i last: (size_t*)last { if (buf[*i] == '?') @@ -368,10 +455,18 @@ * Class swizzle the string to be immutable. We pass it as * OFString*, so it can't be modified anyway. But not swizzling * it would create a real copy each time -[copy] is called. */ pi->isa = [OFString class]; + + if ([pi isEqual: @"xml"] || [pi hasPrefix: @"xml "] || + [pi hasPrefix: @"xml\t"] || [pi hasPrefix: @"xml\r"] || + [pi hasPrefix: @"xml\n"]) + if (![self _parseXMLProcessingInstructions: pi]) + @throw [OFMalformedXMLException + newWithClass: isa + parser: self]; [delegate parser: self foundProcessingInstructions: pi]; [pool release]; Index: src/threading.h ================================================================== --- src/threading.h +++ src/threading.h @@ -88,10 +88,21 @@ CloseHandle(thread); return YES; #endif } + +static OF_INLINE BOOL +of_thread_detach(of_thread_t thread) +{ +#if defined(OF_HAVE_PTHREADS) + return !pthread_detach(thread); +#elif defined(_WIN32) + /* FIXME */ + return YES; +#endif +} static OF_INLINE void of_thread_exit() { #if defined(OF_HAVE_PTHREADS) Index: tests/OFStringTests.m ================================================================== --- tests/OFStringTests.m +++ tests/OFStringTests.m @@ -135,12 +135,12 @@ @"file://testfile.txt"] encoding: OF_STRING_ENCODING_ISO_8859_1]) && [s[1] isEqual: @"testäöü"]) TEST(@"-[appendCStringWithLength:]", - R([s[0] appendCString: "foobarqux" + 3 - withLength: 3]) && [s[0] isEqual: @"foobar"]) + R([s[0] appendCString: "foo\xEF\xBB\xBF" "barqux" + 3 + withLength: 6]) && [s[0] isEqual: @"foobar"]) EXPECT_EXCEPTION(@"Detection of invalid UTF-8 encoding #1", OFInvalidEncodingException, [OFString stringWithCString: "\xE0\x80"]) EXPECT_EXCEPTION(@"Detection of invalid UTF-8 encoding #2", Index: tests/OFXMLParserTests.m ================================================================== --- tests/OFXMLParserTests.m +++ tests/OFXMLParserTests.m @@ -374,8 +374,23 @@ OFMalformedXMLException, [parser parseString: @"a"]) EXPECT_EXCEPTION(@"Detection of junk after the document #2", OFMalformedXMLException, [parser parseString: @""]) + + parser = [OFXMLParser parser]; + EXPECT_EXCEPTION(@"Detection of invalid XML processing instructions #2", + OFMalformedXMLException, + [parser parseString: @""]) + + parser = [OFXMLParser parser]; + EXPECT_EXCEPTION(@"Detection of invalid XML processing instructions #3", + OFMalformedXMLException, + [parser parseString: @""]) + [pool drain]; } @end Index: utils/objfw-config.in ================================================================== --- utils/objfw-config.in +++ utils/objfw-config.in @@ -35,11 +35,11 @@ LIBS="-L${libdir} -lobjfw @LIBS@" PLUGIN_CFLAGS="@PLUGIN_CFLAGS@" PLUGIN_LDFLAGS="@PLUGIN_LDFLAGS@" PLUGIN_SUFFIX="@PLUGIN_SUFFIX@" PROG_SUFFIX="@EXEEXT@" -VERSION="0.4-dev" +VERSION="0.5.2" show_help() { cat <<__EOF__ objfw-config: Available arguments are: