Fixed issue #98: support for wrongly padded B64 words.
This commit is contained in:
parent
5e4bca3747
commit
e7739c0efe
@ -74,8 +74,7 @@ word::word(const string& buffer, const charset& charset, const string& lang)
|
|||||||
|
|
||||||
shared_ptr <word> word::parseNext
|
shared_ptr <word> word::parseNext
|
||||||
(const parsingContext& ctx, const string& buffer, const size_t position,
|
(const parsingContext& ctx, const string& buffer, const size_t position,
|
||||||
const size_t end, size_t* newPosition,
|
const size_t end, size_t* newPosition, parserState* state)
|
||||||
bool prevIsEncoded, bool* isEncoded, bool isFirst)
|
|
||||||
{
|
{
|
||||||
size_t pos = position;
|
size_t pos = position;
|
||||||
|
|
||||||
@ -131,7 +130,7 @@ shared_ptr <word> word::parseNext
|
|||||||
|
|
||||||
if (!unencoded.empty())
|
if (!unencoded.empty())
|
||||||
{
|
{
|
||||||
if (prevIsEncoded && !isFirst)
|
if (state->prevIsEncoded && !state->isFirst)
|
||||||
unencoded = whiteSpaces + unencoded;
|
unencoded = whiteSpaces + unencoded;
|
||||||
|
|
||||||
shared_ptr <word> w = make_shared <word>(unencoded, defaultCharset);
|
shared_ptr <word> w = make_shared <word>(unencoded, defaultCharset);
|
||||||
@ -140,8 +139,8 @@ shared_ptr <word> word::parseNext
|
|||||||
if (newPosition)
|
if (newPosition)
|
||||||
*newPosition = pos;
|
*newPosition = pos;
|
||||||
|
|
||||||
if (isEncoded)
|
state->prevIsEncoded = false;
|
||||||
*isEncoded = false;
|
state->isFirst = false;
|
||||||
|
|
||||||
return (w);
|
return (w);
|
||||||
}
|
}
|
||||||
@ -192,13 +191,13 @@ shared_ptr <word> word::parseNext
|
|||||||
pos += 2; // ?=
|
pos += 2; // ?=
|
||||||
|
|
||||||
shared_ptr <word> w = make_shared <word>();
|
shared_ptr <word> w = make_shared <word>();
|
||||||
w->parse(ctx, buffer, wordStart, pos, NULL);
|
w->parseWithState(ctx, buffer, wordStart, pos, NULL, state);
|
||||||
|
|
||||||
if (newPosition)
|
if (newPosition)
|
||||||
*newPosition = pos;
|
*newPosition = pos;
|
||||||
|
|
||||||
if (isEncoded)
|
state->prevIsEncoded = true;
|
||||||
*isEncoded = true;
|
state->isFirst = false;
|
||||||
|
|
||||||
return (w);
|
return (w);
|
||||||
}
|
}
|
||||||
@ -208,7 +207,7 @@ shared_ptr <word> word::parseNext
|
|||||||
|
|
||||||
if (startPos != end)
|
if (startPos != end)
|
||||||
{
|
{
|
||||||
if (prevIsEncoded && !isFirst)
|
if (state->prevIsEncoded && !state->isFirst)
|
||||||
unencoded = whiteSpaces + unencoded;
|
unencoded = whiteSpaces + unencoded;
|
||||||
|
|
||||||
unencoded += buffer.substr(startPos, end - startPos);
|
unencoded += buffer.substr(startPos, end - startPos);
|
||||||
@ -223,8 +222,8 @@ shared_ptr <word> word::parseNext
|
|||||||
if (newPosition)
|
if (newPosition)
|
||||||
*newPosition = end;
|
*newPosition = end;
|
||||||
|
|
||||||
if (isEncoded)
|
state->prevIsEncoded = false;
|
||||||
*isEncoded = false;
|
state->isFirst = false;
|
||||||
|
|
||||||
return (w);
|
return (w);
|
||||||
}
|
}
|
||||||
@ -242,9 +241,9 @@ const std::vector <shared_ptr <word> > word::parseMultiple
|
|||||||
|
|
||||||
size_t pos = position;
|
size_t pos = position;
|
||||||
|
|
||||||
bool prevIsEncoded = false;
|
parserState state;
|
||||||
|
|
||||||
while ((w = word::parseNext(ctx, buffer, pos, end, &pos, prevIsEncoded, &prevIsEncoded, (w == NULL))) != NULL)
|
while ((w = word::parseNext(ctx, buffer, pos, end, &pos, &state)) != NULL)
|
||||||
res.push_back(w);
|
res.push_back(w);
|
||||||
|
|
||||||
if (newPosition)
|
if (newPosition)
|
||||||
@ -257,6 +256,14 @@ const std::vector <shared_ptr <word> > word::parseMultiple
|
|||||||
void word::parseImpl
|
void word::parseImpl
|
||||||
(const parsingContext& ctx, const string& buffer, const size_t position,
|
(const parsingContext& ctx, const string& buffer, const size_t position,
|
||||||
const size_t end, size_t* newPosition)
|
const size_t end, size_t* newPosition)
|
||||||
|
{
|
||||||
|
parseWithState(ctx, buffer, position, end, newPosition, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void word::parseWithState
|
||||||
|
(const parsingContext& ctx, const string& buffer, const size_t position,
|
||||||
|
const size_t end, size_t* newPosition, parserState* state)
|
||||||
{
|
{
|
||||||
if (position + 6 < end && // 6 = "=?(.+)?(.*)?="
|
if (position + 6 < end && // 6 = "=?(.+)?(.*)?="
|
||||||
buffer[position] == '=' && buffer[position + 1] == '?')
|
buffer[position] == '=' && buffer[position + 1] == '?')
|
||||||
@ -319,12 +326,19 @@ void word::parseImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Decode text
|
// Decode text
|
||||||
|
string encodedBuffer(dataPos, dataEnd);
|
||||||
string decodedBuffer;
|
string decodedBuffer;
|
||||||
|
|
||||||
utility::inputStreamStringAdapter ein(string(dataPos, dataEnd));
|
if (state && !state->undecodedBytes.empty())
|
||||||
|
{
|
||||||
|
encodedBuffer = state->undecodedBytes + encodedBuffer;
|
||||||
|
state->undecodedBytes.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
utility::inputStreamStringAdapter ein(encodedBuffer);
|
||||||
utility::outputStreamStringAdapter eout(decodedBuffer);
|
utility::outputStreamStringAdapter eout(decodedBuffer);
|
||||||
|
|
||||||
theEncoder->decode(ein, eout);
|
const size_t decodedLen = theEncoder->decode(ein, eout);
|
||||||
|
|
||||||
m_buffer = decodedBuffer;
|
m_buffer = decodedBuffer;
|
||||||
|
|
||||||
@ -333,6 +347,21 @@ void word::parseImpl
|
|||||||
if (newPosition)
|
if (newPosition)
|
||||||
*newPosition = (p - buffer.begin());
|
*newPosition = (p - buffer.begin());
|
||||||
|
|
||||||
|
// For Base64 encoding, ensure all bytes have been decoded.
|
||||||
|
// If there are remaining bytes, keep them for the next run.
|
||||||
|
//
|
||||||
|
// This allows decoding some insanities like:
|
||||||
|
// =?utf-8?B?5Lit5?= =?utf-8?B?paH?=
|
||||||
|
if (*encPos == 'B' || *encPos == 'b')
|
||||||
|
{
|
||||||
|
const size_t actualEncodedLen = encodedBuffer.length();
|
||||||
|
const size_t theoricalEncodedLen =
|
||||||
|
((decodedLen + ((decodedLen % 3) ? (3 - (decodedLen % 3)) : 0) ) / 3) * 4;
|
||||||
|
|
||||||
|
if (state && actualEncodedLen != theoricalEncodedLen)
|
||||||
|
state->undecodedBytes.assign(dataPos + theoricalEncodedLen, dataEnd);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,6 +151,20 @@ public:
|
|||||||
bool prevWordIsEncoded;
|
bool prevWordIsEncoded;
|
||||||
bool lastCharIsSpace;
|
bool lastCharIsSpace;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class parserState
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
parserState()
|
||||||
|
: prevIsEncoded(false), isFirst(true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool prevIsEncoded;
|
||||||
|
bool isFirst;
|
||||||
|
std::string undecodedBytes;
|
||||||
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
@ -169,6 +183,14 @@ protected:
|
|||||||
const size_t curLinePos = 0,
|
const size_t curLinePos = 0,
|
||||||
size_t* newLinePos = NULL) const;
|
size_t* newLinePos = NULL) const;
|
||||||
|
|
||||||
|
void parseWithState
|
||||||
|
(const parsingContext& ctx,
|
||||||
|
const string& buffer,
|
||||||
|
const size_t position,
|
||||||
|
const size_t end,
|
||||||
|
size_t* newPosition,
|
||||||
|
parserState* state);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
using component::generate;
|
using component::generate;
|
||||||
@ -193,9 +215,7 @@ private:
|
|||||||
const size_t position,
|
const size_t position,
|
||||||
const size_t end,
|
const size_t end,
|
||||||
size_t* newPosition,
|
size_t* newPosition,
|
||||||
bool prevIsEncoded,
|
parserState* state);
|
||||||
bool* isEncoded,
|
|
||||||
bool isFirst);
|
|
||||||
|
|
||||||
static const std::vector <shared_ptr <word> > parseMultiple
|
static const std::vector <shared_ptr <word> > parseMultiple
|
||||||
(const parsingContext& ctx,
|
(const parsingContext& ctx,
|
||||||
|
@ -59,6 +59,8 @@ VMIME_TEST_SUITE_BEGIN(textTest)
|
|||||||
VMIME_TEST(testInternationalizedEmail_UTF8)
|
VMIME_TEST(testInternationalizedEmail_UTF8)
|
||||||
VMIME_TEST(testInternationalizedEmail_nonUTF8)
|
VMIME_TEST(testInternationalizedEmail_nonUTF8)
|
||||||
VMIME_TEST(testInternationalizedEmail_folding)
|
VMIME_TEST(testInternationalizedEmail_folding)
|
||||||
|
|
||||||
|
VMIME_TEST(testWronglyPaddedB64Words)
|
||||||
VMIME_TEST_LIST_END
|
VMIME_TEST_LIST_END
|
||||||
|
|
||||||
|
|
||||||
@ -595,5 +597,25 @@ VMIME_TEST_SUITE_BEGIN(textTest)
|
|||||||
" encoded text", w2.generate(20));
|
" encoded text", w2.generate(20));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void testWronglyPaddedB64Words()
|
||||||
|
{
|
||||||
|
vmime::text outText;
|
||||||
|
|
||||||
|
vmime::text::decodeAndUnfold("=?utf-8?B?5Lit5?=\n =?utf-8?B?paH?=", &outText);
|
||||||
|
|
||||||
|
VASSERT_EQ("1", "\xe4\xb8\xad\xe6\x96\x87",
|
||||||
|
outText.getConvertedText(vmime::charset("utf-8")));
|
||||||
|
|
||||||
|
vmime::text::decodeAndUnfold("=?utf-8?B?5Lit5p?=\n =?utf-8?B?aH?=", &outText);
|
||||||
|
|
||||||
|
VASSERT_EQ("2", "\xe4\xb8\xad\xe6\x96\x87",
|
||||||
|
outText.getConvertedText(vmime::charset("utf-8")));
|
||||||
|
|
||||||
|
vmime::text::decodeAndUnfold("=?utf-8?B?5Lit5pa?=\n =?utf-8?B?H?=", &outText);
|
||||||
|
|
||||||
|
VASSERT_EQ("3", "\xe4\xb8\xad\xe6\x96\x87",
|
||||||
|
outText.getConvertedText(vmime::charset("utf-8")));
|
||||||
|
}
|
||||||
|
|
||||||
VMIME_TEST_SUITE_END
|
VMIME_TEST_SUITE_END
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user