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
+{ieldlddirty{\*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   oile => invalid file 
URI (Writer cannot open)
                             // convert all backslashes to slashes:
                             sURL = sURL.replace('\', '/');

Reply via email to