Index: src/OFStream.h ================================================================== --- src/OFStream.h +++ src/OFStream.h @@ -674,10 +674,33 @@ * @throw OFReadFailedException Reading failed * @throw OFNotOpenException The stream is not open */ - (OFData *)readDataUntilEndOfStream; +/** + * @brief Reads a string until a `\0` appears in the stream or the end of the + * stream is reached. + * + * @throw OFReadFailedException Reading failed + * @throw OFInvalidEncodingException The string read from the stream has + * invalid encoding + * @throw OFNotOpenException The stream is not open + */ +- (OFString *)readString; + +/** + * @brief Reads a string with the specified encoding until a `\0` appears in + * the stream or the end of the stream is reached. + * + * @param encoding The encoding of the string to read from the stream + * @throw OFReadFailedException Reading failed + * @throw OFInvalidEncodingException The string read from the stream has + * invalid encoding + * @throw OFNotOpenException The stream is not open + */ +- (OFString *)readStringWithEncoding: (OFStringEncoding)encoding; + /** * @brief Reads a string with the specified length from the stream. * * If `\0` appears in the stream, the string will be truncated at the `\0` and * the rest of the bytes of the string will be lost. This way, reading from the @@ -834,10 +857,33 @@ runLoopMode: (OFRunLoopMode)runLoopMode block: (OFStreamAsyncReadLineBlock)block; # endif #endif +/** + * @brief Tries to read a string until a `\0` appears in the stream or the end + * of the stream is reached. + * + * @throw OFReadFailedException Reading failed + * @throw OFInvalidEncodingException The string read from the stream has + * invalid encoding + * @throw OFNotOpenException The stream is not open + */ +- (OFString *)tryReadString; + +/** + * @brief Tries to read a string with the specified encoding until a `\0` + * appears in the stream or the end of the stream is reached. + * + * @param encoding The encoding of the string to read from the stream + * @throw OFReadFailedException Reading failed + * @throw OFInvalidEncodingException The string read from the stream has + * invalid encoding + * @throw OFNotOpenException The stream is not open + */ +- (OFString *)tryReadStringWithEncoding: (OFStringEncoding)encoding; + /** * @brief Tries to read a line from the stream (see @ref readLine) and returns * `nil` if no complete line has been received yet. * * @return The line that was read, autoreleased, or `nil` if the line is not Index: src/OFStream.m ================================================================== --- src/OFStream.m +++ src/OFStream.m @@ -425,10 +425,26 @@ } [data makeImmutable]; return data; } + +- (OFString *)readString +{ + return [self readStringWithEncoding: OFStringEncodingUTF8]; +} + +- (OFString *)readStringWithEncoding: (OFStringEncoding)encoding +{ + OFString *string = nil; + + while ((string = [self tryReadStringWithEncoding: encoding]) == nil) + if (self.atEndOfStream) + return nil; + + return string; +} - (OFString *)readStringWithLength: (size_t)length { return [self readStringWithLength: length encoding: OFStringEncodingUTF8]; @@ -672,10 +688,148 @@ block: block delegate: nil]; } # endif #endif + +- (OFString *)tryReadString +{ + return [self tryReadStringWithEncoding: OFStringEncodingUTF8]; +} + +- (OFString *)tryReadStringWithEncoding: (OFStringEncoding)encoding +{ + size_t pageSize, bufferLength; + char *buffer, *readBuffer; + OFString *ret; + + /* Look if there's something in our buffer */ + if (!_waitingForDelimiter && _readBuffer != NULL) { + for (size_t i = 0; i < _readBufferLength; i++) { + if (_readBuffer[i] == '\0') { + ret = [OFString + stringWithCString: _readBuffer + encoding: encoding + length: i]; + + _readBuffer += i + 1; + _readBufferLength -= i + 1; + + _waitingForDelimiter = false; + return ret; + } + } + } + + /* Read and see if we got a \0 */ + pageSize = [OFSystemInfo pageSize]; + buffer = OFAllocMemory(1, pageSize); + + @try { + if ([self lowlevelIsAtEndOfStream]) { + if (_readBuffer == NULL) { + _waitingForDelimiter = false; + return nil; + } + + ret = [OFString stringWithCString: _readBuffer + encoding: encoding + length: _readBufferLength]; + + OFFreeMemory(_readBufferMemory); + _readBuffer = _readBufferMemory = NULL; + _readBufferLength = 0; + + _waitingForDelimiter = false; + return ret; + } + + bufferLength = [self lowlevelReadIntoBuffer: buffer + length: pageSize]; + + /* Look if there's a \0 */ + for (size_t i = 0; i < bufferLength; i++) { + if (buffer[i] == '\0') { + size_t retLength; + char *retCString; + + retLength = _readBufferLength + i; + retCString = OFAllocMemory(retLength, 1); + + memcpy(retCString, _readBuffer, + _readBufferLength); + memcpy(retCString + _readBufferLength, + buffer, i); + + @try { + ret = [OFString + stringWithCString: retCString + encoding: encoding + length: retLength]; + } @catch (id e) { + if (bufferLength > 0) { + /* + * Append data to _readBuffer + * to prevent loss of data. + */ + readBuffer = OFAllocMemory( + _readBufferLength + + bufferLength, 1); + + memcpy(readBuffer, _readBuffer, + _readBufferLength); + memcpy(readBuffer + + _readBufferLength, + buffer, bufferLength); + + OFFreeMemory(_readBufferMemory); + _readBuffer = readBuffer; + _readBufferMemory = readBuffer; + _readBufferLength += + bufferLength; + } + + @throw e; + } @finally { + OFFreeMemory(retCString); + } + + readBuffer = OFAllocMemory(bufferLength - i - 1, + 1); + if (readBuffer != NULL) + memcpy(readBuffer, buffer + i + 1, + bufferLength - i - 1); + + OFFreeMemory(_readBufferMemory); + _readBuffer = _readBufferMemory = readBuffer; + _readBufferLength = bufferLength - i - 1; + + _waitingForDelimiter = false; + return ret; + } + } + + /* No \0 was found */ + if (bufferLength > 0) { + readBuffer = OFAllocMemory( + _readBufferLength + bufferLength, 1); + + memcpy(readBuffer, _readBuffer, _readBufferLength); + memcpy(readBuffer + _readBufferLength, + buffer, bufferLength); + + OFFreeMemory(_readBufferMemory); + _readBuffer = _readBufferMemory = readBuffer; + _readBufferLength += bufferLength; + } + } @finally { + OFFreeMemory(buffer); + } + + _waitingForDelimiter = true; + return nil; +} - (OFString *)tryReadLine { return [self tryReadLineWithEncoding: OFStringEncodingUTF8]; } @@ -838,11 +992,10 @@ } _waitingForDelimiter = true; return nil; } - - (OFString *)readUntilDelimiter: (OFString *)delimiter { return [self readUntilDelimiter: delimiter encoding: OFStringEncodingUTF8]; Index: tests/OFStreamTests.m ================================================================== --- tests/OFStreamTests.m +++ tests/OFStreamTests.m @@ -48,20 +48,26 @@ string = [stream readLine]; OTAssertNotNil(string); OTAssertEqual(string.length, pageSize - 3); OTAssertEqual(strcmp(string.UTF8String, cString), 0); + + string = [stream readString]; + OTAssertEqualObjects(string, @"aaa"); + + string = [stream readString]; + OTAssertEqualObjects(string, @"b"); } @finally { OFFreeMemory(cString); } } @end @implementation OFTestStream - (bool)lowlevelIsAtEndOfStream { - return (_state > 1); + return (_state > 7); } - (size_t)lowlevelReadIntoBuffer: (void *)buffer length: (size_t)size { size_t pageSize = [OFSystemInfo pageSize]; @@ -82,10 +88,44 @@ memcpy(buffer, "oo\n", 3); memset((char *)buffer + 3, 'X', pageSize - 3); _state++; return pageSize; + case 2: + if (size < 1) + return 0; + + memcpy(buffer, "", 1); + + _state++; + return 1; + case 3: + case 4: + case 5: + if (size < 1) + return 0; + + memcpy(buffer, "a", 1); + + _state++; + return 1; + case 6: + if (size < 1) + return 0; + + memcpy(buffer, "", 1); + + _state++; + return 1; + case 7: + if (size < 1) + return 0; + + memcpy(buffer, "b", 1); + + _state++; + return 1; } return 0; } @end