[Libreoffice-commits] core.git: sw/CppunitTest_sw_htmlexport.mk sw/qa sw/source
Miklos Vajna (via logerrit)
logerrit at kemper.freedesktop.org
Thu Apr 30 11:20:47 UTC 2020
sw/CppunitTest_sw_htmlexport.mk | 1
sw/qa/extras/htmlexport/data/pdf-ole.odt |binary
sw/qa/extras/htmlexport/htmlexport.cxx | 138 ++++++++++++++++++++++++++++++
sw/source/filter/html/htmlreqifreader.cxx | 96 ++++++++++++++++++++
4 files changed, 233 insertions(+), 2 deletions(-)
New commits:
commit 1392fd6a7eaf9f507639096984c2a0108f254795
Author: Miklos Vajna <vmiklos at collabora.com>
AuthorDate: Thu Apr 30 12:40:24 2020 +0200
Commit: Miklos Vajna <vmiklos at collabora.com>
CommitDate: Thu Apr 30 13:20:15 2020 +0200
sw reqif-xhtml export, embedded objects: handle Ole10Native stream
Normally the embedded object has some OLE2 native data, and we insert
our OLE1 header before that. But in case the OLE2 data already has an
Ole10Native stream, then don't create an OLE1-in-OLE2-in-OLE1 output:
it's pointless and some consumers have trouble parsing that.
Change-Id: Ifc8b37494f97da89ce66a147e08a49eaa2f7ae1e
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/93200
Reviewed-by: Miklos Vajna <vmiklos at collabora.com>
Tested-by: Jenkins
diff --git a/sw/CppunitTest_sw_htmlexport.mk b/sw/CppunitTest_sw_htmlexport.mk
index 957152c7fd8b..c85c5b8212cd 100644
--- a/sw/CppunitTest_sw_htmlexport.mk
+++ b/sw/CppunitTest_sw_htmlexport.mk
@@ -22,6 +22,7 @@ $(eval $(call gb_CppunitTest_use_libraries,sw_htmlexport, \
cppu \
cppuhelper \
i18nlangtag \
+ msfilter \
sal \
sfx \
sw \
diff --git a/sw/qa/extras/htmlexport/data/pdf-ole.odt b/sw/qa/extras/htmlexport/data/pdf-ole.odt
new file mode 100644
index 000000000000..184449a4f829
Binary files /dev/null and b/sw/qa/extras/htmlexport/data/pdf-ole.odt differ
diff --git a/sw/qa/extras/htmlexport/htmlexport.cxx b/sw/qa/extras/htmlexport/htmlexport.cxx
index 4967975f1caa..54334aade9f5 100644
--- a/sw/qa/extras/htmlexport/htmlexport.cxx
+++ b/sw/qa/extras/htmlexport/htmlexport.cxx
@@ -28,6 +28,10 @@
#include <svtools/rtfkeywd.hxx>
#include <comphelper/propertyvalue.hxx>
#include <comphelper/propertysequence.hxx>
+#include <svtools/parrtf.hxx>
+#include <rtl/strbuf.hxx>
+#include <svtools/rtftoken.h>
+#include <filter/msfilter/rtfutil.hxx>
class HtmlExportTest : public SwModelTestBase, public HtmlTestTools
{
@@ -883,6 +887,140 @@ CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqifParagraphAlignment)
assertXPathNoAttribute(pDoc, "//reqif-xhtml:p", "align");
}
+namespace
+{
+/// Test RTF parser that just extracts a single OLE2 object from a file.
+class TestReqIfRtfReader : public SvRTFParser
+{
+public:
+ TestReqIfRtfReader(SvStream& rStream);
+ void NextToken(int nToken) override;
+ bool WriteObjectData(SvStream& rOLE);
+
+private:
+ bool m_bInObjData = false;
+ OStringBuffer m_aHex;
+};
+
+TestReqIfRtfReader::TestReqIfRtfReader(SvStream& rStream)
+ : SvRTFParser(rStream)
+{
+}
+
+void TestReqIfRtfReader::NextToken(int nToken)
+{
+ switch (nToken)
+ {
+ case '}':
+ m_bInObjData = false;
+ break;
+ case RTF_TEXTTOKEN:
+ if (m_bInObjData)
+ m_aHex.append(OUStringToOString(aToken, RTL_TEXTENCODING_ASCII_US));
+ break;
+ case RTF_OBJDATA:
+ m_bInObjData = true;
+ break;
+ }
+}
+
+bool TestReqIfRtfReader::WriteObjectData(SvStream& rOLE)
+{
+ OString aObjdata = m_aHex.makeStringAndClear();
+
+ SvMemoryStream aStream;
+ int b = 0;
+ int count = 2;
+
+ // Feed the destination text to a stream.
+ for (int i = 0; i < aObjdata.getLength(); ++i)
+ {
+ char ch = aObjdata[i];
+ if (ch != 0x0d && ch != 0x0a)
+ {
+ b = b << 4;
+ sal_Int8 parsed = msfilter::rtfutil::AsHex(ch);
+ if (parsed == -1)
+ return false;
+ b += parsed;
+ count--;
+ if (!count)
+ {
+ aStream.WriteChar(b);
+ count = 2;
+ b = 0;
+ }
+ }
+ }
+
+ aStream.Seek(0);
+ rOLE.WriteStream(aStream);
+ return true;
+}
+}
+
+CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqifOle1PDF)
+{
+ // Save to reqif-xhtml.
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "pdf-ole.odt";
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.text.TextDocument", {});
+
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ uno::Sequence<beans::PropertyValue> aStoreProperties = {
+ comphelper::makePropertyValue("FilterName", OUString("HTML (StarWriter)")),
+ comphelper::makePropertyValue("FilterOptions", OUString("xhtmlns=reqif-xhtml")),
+ };
+ xStorable->storeToURL(maTempFile.GetURL(), aStoreProperties);
+
+ // Get the .ole path.
+ SvMemoryStream aStream;
+ HtmlExportTest::wrapFragment(maTempFile, aStream);
+ xmlDocPtr pDoc = parseXmlStream(&aStream);
+ CPPUNIT_ASSERT(pDoc);
+ OUString aOlePath = getXPath(
+ pDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:p/reqif-xhtml:object", "data");
+ OUString aOleSuffix(".ole");
+ CPPUNIT_ASSERT(aOlePath.endsWith(aOleSuffix));
+ INetURLObject aUrl(maTempFile.GetURL());
+ aUrl.setBase(aOlePath.copy(0, aOlePath.getLength() - aOleSuffix.getLength()));
+ aUrl.setExtension("ole");
+ OUString aRtfUrl = aUrl.GetMainURL(INetURLObject::DecodeMechanism::NONE);
+
+ // Parse the ole1 data out of that.
+ SvFileStream aRtfStream(aRtfUrl, StreamMode::READ);
+ SvMemoryStream aRtf;
+ aRtf.WriteOString("{\\rtf1");
+ aRtf.WriteStream(aRtfStream);
+ aRtf.WriteOString("}");
+ aRtf.Seek(0);
+ tools::SvRef<TestReqIfRtfReader> xReader(new TestReqIfRtfReader(aRtf));
+ CPPUNIT_ASSERT(xReader->CallParser() != SvParserState::Error);
+ SvMemoryStream aOle1;
+ CPPUNIT_ASSERT(xReader->WriteObjectData(aOle1));
+ CPPUNIT_ASSERT(aOle1.Tell());
+
+ // Check the content of the ole1 data.
+ // Skip ObjectHeader, see [MS-OLEDS] 2.2.4.
+ aOle1.Seek(0);
+ sal_uInt32 nData;
+ aOle1.ReadUInt32(nData); // OLEVersion
+ aOle1.ReadUInt32(nData); // FormatID
+ aOle1.ReadUInt32(nData); // ClassName
+ aOle1.SeekRel(nData);
+ aOle1.ReadUInt32(nData); // TopicName
+ aOle1.SeekRel(nData);
+ aOle1.ReadUInt32(nData); // ItemName
+ aOle1.SeekRel(nData);
+ aOle1.ReadUInt32(nData); // NativeDataSize
+
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 39405
+ // - Actual : 43008
+ // i.e. we did not work with the Ole10Native stream, rather created an OLE1 wrapper around the
+ // OLE1-in-OLE2 data, resulting in additional size.
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(0x99ed), nData);
+}
+
CPPUNIT_PLUGIN_IMPLEMENT();
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/filter/html/htmlreqifreader.cxx b/sw/source/filter/html/htmlreqifreader.cxx
index 02f0e112ef0a..523bc973c587 100644
--- a/sw/source/filter/html/htmlreqifreader.cxx
+++ b/sw/source/filter/html/htmlreqifreader.cxx
@@ -143,14 +143,106 @@ bool ParseOLE2Presentation(SvStream& rOle2, sal_uInt32& nWidth, sal_uInt32& nHei
return true;
}
+/**
+ * Inserts an OLE1 header before an OLE2 storage, assuming that the storage has an Ole10Native
+ * stream.
+ */
+OString InsertOLE1HeaderFromOle10NativeStream(tools::SvRef<SotStorage>& xStorage,
+ SwOLENode& rOLENode, SvStream& rOle1)
+{
+ tools::SvRef<SotStorageStream> xOle1Stream
+ = xStorage->OpenSotStream("\1Ole10Native", StreamMode::STD_READ);
+ sal_uInt32 nOle1Size = 0;
+ xOle1Stream->ReadUInt32(nOle1Size);
+
+ OString aClassName("Package");
+
+ // Write ObjectHeader, see [MS-OLEDS] 2.2.4.
+ rOle1.Seek(0);
+ // OLEVersion.
+ rOle1.WriteUInt32(0x00000501);
+
+ // FormatID is EmbeddedObject.
+ rOle1.WriteUInt32(0x00000002);
+
+ // ClassName
+ rOle1.WriteUInt32(aClassName.isEmpty() ? 0 : aClassName.getLength() + 1);
+ if (!aClassName.isEmpty())
+ {
+ rOle1.WriteOString(aClassName);
+ // Null terminated pascal string.
+ rOle1.WriteChar(0);
+ }
+
+ // TopicName.
+ rOle1.WriteUInt32(0);
+
+ // ItemName.
+ rOle1.WriteUInt32(0);
+
+ // NativeDataSize
+ rOle1.WriteUInt32(nOle1Size);
+
+ // Write the actual native data.
+ rOle1.WriteStream(*xOle1Stream, nOle1Size);
+
+ // Write Presentation.
+ if (!rOLENode.GetGraphic())
+ {
+ return aClassName;
+ }
+
+ const Graphic& rGraphic = *rOLENode.GetGraphic();
+ Size aSize = rOLENode.GetTwipSize();
+ SvMemoryStream aGraphicStream;
+ if (GraphicConverter::Export(aGraphicStream, rGraphic, ConvertDataFormat::WMF) != ERRCODE_NONE)
+ {
+ return aClassName;
+ }
+
+ auto pGraphicAry = static_cast<const sal_uInt8*>(aGraphicStream.GetData());
+ sal_uInt64 nPresentationData = aGraphicStream.TellEnd();
+ msfilter::rtfutil::StripMetafileHeader(pGraphicAry, nPresentationData);
+
+ // OLEVersion.
+ rOle1.WriteUInt32(0x00000501);
+ // FormatID: constant means the ClassName field is present.
+ rOle1.WriteUInt32(0x00000005);
+ // ClassName: null terminated pascal string.
+ OString aPresentationClassName("METAFILEPICT");
+ rOle1.WriteUInt32(aPresentationClassName.getLength() + 1);
+ rOle1.WriteOString(aPresentationClassName);
+ rOle1.WriteChar(0);
+ // Width.
+ rOle1.WriteUInt32(aSize.getWidth());
+ // Height.
+ rOle1.WriteUInt32(aSize.getHeight() * -1);
+ // PresentationDataSize
+ rOle1.WriteUInt32(8 + nPresentationData);
+ // Reserved1-4.
+ rOle1.WriteUInt16(0x0008);
+ rOle1.WriteUInt16(0x31b1);
+ rOle1.WriteUInt16(0x1dd9);
+ rOle1.WriteUInt16(0x0000);
+ rOle1.WriteBytes(pGraphicAry, nPresentationData);
+
+ return aClassName;
+}
+
/// Inserts an OLE1 header before an OLE2 storage.
-OString InsertOLE1Header(SvStream& rOle2, SvStream& rOle1, sal_uInt32& nWidth, sal_uInt32& nHeight)
+OString InsertOLE1Header(SvStream& rOle2, SvStream& rOle1, sal_uInt32& nWidth, sal_uInt32& nHeight,
+ SwOLENode& rOLENode)
{
rOle2.Seek(0);
tools::SvRef<SotStorage> xStorage(new SotStorage(rOle2));
if (xStorage->GetError() != ERRCODE_NONE)
return OString();
+ if (xStorage->IsStream("\1Ole10Native"))
+ {
+ return InsertOLE1HeaderFromOle10NativeStream(xStorage, rOLENode, rOle1);
+ }
+
OString aClassName = ExtractOLEClassName(xStorage);
// Write ObjectHeader, see [MS-OLEDS] 2.2.4.
@@ -304,7 +396,7 @@ bool WrapOleInRtf(SvStream& rOle2, SvStream& rRtf, SwOLENode& rOLENode)
SvMemoryStream aOLE1;
sal_uInt32 nWidth = 0;
sal_uInt32 nHeight = 0;
- OString aClassName = InsertOLE1Header(rOle2, aOLE1, nWidth, nHeight);
+ OString aClassName = InsertOLE1Header(rOle2, aOLE1, nWidth, nHeight, rOLENode);
// Start object.
rRtf.WriteCharPtr("{" OOO_STRING_SVTOOLS_RTF_OBJECT);
More information about the Libreoffice-commits
mailing list