sw/qa/extras/rtfexport/data/hyperlink-with-backslashes.rtf | 3 + sw/qa/extras/rtfexport/rtfexport8.cxx | 25 +++++++++++++ sw/source/filter/ww8/rtfattributeoutput.cxx | 4 +- sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx | 10 ++--- 4 files changed, 36 insertions(+), 6 deletions(-)
New commits: commit 0863f0c4ed983b05c2d86cb42283518e38a77320 Author: Mike Kaganski <mike.kagan...@collabora.com> AuthorDate: Sat Aug 30 00:02:06 2025 +0500 Commit: Mike Kaganski <mike.kagan...@collabora.com> CommitDate: Fri Aug 29 23:44:58 2025 +0200 tdf#168181: process field backslash escaping properly For some reason, import code for HYPERLINK doesn't use vArguments from the top of DomainMapper_Impl::CloseFieldCommand, and instead, it calls pContext->GetCommandParts. But that gives escaped strings that must be unescaped separately, according to the spec. There was a code handling specifically file:/// URLs, obviously to address the problem; but the processing must be universal, both at import and export. Change-Id: I4d91f1c14dd55077e73dca0141422ba4ebda83d3 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/190388 Tested-by: Jenkins Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com> diff --git a/sw/qa/extras/rtfexport/data/hyperlink-with-backslashes.rtf b/sw/qa/extras/rtfexport/data/hyperlink-with-backslashes.rtf new file mode 100644 index 000000000000..fd9efbcf269f --- /dev/null +++ b/sw/qa/extras/rtfexport/data/hyperlink-with-backslashes.rtf @@ -0,0 +1,3 @@ +{ tf1 +{ieldlddirty{\*ldinst {HYPERLINK "c:\\temp\\doc1.doc"}}{ldrslt \ul link}} +\par} \ No newline at end of file diff --git a/sw/qa/extras/rtfexport/rtfexport8.cxx b/sw/qa/extras/rtfexport/rtfexport8.cxx index c2aa3ef1ec2b..74ade0ff3614 100644 --- a/sw/qa/extras/rtfexport/rtfexport8.cxx +++ b/sw/qa/extras/rtfexport/rtfexport8.cxx @@ -1275,6 +1275,31 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf162342) } } +CPPUNIT_TEST_FIXTURE(Test, testTdf168181) +{ + // Given an RTF with a hyperlink pointing to "c: emp\doc1.doc", + // RTF- and field-encoded as "c:\\temp\\doc1.doc": + createSwDoc("hyperlink-with-backslashes.rtf"); + + // Test that the imported target string has single backslashes between hierarchical parts. + // Without the fix, the backslashes were doubled. + CPPUNIT_ASSERT_EQUAL(u"c:\temp\doc1.doc"_ustr, + getProperty<OUString>(getRun(getParagraph(1), 1), u"HyperLinkURL"_ustr)); + + // to test round-trip, deliberately duplicate one backslash: + getRun(getParagraph(1), 1) + .queryThrow<beans::XPropertySet>() + ->setPropertyValue(u"HyperLinkURL"_ustr, uno::Any(u"c:\\temp\doc1.doc"_ustr)); + + saveAndReload(mpFilter); + + // Test that the imported target string has correct expected number of backslashes. + // Without the fix, this returned with single backslashes, because there was no proper + // field-escaping on export: it was "c:\temp\doc1.doc" in RTF, with pairs of backslashes + CPPUNIT_ASSERT_EQUAL(u"c:\\temp\doc1.doc"_ustr, + getProperty<OUString>(getRun(getParagraph(1), 1), u"HyperLinkURL"_ustr)); +} + } // end of anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sw/source/filter/ww8/rtfattributeoutput.cxx b/sw/source/filter/ww8/rtfattributeoutput.cxx index 2af984b8fc42..b10264f862ad 100644 --- a/sw/source/filter/ww8/rtfattributeoutput.cxx +++ b/sw/source/filter/ww8/rtfattributeoutput.cxx @@ -855,7 +855,9 @@ bool RtfAttributeOutput::StartURL(const OUString& rUrl, const OUString& rTarget, m_aRun->append(" HYPERLINK "); m_aRun->append("\""); - m_aRun->append(msfilter::rtfutil::OutString(rUrl, m_rExport.GetCurrentEncoding())); + // In addition to RTF encoding, field text must escape all backslaches + m_aRun->append(msfilter::rtfutil::OutString(rUrl.replaceAll("\", "\\"), + m_rExport.GetCurrentEncoding())); m_aRun->append("\" "); // Adding the target is likely a LO embellishment. diff --git a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx index 56666177af30..17aec3d4fcb2 100644 --- a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx +++ b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx @@ -8287,7 +8287,11 @@ void DomainMapper_Impl::CloseFieldCommand() } else { - sURL = *aPartIt; + // ECMA-376 Part 1 17.16 "Fields and Hyperlinks" explains, that + // "To include a backslash character in text, it shall be preceded + // with another backslash". Double backslashes must be converted + // to single ones. + sURL = aPartIt->replaceAll("\\", "\"); } ++aPartIt; @@ -8297,10 +8301,6 @@ void DomainMapper_Impl::CloseFieldCommand() { if (sURL.startsWith("file:///")) { - // file:///absolute\path\to\file => invalid file URI (Writer cannot open) - // convert all double backslashes to slashes: - sURL = sURL.replaceAll("\\", "/"); - // file:///absolute\path oile => invalid file URI (Writer cannot open) // convert all backslashes to slashes: sURL = sURL.replace('\', '/');