This is an automated email from the ASF dual-hosted git repository.

jimjag pushed a commit to branch AOO50X
in repository https://gitbox.apache.org/repos/asf/openoffice.git


The following commit(s) were added to refs/heads/AOO50X by this push:
     new 302d8b0880 svtools: one shared policy for following URLs out of 
document content
302d8b0880 is described below

commit 302d8b088050ee0c2b3b80426474292d30e54785
Author: Peter <[email protected]>
AuthorDate: Tue Sep 22 21:34:30 2026 +0200

    svtools: one shared policy for following URLs out of document content
    
    Each module that follows a URL named by document content -- linked graphics,
    background images, instance data, media -- decided for itself whether to
    check anything, and the check that does exist was written out inline at the
    call site.
    
    Add svtools/linkpolicy.hxx as the one place that decides, and apply it at 
the
    load sites in editeng, forms, sfx2, sw, avmedia and svgio. A relative
    reference and a file on this machine load as before; anything else asks the
    running document, which is the question the LinkManager already asks.
    
    Two places on those paths assumed success and no longer do: a reference that
    cannot be resolved, and a graphic that is not there.
    
    Co-Authored-By: Claude Opus 5 <[email protected]>
---
 main/avmedia/source/viewer/mediawindow.cxx         |  11 +-
 .../avmedia/source/viewer/mediawindowbase_impl.cxx |   7 +
 main/editeng/source/items/frmitems.cxx             |  10 +
 main/forms/source/xforms/model.cxx                 |   9 +
 main/sfx2/source/appl/fileobj.cxx                  |  15 ++
 main/sfx2/source/appl/linkmgr2.cxx                 |  11 +-
 main/svgio/source/svgreader/linkedimagepolicy.hxx  |  98 ++++++++
 main/svgio/source/svgreader/svgimagenode.cxx       |   8 +-
 main/svtools/Library_svt.mk                        |   1 +
 main/svtools/Package_inc.mk                        |   1 +
 main/svtools/inc/svtools/linkpolicy.hxx            | 251 +++++++++++++++++++++
 main/svtools/source/graphic/grfmgr.cxx             |  31 +--
 main/svtools/source/misc/linkpolicy.cxx            |  91 ++++++++
 main/sw/source/core/graphic/ndgrf.cxx              |  17 +-
 main/sw/source/core/layout/paintfrm.cxx            |  44 +++-
 15 files changed, 554 insertions(+), 51 deletions(-)

diff --git a/main/avmedia/source/viewer/mediawindow.cxx 
b/main/avmedia/source/viewer/mediawindow.cxx
index 63cbb8d594..182b68ae88 100644
--- a/main/avmedia/source/viewer/mediawindow.cxx
+++ b/main/avmedia/source/viewer/mediawindow.cxx
@@ -32,6 +32,7 @@
 #include <unotools/pathoptions.hxx>
 #include <sfx2/filedlghelper.hxx>
 #include <comphelper/processfactory.hxx>
+#include <svtools/linkpolicy.hxx>
 #include <com/sun/star/media/XManager.hpp>
 #include "com/sun/star/ui/dialogs/TemplateDescription.hpp"
 
@@ -525,10 +526,18 @@ uno::Reference< graphic::XGraphic > 
MediaWindow::grabFrame( const ::rtl::OUStrin
                                                             bool 
bAllowToCreateReplacementGraphic,
                                                             double fMediaTime )
 {
-    uno::Reference< media::XPlayer >    xPlayer( createPlayer( rURL ) );
+    uno::Reference< media::XPlayer >    xPlayer;
     uno::Reference< graphic::XGraphic > xRet;
     ::std::auto_ptr< Graphic >          apGraphic;
 
+    // Stricter than mayLoadDocumentReference: a grabbed frame is drawn into 
the
+    // document and exported with it, so any reference that carries a scheme is
+    // put to the document, a local file included. A relative reference is the
+    // document's own content and is grabbed as before.
+    if( !::svt::linkpolicy::isAbsoluteUrl( rURL )
+        || ::svt::linkpolicy::mayFollowDocumentLink( rURL ) )
+        xPlayer = createPlayer( rURL );
+
     if( xPlayer.is() )
     {
         uno::Reference< media::XFrameGrabber > xGrabber( 
xPlayer->createFrameGrabber() );
diff --git a/main/avmedia/source/viewer/mediawindowbase_impl.cxx 
b/main/avmedia/source/viewer/mediawindowbase_impl.cxx
index 94611576b5..8df4145924 100644
--- a/main/avmedia/source/viewer/mediawindowbase_impl.cxx
+++ b/main/avmedia/source/viewer/mediawindowbase_impl.cxx
@@ -29,6 +29,7 @@
 #include <comphelper/processfactory.hxx>
 #include <com/sun/star/lang/XMultiComponentFactory.hpp>
 #include <com/sun/star/media/XManager.hpp>
+#include <svtools/linkpolicy.hxx>
 #ifndef _COM_SUN_STAR_LANG_XCOMPONENT_HDL_
 #include <com/sun/star/lang/XComponent.hdl>
 #endif
@@ -74,6 +75,12 @@ uno::Reference< media::XPlayer > 
MediaWindowBaseImpl::createPlayer( const ::rtl:
 
     rbJavaBased = sal_False;
 
+    // Every player in the office is built here, over a URL that comes out of
+    // document content, so the shared policy decides first (see
+    // svtools/linkpolicy.hxx).
+    if( !::svt::linkpolicy::mayLoadDocumentReference( rURL ) )
+        return xPlayer;
+
     if( xContext.is() )
     {
         static const ServiceManager aServiceManagers[] =
diff --git a/main/editeng/source/items/frmitems.cxx 
b/main/editeng/source/items/frmitems.cxx
index 78dfb27a0a..a7b9c9fa59 100644
--- a/main/editeng/source/items/frmitems.cxx
+++ b/main/editeng/source/items/frmitems.cxx
@@ -53,6 +53,7 @@
 #include <limits.h>
 #include <comphelper/processfactory.hxx>
 #include <svtools/grfmgr.hxx>
+#include <svtools/linkpolicy.hxx>
 #include <tools/urlobj.hxx>
 #include <comphelper/types.hxx>
 #include <svl/memberid.hrc>
@@ -4092,6 +4093,15 @@ const GraphicObject* SvxBrushItem::GetGraphicObject() 
const
                                pImpl->aDoneLink = aTmp;
                        } */
 
+                       // The link is document content, so the shared policy 
decides (see
+                       // svtools/linkpolicy.hxx). A refusal is final for this 
item: no stream is
+                       // opened, and no retry.
+                       if( !::svt::linkpolicy::mayLoadDocumentReference( 
*pStrLink ) )
+                       {
+                               const_cast < SvxBrushItem*> (this)->bLoadAgain 
= sal_False;
+                               return pImpl->pGraphicObject;
+                       }
+
                        pImpl->pStream = utl::UcbStreamHelper::CreateStream( 
*pStrLink, STREAM_STD_READ );
                        if( pImpl->pStream && !pImpl->pStream->GetError() )
                        {
diff --git a/main/forms/source/xforms/model.cxx 
b/main/forms/source/xforms/model.cxx
index 2cda345230..2c4d386409 100644
--- a/main/forms/source/xforms/model.cxx
+++ b/main/forms/source/xforms/model.cxx
@@ -42,6 +42,7 @@
 
 #include <comphelper/propertysetinfo.hxx>
 #include <cppuhelper/typeprovider.hxx>
+#include <svtools/linkpolicy.hxx>
 
 #include <algorithm>
 
@@ -452,6 +453,14 @@ void Model::loadInstance( sal_Int32 nInstance )
     // if we have a URL, load the document and set it into the instance
     if( sURL.getLength() > 0 )
     {
+        // The instance src is document content, so the shared policy decides 
(see
+        // svtools/linkpolicy.hxx).
+        if( !::svt::linkpolicy::mayLoadDocumentReference( sURL ) )
+        {
+            // Leave the instance empty, as for a src that cannot be read.
+            return;
+        }
+
         try
         {
             Reference<XInputStream> xInput =
diff --git a/main/sfx2/source/appl/fileobj.cxx 
b/main/sfx2/source/appl/fileobj.cxx
index 0b9e765b73..e0c4c7e8d7 100644
--- a/main/sfx2/source/appl/fileobj.cxx
+++ b/main/sfx2/source/appl/fileobj.cxx
@@ -28,6 +28,7 @@
 #include <tools/stream.hxx>
 #include <sot/formats.hxx>
 #include <svtools/filter.hxx>
+#include <svtools/linkpolicy.hxx>
 #include <sfx2/lnkbase.hxx>
 #include <sfx2/app.hxx>
 #include <sfx2/progress.hxx>
@@ -285,6 +286,15 @@ sal_Bool SvFileObject::LoadFile_Impl()
        if( bWaitForData || !bLoadAgain || xMed.Is() || pDownLoadData )
                return sal_False;
 
+       // The name is document content, so the shared policy decides before the
+       // fetch (see svtools/linkpolicy.hxx). The question is the LinkManager's
+       // existing one, asked at most once per document.
+       if( !::svt::linkpolicy::mayLoadDocumentReference( sFileNm ) )
+       {
+               bLoadAgain = sal_False;
+               return sal_False;
+       }
+
        // z.Z. nur auf die aktuelle DocShell
        xMed = new SfxMedium( sFileNm, STREAM_STD_READ, sal_True );
     SvLinkSource::StreamToLoadFrom aStreamToLoadFrom =
@@ -328,6 +338,11 @@ sal_Bool SvFileObject::LoadFile_Impl()
 
 sal_Bool SvFileObject::GetGraphic_Impl( Graphic& rGrf, SvStream* pStream )
 {
+       // A second way out: with no stream and no medium the filter opens 
sFileNm
+       // itself. Ask here too; the answer is remembered per document.
+       if( !::svt::linkpolicy::mayLoadDocumentReference( sFileNm ) )
+               return sal_False;
+
        GraphicFilter* pGF = GraphicFilter::GetGraphicFilter();
 
        const sal_uInt16 nFilter = sFilter.Len() && pGF->GetImportFormatCount()
diff --git a/main/sfx2/source/appl/linkmgr2.cxx 
b/main/sfx2/source/appl/linkmgr2.cxx
index 2302d7f972..7811724c35 100644
--- a/main/sfx2/source/appl/linkmgr2.cxx
+++ b/main/sfx2/source/appl/linkmgr2.cxx
@@ -41,6 +41,7 @@
 #include <svl/eitem.hxx>
 #include <svl/intitem.hxx>
 #include <unotools/localfilehelper.hxx>
+#include <svtools/linkpolicy.hxx>
 #include <comphelper/processfactory.hxx>
 #include <i18npool/mslangid.hxx>
 #include <sfx2/request.hxx>
@@ -577,13 +578,9 @@ sal_Bool LinkManager::urlIsSafe( const 
::com::sun::star::util::URL &url )
 
 sal_Bool LinkManager::urlIsVendor( const ::rtl::OUString &url )
 {
-       if ( url.matchIgnoreAsciiCaseAsciiL( "vnd.sun.star.", 13, 0 ) ) {
-               return url.matchIgnoreAsciiCaseAsciiL ( "expand", 6, 13 ) ||
-                       url.matchIgnoreAsciiCaseAsciiL ( "script", 6, 13 ) ||
-                       url.matchIgnoreAsciiCaseAsciiL ( "tdoc", 4, 13 ) ||
-                       url.matchIgnoreAsciiCaseAsciiL ( "uno", 3, 13 );
-       }
-       return sal_False;
+       // One definition of this scheme set, shared with the modules below sfx2
+       // that also have to apply it (see svtools/linkpolicy.hxx).
+       return ::svt::linkpolicy::isVendorUrl( url ) ? sal_True : sal_False;
 }
 
 
diff --git a/main/svgio/source/svgreader/linkedimagepolicy.hxx 
b/main/svgio/source/svgreader/linkedimagepolicy.hxx
new file mode 100644
index 0000000000..3ee4887e8c
--- /dev/null
+++ b/main/svgio/source/svgreader/linkedimagepolicy.hxx
@@ -0,0 +1,98 @@
+/**************************************************************
+ *
+ * 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
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *************************************************************/
+
+#ifndef INCLUDED_SVGIO_LINKEDIMAGEPOLICY_HXX
+#define INCLUDED_SVGIO_LINKEDIMAGEPOLICY_HXX
+
+#include "sal/config.h"
+
+#include "rtl/uri.hxx"
+#include "rtl/ustring.hxx"
+#include "svtools/linkpolicy.hxx"
+
+namespace svgio { namespace svgreader {
+
+/** Resolves an <image> reference against the SVG's own location and decides
+    whether it may be opened.
+
+    An <image> href is document content, read while the SVG is decomposed. The
+    reference is only followed when it stays with the SVG: the feature it
+    exists for is an image delivered alongside the file (a relative href), so
+    a target that resolves to some other place on the machine, to another
+    host, or into a different scheme is not part of that feature and is
+    refused.
+
+    Nothing here needs a document or a service: the decision is made from the
+    two strings, so it holds the same way on every route that reaches the
+    decompose step, and for a base URL that is itself remote (a remote base
+    contains no local files, so every local target falls outside it).
+
+    @param rBase
+    the SVG's own absolute location, as the document reports it.
+
+    @param rHref
+    the reference as it was written in the file, relative or absolute.
+
+    @param rAbsoluteUrl
+    on success, the resolved URL to open; untouched otherwise.
+
+    @return true when the reference may be opened.
+ */
+inline bool resolveLinkedImageUrl(
+    ::rtl::OUString const & rBase,
+    ::rtl::OUString const & rHref,
+    ::rtl::OUString & rAbsoluteUrl )
+{
+    if ( rBase.getLength() == 0 || rHref.getLength() == 0 )
+        return false;
+
+    // A malformed base makes the reference unresolvable. convertRelToAbs
+    // signals that by throwing, and the throw must not leave the decompose
+    // call -- it would cross a UNO interface that does not declare it.
+    ::rtl::OUString aAbsUrl;
+    try
+    {
+        aAbsUrl = ::rtl::Uri::convertRelToAbs( rBase, rHref );
+    }
+    catch ( const ::rtl::MalformedUriException & )
+    {
+        return false;
+    }
+    if ( aAbsUrl.getLength() == 0 )
+        return false;
+
+    // This is opened as a plain file, so a file: reference on this machine is
+    // the only thing that can be read here; the test also refuses the schemes
+    // that address process state and a UNC reference to another host.
+    if ( !::svt::linkpolicy::isLocalFileUrl( aAbsUrl ) )
+        return false;
+
+    // ... and only where the SVG itself sits.
+    if ( !::svt::linkpolicy::isContainedUnder( rBase, aAbsUrl ) )
+        return false;
+
+    rAbsoluteUrl = aAbsUrl;
+    return true;
+}
+
+} }
+
+#endif
diff --git a/main/svgio/source/svgreader/svgimagenode.cxx 
b/main/svgio/source/svgreader/svgimagenode.cxx
index 4014766e06..18fbeba63b 100644
--- a/main/svgio/source/svgreader/svgimagenode.cxx
+++ b/main/svgio/source/svgreader/svgimagenode.cxx
@@ -36,6 +36,7 @@
 #include <basegfx/polygon/b2dpolygontools.hxx>
 #include <basegfx/polygon/b2dpolygon.hxx>
 #include <rtl/uri.hxx>
+#include "linkedimagepolicy.hxx"
 #include <drawinglayer/geometry/viewinformation2d.hxx>
 
 //////////////////////////////////////////////////////////////////////////////
@@ -244,9 +245,12 @@ namespace svgio
 
                         if(rPath.getLength())
                         {
-                            const rtl::OUString 
aAbsUrl(rtl::Uri::convertRelToAbs(rPath, maUrl));
+                            // The reference is document content: resolve it
+                            // against this file's own location and only open 
it
+                            // when it stays there (see linkedimagepolicy.hxx).
+                            rtl::OUString aAbsUrl;
 
-                            if(aAbsUrl.getLength())
+                            if(resolveLinkedImageUrl(rPath, maUrl, aAbsUrl))
                             {
                                 SvFileStream aStream(aAbsUrl, STREAM_STD_READ);
                                 Graphic aGraphic;
diff --git a/main/svtools/Library_svt.mk b/main/svtools/Library_svt.mk
index aae315f1fd..9864002d26 100644
--- a/main/svtools/Library_svt.mk
+++ b/main/svtools/Library_svt.mk
@@ -218,6 +218,7 @@ $(eval $(call gb_Library_add_exception_objects,svt,\
        svtools/source/misc/imap3 \
        svtools/source/misc/itemdel \
        svtools/source/misc/langtab \
+       svtools/source/misc/linkpolicy \
        svtools/source/misc/stringtransfer \
        svtools/source/misc/svtaccessiblefactory \
        svtools/source/misc/svtdata \
diff --git a/main/svtools/Package_inc.mk b/main/svtools/Package_inc.mk
index 9bb8dcceaa..829a5bfd09 100644
--- a/main/svtools/Package_inc.mk
+++ b/main/svtools/Package_inc.mk
@@ -99,6 +99,7 @@ $(eval $(call 
gb_Package_add_file,svtools_inc,inc/svtools/ivctrl.hxx,svtools/ivc
 $(eval $(call 
gb_Package_add_file,svtools_inc,inc/svtools/javacontext.hxx,svtools/javacontext.hxx))
 $(eval $(call 
gb_Package_add_file,svtools_inc,inc/svtools/javainteractionhandler.hxx,svtools/javainteractionhandler.hxx))
 $(eval $(call 
gb_Package_add_file,svtools_inc,inc/svtools/langtab.hxx,svtools/langtab.hxx))
+$(eval $(call 
gb_Package_add_file,svtools_inc,inc/svtools/linkpolicy.hxx,svtools/linkpolicy.hxx))
 $(eval $(call 
gb_Package_add_file,svtools_inc,inc/svtools/localresaccess.hxx,svtools/localresaccess.hxx))
 $(eval $(call 
gb_Package_add_file,svtools_inc,inc/svtools/menuoptions.hxx,svtools/menuoptions.hxx))
 $(eval $(call 
gb_Package_add_file,svtools_inc,inc/svtools/miscopt.hxx,svtools/miscopt.hxx))
diff --git a/main/svtools/inc/svtools/linkpolicy.hxx 
b/main/svtools/inc/svtools/linkpolicy.hxx
new file mode 100644
index 0000000000..473a09cdf3
--- /dev/null
+++ b/main/svtools/inc/svtools/linkpolicy.hxx
@@ -0,0 +1,251 @@
+/**************************************************************
+ *
+ * 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
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *************************************************************/
+
+#ifndef INCLUDED_SVTOOLS_LINKPOLICY_HXX
+#define INCLUDED_SVTOOLS_LINKPOLICY_HXX
+
+#include "sal/config.h"
+
+#include "rtl/string.h"
+#include "rtl/uri.hxx"
+#include "rtl/ustring.hxx"
+#include "svtools/svtdllapi.h"
+
+/** Policy for following a URL that came out of document content.
+
+    Document content names resources by URL -- linked graphics, background
+    images, instance data, media. These helpers are the one place that decides
+    what may be followed, so the modules that hold the actual load calls
+    (editeng, svgio, forms, sw, svtools itself) all apply the same rule instead
+    of each carrying its own test.
+
+    The classifying functions are inline and depend on nothing but rtl, so they
+    can be exercised directly. mayFollowDocumentLink() needs the running
+    document and therefore lives in the library.
+ */
+namespace svt { namespace linkpolicy {
+
+/** Tells whether a URL uses one of the schemes that address process state or
+    an internal object tree rather than an ordinary resource.
+
+    vnd.sun.star.expand resolves bootstrap macros; script, tdoc and uno reach
+    the scripting engine, the loaded document tree and the object model. None
+    of them is a meaningful target for document content, so these are never
+    followed regardless of any other permission.
+ */
+inline bool isVendorUrl( ::rtl::OUString const & rUrl )
+{
+    if ( !rUrl.matchIgnoreAsciiCaseAsciiL(
+              RTL_CONSTASCII_STRINGPARAM( "vnd.sun.star." ) ) )
+        return false;
+    sal_Int32 const n = RTL_CONSTASCII_LENGTH( "vnd.sun.star." );
+    return rUrl.matchIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( 
"expand" ), n )
+        || rUrl.matchIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( 
"script" ), n )
+        || rUrl.matchIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "tdoc" 
), n )
+        || rUrl.matchIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "uno" 
), n );
+}
+
+/** Tells whether a reference carries a scheme, i.e. names a resource by
+    itself rather than relative to the document that holds it.
+
+    A scheme is a name followed by ':' before any path separator. Callers use
+    this to leave a document-relative reference alone: relative references
+    stay inside the document's own area and are the ordinary case, while a
+    scheme is what lets a reference point somewhere else entirely.
+ */
+inline bool isAbsoluteUrl( ::rtl::OUString const & rUrl )
+{
+    sal_Int32 const nColon = rUrl.indexOf( ':' );
+    if ( nColon <= 0 )
+        return false;
+    sal_Int32 const nSlash = rUrl.indexOf( '/' );
+    if ( nSlash != -1 && nSlash < nColon )
+        return false;
+    // A scheme starts with a letter (RFC 3986); a Windows drive letter
+    // followed by ':' is one character and cannot be confused with it.
+    sal_Unicode const c = rUrl.getStr()[0];
+    if ( !( ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) ) )
+        return false;
+    return nColon > 1;
+}
+
+/** Tells whether a URL is a file: reference whose authority names a host
+    other than this one.
+
+    On Windows such a reference resolves to a UNC path, so the access is a
+    request to another machine rather than a read of a local file. An empty
+    authority and localhost are this machine and are not affected.
+ */
+inline bool isOffHostFileUrl( ::rtl::OUString const & rUrl )
+{
+    if ( !rUrl.matchIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "file:" 
) ) )
+        return false;
+    ::rtl::OUString aRest( rUrl.copy( RTL_CONSTASCII_LENGTH( "file:" ) ) );
+
+    // A backslash is not URL syntax; on Windows it would form a UNC
+    // reference once the URL is converted to a system path.
+    if ( aRest.indexOf( '\\' ) != -1 )
+        return true;
+
+    if ( !aRest.matchIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "//" ) 
) )
+        return false;                       // no authority component at all
+
+    sal_Int32 const nEnd = aRest.indexOf( '/', 2 );
+    ::rtl::OUString const aAuthority(
+        nEnd == -1 ? aRest.copy( 2 ) : aRest.copy( 2, nEnd - 2 ) );
+    if ( aAuthority.getLength() != 0
+         && !aAuthority.equalsIgnoreAsciiCaseAsciiL(
+                RTL_CONSTASCII_STRINGPARAM( "localhost" ) ) )
+        return true;
+
+    // An empty authority followed by a path that starts another one
+    // (file:////host/share) names a different host just the same.
+    ::rtl::OUString const aPath(
+        nEnd == -1 ? ::rtl::OUString() : aRest.copy( nEnd ) );
+    return aPath.matchIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "//" 
) );
+}
+
+/** Tells whether a URL names a file on this machine: the file: scheme, an
+    absolute path, and no authority pointing elsewhere.
+ */
+inline bool isLocalFileUrl( ::rtl::OUString const & rUrl )
+{
+    if ( !rUrl.matchIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "file:" 
) ) )
+        return false;
+    if ( isOffHostFileUrl( rUrl ) )
+        return false;
+    ::rtl::OUString aRest( rUrl.copy( RTL_CONSTASCII_LENGTH( "file:" ) ) );
+    if ( aRest.matchIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "//" ) 
) )
+    {
+        sal_Int32 const nEnd = aRest.indexOf( '/', 2 );
+        if ( nEnd == -1 )
+            return false;                   // authority only, no path
+        aRest = aRest.copy( nEnd );
+    }
+    return aRest.getLength() > 1 && aRest.getStr()[0] == '/';
+}
+
+/** Tells whether rTarget stays inside the directory rBase sits in.
+
+    Both are expected to be absolute URLs, rTarget already resolved against
+    rBase. Comparing the whole string means the scheme and the authority have
+    to agree as well, so a target on another host or in another scheme is not
+    contained. A segment that would climb back out is refused whether it is
+    spelled plainly or percent-encoded, because the escape only disappears
+    when the URL is turned into a system path.
+
+    This is the test for the case where a document may reference a file
+    delivered alongside it, but nothing else.
+ */
+inline bool isContainedUnder(
+    ::rtl::OUString const & rBase, ::rtl::OUString const & rTarget )
+{
+    if ( rBase.getLength() == 0 || rTarget.getLength() == 0 )
+        return false;
+
+    // The directory rBase lives in, trailing separator included.
+    sal_Int32 const nSlash = rBase.lastIndexOf( '/' );
+    if ( nSlash == -1 )
+        return false;
+    ::rtl::OUString const aDir( rBase.copy( 0, nSlash + 1 ) );
+
+    if ( !rTarget.match( aDir ) )
+        return false;
+    ::rtl::OUString aRest( rTarget.copy( aDir.getLength() ) );
+    if ( aRest.getLength() == 0 )
+        return false;                       // the directory itself
+    if ( aRest.getStr()[0] == '/' )
+        return false;                       // would open an authority
+
+    // Refuse a climbing segment in either spelling. Decoding can throw on
+    // malformed escapes; that is a refusal too.
+    ::rtl::OUString aDecoded;
+    try
+    {
+        aDecoded = ::rtl::Uri::decode(
+            aRest, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 );
+    }
+    catch ( ... )
+    {
+        return false;
+    }
+    if ( aDecoded.getLength() == 0 )
+        return false;
+    if ( aDecoded.indexOf( '\\' ) != -1 )
+        return false;
+
+    for ( sal_Int32 i = 0; i != -1; )
+    {
+        ::rtl::OUString const aSegment( aDecoded.getToken( 0, '/', i ) );
+        if ( aSegment.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM( ".." ) ) )
+            return false;
+    }
+    return true;
+}
+
+/** Decides whether document content may follow rUrl.
+
+    A vendor URL (see isVendorUrl) is never followed. Otherwise the running
+    document decides, through com::sun::star::document::XLinkAuthorizer, which
+    refuses the request, permits it, or asks the user -- the same decision the
+    linked-graphic loader has been making, now shared. When no document can be
+    reached the URL is followed, as that loader has always done; callers that
+    can enforce something stricter on their own (a containment rule, a scheme
+    restriction) should apply it in addition to this.
+ */
+SVT_DLLPUBLIC bool mayFollowDocumentLink( ::rtl::OUString const & rUrl );
+
+/** Decides whether a reference out of document content may be loaded.
+
+    This is the form a load site wants unless it has a reason for something
+    else. A relative reference stays in the document's own area, and a file on
+    this machine is what a document has always been able to name, so both are
+    loaded as before -- no new question is put to the user for the ordinary
+    case. Anything else either leaves this machine (a remote scheme, or a
+    file: URL whose authority names another host) or addresses process state,
+    and needs permission first.
+
+    A load site that can be stricter should be: GraphicObject asks about every
+    URL, including local ones, and keeps doing so.
+ */
+inline bool mayLoadDocumentReference( ::rtl::OUString const & rUrl )
+{
+    if ( rUrl.getLength() == 0 )
+        return false;
+    if ( !isAbsoluteUrl( rUrl ) )
+    {
+        // No scheme, so relative to the document -- except for a system path 
that
+        // opens with a host name, which names another machine just the same. A
+        // caller that does not convert to a URL first hands such a path on
+        // unchanged.
+        if ( rUrl.matchIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( 
"\\\\" ) )
+             || rUrl.matchIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( 
"//" ) ) )
+            return mayFollowDocumentLink( rUrl );
+        return true;
+    }
+    if ( isLocalFileUrl( rUrl ) )
+        return true;
+    return mayFollowDocumentLink( rUrl );
+}
+
+} }
+
+#endif
diff --git a/main/svtools/source/graphic/grfmgr.cxx 
b/main/svtools/source/graphic/grfmgr.cxx
index ccf81764c7..cfe7959727 100644
--- a/main/svtools/source/graphic/grfmgr.cxx
+++ b/main/svtools/source/graphic/grfmgr.cxx
@@ -44,6 +44,7 @@
 #include <vcl/salbtype.hxx>
 #include <unotools/cacheoptions.hxx>
 #include <svtools/grfmgr.hxx>
+#include <svtools/linkpolicy.hxx>
 
 // --> OD 2010-01-04 #i105243#
 #include <vcl/pdfextoutdevdata.hxx>
@@ -1447,32 +1448,10 @@ GraphicObject 
GraphicObject::CreateGraphicObjectFromURL( const ::rtl::OUString &
                Graphic         aGraphic;
                if ( aURL.Len() )
                {
-                       /* We must obtain authorization from the current 
document, and we
-                          need a ServiceManager to access it. Because 
utl::UcbStreamHelper
-                          relies on the ::ucbhelper::ContentBroker instance, 
we will
-                          use its ServiceManager. */
-                       ::ucbhelper::ContentBroker* pBroker = 
::ucbhelper::ContentBroker::get();
-                       if ( pBroker ) {
-                               uno::Reference< lang::XMultiServiceFactory > 
xFactory = pBroker->getServiceManager();
-                               uno::Any desktop( xFactory->createInstance( 
::rtl::OUString::createFromAscii( "com.sun.star.frame.Desktop" ) ) );
-                               uno::Reference< com::sun::star::frame::XDesktop 
> xDesktop( desktop, uno::UNO_QUERY );
-                               if ( xDesktop.is() ) {
-                                       uno::Reference< 
::com::sun::star::frame::XFrame > xFrame = xDesktop->getCurrentFrame();
-                                       if ( xFrame.is() ) {
-                                               uno::Reference< 
::com::sun::star::frame::XController > xController = xFrame->getController();
-                                               if ( xController.is() ) {
-                                                       uno::Reference< 
::com::sun::star::frame::XModel > xModel = xController->getModel();
-                                                       if ( xModel.is() ) {
-                                                               uno::Reference< 
com::sun::star::document::XLinkAuthorizer > xLinkAuthorizer( xModel, 
uno::UNO_QUERY);
-                                                               if ( 
xLinkAuthorizer.is() ) {
-                                                                       if ( 
!xLinkAuthorizer->authorizeLinks( aURL ) )
-                                                                               
return GraphicObject( aGraphic );
-                                                               }
-                                                       }
-                                               }
-                                       }
-                               }
-                       }
+                       // The URL comes out of document content, so it is 
followed only if
+                       // the shared policy permits it (see 
svtools/linkpolicy.hxx).
+                       if ( !::svt::linkpolicy::mayFollowDocumentLink( rURL ) )
+                               return GraphicObject( aGraphic );
 
                        SvStream*       pStream = 
utl::UcbStreamHelper::CreateStream( aURL, STREAM_READ );
                        if( pStream )
diff --git a/main/svtools/source/misc/linkpolicy.cxx 
b/main/svtools/source/misc/linkpolicy.cxx
new file mode 100644
index 0000000000..02a848875a
--- /dev/null
+++ b/main/svtools/source/misc/linkpolicy.cxx
@@ -0,0 +1,91 @@
+/**************************************************************
+ *
+ * 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
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *************************************************************/
+
+// MARKER(update_precomp.py): autogen include statement, do not remove
+#include "precompiled_svtools.hxx"
+
+#include <com/sun/star/document/XLinkAuthorizer.hpp>
+#include <com/sun/star/frame/XController.hpp>
+#include <com/sun/star/frame/XDesktop.hpp>
+#include <com/sun/star/frame/XFrame.hpp>
+#include <com/sun/star/frame/XModel.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/uno/Reference.hxx>
+#include <ucbhelper/contentbroker.hxx>
+
+#include <svtools/linkpolicy.hxx>
+
+using namespace ::com::sun::star;
+
+namespace svt { namespace linkpolicy {
+
+bool mayFollowDocumentLink( ::rtl::OUString const & rUrl )
+{
+    if ( rUrl.getLength() == 0 )
+        return false;
+
+    // These schemes reach process state or the object model; no permission
+    // makes them a sensible target for document content.
+    if ( isVendorUrl( rUrl ) )
+        return false;
+
+    /* Ask the current document, which needs a ServiceManager to reach.
+       Because utl::UcbStreamHelper relies on the ::ucbhelper::ContentBroker
+       instance, use its ServiceManager. */
+    ::ucbhelper::ContentBroker* pBroker = ::ucbhelper::ContentBroker::get();
+    if ( pBroker )
+    {
+        uno::Reference< lang::XMultiServiceFactory > xFactory =
+            pBroker->getServiceManager();
+        if ( xFactory.is() )
+        {
+            uno::Any desktop( xFactory->createInstance(
+                ::rtl::OUString::createFromAscii( "com.sun.star.frame.Desktop" 
) ) );
+            uno::Reference< frame::XDesktop > xDesktop( desktop, 
uno::UNO_QUERY );
+            if ( xDesktop.is() )
+            {
+                uno::Reference< frame::XFrame > xFrame( 
xDesktop->getCurrentFrame() );
+                if ( xFrame.is() )
+                {
+                    uno::Reference< frame::XController > xController(
+                        xFrame->getController() );
+                    if ( xController.is() )
+                    {
+                        uno::Reference< frame::XModel > xModel(
+                            xController->getModel() );
+                        if ( xModel.is() )
+                        {
+                            uno::Reference< document::XLinkAuthorizer >
+                                xLinkAuthorizer( xModel, uno::UNO_QUERY );
+                            if ( xLinkAuthorizer.is() )
+                                return xLinkAuthorizer->authorizeLinks( rUrl );
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    // No document to ask: behave as the linked-graphic loader always has.
+    return true;
+}
+
+} }
diff --git a/main/sw/source/core/graphic/ndgrf.cxx 
b/main/sw/source/core/graphic/ndgrf.cxx
index 05dd8e6050..61936e769a 100644
--- a/main/sw/source/core/graphic/ndgrf.cxx
+++ b/main/sw/source/core/graphic/ndgrf.cxx
@@ -30,6 +30,7 @@
 #endif
 #include <svtools/imap.hxx>
 #include <svtools/filter.hxx>
+#include <svtools/linkpolicy.hxx>
 #include <sot/storage.hxx>
 #include <sfx2/linkmgr.hxx>
 #include <editeng/boxitem.hxx>
@@ -161,11 +162,19 @@ SwGrfNode::SwGrfNode(
     if ( IsLinkedFile() )
     {
         INetURLObject aUrl( rGrfName );
-        if ( INET_PROT_FILE == aUrl.GetProtocol() &&
-             FStatHelper::IsDocument( aUrl.GetMainURL( 
INetURLObject::NO_DECODE ) ) )
+        if ( INET_PROT_FILE == aUrl.GetProtocol() )
         {
-            // File vorhanden, Verbindung herstellen ohne ein Update
-            ( (SwBaseLink*) &refLink )->Connect();
+            const String aMainUrl( aUrl.GetMainURL( INetURLObject::NO_DECODE ) 
);
+
+            // The name is document content, so the shared policy decides 
before the stat
+            // (see svtools/linkpolicy.hxx): where the authority names another 
host, the
+            // stat is itself a request to that host.
+            if ( ::svt::linkpolicy::mayLoadDocumentReference( aMainUrl ) &&
+                 FStatHelper::IsDocument( aMainUrl ) )
+            {
+                // File vorhanden, Verbindung herstellen ohne ein Update
+                ( (SwBaseLink*) &refLink )->Connect();
+            }
         }
     }
 }
diff --git a/main/sw/source/core/layout/paintfrm.cxx 
b/main/sw/source/core/layout/paintfrm.cxx
index 8a54bcadf4..08f25ed32d 100644
--- a/main/sw/source/core/layout/paintfrm.cxx
+++ b/main/sw/source/core/layout/paintfrm.cxx
@@ -1610,6 +1610,14 @@ void lcl_DrawGraphic( const SvxBrushItem& rBrush, 
OutputDevice *pOut,
        //Hier kein Link, wir wollen die Grafik synchron laden!
     ((SvxBrushItem&)rBrush).SetDoneLink( Link() );
        GraphicObject *pGrf = (GraphicObject*)rBrush.GetGraphicObject();
+       if ( !pGrf )
+       {
+               // The graphic is missing, or loading it was not permitted; 
there is
+               // nothing to draw and nothing to draw a background behind.
+               if ( bNotInside )
+                       pOut->Pop();
+               return;
+       }
 
     /// OD 17.10.2002 #103876# - outsourcing drawing of background with a 
background color.
     ::lcl_DrawGraphicBackgrd( rBrush, pOut, aAlignedGrfRect, *pGrf, bGrfNum, 
bBackgrdAlreadyDrawn );
@@ -1829,6 +1837,14 @@ void MA_FASTCALL DrawGraphic(
             // before drawing tiled graphic in loop
             // determine graphic object
             GraphicObject* pGraphicObj = const_cast< GraphicObject* 
>(pBrush->GetGraphicObject());
+            if ( !pGraphicObj )
+            {
+                // Nothing to tile: the graphic is missing or was not permitted
+                // to load. Leave the background colour to the code below, as
+                // for a brush with no graphic at all.
+                bDraw = sal_False;
+                break;
+            }
             // calculate aligned paint rectangle
             SwRect aAlignedPaintRect = rOut;
             ::SwAlignRect( aAlignedPaintRect, &rSh );
@@ -1918,21 +1934,27 @@ void MA_FASTCALL DrawGraphic(
              (ePos != GPOS_TILED) && (ePos != GPOS_AREA)
            )
         {
+            // A brush can name a graphic that is not there to load -- the file
+            // is missing, or loading it was not permitted -- in which case
+            // there is nothing to ask about its transparency.
             GraphicObject *pGrf = (GraphicObject*)pBrush->GetGraphicObject();
-            if ( bConsiderBackgroundTransparency )
+            if ( pGrf )
             {
-                GraphicAttr pGrfAttr = pGrf->GetAttr();
-                if ( (pGrfAttr.GetTransparency() != 0) &&
-                     ( pBrush && (pBrush->GetColor() == COL_TRANSPARENT) )
-                   )
+                if ( bConsiderBackgroundTransparency )
                 {
-                    bTransparentGrfWithNoFillBackgrd = true;
-                    nGrfTransparency = pGrfAttr.GetTransparency();
+                    GraphicAttr pGrfAttr = pGrf->GetAttr();
+                    if ( (pGrfAttr.GetTransparency() != 0) &&
+                         ( pBrush && (pBrush->GetColor() == COL_TRANSPARENT) )
+                       )
+                    {
+                        bTransparentGrfWithNoFillBackgrd = true;
+                        nGrfTransparency = pGrfAttr.GetTransparency();
+                    }
+                }
+                if ( pGrf->IsTransparent() )
+                {
+                    bGrfIsTransparent = true;
                 }
-            }
-            if ( pGrf->IsTransparent() )
-            {
-                bGrfIsTransparent = true;
             }
         }
 

Reply via email to