diff --git a/src/vmime/headerField.cpp b/src/vmime/headerField.cpp index f4c6187e..b88a5cd2 100644 --- a/src/vmime/headerField.cpp +++ b/src/vmime/headerField.cpp @@ -144,75 +144,18 @@ shared_ptr headerField::parseNext const size_t contentsStart = pos; size_t contentsEnd = 0; - // Extract the field value - while (pos < end) + bool firstLine = true; + + // Parse field value, taking care of line folding (value on multiple lines) + for (size_t eol = 0 ; parserHelpers::findEOL(buffer, pos, end, &eol) ; pos = eol) { - c = buffer[pos]; - - // Check for folded line - if (c == '\r' && pos + 2 < end && buffer[pos + 1] == '\n' && - (buffer[pos + 2] == ' ' || buffer[pos + 2] == '\t')) - { - pos += 3; - } - // Check for end of contents - if (c == '\r' && pos + 1 < end && buffer[pos + 1] == '\n') - { - contentsEnd = pos; - pos += 2; + // If the line does not start with a folding indicator (SPACE or TAB), + // and this is not the first line, then stop parsing lines + if (!firstLine && !(buffer[pos] == ' ' || buffer[pos] == '\t')) break; - } - else if (c == '\n') - { - contentsEnd = pos; - ++pos; - break; - } - while (pos < end) - { - c = buffer[pos]; - - // Check for end of line - if (c == '\r' && pos + 1 < end && buffer[pos + 1] == '\n') - { - contentsEnd = pos; - pos += 2; - break; - } - else if (c == '\n') - { - contentsEnd = pos; - ++pos; - break; - } - - ++pos; - } - - // Handle the case of folded lines - if (buffer[pos] == ' ' || buffer[pos] == '\t') - { - // This is a folding white-space: we keep it as is and - // we continue with contents parsing... - - // If the line contains only space characters, we assume it is - // the end of the headers. This is not strictly standard-compliant - // but, hey, we can't fail when parsing some malformed mails... - while (pos < end && (buffer[pos] == ' ' || buffer[pos] == '\t')) - ++pos; - - if ((pos < end && buffer[pos] == '\n') || - (pos + 1 < end && buffer[pos] == '\r' && buffer[pos + 1] == '\n')) - { - break; - } - } - else - { - // End of this field - break; - } + contentsEnd = eol; + firstLine = false; } if (pos == end && contentsEnd == 0) diff --git a/src/vmime/parserHelpers.hpp b/src/vmime/parserHelpers.hpp index ce3f422a..430487b4 100644 --- a/src/vmime/parserHelpers.hpp +++ b/src/vmime/parserHelpers.hpp @@ -87,6 +87,45 @@ public: const unsigned int x = static_cast (c); return (x >= 0x20 && x <= 0x7E); } + + + /** Finds the next EOL sequence in the specified buffer. + * An EOL sequence may be a CR+LF sequence, or a LF sequence. + * + * @param buffer search buffer + * @param currentPos start searching from this position + * @param end stop searching at this position + * @param eol will receive the position after the EOL sequence + * @return true if an EOL sequence has been found, or false if + * no EOL sequence was found before the end of the buffer + */ + static bool findEOL(const string& buffer, const size_t currentPos, const size_t end, size_t* eol) + { + size_t pos = currentPos; + + if (pos == end) + return false; + + while (pos < end) + { + if (buffer[pos] == '\r' && pos + 1 < end && buffer[pos + 1] == '\n') + { + *eol = pos + 2; + return true; + } + else if (buffer[pos] == '\n') + { + *eol = pos + 1; + return true; + } + + ++pos; + } + + *eol = end; + + return true; + } }; diff --git a/tests/parser/headerFieldTest.cpp b/tests/parser/headerFieldTest.cpp index 5281a3a2..0642dae7 100644 --- a/tests/parser/headerFieldTest.cpp +++ b/tests/parser/headerFieldTest.cpp @@ -30,6 +30,7 @@ VMIME_TEST_SUITE_BEGIN(headerFieldTest) VMIME_TEST(testBadValueType) VMIME_TEST(testValueOnNextLine) VMIME_TEST(testStripSpacesAtEnd) + VMIME_TEST(testValueWithEmptyLine) VMIME_TEST_LIST_END @@ -84,4 +85,20 @@ VMIME_TEST_SUITE_BEGIN(headerFieldTest) VASSERT_EQ("Field value", toHex("field data"), toHex(hvalue->getWholeBuffer())); } + void testValueWithEmptyLine() + { + vmime::parsingContext ctx; + + const vmime::string buffer = "Field: \r\n\tdata1\r\n\tdata2\r\n\t\r\n\tdata3"; + + vmime::shared_ptr hfield = + vmime::headerField::parseNext(ctx, buffer, 0, buffer.size()); + + vmime::shared_ptr hvalue = + hfield->getValue (); + + VASSERT_EQ("Field name", "Field", hfield->getName()); + VASSERT_EQ("Field value", "data1 data2 data3", hvalue->getWholeBuffer()); + } + VMIME_TEST_SUITE_END