diff options
Diffstat (limited to 'tools/source/inet')
-rw-r--r-- | tools/source/inet/inetmime.cxx | 1410 | ||||
-rw-r--r-- | tools/source/inet/inetmsg.cxx | 289 | ||||
-rw-r--r-- | tools/source/inet/inetstrm.cxx | 297 |
3 files changed, 1996 insertions, 0 deletions
diff --git a/tools/source/inet/inetmime.cxx b/tools/source/inet/inetmime.cxx new file mode 100644 index 000000000..fd00fe3a4 --- /dev/null +++ b/tools/source/inet/inetmime.cxx @@ -0,0 +1,1410 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <algorithm> +#include <limits> +#include <forward_list> +#include <memory> + +#include <sal/log.hxx> +#include <rtl/ustring.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/tencinfo.h> +#include <tools/inetmime.hxx> +#include <rtl/character.hxx> + +namespace { + +rtl_TextEncoding getCharsetEncoding(const char * pBegin, + const char * pEnd); + +/** Check for US-ASCII white space character. + + @param nChar Some UCS-4 character. + + @return True if nChar is a US-ASCII white space character (US-ASCII + 0x09 or 0x20). + */ +bool isWhiteSpace(sal_uInt32 nChar) +{ + return nChar == '\t' || nChar == ' '; +} + +/** Get the Base 64 digit weight of a US-ASCII character. + + @param nChar Some UCS-4 character. + + @return If nChar is a US-ASCII Base 64 digit character (US-ASCII + 'A'--'F', or 'a'--'f', '0'--'9', '+', or '/'), return the + corresponding weight (0--63); if nChar is the US-ASCII Base 64 padding + character (US-ASCII '='), return -1; otherwise, return -2. + */ +int getBase64Weight(sal_uInt32 nChar) +{ + return rtl::isAsciiUpperCase(nChar) ? int(nChar - 'A') : + rtl::isAsciiLowerCase(nChar) ? int(nChar - 'a' + 26) : + rtl::isAsciiDigit(nChar) ? int(nChar - '0' + 52) : + nChar == '+' ? 62 : + nChar == '/' ? 63 : + nChar == '=' ? -1 : -2; +} + +bool startsWithLineFolding(const sal_Unicode * pBegin, + const sal_Unicode * pEnd) +{ + DBG_ASSERT(pBegin && pBegin <= pEnd, + "startsWithLineFolding(): Bad sequence"); + + return pEnd - pBegin >= 3 && pBegin[0] == 0x0D && pBegin[1] == 0x0A + && isWhiteSpace(pBegin[2]); // CR, LF +} + +rtl_TextEncoding translateFromMIME(rtl_TextEncoding + eEncoding) +{ +#if defined(_WIN32) + return eEncoding == RTL_TEXTENCODING_ISO_8859_1 ? + RTL_TEXTENCODING_MS_1252 : eEncoding; +#else + return eEncoding; +#endif +} + +bool isMIMECharsetEncoding(rtl_TextEncoding eEncoding) +{ + return rtl_isOctetTextEncoding(eEncoding); +} + +std::unique_ptr<sal_Unicode[]> convertToUnicode(const char * pBegin, + const char * pEnd, + rtl_TextEncoding eEncoding, + sal_Size & rSize) +{ + if (eEncoding == RTL_TEXTENCODING_DONTKNOW) + return nullptr; + rtl_TextToUnicodeConverter hConverter + = rtl_createTextToUnicodeConverter(eEncoding); + rtl_TextToUnicodeContext hContext + = rtl_createTextToUnicodeContext(hConverter); + std::unique_ptr<sal_Unicode[]> pBuffer; + sal_uInt32 nInfo; + for (sal_Size nBufferSize = pEnd - pBegin;; + nBufferSize += nBufferSize / 3 + 1) + { + pBuffer.reset(new sal_Unicode[nBufferSize]); + sal_Size nSrcCvtBytes; + rSize = rtl_convertTextToUnicode( + hConverter, hContext, pBegin, pEnd - pBegin, pBuffer.get(), + nBufferSize, + RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR + | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR + | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR, + &nInfo, &nSrcCvtBytes); + if (nInfo != RTL_TEXTTOUNICODE_INFO_DESTBUFFERTOOSMALL) + break; + pBuffer.reset(); + rtl_resetTextToUnicodeContext(hConverter, hContext); + } + rtl_destroyTextToUnicodeContext(hConverter, hContext); + rtl_destroyTextToUnicodeConverter(hConverter); + if (nInfo != 0) + { + pBuffer.reset(); + } + return pBuffer; +} + +/** Put the UTF-16 encoding of a UTF-32 character into a buffer. + + @param pBuffer Points to a buffer, must not be null. + + @param nUTF32 A UTF-32 character, must be in the range 0..0x10FFFF. + + @return A pointer past the UTF-16 characters put into the buffer + (i.e., pBuffer + 1 or pBuffer + 2). + */ +sal_Unicode * putUTF32Character(sal_Unicode * pBuffer, + sal_uInt32 nUTF32) +{ + DBG_ASSERT(rtl::isUnicodeCodePoint(nUTF32), "putUTF32Character(): Bad char"); + if (nUTF32 < 0x10000) + *pBuffer++ = sal_Unicode(nUTF32); + else + { + nUTF32 -= 0x10000; + *pBuffer++ = sal_Unicode(0xD800 | (nUTF32 >> 10)); + *pBuffer++ = sal_Unicode(0xDC00 | (nUTF32 & 0x3FF)); + } + return pBuffer; +} + +void writeUTF8(OStringBuffer & rSink, sal_uInt32 nChar) +{ + // See RFC 2279 for a discussion of UTF-8. + DBG_ASSERT(nChar < 0x80000000, "writeUTF8(): Bad char"); + + if (nChar < 0x80) + rSink.append(char(nChar)); + else if (nChar < 0x800) + rSink.append(char(nChar >> 6 | 0xC0)) + .append(char((nChar & 0x3F) | 0x80)); + else if (nChar < 0x10000) + rSink.append(char(nChar >> 12 | 0xE0)) + .append(char((nChar >> 6 & 0x3F) | 0x80)) + .append(char((nChar & 0x3F) | 0x80)); + else if (nChar < 0x200000) + rSink.append(char(nChar >> 18 | 0xF0)) + .append(char((nChar >> 12 & 0x3F) | 0x80)) + .append(char((nChar >> 6 & 0x3F) | 0x80)) + .append(char((nChar & 0x3F) | 0x80)); + else if (nChar < 0x4000000) + rSink.append(char(nChar >> 24 | 0xF8)) + .append(char((nChar >> 18 & 0x3F) | 0x80)) + .append(char((nChar >> 12 & 0x3F) | 0x80)) + .append(char((nChar >> 6 & 0x3F) | 0x80)) + .append(char((nChar & 0x3F) | 0x80)); + else + rSink.append(char(nChar >> 30 | 0xFC)) + .append(char((nChar >> 24 & 0x3F) | 0x80)) + .append(char((nChar >> 18 & 0x3F) | 0x80)) + .append(char((nChar >> 12 & 0x3F) | 0x80)) + .append(char((nChar >> 6 & 0x3F) | 0x80)) + .append(char((nChar & 0x3F) | 0x80)); +} + +bool translateUTF8Char(const char *& rBegin, + const char * pEnd, + sal_uInt32 & rCharacter) +{ + if (rBegin == pEnd || static_cast< unsigned char >(*rBegin) < 0x80 + || static_cast< unsigned char >(*rBegin) >= 0xFE) + return false; + + int nCount; + sal_uInt32 nMin; + sal_uInt32 nUCS4; + const char * p = rBegin; + if (static_cast< unsigned char >(*p) < 0xE0) + { + nCount = 1; + nMin = 0x80; + nUCS4 = static_cast< unsigned char >(*p) & 0x1F; + } + else if (static_cast< unsigned char >(*p) < 0xF0) + { + nCount = 2; + nMin = 0x800; + nUCS4 = static_cast< unsigned char >(*p) & 0xF; + } + else if (static_cast< unsigned char >(*p) < 0xF8) + { + nCount = 3; + nMin = 0x10000; + nUCS4 = static_cast< unsigned char >(*p) & 7; + } + else if (static_cast< unsigned char >(*p) < 0xFC) + { + nCount = 4; + nMin = 0x200000; + nUCS4 = static_cast< unsigned char >(*p) & 3; + } + else + { + nCount = 5; + nMin = 0x4000000; + nUCS4 = static_cast< unsigned char >(*p) & 1; + } + ++p; + + for (; nCount-- > 0; ++p) + if ((static_cast< unsigned char >(*p) & 0xC0) == 0x80) + nUCS4 = (nUCS4 << 6) | (static_cast< unsigned char >(*p) & 0x3F); + else + return false; + + if (!rtl::isUnicodeCodePoint(nUCS4) || nUCS4 < nMin) + return false; + + rCharacter = nUCS4; + rBegin = p; + return true; +} + +void appendISO88591(OUStringBuffer & rText, char const * pBegin, + char const * pEnd); + +struct Parameter +{ + OString m_aAttribute; + OString m_aCharset; + OString m_aLanguage; + OString m_aValue; + sal_uInt32 m_nSection; + bool m_bExtended; + + bool operator<(const Parameter& rhs) const // is used by std::list<Parameter>::sort + { + int nComp = m_aAttribute.compareTo(rhs.m_aAttribute); + return nComp < 0 || + (nComp == 0 && m_nSection < rhs.m_nSection); + } + struct IsSameSection // is used to check container for duplicates with std::any_of + { + const OString& rAttribute; + const sal_uInt32 nSection; + bool operator()(const Parameter& r) const + { return r.m_aAttribute == rAttribute && r.m_nSection == nSection; } + }; +}; + +typedef std::forward_list<Parameter> ParameterList; + +bool parseParameters(ParameterList const & rInput, + INetContentTypeParameterList * pOutput); + +// appendISO88591 + +void appendISO88591(OUStringBuffer & rText, char const * pBegin, + char const * pEnd) +{ + sal_Int32 nLength = pEnd - pBegin; + std::unique_ptr<sal_Unicode[]> pBuffer(new sal_Unicode[nLength]); + for (sal_Unicode * p = pBuffer.get(); pBegin != pEnd;) + *p++ = static_cast<unsigned char>(*pBegin++); + rText.append(pBuffer.get(), nLength); +} + +// parseParameters + +bool parseParameters(ParameterList const & rInput, + INetContentTypeParameterList * pOutput) +{ + if (pOutput) + pOutput->clear(); + + for (auto it = rInput.begin(), itPrev = rInput.end(); it != rInput.end() ; itPrev = it++) + { + if (it->m_nSection > 0 + && (itPrev == rInput.end() + || itPrev->m_nSection != it->m_nSection - 1 + || itPrev->m_aAttribute != it->m_aAttribute)) + return false; + } + + if (pOutput) + for (auto it = rInput.begin(), itNext = rInput.begin(); it != rInput.end(); it = itNext) + { + bool bCharset = !it->m_aCharset.isEmpty(); + rtl_TextEncoding eEncoding = RTL_TEXTENCODING_DONTKNOW; + if (bCharset) + eEncoding + = getCharsetEncoding(it->m_aCharset.getStr(), + it->m_aCharset.getStr() + + it->m_aCharset.getLength()); + OUStringBuffer aValue(64); + bool bBadEncoding = false; + itNext = it; + do + { + sal_Size nSize; + std::unique_ptr<sal_Unicode[]> pUnicode + = convertToUnicode(itNext->m_aValue.getStr(), + itNext->m_aValue.getStr() + + itNext->m_aValue.getLength(), + bCharset && it->m_bExtended ? + eEncoding : + RTL_TEXTENCODING_UTF8, + nSize); + if (!pUnicode && !(bCharset && it->m_bExtended)) + pUnicode = convertToUnicode( + itNext->m_aValue.getStr(), + itNext->m_aValue.getStr() + + itNext->m_aValue.getLength(), + RTL_TEXTENCODING_ISO_8859_1, nSize); + if (!pUnicode) + { + bBadEncoding = true; + break; + } + aValue.append(pUnicode.get(), static_cast<sal_Int32>(nSize)); + ++itNext; + } + while (itNext != rInput.end() && itNext->m_nSection != 0); + + if (bBadEncoding) + { + aValue.setLength(0); + itNext = it; + do + { + if (itNext->m_bExtended) + { + for (sal_Int32 i = 0; i < itNext->m_aValue.getLength(); ++i) + aValue.append( + static_cast<sal_Unicode>( + static_cast<unsigned char>(itNext->m_aValue[i]) + | 0xF800)); // map to unicode corporate use sub area + } + else + { + for (sal_Int32 i = 0; i < itNext->m_aValue.getLength(); ++i) + aValue.append( static_cast<char>(itNext->m_aValue[i]) ); + } + ++itNext; + } + while (itNext != rInput.end() && itNext->m_nSection != 0); + } + auto const ret = pOutput->insert( + {it->m_aAttribute, + {it->m_aCharset, it->m_aLanguage, aValue.makeStringAndClear(), !bBadEncoding}}); + SAL_INFO_IF(!ret.second, "tools", + "INetMIME: dropping duplicate parameter: " << it->m_aAttribute); + } + return true; +} + +/** Check whether some character is valid within an RFC 2045 <token>. + + @param nChar Some UCS-4 character. + + @return True if nChar is valid within an RFC 2047 <token> (US-ASCII + 'A'--'Z', 'a'--'z', '0'--'9', '!', '#', '$', '%', '&', ''', '*', '+', + '-', '.', '^', '_', '`', '{', '|', '}', or '~'). + */ +bool isTokenChar(sal_uInt32 nChar) +{ + static const bool aMap[128] + = { false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, true, false, true, true, true, true, true, // !"#$%&' + false, false, true, true, false, true, true, false, //()*+,-./ + true, true, true, true, true, true, true, true, //01234567 + true, true, false, false, false, false, false, false, //89:;<=>? + false, true, true, true, true, true, true, true, //@ABCDEFG + true, true, true, true, true, true, true, true, //HIJKLMNO + true, true, true, true, true, true, true, true, //PQRSTUVW + true, true, true, false, false, false, true, true, //XYZ[\]^_ + true, true, true, true, true, true, true, true, //`abcdefg + true, true, true, true, true, true, true, true, //hijklmno + true, true, true, true, true, true, true, true, //pqrstuvw + true, true, true, true, true, true, true, false //xyz{|}~ + }; + return rtl::isAscii(nChar) && aMap[nChar]; +} + +const sal_Unicode * skipComment(const sal_Unicode * pBegin, + const sal_Unicode * pEnd) +{ + DBG_ASSERT(pBegin && pBegin <= pEnd, + "skipComment(): Bad sequence"); + + if (pBegin != pEnd && *pBegin == '(') + { + sal_uInt32 nLevel = 0; + for (const sal_Unicode * p = pBegin; p != pEnd;) + switch (*p++) + { + case '(': + ++nLevel; + break; + + case ')': + if (--nLevel == 0) + return p; + break; + + case '\\': + if (p != pEnd) + ++p; + break; + } + } + return pBegin; +} + +const sal_Unicode * skipLinearWhiteSpaceComment(const sal_Unicode * + pBegin, + const sal_Unicode * + pEnd) +{ + DBG_ASSERT(pBegin && pBegin <= pEnd, + "skipLinearWhiteSpaceComment(): Bad sequence"); + + while (pBegin != pEnd) + switch (*pBegin) + { + case '\t': + case ' ': + ++pBegin; + break; + + case 0x0D: // CR + if (startsWithLineFolding(pBegin, pEnd)) + pBegin += 3; + else + return pBegin; + break; + + case '(': + { + const sal_Unicode * p = skipComment(pBegin, pEnd); + if (p == pBegin) + return pBegin; + pBegin = p; + break; + } + + default: + return pBegin; + } + return pBegin; +} + +const sal_Unicode * skipQuotedString(const sal_Unicode * pBegin, + const sal_Unicode * pEnd) +{ + DBG_ASSERT(pBegin && pBegin <= pEnd, + "skipQuotedString(): Bad sequence"); + + if (pBegin != pEnd && *pBegin == '"') + for (const sal_Unicode * p = pBegin + 1; p != pEnd;) + switch (*p++) + { + case 0x0D: // CR + if (pEnd - p < 2 || *p++ != 0x0A // LF + || !isWhiteSpace(*p++)) + return pBegin; + break; + + case '"': + return p; + + case '\\': + if (p != pEnd) + ++p; + break; + } + return pBegin; +} + +sal_Unicode const * scanParameters(sal_Unicode const * pBegin, + sal_Unicode const * pEnd, + INetContentTypeParameterList * + pParameters) +{ + ParameterList aList; + sal_Unicode const * pParameterBegin = pBegin; + for (sal_Unicode const * p = pParameterBegin;;) + { + pParameterBegin = skipLinearWhiteSpaceComment(p, pEnd); + if (pParameterBegin == pEnd || *pParameterBegin != ';') + break; + p = pParameterBegin + 1; + + sal_Unicode const * pAttributeBegin + = skipLinearWhiteSpaceComment(p, pEnd); + p = pAttributeBegin; + bool bDowncaseAttribute = false; + while (p != pEnd && isTokenChar(*p) && *p != '*') + { + bDowncaseAttribute = bDowncaseAttribute || rtl::isAsciiUpperCase(*p); + ++p; + } + if (p == pAttributeBegin) + break; + OString aAttribute(pAttributeBegin, p - pAttributeBegin, RTL_TEXTENCODING_ASCII_US); + if (bDowncaseAttribute) + aAttribute = aAttribute.toAsciiLowerCase(); + + sal_uInt32 nSection = 0; + if (p != pEnd && *p == '*') + { + ++p; + if (p != pEnd && rtl::isAsciiDigit(*p) + && !INetMIME::scanUnsigned(p, pEnd, false, nSection)) + break; + } + + bool bPresent = std::any_of(aList.begin(), aList.end(), + Parameter::IsSameSection{aAttribute, nSection}); + if (bPresent) + break; + + bool bExtended = false; + if (p != pEnd && *p == '*') + { + ++p; + bExtended = true; + } + + p = skipLinearWhiteSpaceComment(p, pEnd); + + if (p == pEnd || *p != '=') + break; + + p = skipLinearWhiteSpaceComment(p + 1, pEnd); + + OString aCharset; + OString aLanguage; + OString aValue; + if (bExtended) + { + if (nSection == 0) + { + sal_Unicode const * pCharsetBegin = p; + bool bDowncaseCharset = false; + while (p != pEnd && isTokenChar(*p) && *p != '\'') + { + bDowncaseCharset = bDowncaseCharset || rtl::isAsciiUpperCase(*p); + ++p; + } + if (p == pCharsetBegin) + break; + if (pParameters) + { + aCharset = OString( + pCharsetBegin, + p - pCharsetBegin, + RTL_TEXTENCODING_ASCII_US); + if (bDowncaseCharset) + aCharset = aCharset.toAsciiLowerCase(); + } + + if (p == pEnd || *p != '\'') + break; + ++p; + + sal_Unicode const * pLanguageBegin = p; + bool bDowncaseLanguage = false; + int nLetters = 0; + for (; p != pEnd; ++p) + if (rtl::isAsciiAlpha(*p)) + { + if (++nLetters > 8) + break; + bDowncaseLanguage = bDowncaseLanguage + || rtl::isAsciiUpperCase(*p); + } + else if (*p == '-') + { + if (nLetters == 0) + break; + nLetters = 0; + } + else + break; + if (nLetters == 0 || nLetters > 8) + break; + if (pParameters) + { + aLanguage = OString( + pLanguageBegin, + p - pLanguageBegin, + RTL_TEXTENCODING_ASCII_US); + if (bDowncaseLanguage) + aLanguage = aLanguage.toAsciiLowerCase(); + } + + if (p == pEnd || *p != '\'') + break; + ++p; + } + if (pParameters) + { + OStringBuffer aSink; + while (p != pEnd) + { + auto q = p; + sal_uInt32 nChar = INetMIME::getUTF32Character(q, pEnd); + if (rtl::isAscii(nChar) && !isTokenChar(nChar)) + break; + p = q; + if (nChar == '%' && p + 1 < pEnd) + { + int nWeight1 = INetMIME::getHexWeight(p[0]); + int nWeight2 = INetMIME::getHexWeight(p[1]); + if (nWeight1 >= 0 && nWeight2 >= 0) + { + aSink.append(char(nWeight1 << 4 | nWeight2)); + p += 2; + continue; + } + } + writeUTF8(aSink, nChar); + } + aValue = aSink.makeStringAndClear(); + } + else + while (p != pEnd && (isTokenChar(*p) || !rtl::isAscii(*p))) + ++p; + } + else if (p != pEnd && *p == '"') + if (pParameters) + { + OStringBuffer aSink(256); + bool bInvalid = false; + for (++p;;) + { + if (p == pEnd) + { + bInvalid = true; + break; + } + sal_uInt32 nChar = INetMIME::getUTF32Character(p, pEnd); + if (nChar == '"') + break; + else if (nChar == 0x0D) // CR + { + if (pEnd - p < 2 || *p++ != 0x0A // LF + || !isWhiteSpace(*p)) + { + bInvalid = true; + break; + } + nChar = static_cast<unsigned char>(*p++); + } + else if (nChar == '\\') + { + if (p == pEnd) + { + bInvalid = true; + break; + } + nChar = INetMIME::getUTF32Character(p, pEnd); + } + writeUTF8(aSink, nChar); + } + if (bInvalid) + break; + aValue = aSink.makeStringAndClear(); + } + else + { + sal_Unicode const * pStringEnd = skipQuotedString(p, pEnd); + if (p == pStringEnd) + break; + p = pStringEnd; + } + else + { + sal_Unicode const * pTokenBegin = p; + while (p != pEnd && (isTokenChar(*p) || !rtl::isAscii(*p))) + ++p; + if (p == pTokenBegin) + break; + if (pParameters) + aValue = OString( + pTokenBegin, p - pTokenBegin, + RTL_TEXTENCODING_UTF8); + } + aList.emplace_front(Parameter{aAttribute, aCharset, aLanguage, aValue, nSection, bExtended}); + } + aList.sort(); + return parseParameters(aList, pParameters) ? pParameterBegin : pBegin; +} + +bool equalIgnoreCase(const char * pBegin1, + const char * pEnd1, + const char * pString2) +{ + DBG_ASSERT(pBegin1 && pBegin1 <= pEnd1 && pString2, + "equalIgnoreCase(): Bad sequences"); + + while (*pString2 != 0) + if (pBegin1 == pEnd1 + || (rtl::toAsciiUpperCase(static_cast<unsigned char>(*pBegin1++)) + != rtl::toAsciiUpperCase( + static_cast<unsigned char>(*pString2++)))) + return false; + return pBegin1 == pEnd1; +} + +struct EncodingEntry +{ + char const * m_aName; + rtl_TextEncoding m_eEncoding; +}; + +// The source for the following table is <ftp://ftp.iana.org/in-notes/iana/ +// assignments/character-sets> as of Jan, 21 2000 12:46:00, unless otherwise +// noted: +static EncodingEntry const aEncodingMap[] + = { { "US-ASCII", RTL_TEXTENCODING_ASCII_US }, + { "ANSI_X3.4-1968", RTL_TEXTENCODING_ASCII_US }, + { "ISO-IR-6", RTL_TEXTENCODING_ASCII_US }, + { "ANSI_X3.4-1986", RTL_TEXTENCODING_ASCII_US }, + { "ISO_646.IRV:1991", RTL_TEXTENCODING_ASCII_US }, + { "ASCII", RTL_TEXTENCODING_ASCII_US }, + { "ISO646-US", RTL_TEXTENCODING_ASCII_US }, + { "US", RTL_TEXTENCODING_ASCII_US }, + { "IBM367", RTL_TEXTENCODING_ASCII_US }, + { "CP367", RTL_TEXTENCODING_ASCII_US }, + { "CSASCII", RTL_TEXTENCODING_ASCII_US }, + { "ISO-8859-1", RTL_TEXTENCODING_ISO_8859_1 }, + { "ISO_8859-1:1987", RTL_TEXTENCODING_ISO_8859_1 }, + { "ISO-IR-100", RTL_TEXTENCODING_ISO_8859_1 }, + { "ISO_8859-1", RTL_TEXTENCODING_ISO_8859_1 }, + { "LATIN1", RTL_TEXTENCODING_ISO_8859_1 }, + { "L1", RTL_TEXTENCODING_ISO_8859_1 }, + { "IBM819", RTL_TEXTENCODING_ISO_8859_1 }, + { "CP819", RTL_TEXTENCODING_ISO_8859_1 }, + { "CSISOLATIN1", RTL_TEXTENCODING_ISO_8859_1 }, + { "ISO-8859-2", RTL_TEXTENCODING_ISO_8859_2 }, + { "ISO_8859-2:1987", RTL_TEXTENCODING_ISO_8859_2 }, + { "ISO-IR-101", RTL_TEXTENCODING_ISO_8859_2 }, + { "ISO_8859-2", RTL_TEXTENCODING_ISO_8859_2 }, + { "LATIN2", RTL_TEXTENCODING_ISO_8859_2 }, + { "L2", RTL_TEXTENCODING_ISO_8859_2 }, + { "CSISOLATIN2", RTL_TEXTENCODING_ISO_8859_2 }, + { "ISO-8859-3", RTL_TEXTENCODING_ISO_8859_3 }, + { "ISO_8859-3:1988", RTL_TEXTENCODING_ISO_8859_3 }, + { "ISO-IR-109", RTL_TEXTENCODING_ISO_8859_3 }, + { "ISO_8859-3", RTL_TEXTENCODING_ISO_8859_3 }, + { "LATIN3", RTL_TEXTENCODING_ISO_8859_3 }, + { "L3", RTL_TEXTENCODING_ISO_8859_3 }, + { "CSISOLATIN3", RTL_TEXTENCODING_ISO_8859_3 }, + { "ISO-8859-4", RTL_TEXTENCODING_ISO_8859_4 }, + { "ISO_8859-4:1988", RTL_TEXTENCODING_ISO_8859_4 }, + { "ISO-IR-110", RTL_TEXTENCODING_ISO_8859_4 }, + { "ISO_8859-4", RTL_TEXTENCODING_ISO_8859_4 }, + { "LATIN4", RTL_TEXTENCODING_ISO_8859_4 }, + { "L4", RTL_TEXTENCODING_ISO_8859_4 }, + { "CSISOLATIN4", RTL_TEXTENCODING_ISO_8859_4 }, + { "ISO-8859-5", RTL_TEXTENCODING_ISO_8859_5 }, + { "ISO_8859-5:1988", RTL_TEXTENCODING_ISO_8859_5 }, + { "ISO-IR-144", RTL_TEXTENCODING_ISO_8859_5 }, + { "ISO_8859-5", RTL_TEXTENCODING_ISO_8859_5 }, + { "CYRILLIC", RTL_TEXTENCODING_ISO_8859_5 }, + { "CSISOLATINCYRILLIC", RTL_TEXTENCODING_ISO_8859_5 }, + { "ISO-8859-6", RTL_TEXTENCODING_ISO_8859_6 }, + { "ISO_8859-6:1987", RTL_TEXTENCODING_ISO_8859_6 }, + { "ISO-IR-127", RTL_TEXTENCODING_ISO_8859_6 }, + { "ISO_8859-6", RTL_TEXTENCODING_ISO_8859_6 }, + { "ECMA-114", RTL_TEXTENCODING_ISO_8859_6 }, + { "ASMO-708", RTL_TEXTENCODING_ISO_8859_6 }, + { "ARABIC", RTL_TEXTENCODING_ISO_8859_6 }, + { "CSISOLATINARABIC", RTL_TEXTENCODING_ISO_8859_6 }, + { "ISO-8859-7", RTL_TEXTENCODING_ISO_8859_7 }, + { "ISO_8859-7:1987", RTL_TEXTENCODING_ISO_8859_7 }, + { "ISO-IR-126", RTL_TEXTENCODING_ISO_8859_7 }, + { "ISO_8859-7", RTL_TEXTENCODING_ISO_8859_7 }, + { "ELOT_928", RTL_TEXTENCODING_ISO_8859_7 }, + { "ECMA-118", RTL_TEXTENCODING_ISO_8859_7 }, + { "GREEK", RTL_TEXTENCODING_ISO_8859_7 }, + { "GREEK8", RTL_TEXTENCODING_ISO_8859_7 }, + { "CSISOLATINGREEK", RTL_TEXTENCODING_ISO_8859_7 }, + { "ISO-8859-8", RTL_TEXTENCODING_ISO_8859_8 }, + { "ISO_8859-8:1988", RTL_TEXTENCODING_ISO_8859_8 }, + { "ISO-IR-138", RTL_TEXTENCODING_ISO_8859_8 }, + { "ISO_8859-8", RTL_TEXTENCODING_ISO_8859_8 }, + { "HEBREW", RTL_TEXTENCODING_ISO_8859_8 }, + { "CSISOLATINHEBREW", RTL_TEXTENCODING_ISO_8859_8 }, + { "ISO-8859-9", RTL_TEXTENCODING_ISO_8859_9 }, + { "ISO_8859-9:1989", RTL_TEXTENCODING_ISO_8859_9 }, + { "ISO-IR-148", RTL_TEXTENCODING_ISO_8859_9 }, + { "ISO_8859-9", RTL_TEXTENCODING_ISO_8859_9 }, + { "LATIN5", RTL_TEXTENCODING_ISO_8859_9 }, + { "L5", RTL_TEXTENCODING_ISO_8859_9 }, + { "CSISOLATIN5", RTL_TEXTENCODING_ISO_8859_9 }, + { "ISO-8859-14", RTL_TEXTENCODING_ISO_8859_14 }, // RFC 2047 + { "ISO_8859-15", RTL_TEXTENCODING_ISO_8859_15 }, + { "ISO-8859-15", RTL_TEXTENCODING_ISO_8859_15 }, // RFC 2047 + { "MACINTOSH", RTL_TEXTENCODING_APPLE_ROMAN }, + { "MAC", RTL_TEXTENCODING_APPLE_ROMAN }, + { "CSMACINTOSH", RTL_TEXTENCODING_APPLE_ROMAN }, + { "IBM437", RTL_TEXTENCODING_IBM_437 }, + { "CP437", RTL_TEXTENCODING_IBM_437 }, + { "437", RTL_TEXTENCODING_IBM_437 }, + { "CSPC8CODEPAGE437", RTL_TEXTENCODING_IBM_437 }, + { "IBM850", RTL_TEXTENCODING_IBM_850 }, + { "CP850", RTL_TEXTENCODING_IBM_850 }, + { "850", RTL_TEXTENCODING_IBM_850 }, + { "CSPC850MULTILINGUAL", RTL_TEXTENCODING_IBM_850 }, + { "IBM860", RTL_TEXTENCODING_IBM_860 }, + { "CP860", RTL_TEXTENCODING_IBM_860 }, + { "860", RTL_TEXTENCODING_IBM_860 }, + { "CSIBM860", RTL_TEXTENCODING_IBM_860 }, + { "IBM861", RTL_TEXTENCODING_IBM_861 }, + { "CP861", RTL_TEXTENCODING_IBM_861 }, + { "861", RTL_TEXTENCODING_IBM_861 }, + { "CP-IS", RTL_TEXTENCODING_IBM_861 }, + { "CSIBM861", RTL_TEXTENCODING_IBM_861 }, + { "IBM863", RTL_TEXTENCODING_IBM_863 }, + { "CP863", RTL_TEXTENCODING_IBM_863 }, + { "863", RTL_TEXTENCODING_IBM_863 }, + { "CSIBM863", RTL_TEXTENCODING_IBM_863 }, + { "IBM865", RTL_TEXTENCODING_IBM_865 }, + { "CP865", RTL_TEXTENCODING_IBM_865 }, + { "865", RTL_TEXTENCODING_IBM_865 }, + { "CSIBM865", RTL_TEXTENCODING_IBM_865 }, + { "IBM775", RTL_TEXTENCODING_IBM_775 }, + { "CP775", RTL_TEXTENCODING_IBM_775 }, + { "CSPC775BALTIC", RTL_TEXTENCODING_IBM_775 }, + { "IBM852", RTL_TEXTENCODING_IBM_852 }, + { "CP852", RTL_TEXTENCODING_IBM_852 }, + { "852", RTL_TEXTENCODING_IBM_852 }, + { "CSPCP852", RTL_TEXTENCODING_IBM_852 }, + { "IBM855", RTL_TEXTENCODING_IBM_855 }, + { "CP855", RTL_TEXTENCODING_IBM_855 }, + { "855", RTL_TEXTENCODING_IBM_855 }, + { "CSIBM855", RTL_TEXTENCODING_IBM_855 }, + { "IBM857", RTL_TEXTENCODING_IBM_857 }, + { "CP857", RTL_TEXTENCODING_IBM_857 }, + { "857", RTL_TEXTENCODING_IBM_857 }, + { "CSIBM857", RTL_TEXTENCODING_IBM_857 }, + { "IBM862", RTL_TEXTENCODING_IBM_862 }, + { "CP862", RTL_TEXTENCODING_IBM_862 }, + { "862", RTL_TEXTENCODING_IBM_862 }, + { "CSPC862LATINHEBREW", RTL_TEXTENCODING_IBM_862 }, + { "IBM864", RTL_TEXTENCODING_IBM_864 }, + { "CP864", RTL_TEXTENCODING_IBM_864 }, + { "CSIBM864", RTL_TEXTENCODING_IBM_864 }, + { "IBM866", RTL_TEXTENCODING_IBM_866 }, + { "CP866", RTL_TEXTENCODING_IBM_866 }, + { "866", RTL_TEXTENCODING_IBM_866 }, + { "CSIBM866", RTL_TEXTENCODING_IBM_866 }, + { "IBM869", RTL_TEXTENCODING_IBM_869 }, + { "CP869", RTL_TEXTENCODING_IBM_869 }, + { "869", RTL_TEXTENCODING_IBM_869 }, + { "CP-GR", RTL_TEXTENCODING_IBM_869 }, + { "CSIBM869", RTL_TEXTENCODING_IBM_869 }, + { "WINDOWS-1250", RTL_TEXTENCODING_MS_1250 }, + { "WINDOWS-1251", RTL_TEXTENCODING_MS_1251 }, + { "WINDOWS-1253", RTL_TEXTENCODING_MS_1253 }, + { "WINDOWS-1254", RTL_TEXTENCODING_MS_1254 }, + { "WINDOWS-1255", RTL_TEXTENCODING_MS_1255 }, + { "WINDOWS-1256", RTL_TEXTENCODING_MS_1256 }, + { "WINDOWS-1257", RTL_TEXTENCODING_MS_1257 }, + { "WINDOWS-1258", RTL_TEXTENCODING_MS_1258 }, + { "SHIFT_JIS", RTL_TEXTENCODING_SHIFT_JIS }, + { "MS_KANJI", RTL_TEXTENCODING_SHIFT_JIS }, + { "CSSHIFTJIS", RTL_TEXTENCODING_SHIFT_JIS }, + { "GB2312", RTL_TEXTENCODING_GB_2312 }, + { "CSGB2312", RTL_TEXTENCODING_GB_2312 }, + { "BIG5", RTL_TEXTENCODING_BIG5 }, + { "CSBIG5", RTL_TEXTENCODING_BIG5 }, + { "EUC-JP", RTL_TEXTENCODING_EUC_JP }, + { "EXTENDED_UNIX_CODE_PACKED_FORMAT_FOR_JAPANESE", + RTL_TEXTENCODING_EUC_JP }, + { "CSEUCPKDFMTJAPANESE", RTL_TEXTENCODING_EUC_JP }, + { "ISO-2022-JP", RTL_TEXTENCODING_ISO_2022_JP }, + { "CSISO2022JP", RTL_TEXTENCODING_ISO_2022_JP }, + { "ISO-2022-CN", RTL_TEXTENCODING_ISO_2022_CN }, + { "KOI8-R", RTL_TEXTENCODING_KOI8_R }, + { "CSKOI8R", RTL_TEXTENCODING_KOI8_R }, + { "UTF-7", RTL_TEXTENCODING_UTF7 }, + { "UTF-8", RTL_TEXTENCODING_UTF8 }, + { "ISO-8859-10", RTL_TEXTENCODING_ISO_8859_10 }, // RFC 2047 + { "ISO-8859-13", RTL_TEXTENCODING_ISO_8859_13 }, // RFC 2047 + { "EUC-KR", RTL_TEXTENCODING_EUC_KR }, + { "CSEUCKR", RTL_TEXTENCODING_EUC_KR }, + { "ISO-2022-KR", RTL_TEXTENCODING_ISO_2022_KR }, + { "CSISO2022KR", RTL_TEXTENCODING_ISO_2022_KR }, + { "ISO-10646-UCS-4", RTL_TEXTENCODING_UCS4 }, + { "CSUCS4", RTL_TEXTENCODING_UCS4 }, + { "ISO-10646-UCS-2", RTL_TEXTENCODING_UCS2 }, + { "CSUNICODE", RTL_TEXTENCODING_UCS2 } }; + +rtl_TextEncoding getCharsetEncoding(char const * pBegin, + char const * pEnd) +{ + for (const EncodingEntry& i : aEncodingMap) + if (equalIgnoreCase(pBegin, pEnd, i.m_aName)) + return i.m_eEncoding; + return RTL_TEXTENCODING_DONTKNOW; +} + +} + +// INetMIME + +// static +bool INetMIME::isAtomChar(sal_uInt32 nChar) +{ + static const bool aMap[128] + = { false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, true, false, true, true, true, true, true, // !"#$%&' + false, false, true, true, false, true, false, true, //()*+,-./ + true, true, true, true, true, true, true, true, //01234567 + true, true, false, false, false, true, false, true, //89:;<=>? + false, true, true, true, true, true, true, true, //@ABCDEFG + true, true, true, true, true, true, true, true, //HIJKLMNO + true, true, true, true, true, true, true, true, //PQRSTUVW + true, true, true, false, false, false, true, true, //XYZ[\]^_ + true, true, true, true, true, true, true, true, //`abcdefg + true, true, true, true, true, true, true, true, //hijklmno + true, true, true, true, true, true, true, true, //pqrstuvw + true, true, true, true, true, true, true, false //xyz{|}~ + }; + return rtl::isAscii(nChar) && aMap[nChar]; +} + +// static +bool INetMIME::isIMAPAtomChar(sal_uInt32 nChar) +{ + static const bool aMap[128] + = { false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, true, false, true, true, false, true, true, // !"#$%&' + false, false, false, true, true, true, true, true, //()*+,-./ + true, true, true, true, true, true, true, true, //01234567 + true, true, true, true, true, true, true, true, //89:;<=>? + true, true, true, true, true, true, true, true, //@ABCDEFG + true, true, true, true, true, true, true, true, //HIJKLMNO + true, true, true, true, true, true, true, true, //PQRSTUVW + true, true, true, true, false, true, true, true, //XYZ[\]^_ + true, true, true, true, true, true, true, true, //`abcdefg + true, true, true, true, true, true, true, true, //hijklmno + true, true, true, true, true, true, true, true, //pqrstuvw + true, true, true, false, true, true, true, false //xyz{|}~ + }; + return rtl::isAscii(nChar) && aMap[nChar]; +} + +// static +bool INetMIME::equalIgnoreCase(const sal_Unicode * pBegin1, + const sal_Unicode * pEnd1, + const char * pString2) +{ + DBG_ASSERT(pBegin1 && pBegin1 <= pEnd1 && pString2, + "INetMIME::equalIgnoreCase(): Bad sequences"); + + while (*pString2 != 0) + if (pBegin1 == pEnd1 + || (rtl::toAsciiUpperCase(*pBegin1++) + != rtl::toAsciiUpperCase( + static_cast<unsigned char>(*pString2++)))) + return false; + return pBegin1 == pEnd1; +} + +// static +bool INetMIME::scanUnsigned(const sal_Unicode *& rBegin, + const sal_Unicode * pEnd, bool bLeadingZeroes, + sal_uInt32 & rValue) +{ + sal_uInt64 nTheValue = 0; + const sal_Unicode * p = rBegin; + for ( ; p != pEnd; ++p) + { + int nWeight = getWeight(*p); + if (nWeight < 0) + break; + nTheValue = 10 * nTheValue + nWeight; + if (nTheValue > std::numeric_limits< sal_uInt32 >::max()) + return false; + } + if (nTheValue == 0 && (p == rBegin || (!bLeadingZeroes && p - rBegin != 1))) + return false; + rBegin = p; + rValue = sal_uInt32(nTheValue); + return true; +} + +// static +sal_Unicode const * INetMIME::scanContentType( + OUString const & rStr, OUString * pType, + OUString * pSubType, INetContentTypeParameterList * pParameters) +{ + sal_Unicode const * pBegin = rStr.getStr(); + sal_Unicode const * pEnd = pBegin + rStr.getLength(); + sal_Unicode const * p = skipLinearWhiteSpaceComment(pBegin, pEnd); + sal_Unicode const * pTypeBegin = p; + while (p != pEnd && isTokenChar(*p)) + { + ++p; + } + if (p == pTypeBegin) + return nullptr; + sal_Unicode const * pTypeEnd = p; + + p = skipLinearWhiteSpaceComment(p, pEnd); + if (p == pEnd || *p++ != '/') + return nullptr; + + p = skipLinearWhiteSpaceComment(p, pEnd); + sal_Unicode const * pSubTypeBegin = p; + while (p != pEnd && isTokenChar(*p)) + { + ++p; + } + if (p == pSubTypeBegin) + return nullptr; + sal_Unicode const * pSubTypeEnd = p; + + if (pType != nullptr) + { + *pType = OUString(pTypeBegin, pTypeEnd - pTypeBegin).toAsciiLowerCase(); + } + if (pSubType != nullptr) + { + *pSubType = OUString(pSubTypeBegin, pSubTypeEnd - pSubTypeBegin) + .toAsciiLowerCase(); + } + + return scanParameters(p, pEnd, pParameters); +} + +// static +OUString INetMIME::decodeHeaderFieldBody(const OString& rBody) +{ + // Due to a bug in INetCoreRFC822MessageStream::ConvertTo7Bit(), old + // versions of StarOffice send mails with header fields where encoded + // words can be preceded by '=', ',', '.', '"', or '(', and followed by + // '=', ',', '.', '"', ')', without any required white space in between. + // And there appear to exist some broken mailers that only encode single + // letters within words, like "Appel + // =?iso-8859-1?Q?=E0?=t=?iso-8859-1?Q?=E9?=moin", so it seems best to + // detect encoded words even when not properly surrounded by white space. + + // Non US-ASCII characters in rBody are treated as ISO-8859-1. + + // encoded-word = "=?" + // 1*(%x21 / %x23-27 / %x2A-2B / %x2D / %30-39 / %x41-5A / %x5E-7E) + // ["*" 1*8ALPHA *("-" 1*8ALPHA)] "?" + // ("B?" *(4base64) (4base64 / 3base64 "=" / 2base64 "==") + // / "Q?" 1*(%x21-3C / %x3E / %x40-7E / "=" 2HEXDIG)) + // "?=" + + // base64 = ALPHA / DIGIT / "+" / "/" + + const char * pBegin = rBody.getStr(); + const char * pEnd = pBegin + rBody.getLength(); + + OUStringBuffer sDecoded; + const char * pCopyBegin = pBegin; + + /* bool bStartEncodedWord = true; */ + const char * pWSPBegin = pBegin; + + for (const char * p = pBegin; p != pEnd;) + { + OUString sEncodedText; + if (*p == '=' /* && bStartEncodedWord */) + { + const char * q = p + 1; + bool bEncodedWord = q != pEnd && *q++ == '?'; + + rtl_TextEncoding eCharsetEncoding = RTL_TEXTENCODING_DONTKNOW; + if (bEncodedWord) + { + const char * pCharsetBegin = q; + const char * pLanguageBegin = nullptr; + int nAlphaCount = 0; + for (bool bDone = false; !bDone;) + if (q == pEnd) + { + bEncodedWord = false; + bDone = true; + } + else + { + char cChar = *q++; + switch (cChar) + { + case '*': + pLanguageBegin = q - 1; + nAlphaCount = 0; + break; + + case '-': + if (pLanguageBegin != nullptr) + { + if (nAlphaCount == 0) + pLanguageBegin = nullptr; + else + nAlphaCount = 0; + } + break; + + case '?': + if (pCharsetBegin == q - 1) + bEncodedWord = false; + else + { + eCharsetEncoding + = getCharsetEncoding( + pCharsetBegin, + pLanguageBegin == nullptr + || nAlphaCount == 0 ? + q - 1 : pLanguageBegin); + bEncodedWord = isMIMECharsetEncoding( + eCharsetEncoding); + eCharsetEncoding + = translateFromMIME(eCharsetEncoding); + } + bDone = true; + break; + + default: + if (pLanguageBegin != nullptr + && (!rtl::isAsciiAlpha( + static_cast<unsigned char>(cChar)) + || ++nAlphaCount > 8)) + pLanguageBegin = nullptr; + break; + } + } + } + + bool bEncodingB = false; + if (bEncodedWord) + { + if (q == pEnd) + bEncodedWord = false; + else + { + switch (*q++) + { + case 'B': + case 'b': + bEncodingB = true; + break; + + case 'Q': + case 'q': + bEncodingB = false; + break; + + default: + bEncodedWord = false; + break; + } + } + } + + bEncodedWord = bEncodedWord && q != pEnd && *q++ == '?'; + + OStringBuffer sText; + if (bEncodedWord) + { + if (bEncodingB) + { + for (bool bDone = false; !bDone;) + { + if (pEnd - q < 4) + { + bEncodedWord = false; + bDone = true; + } + else + { + bool bFinal = false; + int nCount = 3; + sal_uInt32 nValue = 0; + for (int nShift = 18; nShift >= 0; nShift -= 6) + { + int nWeight = getBase64Weight(*q++); + if (nWeight == -2) + { + bEncodedWord = false; + bDone = true; + break; + } + if (nWeight == -1) + { + if (!bFinal) + { + if (nShift >= 12) + { + bEncodedWord = false; + bDone = true; + break; + } + bFinal = true; + nCount = nShift == 6 ? 1 : 2; + } + } + else + nValue |= nWeight << nShift; + } + if (bEncodedWord) + { + for (int nShift = 16; nCount-- > 0; nShift -= 8) + sText.append(char(nValue >> nShift & 0xFF)); + if (*q == '?') + { + ++q; + bDone = true; + } + if (bFinal && !bDone) + { + bEncodedWord = false; + bDone = true; + } + } + } + } + } + else + { + const char * pEncodedTextBegin = q; + const char * pEncodedTextCopyBegin = q; + for (bool bDone = false; !bDone;) + if (q == pEnd) + { + bEncodedWord = false; + bDone = true; + } + else + { + sal_uInt32 nChar = static_cast<unsigned char>(*q++); + switch (nChar) + { + case '=': + { + if (pEnd - q < 2) + { + bEncodedWord = false; + bDone = true; + break; + } + int nDigit1 = getHexWeight(q[0]); + int nDigit2 = getHexWeight(q[1]); + if (nDigit1 < 0 || nDigit2 < 0) + { + bEncodedWord = false; + bDone = true; + break; + } + sText.append(rBody.copy( + (pEncodedTextCopyBegin - pBegin), + (q - 1 - pEncodedTextCopyBegin))); + sText.append(char(nDigit1 << 4 | nDigit2)); + q += 2; + pEncodedTextCopyBegin = q; + break; + } + + case '?': + if (q - pEncodedTextBegin > 1) + sText.append(rBody.copy( + (pEncodedTextCopyBegin - pBegin), + (q - 1 - pEncodedTextCopyBegin))); + else + bEncodedWord = false; + bDone = true; + break; + + case '_': + sText.append(rBody.copy( + (pEncodedTextCopyBegin - pBegin), + (q - 1 - pEncodedTextCopyBegin))); + sText.append(' '); + pEncodedTextCopyBegin = q; + break; + + default: + if (!isVisible(nChar)) + { + bEncodedWord = false; + bDone = true; + } + break; + } + } + } + } + + bEncodedWord = bEncodedWord && q != pEnd && *q++ == '='; + + std::unique_ptr<sal_Unicode[]> pUnicodeBuffer; + sal_Size nUnicodeSize = 0; + if (bEncodedWord) + { + pUnicodeBuffer + = convertToUnicode(sText.getStr(), + sText.getStr() + sText.getLength(), + eCharsetEncoding, nUnicodeSize); + if (!pUnicodeBuffer) + bEncodedWord = false; + } + + if (bEncodedWord) + { + appendISO88591(sDecoded, pCopyBegin, pWSPBegin); + sDecoded.append( + pUnicodeBuffer.get(), + static_cast< sal_Int32 >(nUnicodeSize)); + pUnicodeBuffer.reset(); + p = q; + pCopyBegin = p; + + pWSPBegin = p; + while (p != pEnd && isWhiteSpace(*p)) + ++p; + /* bStartEncodedWord = p != pWSPBegin; */ + continue; + } + } + + if (!sEncodedText.isEmpty()) + sDecoded.append(sEncodedText); + + if (p == pEnd) + break; + + switch (*p++) + { + case '"': + /* bStartEncodedWord = true; */ + break; + + case '(': + /* bStartEncodedWord = true; */ + break; + + case ')': + /* bStartEncodedWord = false; */ + break; + + default: + { + const char * pUTF8Begin = p - 1; + const char * pUTF8End = pUTF8Begin; + sal_uInt32 nCharacter = 0; + if (translateUTF8Char(pUTF8End, pEnd, nCharacter)) + { + appendISO88591(sDecoded, pCopyBegin, p - 1); + sal_Unicode aUTF16Buf[2]; + sal_Int32 nUTF16Len = putUTF32Character(aUTF16Buf, nCharacter) - aUTF16Buf; + sDecoded.append(aUTF16Buf, nUTF16Len); + p = pUTF8End; + pCopyBegin = p; + } + /* bStartEncodedWord = false; */ + break; + } + } + pWSPBegin = p; + } + + appendISO88591(sDecoded, pCopyBegin, pEnd); + return sDecoded.makeStringAndClear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/inet/inetmsg.cxx b/tools/source/inet/inetmsg.cxx new file mode 100644 index 000000000..f8912bb9d --- /dev/null +++ b/tools/source/inet/inetmsg.cxx @@ -0,0 +1,289 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/types.h> +#include <tools/datetime.hxx> +#include <tools/inetmsg.hxx> +#include <comphelper/string.hxx> +#include <rtl/character.hxx> + +#include <map> + +void INetMIMEMessage::SetHeaderField_Impl ( + const OString &rName, + const OUString &rValue, + sal_uInt32 &rnIndex) +{ + SetHeaderField_Impl ( + INetMessageHeader (rName, rValue.toUtf8()), rnIndex); +} + +/* ParseDateField and local helper functions. + * + * Parses a String in (implied) GMT format into class Date and tools::Time objects. + * Four formats are accepted: + * + * [Wkd,] 1*2DIGIT Mon 2*4DIGIT 00:00:00 [GMT] (rfc1123) + * [Wkd,] 00 Mon 0000 00:00:00 [GMT]) (rfc822, rfc1123) + * Weekday, 00-Mon-00 00:00:00 [GMT] (rfc850, rfc1036) + * Wkd Mon 00 00:00:00 0000 [GMT] (ctime) + * 1*DIGIT (delta seconds) + */ + +static const char *months[12] = +{ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +static sal_uInt16 ParseNumber(const OString& rStr, sal_Int32& nIndex) +{ + sal_Int32 n = nIndex; + while ((n < rStr.getLength()) + && rtl::isAsciiDigit(static_cast<unsigned char>(rStr[n]))) + n++; + + OString aNum(rStr.copy(nIndex, (n - nIndex))); + nIndex = n; + + return static_cast<sal_uInt16>(aNum.toInt32()); +} + +static sal_uInt16 ParseMonth(const OString& rStr, sal_Int32& nIndex) +{ + sal_Int32 n = nIndex; + while ((n < rStr.getLength()) + && rtl::isAsciiAlpha(static_cast<unsigned char>(rStr[n]))) + n++; + + OString aMonth(rStr.copy(nIndex, 3)); + nIndex = n; + + sal_uInt16 i; + for (i = 0; i < 12; i++) + if (aMonth.equalsIgnoreAsciiCase(months[i])) break; + return (i + 1); +} + +bool INetMIMEMessage::ParseDateField ( + const OUString& rDateFieldW, DateTime& rDateTime) +{ + OString aDateField(OUStringToOString(rDateFieldW, + RTL_TEXTENCODING_ASCII_US)); + + if (aDateField.isEmpty()) return false; + + if (aDateField.indexOf(':') != -1) + { + // Some DateTime format. + sal_Int32 nIndex = 0; + + // Skip over <Wkd> or <Weekday>, leading and trailing space. + while ((nIndex < aDateField.getLength()) && + (aDateField[nIndex] == ' ')) + nIndex++; + + while ( + (nIndex < aDateField.getLength()) && + (rtl::isAsciiAlpha (static_cast<unsigned char>(aDateField[nIndex])) || + (aDateField[nIndex] == ',') )) + nIndex++; + + while ((nIndex < aDateField.getLength()) && + (aDateField[nIndex] == ' ')) + nIndex++; + + if (rtl::isAsciiAlpha (static_cast<unsigned char>(aDateField[nIndex]))) + { + // Format: ctime(). + if ((aDateField.getLength() - nIndex) < 20) return false; + + rDateTime.SetMonth (ParseMonth (aDateField, nIndex)); nIndex++; + rDateTime.SetDay (ParseNumber (aDateField, nIndex)); nIndex++; + + rDateTime.SetHour (ParseNumber (aDateField, nIndex)); nIndex++; + rDateTime.SetMin (ParseNumber (aDateField, nIndex)); nIndex++; + rDateTime.SetSec (ParseNumber (aDateField, nIndex)); nIndex++; + rDateTime.SetNanoSec (0); + + sal_uInt16 nYear = ParseNumber (aDateField, nIndex); + if (nYear < 100) nYear += 1900; + rDateTime.SetYear (nYear); + } + else + { + // Format: RFC1036 or RFC1123. + if ((aDateField.getLength() - nIndex) < 17) return false; + + rDateTime.SetDay (ParseNumber (aDateField, nIndex)); nIndex++; + rDateTime.SetMonth (ParseMonth (aDateField, nIndex)); nIndex++; + + sal_uInt16 nYear = ParseNumber (aDateField, nIndex); nIndex++; + if (nYear < 100) nYear += 1900; + rDateTime.SetYear (nYear); + + rDateTime.SetHour (ParseNumber (aDateField, nIndex)); nIndex++; + rDateTime.SetMin (ParseNumber (aDateField, nIndex)); nIndex++; + rDateTime.SetSec (ParseNumber (aDateField, nIndex)); nIndex++; + rDateTime.SetNanoSec (0); + + const char cPossiblePlusMinus = nIndex < aDateField.getLength() ? aDateField[nIndex] : 0; + if (cPossiblePlusMinus == '+' || cPossiblePlusMinus == '-') + { + // Offset from GMT: "(+|-)HHMM". + bool bEast = (aDateField[nIndex++] == '+'); + sal_uInt16 nOffset = ParseNumber (aDateField, nIndex); + if (nOffset > 0) + { + tools::Time aDiff( tools::Time::EMPTY ); + aDiff.SetHour (nOffset / 100); + aDiff.SetMin (nOffset % 100); + aDiff.SetSec (0); + aDiff.SetNanoSec (0); + + if (bEast) + rDateTime -= aDiff; + else + rDateTime += aDiff; + } + } + } + } + else if (comphelper::string::isdigitAsciiString(aDateField)) + { + // Format: delta seconds. + tools::Time aDelta (0); + aDelta.SetTime (aDateField.toInt32() * 100); + + DateTime aNow( DateTime::SYSTEM ); + aNow += aDelta; + aNow.ConvertToUTC(); + + rDateTime.SetDate (aNow.GetDate()); + rDateTime.SetTime (aNow.GetTime()); + } + else + { + // Junk. + return false; + } + + return (rDateTime.IsValidAndGregorian() && + !((rDateTime.GetSec() > 59) || + (rDateTime.GetMin() > 59) || + (rDateTime.GetHour() > 23) )); +} + +static const std::map<InetMessageMime, const char*> ImplINetMIMEMessageHeaderData = +{ + { InetMessageMime::VERSION, "MIME-Version"}, + { InetMessageMime::CONTENT_DISPOSITION, "Content-Disposition"}, + { InetMessageMime::CONTENT_TYPE, "Content-Type"}, + { InetMessageMime::CONTENT_TRANSFER_ENCODING, "Content-Transfer-Encoding"} +}; + +INetMIMEMessage::INetMIMEMessage() + : pParent(nullptr) +{ + for (sal_uInt16 i = 0; i < static_cast<int>(InetMessageMime::NUMHDR); i++) + m_nMIMEIndex[static_cast<InetMessageMime>(i)] = SAL_MAX_UINT32; +} + +INetMIMEMessage::~INetMIMEMessage() +{ +} + +void INetMIMEMessage::SetMIMEVersion (const OUString& rVersion) +{ + SetHeaderField_Impl ( + ImplINetMIMEMessageHeaderData.at(InetMessageMime::VERSION), rVersion, + m_nMIMEIndex[InetMessageMime::VERSION]); +} + +void INetMIMEMessage::SetContentDisposition (const OUString& rDisposition) +{ + SetHeaderField_Impl ( + ImplINetMIMEMessageHeaderData.at(InetMessageMime::CONTENT_DISPOSITION), rDisposition, + m_nMIMEIndex[InetMessageMime::CONTENT_DISPOSITION]); +} + +void INetMIMEMessage::SetContentType (const OUString& rType) +{ + SetHeaderField_Impl ( + ImplINetMIMEMessageHeaderData.at(InetMessageMime::CONTENT_TYPE), rType, + m_nMIMEIndex[InetMessageMime::CONTENT_TYPE]); +} + +void INetMIMEMessage::SetContentTransferEncoding ( + const OUString& rEncoding) +{ + SetHeaderField_Impl ( + ImplINetMIMEMessageHeaderData.at(InetMessageMime::CONTENT_TRANSFER_ENCODING), rEncoding, + m_nMIMEIndex[InetMessageMime::CONTENT_TRANSFER_ENCODING]); +} + +OUString INetMIMEMessage::GetDefaultContentType() +{ + if (pParent != nullptr) + { + OUString aParentCT (pParent->GetContentType()); + if (aParentCT.isEmpty()) + aParentCT = pParent->GetDefaultContentType(); + + if (aParentCT.equalsIgnoreAsciiCase("multipart/digest")) + return "message/rfc822"; + } + return "text/plain; charset=us-ascii"; +} + +void INetMIMEMessage::EnableAttachMultipartFormDataChild() +{ + // Check context. + if (IsContainer()) + return; + + // Generate a unique boundary from current time. + char sTail[16 + 1]; + tools::Time aCurTime( tools::Time::SYSTEM ); + sal_uInt64 nThis = reinterpret_cast< sal_uIntPtr >( this ); // we can be on a 64bit architecture + nThis = ( ( nThis >> 32 ) ^ nThis ) & SAL_MAX_UINT32; + sprintf (sTail, "%08X%08X", + static_cast< unsigned int >(aCurTime.GetTime()), + static_cast< unsigned int >(nThis)); + m_aBoundary = "------------_4D48"; + m_aBoundary += sTail; + + // Set header fields. + SetMIMEVersion("1.0"); + SetContentType( + OUString::fromUtf8("multipart/form-data; boundary=" + m_aBoundary)); + SetContentTransferEncoding("7bit"); +} + +void INetMIMEMessage::AttachChild(std::unique_ptr<INetMIMEMessage> pChildMsg) +{ + assert(IsContainer()); + if (IsContainer()) + { + pChildMsg->pParent = this; + aChildren.push_back( std::move(pChildMsg) ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/inet/inetstrm.cxx b/tools/source/inet/inetstrm.cxx new file mode 100644 index 000000000..e2a0e9770 --- /dev/null +++ b/tools/source/inet/inetstrm.cxx @@ -0,0 +1,297 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <cassert> + +#include <sal/types.h> +#include <rtl/strbuf.hxx> +#include <tools/inetmsg.hxx> +#include <tools/inetstrm.hxx> + +int INetMIMEMessageStream::GetHeaderLine(char* pData, sal_uInt32 nSize) +{ + char* pWBuf = pData; + + sal_uInt32 i, n; + + if (maMsgBuffer.Tell() == 0) + { + // Insert formatted header into buffer. + n = pSourceMsg->GetHeaderCount(); + for (i = 0; i < n; i++) + { + INetMessageHeader aHeader (pSourceMsg->GetHeaderField(i)); + if (aHeader.GetValue().getLength()) + { + // NYI: Folding long lines. + maMsgBuffer.WriteOString( aHeader.GetName() ); + maMsgBuffer.WriteCharPtr( ": " ); + maMsgBuffer.WriteOString( aHeader.GetValue() ); + maMsgBuffer.WriteCharPtr( "\r\n" ); + } + } + + pMsgWrite = const_cast<char *>(static_cast<char const *>(maMsgBuffer.GetData())); + pMsgRead = pMsgWrite + maMsgBuffer.Tell(); + } + + n = pMsgRead - pMsgWrite; + if (n > 0) + { + // Move to caller. + if (nSize < n) n = nSize; + for (i = 0; i < n; i++) *pWBuf++ = *pMsgWrite++; + } + else + { + // Reset buffer. + maMsgBuffer.Seek(STREAM_SEEK_TO_BEGIN); + } + + return (pWBuf - pData); +} + +int INetMIMEMessageStream::GetBodyLine(char* pData, sal_uInt32 nSize) +{ + char* pWBuf = pData; + char* pWEnd = pData + nSize; + + if (pSourceMsg->GetDocumentLB()) + { + if (pMsgStrm == nullptr) + pMsgStrm.reset(new SvStream (pSourceMsg->GetDocumentLB())); + + sal_uInt32 nRead = pMsgStrm->ReadBytes(pWBuf, (pWEnd - pWBuf)); + pWBuf += nRead; + } + + return (pWBuf - pData); +} + +int INetMIMEMessageStream::GetMsgLine(char* pData, sal_uInt32 nSize) +{ + // Check for header or body. + if (!bHeaderGenerated) + { + if (!done) + { + // Prepare special header fields. + if (pSourceMsg->GetParent()) + { + OUString aPCT(pSourceMsg->GetParent()->GetContentType()); + if (aPCT.startsWithIgnoreAsciiCase("message/rfc822")) + pSourceMsg->SetMIMEVersion("1.0"); + else + pSourceMsg->SetMIMEVersion(OUString()); + } + else + { + pSourceMsg->SetMIMEVersion("1.0"); + } + + // Check ContentType. + OUString aContentType(pSourceMsg->GetContentType()); + if (!aContentType.isEmpty()) + { + // Determine default Content-Type. + OUString aDefaultType = pSourceMsg->GetDefaultContentType(); + + if (aDefaultType.equalsIgnoreAsciiCase(aContentType)) + { + // No need to specify default. + pSourceMsg->SetContentType(OUString()); + } + } + + // No need to specify default. + pSourceMsg->SetContentTransferEncoding(OUString()); + + // Mark we're done. + done = true; + } + + // Generate the message header. + int nRead = GetHeaderLine(pData, nSize); + if (nRead <= 0) + { + // Reset state. + done = false; + } + return nRead; + } + else + { + // Generate the message body. + if (pSourceMsg->IsContainer()) + { + // Encapsulated message body. + while (!done) + { + if (pChildStrm == nullptr) + { + INetMIMEMessage *pChild = pSourceMsg->GetChild(nChildIndex); + if (pChild) + { + // Increment child index. + nChildIndex++; + + // Create child stream. + pChildStrm.reset(new INetMIMEMessageStream(pChild, false)); + + if (pSourceMsg->IsMultipart()) + { + // Insert multipart delimiter. + OStringBuffer aDelim("--"); + aDelim.append(pSourceMsg->GetMultipartBoundary()); + aDelim.append("\r\n"); + + memcpy(pData, aDelim.getStr(), + aDelim.getLength()); + return aDelim.getLength(); + } + } + else + { + // No more parts. Mark we're done. + done = true; + nChildIndex = 0; + + if (pSourceMsg->IsMultipart()) + { + // Insert close delimiter. + OStringBuffer aDelim("--"); + aDelim.append(pSourceMsg->GetMultipartBoundary()); + aDelim.append("--\r\n"); + + memcpy(pData, aDelim.getStr(), + aDelim.getLength()); + return aDelim.getLength(); + } + } + } + else + { + // Read current child stream. + int nRead = pChildStrm->Read(pData, nSize); + if (nRead > 0) + { + return nRead; + } + else + { + // Cleanup exhausted child stream. + pChildStrm.reset(); + } + } + } + return 0; + } + else + { + // Single part message body. + if (pSourceMsg->GetDocumentLB() == nullptr) + { + // Empty message body. + return 0; + } + + // No Encoding. + return GetBodyLine(pData, nSize); + } + } +} + +namespace +{ + +const int BUFFER_SIZE = 2048; + +} + +INetMIMEMessageStream::INetMIMEMessageStream( + INetMIMEMessage *pMsg, bool headerGenerated): + pSourceMsg(pMsg), + bHeaderGenerated(headerGenerated), + mvBuffer(BUFFER_SIZE), + pMsgRead(nullptr), + pMsgWrite(nullptr), + done(false), + nChildIndex(0) +{ + assert(pMsg != nullptr); + maMsgBuffer.SetStreamCharSet(RTL_TEXTENCODING_ASCII_US); + pRead = pWrite = mvBuffer.data(); +} + +INetMIMEMessageStream::~INetMIMEMessageStream() +{ + pChildStrm.reset(); +} + +int INetMIMEMessageStream::Read(char* pData, sal_uInt32 nSize) +{ + char* pWBuf = pData; + char* pWEnd = pData + nSize; + + while (pWBuf < pWEnd) + { + // Caller's buffer not yet filled. + sal_uInt32 n = pRead - pWrite; + if (n > 0) + { + // Bytes still in buffer. + sal_uInt32 m = pWEnd - pWBuf; + if (m < n) n = m; + for (sal_uInt32 i = 0; i < n; i++) *pWBuf++ = *pWrite++; + } + else + { + // Buffer empty. Reset to <Begin-of-Buffer>. + pRead = pWrite = mvBuffer.data(); + + // Read next message line. + int nRead = GetMsgLine(mvBuffer.data(), mvBuffer.size()); + if (nRead > 0) + { + // Set read pointer. + pRead = mvBuffer.data() + nRead; + } + else + { + if (!bHeaderGenerated) + { + // Header generated. Insert empty line. + bHeaderGenerated = true; + *pRead++ = '\r'; + *pRead++ = '\n'; + } + else + { + // Body generated. + return (pWBuf - pData); + } + } + } + } + return (pWBuf - pData); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |