xref: /trunk/main/svtools/inc/svtools/linkpolicy.hxx (revision 137931e43899a1545869cb6af6ea936d8cee4956)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 #ifndef INCLUDED_SVTOOLS_LINKPOLICY_HXX
23 #define INCLUDED_SVTOOLS_LINKPOLICY_HXX
24 
25 #include "sal/config.h"
26 
27 #include "rtl/string.h"
28 #include "rtl/uri.hxx"
29 #include "rtl/ustring.hxx"
30 #include "svtools/svtdllapi.h"
31 
32 /** Policy for following a URL that came out of document content.
33 
34     Document content names resources by URL -- linked graphics, background
35     images, instance data, media. These helpers are the one place that decides
36     what may be followed, so the modules that hold the actual load calls
37     (editeng, svgio, forms, sw, svtools itself) all apply the same rule instead
38     of each carrying its own test.
39 
40     The classifying functions are inline and depend on nothing but rtl, so they
41     can be exercised directly. mayFollowDocumentLink() needs the running
42     document and therefore lives in the library.
43  */
44 namespace svt { namespace linkpolicy {
45 
46 /** Tells whether a URL uses one of the schemes that address process state or
47     an internal object tree rather than an ordinary resource.
48 
49     vnd.sun.star.expand resolves bootstrap macros; script, tdoc and uno reach
50     the scripting engine, the loaded document tree and the object model. None
51     of them is a meaningful target for document content, so these are never
52     followed regardless of any other permission.
53  */
isVendorUrl(::rtl::OUString const & rUrl)54 inline bool isVendorUrl( ::rtl::OUString const & rUrl )
55 {
56     if ( !rUrl.matchIgnoreAsciiCaseAsciiL(
57               RTL_CONSTASCII_STRINGPARAM( "vnd.sun.star." ) ) )
58         return false;
59     sal_Int32 const n = RTL_CONSTASCII_LENGTH( "vnd.sun.star." );
60     return rUrl.matchIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "expand" ), n )
61         || rUrl.matchIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "script" ), n )
62         || rUrl.matchIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "tdoc" ), n )
63         || rUrl.matchIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "uno" ), n );
64 }
65 
66 /** Tells whether a reference carries a scheme, i.e. names a resource by
67     itself rather than relative to the document that holds it.
68 
69     A scheme is a name followed by ':' before any path separator. Callers use
70     this to leave a document-relative reference alone: relative references
71     stay inside the document's own area and are the ordinary case, while a
72     scheme is what lets a reference point somewhere else entirely.
73  */
isAbsoluteUrl(::rtl::OUString const & rUrl)74 inline bool isAbsoluteUrl( ::rtl::OUString const & rUrl )
75 {
76     sal_Int32 const nColon = rUrl.indexOf( ':' );
77     if ( nColon <= 0 )
78         return false;
79     sal_Int32 const nSlash = rUrl.indexOf( '/' );
80     if ( nSlash != -1 && nSlash < nColon )
81         return false;
82     // A scheme starts with a letter (RFC 3986); a Windows drive letter
83     // followed by ':' is one character and cannot be confused with it.
84     sal_Unicode const c = rUrl.getStr()[0];
85     if ( !( ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) ) )
86         return false;
87     return nColon > 1;
88 }
89 
90 /** Tells whether a URL is a file: reference whose authority names a host
91     other than this one.
92 
93     On Windows such a reference resolves to a UNC path, so the access is a
94     request to another machine rather than a read of a local file. An empty
95     authority and localhost are this machine and are not affected.
96  */
isOffHostFileUrl(::rtl::OUString const & rUrl)97 inline bool isOffHostFileUrl( ::rtl::OUString const & rUrl )
98 {
99     if ( !rUrl.matchIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "file:" ) ) )
100         return false;
101     ::rtl::OUString aRest( rUrl.copy( RTL_CONSTASCII_LENGTH( "file:" ) ) );
102 
103     // A backslash is not URL syntax; on Windows it would form a UNC
104     // reference once the URL is converted to a system path.
105     if ( aRest.indexOf( '\\' ) != -1 )
106         return true;
107 
108     if ( !aRest.matchIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "//" ) ) )
109         return false;                       // no authority component at all
110 
111     sal_Int32 const nEnd = aRest.indexOf( '/', 2 );
112     ::rtl::OUString const aAuthority(
113         nEnd == -1 ? aRest.copy( 2 ) : aRest.copy( 2, nEnd - 2 ) );
114     if ( aAuthority.getLength() != 0
115          && !aAuthority.equalsIgnoreAsciiCaseAsciiL(
116                 RTL_CONSTASCII_STRINGPARAM( "localhost" ) ) )
117         return true;
118 
119     // An empty authority followed by a path that starts another one
120     // (file:////host/share) names a different host just the same.
121     ::rtl::OUString const aPath(
122         nEnd == -1 ? ::rtl::OUString() : aRest.copy( nEnd ) );
123     return aPath.matchIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "//" ) );
124 }
125 
126 /** Tells whether a URL names a file on this machine: the file: scheme, an
127     absolute path, and no authority pointing elsewhere.
128  */
isLocalFileUrl(::rtl::OUString const & rUrl)129 inline bool isLocalFileUrl( ::rtl::OUString const & rUrl )
130 {
131     if ( !rUrl.matchIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "file:" ) ) )
132         return false;
133     if ( isOffHostFileUrl( rUrl ) )
134         return false;
135     ::rtl::OUString aRest( rUrl.copy( RTL_CONSTASCII_LENGTH( "file:" ) ) );
136     if ( aRest.matchIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "//" ) ) )
137     {
138         sal_Int32 const nEnd = aRest.indexOf( '/', 2 );
139         if ( nEnd == -1 )
140             return false;                   // authority only, no path
141         aRest = aRest.copy( nEnd );
142     }
143     return aRest.getLength() > 1 && aRest.getStr()[0] == '/';
144 }
145 
146 /** Tells whether rTarget stays inside the directory rBase sits in.
147 
148     Both are expected to be absolute URLs, rTarget already resolved against
149     rBase. Comparing the whole string means the scheme and the authority have
150     to agree as well, so a target on another host or in another scheme is not
151     contained. A segment that would climb back out is refused whether it is
152     spelled plainly or percent-encoded, because the escape only disappears
153     when the URL is turned into a system path.
154 
155     This is the test for the case where a document may reference a file
156     delivered alongside it, but nothing else.
157  */
isContainedUnder(::rtl::OUString const & rBase,::rtl::OUString const & rTarget)158 inline bool isContainedUnder(
159     ::rtl::OUString const & rBase, ::rtl::OUString const & rTarget )
160 {
161     if ( rBase.getLength() == 0 || rTarget.getLength() == 0 )
162         return false;
163 
164     // The directory rBase lives in, trailing separator included.
165     sal_Int32 const nSlash = rBase.lastIndexOf( '/' );
166     if ( nSlash == -1 )
167         return false;
168     ::rtl::OUString const aDir( rBase.copy( 0, nSlash + 1 ) );
169 
170     if ( !rTarget.match( aDir ) )
171         return false;
172     ::rtl::OUString aRest( rTarget.copy( aDir.getLength() ) );
173     if ( aRest.getLength() == 0 )
174         return false;                       // the directory itself
175     if ( aRest.getStr()[0] == '/' )
176         return false;                       // would open an authority
177 
178     // Refuse a climbing segment in either spelling. Decoding can throw on
179     // malformed escapes; that is a refusal too.
180     ::rtl::OUString aDecoded;
181     try
182     {
183         aDecoded = ::rtl::Uri::decode(
184             aRest, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 );
185     }
186     catch ( ... )
187     {
188         return false;
189     }
190     if ( aDecoded.getLength() == 0 )
191         return false;
192     if ( aDecoded.indexOf( '\\' ) != -1 )
193         return false;
194 
195     for ( sal_Int32 i = 0; i != -1; )
196     {
197         ::rtl::OUString const aSegment( aDecoded.getToken( 0, '/', i ) );
198         if ( aSegment.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM( ".." ) ) )
199             return false;
200     }
201     return true;
202 }
203 
204 /** Decides whether document content may follow rUrl.
205 
206     A vendor URL (see isVendorUrl) is never followed. Otherwise the running
207     document decides, through com::sun::star::document::XLinkAuthorizer, which
208     refuses the request, permits it, or asks the user -- the same decision the
209     linked-graphic loader has been making, now shared. When no document can be
210     reached the URL is followed, as that loader has always done; callers that
211     can enforce something stricter on their own (a containment rule, a scheme
212     restriction) should apply it in addition to this.
213  */
214 SVT_DLLPUBLIC bool mayFollowDocumentLink( ::rtl::OUString const & rUrl );
215 
216 /** Decides whether a reference out of document content may be loaded.
217 
218     This is the form a load site wants unless it has a reason for something
219     else. A relative reference stays in the document's own area, and a file on
220     this machine is what a document has always been able to name, so both are
221     loaded as before -- no new question is put to the user for the ordinary
222     case. Anything else either leaves this machine (a remote scheme, or a
223     file: URL whose authority names another host) or addresses process state,
224     and needs permission first.
225 
226     A load site that can be stricter should be: GraphicObject asks about every
227     URL, including local ones, and keeps doing so.
228  */
mayLoadDocumentReference(::rtl::OUString const & rUrl)229 inline bool mayLoadDocumentReference( ::rtl::OUString const & rUrl )
230 {
231     if ( rUrl.getLength() == 0 )
232         return false;
233     if ( !isAbsoluteUrl( rUrl ) )
234     {
235         // No scheme, so relative to the document -- except for a system path that
236         // opens with a host name, which names another machine just the same. A
237         // caller that does not convert to a URL first hands such a path on
238         // unchanged.
239         if ( rUrl.matchIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "\\\\" ) )
240              || rUrl.matchIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "//" ) ) )
241             return mayFollowDocumentLink( rUrl );
242         return true;
243     }
244     if ( isLocalFileUrl( rUrl ) )
245         return true;
246     return mayFollowDocumentLink( rUrl );
247 }
248 
249 } }
250 
251 #endif
252