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