/src/libreoffice/svl/source/misc/urihelper.cxx
Line | Count | Source |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* |
3 | | * This file is part of the LibreOffice project. |
4 | | * |
5 | | * This Source Code Form is subject to the terms of the Mozilla Public |
6 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
7 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
8 | | * |
9 | | * This file incorporates work covered by the following license notice: |
10 | | * |
11 | | * Licensed to the Apache Software Foundation (ASF) under one or more |
12 | | * contributor license agreements. See the NOTICE file distributed |
13 | | * with this work for additional information regarding copyright |
14 | | * ownership. The ASF licenses this file to you under the Apache |
15 | | * License, Version 2.0 (the "License"); you may not use this file |
16 | | * except in compliance with the License. You may obtain a copy of |
17 | | * the License at http://www.apache.org/licenses/LICENSE-2.0 . |
18 | | */ |
19 | | |
20 | | #include <memory> |
21 | | #include <string_view> |
22 | | |
23 | | #include <sal/config.h> |
24 | | |
25 | | #include <unicode/idna.h> |
26 | | |
27 | | #include <svl/urihelper.hxx> |
28 | | #include <com/sun/star/ucb/Command.hpp> |
29 | | #include <com/sun/star/ucb/IllegalIdentifierException.hpp> |
30 | | #include <com/sun/star/ucb/UniversalContentBroker.hpp> |
31 | | #include <com/sun/star/ucb/UnsupportedCommandException.hpp> |
32 | | #include <com/sun/star/ucb/XCommandEnvironment.hpp> |
33 | | #include <com/sun/star/ucb/XCommandProcessor.hpp> |
34 | | #include <com/sun/star/ucb/XContent.hpp> |
35 | | #include <com/sun/star/ucb/XUniversalContentBroker.hpp> |
36 | | #include <com/sun/star/uno/Any.hxx> |
37 | | #include <com/sun/star/uno/Exception.hpp> |
38 | | #include <com/sun/star/uno/Reference.hxx> |
39 | | #include <com/sun/star/uno/RuntimeException.hpp> |
40 | | #include <com/sun/star/uno/XComponentContext.hpp> |
41 | | #include <com/sun/star/uri/UriReferenceFactory.hpp> |
42 | | #include <com/sun/star/uri/XUriReference.hpp> |
43 | | #include <com/sun/star/uri/XUriReferenceFactory.hpp> |
44 | | #include <comphelper/processfactory.hxx> |
45 | | #include <osl/diagnose.h> |
46 | | #include <rtl/character.hxx> |
47 | | #include <rtl/ustrbuf.hxx> |
48 | | #include <rtl/ustring.hxx> |
49 | | #include <sal/types.h> |
50 | | #include <sal/log.hxx> |
51 | | #include <tools/inetmime.hxx> |
52 | | #include <unotools/charclass.hxx> |
53 | | |
54 | | using namespace com::sun::star; |
55 | | |
56 | | OUString URIHelper::SmartRel2Abs(INetURLObject const & rTheBaseURIRef, |
57 | | OUString const & rTheRelURIRef, |
58 | | Link<OUString *, bool> const & rMaybeFileHdl, |
59 | | bool bCheckFileExists, |
60 | | bool bIgnoreFragment, |
61 | | INetURLObject::EncodeMechanism eEncodeMechanism, |
62 | | INetURLObject::DecodeMechanism eDecodeMechanism, |
63 | | rtl_TextEncoding eCharset, |
64 | | FSysStyle eStyle) |
65 | 74.2k | { |
66 | | // Backwards compatibility: |
67 | 74.2k | if( rTheRelURIRef.startsWith("#") ) |
68 | 2.99k | return rTheRelURIRef; |
69 | | |
70 | 71.2k | INetURLObject aAbsURIRef; |
71 | 71.2k | if (rTheBaseURIRef.HasError()) |
72 | 66.9k | aAbsURIRef. SetSmartURL(rTheRelURIRef, eEncodeMechanism, eCharset, eStyle); |
73 | 4.26k | else |
74 | 4.26k | { |
75 | 4.26k | bool bWasAbsolute; |
76 | 4.26k | aAbsURIRef = rTheBaseURIRef.smartRel2Abs(rTheRelURIRef, |
77 | 4.26k | bWasAbsolute, |
78 | 4.26k | bIgnoreFragment, |
79 | 4.26k | eEncodeMechanism, |
80 | 4.26k | eCharset, |
81 | 4.26k | false/*bRelativeNonURIs*/, |
82 | 4.26k | eStyle); |
83 | 4.26k | if (bCheckFileExists |
84 | 435 | && !bWasAbsolute |
85 | 427 | && (aAbsURIRef.GetProtocol() == INetProtocol::File)) |
86 | 237 | { |
87 | 237 | INetURLObject aNonFileURIRef; |
88 | 237 | aNonFileURIRef.SetSmartURL(rTheRelURIRef, |
89 | 237 | eEncodeMechanism, |
90 | 237 | eCharset, |
91 | 237 | eStyle); |
92 | 237 | if (!aNonFileURIRef.HasError() |
93 | 232 | && aNonFileURIRef.GetProtocol() != INetProtocol::File) |
94 | 0 | { |
95 | 0 | bool bMaybeFile = false; |
96 | 0 | if (rMaybeFileHdl.IsSet()) |
97 | 0 | { |
98 | 0 | OUString aFilePath(rTheRelURIRef); |
99 | 0 | bMaybeFile = rMaybeFileHdl.Call(&aFilePath); |
100 | 0 | } |
101 | 0 | if (!bMaybeFile) |
102 | 0 | aAbsURIRef = std::move(aNonFileURIRef); |
103 | 0 | } |
104 | 237 | } |
105 | 4.26k | } |
106 | 71.2k | return aAbsURIRef.GetMainURL(eDecodeMechanism, eCharset); |
107 | 74.2k | } |
108 | | |
109 | | namespace { Link<OUString *, bool> gMaybeFileHdl; } |
110 | | |
111 | | void URIHelper::SetMaybeFileHdl(Link<OUString *, bool> const & rTheMaybeFileHdl) |
112 | 0 | { |
113 | 0 | gMaybeFileHdl = rTheMaybeFileHdl; |
114 | 0 | } |
115 | | |
116 | | Link<OUString *, bool> const & URIHelper::GetMaybeFileHdl() |
117 | 34.5k | { |
118 | 34.5k | return gMaybeFileHdl; |
119 | 34.5k | } |
120 | | |
121 | | namespace { |
122 | | |
123 | | bool isAbsoluteHierarchicalUriReference( |
124 | | css::uno::Reference< css::uri::XUriReference > const & uriReference) |
125 | 0 | { |
126 | 0 | return uriReference.is() && uriReference->isAbsolute() |
127 | 0 | && !uriReference->hasRelativePath(); |
128 | 0 | } |
129 | | |
130 | | // To improve performance, assume that if for any prefix URL of a given |
131 | | // hierarchical URL either a UCB content cannot be created, or the UCB content |
132 | | // does not support the getCasePreservingURL command, then this will hold for |
133 | | // any other prefix URL of the given URL, too: |
134 | | enum Result { Success, GeneralFailure, SpecificFailure }; |
135 | | |
136 | | Result normalizePrefix( css::uno::Reference< css::ucb::XUniversalContentBroker > const & broker, |
137 | | OUString const & uri, OUString * normalized) |
138 | 0 | { |
139 | 0 | assert(broker.is() && normalized != nullptr); |
140 | 0 | css::uno::Reference< css::ucb::XContent > content; |
141 | 0 | try { |
142 | 0 | content = broker->queryContent(broker->createContentIdentifier(uri)); |
143 | 0 | } catch (css::ucb::IllegalIdentifierException &) {} |
144 | 0 | if (!content.is()) { |
145 | 0 | return GeneralFailure; |
146 | 0 | } |
147 | 0 | try { |
148 | 0 | bool ok = |
149 | 0 | (css::uno::Reference< css::ucb::XCommandProcessor >( |
150 | 0 | content, css::uno::UNO_QUERY_THROW)->execute( |
151 | 0 | css::ucb::Command(u"getCasePreservingURL"_ustr, |
152 | 0 | -1, css::uno::Any()), |
153 | 0 | 0, |
154 | 0 | css::uno::Reference< css::ucb::XCommandEnvironment >()) |
155 | 0 | >>= *normalized); |
156 | 0 | OSL_ASSERT(ok); |
157 | 0 | } catch (css::uno::RuntimeException &) { |
158 | 0 | throw; |
159 | 0 | } catch (css::ucb::UnsupportedCommandException &) { |
160 | 0 | return GeneralFailure; |
161 | 0 | } catch (css::uno::Exception &) { |
162 | 0 | return SpecificFailure; |
163 | 0 | } |
164 | 0 | return Success; |
165 | 0 | } |
166 | | |
167 | | OUString normalize( |
168 | | css::uno::Reference< css::ucb::XUniversalContentBroker > const & broker, |
169 | | css::uno::Reference< css::uri::XUriReferenceFactory > const & uriFactory, |
170 | | OUString const & uriReference) |
171 | 0 | { |
172 | | // normalizePrefix can potentially fail (a typically example being a file |
173 | | // URL that denotes a non-existing resource); in such a case, try to |
174 | | // normalize as long a prefix of the given URL as possible (i.e., normalize |
175 | | // all the existing directories within the path): |
176 | 0 | OUString normalized; |
177 | 0 | sal_Int32 n = uriReference.indexOf('#'); |
178 | 0 | normalized = n == -1 ? uriReference : uriReference.copy(0, n); |
179 | 0 | switch (normalizePrefix(broker, normalized, &normalized)) { |
180 | 0 | case Success: |
181 | 0 | return n == -1 ? normalized : normalized + uriReference.subView(n); |
182 | 0 | case GeneralFailure: |
183 | 0 | return uriReference; |
184 | 0 | case SpecificFailure: |
185 | 0 | default: |
186 | 0 | break; |
187 | 0 | } |
188 | 0 | css::uno::Reference< css::uri::XUriReference > ref( |
189 | 0 | uriFactory->parse(uriReference)); |
190 | 0 | if (!isAbsoluteHierarchicalUriReference(ref)) { |
191 | 0 | return uriReference; |
192 | 0 | } |
193 | 0 | sal_Int32 count = ref->getPathSegmentCount(); |
194 | 0 | if (count < 2) { |
195 | 0 | return uriReference; |
196 | 0 | } |
197 | 0 | OUStringBuffer head(ref->getScheme()); |
198 | 0 | head.append(':'); |
199 | 0 | if (ref->hasAuthority()) { |
200 | 0 | head.append("//" + ref->getAuthority()); |
201 | 0 | } |
202 | 0 | for (sal_Int32 i = count - 1; i > 0; --i) { |
203 | 0 | OUStringBuffer buf(head); |
204 | 0 | for (sal_Int32 j = 0; j < i; ++j) { |
205 | 0 | buf.append('/'); |
206 | 0 | buf.append(ref->getPathSegment(j)); |
207 | 0 | } |
208 | 0 | normalized = buf.makeStringAndClear(); |
209 | 0 | if (normalizePrefix(broker, normalized, &normalized) != SpecificFailure) |
210 | 0 | { |
211 | 0 | buf.append(normalized); |
212 | 0 | css::uno::Reference< css::uri::XUriReference > preRef( |
213 | 0 | uriFactory->parse(normalized)); |
214 | 0 | if (!isAbsoluteHierarchicalUriReference(preRef)) { |
215 | | // This could only happen if something is inconsistent: |
216 | 0 | break; |
217 | 0 | } |
218 | 0 | sal_Int32 preCount = preRef->getPathSegmentCount(); |
219 | | // normalizePrefix may have added or removed a final slash: |
220 | 0 | if (preCount != i) { |
221 | 0 | if (preCount == i - 1) { |
222 | 0 | buf.append('/'); |
223 | 0 | } else if (preCount - 1 == i && !buf.isEmpty() |
224 | 0 | && buf[buf.getLength() - 1] == '/') |
225 | 0 | { |
226 | 0 | buf.setLength(buf.getLength() - 1); |
227 | 0 | } else { |
228 | | // This could only happen if something is inconsistent: |
229 | 0 | break; |
230 | 0 | } |
231 | 0 | } |
232 | 0 | for (sal_Int32 j = i; j < count; ++j) { |
233 | 0 | buf.append('/'); |
234 | 0 | buf.append(ref->getPathSegment(j)); |
235 | 0 | } |
236 | 0 | if (ref->hasQuery()) { |
237 | 0 | buf.append('?'); |
238 | 0 | buf.append(ref->getQuery()); |
239 | 0 | } |
240 | 0 | if (ref->hasFragment()) { |
241 | 0 | buf.append('#'); |
242 | 0 | buf.append(ref->getFragment()); |
243 | 0 | } |
244 | 0 | return buf.makeStringAndClear(); |
245 | 0 | } |
246 | 0 | } |
247 | 0 | return uriReference; |
248 | 0 | } |
249 | | |
250 | | } |
251 | | |
252 | | css::uno::Reference< css::uri::XUriReference > |
253 | | URIHelper::normalizedMakeRelative( |
254 | | css::uno::Reference< css::uno::XComponentContext > const & context, |
255 | | OUString const & baseUriReference, OUString const & uriReference) |
256 | 0 | { |
257 | 0 | OSL_ASSERT(context.is()); |
258 | 0 | css::uno::Reference< css::ucb::XUniversalContentBroker > broker( |
259 | 0 | css::ucb::UniversalContentBroker::create(context)); |
260 | 0 | css::uno::Reference< css::uri::XUriReferenceFactory > uriFactory( |
261 | 0 | css::uri::UriReferenceFactory::create(context)); |
262 | 0 | return uriFactory->makeRelative( |
263 | 0 | uriFactory->parse(normalize(broker, uriFactory, baseUriReference)), |
264 | 0 | uriFactory->parse(normalize(broker, uriFactory, uriReference)), true, |
265 | 0 | true, false); |
266 | 0 | } |
267 | | |
268 | | OUString URIHelper::simpleNormalizedMakeRelative( |
269 | | OUString const & baseUriReference, OUString const & uriReference) |
270 | 0 | { |
271 | 0 | css::uno::Reference< css::uri::XUriReference > rel( |
272 | 0 | URIHelper::normalizedMakeRelative( |
273 | 0 | comphelper::getProcessComponentContext(), baseUriReference, |
274 | 0 | uriReference)); |
275 | 0 | return rel.is() ? rel->getUriReference() : uriReference; |
276 | 0 | } |
277 | | |
278 | | |
279 | | // FindFirstURLInText |
280 | | |
281 | | |
282 | | namespace { |
283 | | |
284 | | sal_Int32 nextChar(std::u16string_view rStr, sal_Int32 nPos) |
285 | 0 | { |
286 | 0 | return rtl::isHighSurrogate(rStr[nPos]) |
287 | 0 | && rStr.size() - nPos >= 2 |
288 | 0 | && rtl::isLowSurrogate(rStr[nPos + 1]) ? |
289 | 0 | nPos + 2 : nPos + 1; |
290 | 0 | } |
291 | | |
292 | | bool isBoundary1(CharClass const & rCharClass, OUString const & rStr, |
293 | | sal_Int32 nPos, sal_Int32 nEnd) |
294 | 0 | { |
295 | 0 | if (nPos == nEnd) |
296 | 0 | return true; |
297 | 0 | if (rCharClass.isLetterNumeric(rStr, nPos)) |
298 | 0 | return false; |
299 | 0 | switch (rStr[nPos]) |
300 | 0 | { |
301 | 0 | case '$': |
302 | 0 | case '%': |
303 | 0 | case '&': |
304 | 0 | case '-': |
305 | 0 | case '/': |
306 | 0 | case '@': |
307 | 0 | case '\\': |
308 | 0 | return false; |
309 | 0 | default: |
310 | 0 | return true; |
311 | 0 | } |
312 | 0 | } |
313 | | |
314 | | bool isBoundary2(CharClass const & rCharClass, OUString const & rStr, |
315 | | sal_Int32 nPos, sal_Int32 nEnd) |
316 | 0 | { |
317 | 0 | if (nPos == nEnd) |
318 | 0 | return true; |
319 | 0 | if (rCharClass.isLetterNumeric(rStr, nPos)) |
320 | 0 | return false; |
321 | 0 | switch (rStr[nPos]) |
322 | 0 | { |
323 | 0 | case '!': |
324 | 0 | case '#': |
325 | 0 | case '$': |
326 | 0 | case '%': |
327 | 0 | case '&': |
328 | 0 | case '\'': |
329 | 0 | case '*': |
330 | 0 | case '+': |
331 | 0 | case '-': |
332 | 0 | case '/': |
333 | 0 | case '=': |
334 | 0 | case '?': |
335 | 0 | case '@': |
336 | 0 | case '^': |
337 | 0 | case '_': |
338 | 0 | case '`': |
339 | 0 | case '{': |
340 | 0 | case '|': |
341 | 0 | case '}': |
342 | 0 | case '~': |
343 | 0 | return false; |
344 | 0 | default: |
345 | 0 | return true; |
346 | 0 | } |
347 | 0 | } |
348 | | |
349 | | // tdf#145381 Added MatchingBracketDepth counter to detect matching closing |
350 | | // brackets that are part of the uri |
351 | | bool checkWChar(CharClass const & rCharClass, OUString const & rStr, |
352 | | sal_Int32 * pPos, sal_Int32 * pEnd, |
353 | | sal_Int32 * pMatchingBracketDepth = nullptr, |
354 | | bool bBackslash = false, bool bPipe = false) |
355 | 0 | { |
356 | 0 | sal_Unicode c = rStr[*pPos]; |
357 | 0 | if (rtl::isAscii(c)) |
358 | 0 | { |
359 | 0 | static sal_uInt8 const aMap[128] |
360 | 0 | = { 0, 0, 0, 0, 0, 0, 0, 0, |
361 | 0 | 0, 0, 0, 0, 0, 0, 0, 0, |
362 | 0 | 0, 0, 0, 0, 0, 0, 0, 0, |
363 | 0 | 0, 0, 0, 0, 0, 0, 0, 0, |
364 | 0 | 0, 1, 0, 0, 4, 4, 4, 1, // !"#$%&' |
365 | 0 | 5, 6, 1, 1, 1, 4, 1, 4, // ()*+,-./ |
366 | 0 | 4, 4, 4, 4, 4, 4, 4, 4, // 01234567 |
367 | 0 | 4, 4, 1, 1, 0, 1, 0, 1, // 89:;<=>? |
368 | 0 | 4, 4, 4, 4, 4, 4, 4, 4, // @ABCDEFG |
369 | 0 | 4, 4, 4, 4, 4, 4, 4, 4, // HIJKLMNO |
370 | 0 | 4, 4, 4, 4, 4, 4, 4, 4, // PQRSTUVW |
371 | 0 | 4, 4, 4, 1, 2, 1, 0, 1, // XYZ[\]^_ |
372 | 0 | 0, 4, 4, 4, 4, 4, 4, 4, // `abcdefg |
373 | 0 | 4, 4, 4, 4, 4, 4, 4, 4, // hijklmno |
374 | 0 | 4, 4, 4, 4, 4, 4, 4, 4, // pqrstuvw |
375 | 0 | 4, 4, 4, 0, 3, 0, 1, 0 }; // xyz{|}~ |
376 | 0 | switch (aMap[c]) |
377 | 0 | { |
378 | 0 | default: // not uric |
379 | 0 | return false; |
380 | | |
381 | 0 | case 1: // uric |
382 | 0 | ++(*pPos); |
383 | 0 | return true; |
384 | | |
385 | 0 | case 2: // "\" |
386 | 0 | if (bBackslash) |
387 | 0 | { |
388 | 0 | *pEnd = ++(*pPos); |
389 | 0 | return true; |
390 | 0 | } |
391 | 0 | else |
392 | 0 | return false; |
393 | | |
394 | 0 | case 3: // "|" |
395 | 0 | if (bPipe) |
396 | 0 | { |
397 | 0 | *pEnd = ++(*pPos); |
398 | 0 | return true; |
399 | 0 | } |
400 | 0 | else |
401 | 0 | return false; |
402 | | |
403 | 0 | case 4: // alpha, digit, "$", "%", "&", "-", "/", "@" (see |
404 | | // isBoundary1) |
405 | 0 | *pEnd = ++(*pPos); |
406 | 0 | return true; |
407 | | |
408 | 0 | case 5: // opening bracket |
409 | 0 | ++(*pPos); |
410 | 0 | if(nullptr != pMatchingBracketDepth) |
411 | 0 | ++(*pMatchingBracketDepth); |
412 | 0 | return true; |
413 | | |
414 | 0 | case 6: // closing bracket |
415 | 0 | ++(*pPos); |
416 | 0 | if(nullptr != pMatchingBracketDepth && *pMatchingBracketDepth > 0) |
417 | 0 | { |
418 | 0 | --(*pMatchingBracketDepth); |
419 | | // tdf#145381 When there was an opening bracket, detect this closing bracket |
420 | | // as part of the uri |
421 | 0 | *pEnd = *pPos; |
422 | 0 | } |
423 | 0 | return true; |
424 | |
|
425 | 0 | } |
426 | 0 | } |
427 | 0 | else if (rCharClass.isLetterNumeric(rStr, *pPos)) |
428 | 0 | { |
429 | 0 | *pEnd = *pPos = nextChar(rStr, *pPos); |
430 | 0 | return true; |
431 | 0 | } |
432 | 0 | else |
433 | 0 | return false; |
434 | 0 | } |
435 | | |
436 | | sal_uInt32 scanDomain(OUString const & rStr, sal_Int32 * pPos, |
437 | | sal_Int32 nEnd) |
438 | 0 | { |
439 | 0 | sal_Unicode const * pBuffer = rStr.getStr(); |
440 | 0 | sal_Unicode const * p = pBuffer + *pPos; |
441 | 0 | sal_uInt32 nLabels = INetURLObject::scanDomain(p, pBuffer + nEnd, false); |
442 | 0 | *pPos = sal::static_int_cast< sal_Int32 >(p - pBuffer); |
443 | 0 | return nLabels; |
444 | 0 | } |
445 | | |
446 | | } |
447 | | |
448 | | OUString URIHelper::FindFirstURLInText(OUString const & rText, |
449 | | sal_Int32 & rBegin, |
450 | | sal_Int32 & rEnd, |
451 | | CharClass const & rCharClass, |
452 | | INetURLObject::EncodeMechanism eMechanism, |
453 | | rtl_TextEncoding eCharset) |
454 | 0 | { |
455 | 0 | if (rBegin > rEnd || rEnd > rText.getLength()) |
456 | 0 | return OUString(); |
457 | | |
458 | | // Search for the first substring of [rBegin..rEnd[ that matches any of the |
459 | | // following productions (for which the appropriate style bit is set in |
460 | | // eStyle, if applicable). |
461 | | |
462 | | // 1st Production (known scheme): |
463 | | // \B1 <one of the known schemes, except file> ":" 1*wchar ["#" 1*wchar] |
464 | | // \B1 |
465 | | |
466 | | // 2nd Production (file): |
467 | | // \B1 "FILE:" 1*(wchar / "\" / "|") ["#" 1*wchar] \B1 |
468 | | |
469 | | // 3rd Production (ftp): |
470 | | // \B1 "FTP" 2*("." label) ["/" *wchar] ["#" 1*wchar] \B1 |
471 | | |
472 | | // 4th Production (http): |
473 | | // \B1 "WWW" 2*("." label) ["/" *wchar] ["#" 1*wchar] \B1 |
474 | | |
475 | | // 5th Production (mailto): |
476 | | // \B2 local-part "@" domain \B1 |
477 | | |
478 | | // 6th Production (UNC file): |
479 | | // \B1 "\\" domain "\" *(wchar / "\") \B1 |
480 | | |
481 | | // 7th Production (DOS file): |
482 | | // \B1 ALPHA ":\" *(wchar / "\") \B1 |
483 | | |
484 | | // 8th Production (Unix-like DOS file): |
485 | | // \B1 ALPHA ":/" *(wchar / "\") \B1 |
486 | | |
487 | | // The productions use the following auxiliary rules. |
488 | | |
489 | | // local-part = atom *("." atom) |
490 | | // atom = 1*(alphanum / "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" |
491 | | // / "-" / "/" / "=" / "?" / "^" / "_" / "`" / "{" / "|" / "}" |
492 | | // / "~") |
493 | | // domain = label *("." label) |
494 | | // label = alphanum [*(alphanum / "-") alphanum] |
495 | | // alphanum = ALPHA / DIGIT |
496 | | // wchar = <any uric character (ignoring the escaped rule), or "%", or |
497 | | // a letter or digit (according to rCharClass)> |
498 | | |
499 | | // "\B1" (boundary 1) stands for the beginning or end of the block of text, |
500 | | // or a character that is neither (a) a letter or digit (according to |
501 | | // rCharClass), nor (b) any of "$", "%", "&", "-", "/", "@", or "\". |
502 | | // (FIXME: What was the rationale for this set of punctuation characters?) |
503 | | |
504 | | // "\B2" (boundary 2) stands for the beginning or end of the block of text, |
505 | | // or a character that is neither (a) a letter or digit (according to |
506 | | // rCharClass), nor (b) any of "!", "#", "$", "%", "&", "'", "*", "+", "-", |
507 | | // "/", "=", "?", "@", "^", "_", "`", "{", "|", "}", or "~" (i.e., an RFC |
508 | | // 822 <atom> character, or "@" from \B1's set above). |
509 | | |
510 | | // Productions 1--4, and 6--8 try to find a maximum-length match, but they |
511 | | // stop at the first <wchar> character that is a "\B1" character which is |
512 | | // only followed by "\B1" characters (taking "\" and "|" characters into |
513 | | // account appropriately). Production 5 simply tries to find a maximum- |
514 | | // length match. |
515 | | |
516 | | // Productions 1--4 use the given eMechanism and eCharset. Productions 5--9 |
517 | | // use EncodeMechanism::All. |
518 | | |
519 | | // Productions 6--9 are only applicable if the FSysStyle::Dos bit is set in |
520 | | // eStyle. |
521 | | |
522 | | // tdf#145381: In addition to the productions I added a mechanism to detect |
523 | | // matching brackets. The task presents the case of a url that ends on a |
524 | | // closing bracket. This needs to be detected as part of the uri in the case |
525 | | // that a matching opening bracket exists. |
526 | | |
527 | 0 | bool bBoundary1 = true; |
528 | 0 | bool bBoundary2 = true; |
529 | 0 | for (sal_Int32 nPos = rBegin; nPos != rEnd; nPos = nextChar(rText, nPos)) |
530 | 0 | { |
531 | 0 | sal_Unicode c = rText[nPos]; |
532 | 0 | if (bBoundary1) |
533 | 0 | { |
534 | 0 | if (rtl::isAsciiAlpha(c)) |
535 | 0 | { |
536 | 0 | sal_Int32 i = nPos; |
537 | 0 | INetProtocol eScheme = INetURLObject::CompareProtocolScheme(rText.subView(i, rEnd - i)); |
538 | 0 | if (eScheme == INetProtocol::File) // 2nd |
539 | 0 | { |
540 | 0 | while (rText[i++] != ':') ; |
541 | 0 | sal_Int32 nPrefixEnd = i; |
542 | 0 | sal_Int32 nUriEnd = i; |
543 | 0 | while (i != rEnd |
544 | 0 | && checkWChar(rCharClass, rText, &i, &nUriEnd, nullptr, true, |
545 | 0 | true)) ; |
546 | 0 | if (i != nPrefixEnd && i != rEnd && rText[i] == '#') |
547 | 0 | { |
548 | 0 | ++i; |
549 | 0 | while (i != rEnd |
550 | 0 | && checkWChar(rCharClass, rText, &i, &nUriEnd)) ; |
551 | 0 | } |
552 | 0 | if (nUriEnd != nPrefixEnd |
553 | 0 | && isBoundary1(rCharClass, rText, nUriEnd, rEnd)) |
554 | 0 | { |
555 | 0 | INetURLObject aUri(rText.subView(nPos, nUriEnd - nPos), |
556 | 0 | INetProtocol::File, eMechanism, eCharset, |
557 | 0 | FSysStyle::Detect); |
558 | 0 | if (!aUri.HasError()) |
559 | 0 | { |
560 | 0 | rBegin = nPos; |
561 | 0 | rEnd = nUriEnd; |
562 | 0 | return |
563 | 0 | aUri.GetMainURL(INetURLObject::DecodeMechanism::ToIUri); |
564 | 0 | } |
565 | 0 | } |
566 | 0 | } |
567 | 0 | else if (eScheme != INetProtocol::NotValid) // 1st |
568 | 0 | { |
569 | 0 | while (rText[i++] != ':') ; |
570 | 0 | sal_Int32 nPrefixEnd = i; |
571 | 0 | sal_Int32 nUriEnd = i; |
572 | 0 | sal_Int32 nMatchingBracketDepth = 0; |
573 | 0 | while (i != rEnd |
574 | 0 | && checkWChar(rCharClass, rText, &i, &nUriEnd, |
575 | 0 | &nMatchingBracketDepth)) ; |
576 | 0 | if (i != nPrefixEnd && i != rEnd && rText[i] == '#') |
577 | 0 | { |
578 | 0 | ++i; |
579 | 0 | while (i != rEnd |
580 | 0 | && checkWChar(rCharClass, rText, &i, &nUriEnd)) ; |
581 | 0 | } |
582 | 0 | if (nUriEnd != nPrefixEnd |
583 | 0 | && (isBoundary1(rCharClass, rText, nUriEnd, rEnd) |
584 | 0 | || rText[nUriEnd] == '\\')) |
585 | 0 | { |
586 | 0 | INetURLObject aUri(rText.subView(nPos, nUriEnd - nPos), |
587 | 0 | INetProtocol::Http, eMechanism, |
588 | 0 | eCharset); |
589 | 0 | if (!aUri.HasError()) |
590 | 0 | { |
591 | 0 | rBegin = nPos; |
592 | 0 | rEnd = nUriEnd; |
593 | 0 | return |
594 | 0 | aUri.GetMainURL(INetURLObject::DecodeMechanism::ToIUri); |
595 | 0 | } |
596 | 0 | } |
597 | 0 | } |
598 | | |
599 | | // 3rd, 4th: |
600 | 0 | i = nPos; |
601 | 0 | sal_uInt32 nLabels = scanDomain(rText, &i, rEnd); |
602 | 0 | if (nLabels >= 3 |
603 | 0 | && rText[nPos + 3] == '.' |
604 | 0 | && (((rText[nPos] == 'w' |
605 | 0 | || rText[nPos] == 'W') |
606 | 0 | && (rText[nPos + 1] == 'w' |
607 | 0 | || rText[nPos + 1] == 'W') |
608 | 0 | && (rText[nPos + 2] == 'w' |
609 | 0 | || rText[nPos + 2] == 'W')) |
610 | 0 | || ((rText[nPos] == 'f' |
611 | 0 | || rText[nPos] == 'F') |
612 | 0 | && (rText[nPos + 1] == 't' |
613 | 0 | || rText[nPos + 1] == 'T') |
614 | 0 | && (rText[nPos + 2] == 'p' |
615 | 0 | || rText[nPos + 2] == 'P')))) |
616 | | // (note that rText.GetChar(nPos + 3) is guaranteed to be |
617 | | // valid) |
618 | 0 | { |
619 | 0 | sal_Int32 nUriEnd = i; |
620 | 0 | if (i != rEnd && rText[i] == '/') |
621 | 0 | { |
622 | 0 | nUriEnd = ++i; |
623 | 0 | while (i != rEnd |
624 | 0 | && checkWChar(rCharClass, rText, &i, &nUriEnd)) ; |
625 | 0 | } |
626 | 0 | if (i != rEnd && rText[i] == '#') |
627 | 0 | { |
628 | 0 | ++i; |
629 | 0 | while (i != rEnd |
630 | 0 | && checkWChar(rCharClass, rText, &i, &nUriEnd)) ; |
631 | 0 | } |
632 | 0 | if (isBoundary1(rCharClass, rText, nUriEnd, rEnd) |
633 | 0 | || rText[nUriEnd] == '\\') |
634 | 0 | { |
635 | 0 | INetURLObject aUri(rText.subView(nPos, nUriEnd - nPos), |
636 | 0 | INetProtocol::Http, eMechanism, |
637 | 0 | eCharset); |
638 | 0 | if (!aUri.HasError()) |
639 | 0 | { |
640 | 0 | rBegin = nPos; |
641 | 0 | rEnd = nUriEnd; |
642 | 0 | return |
643 | 0 | aUri.GetMainURL(INetURLObject::DecodeMechanism::ToIUri); |
644 | 0 | } |
645 | 0 | } |
646 | 0 | } |
647 | | |
648 | 0 | if (rEnd - nPos >= 3 |
649 | 0 | && rText[nPos + 1] == ':' |
650 | 0 | && (rText[nPos + 2] == '/' |
651 | 0 | || rText[nPos + 2] == '\\')) // 7th, 8th |
652 | 0 | { |
653 | 0 | i = nPos + 3; |
654 | 0 | sal_Int32 nUriEnd = i; |
655 | 0 | while (i != rEnd |
656 | 0 | && checkWChar(rCharClass, rText, &i, &nUriEnd)) ; |
657 | 0 | if (isBoundary1(rCharClass, rText, nUriEnd, rEnd)) |
658 | 0 | { |
659 | 0 | INetURLObject aUri(rText.subView(nPos, nUriEnd - nPos), |
660 | 0 | INetProtocol::File, |
661 | 0 | INetURLObject::EncodeMechanism::All, |
662 | 0 | RTL_TEXTENCODING_UTF8, |
663 | 0 | FSysStyle::Dos); |
664 | 0 | if (!aUri.HasError()) |
665 | 0 | { |
666 | 0 | rBegin = nPos; |
667 | 0 | rEnd = nUriEnd; |
668 | 0 | return |
669 | 0 | aUri.GetMainURL(INetURLObject::DecodeMechanism::ToIUri); |
670 | 0 | } |
671 | 0 | } |
672 | 0 | } |
673 | 0 | } |
674 | 0 | else if (rEnd - nPos >= 2 |
675 | 0 | && rText[nPos] == '\\' |
676 | 0 | && rText[nPos + 1] == '\\') // 6th |
677 | 0 | { |
678 | 0 | sal_Int32 i = nPos + 2; |
679 | 0 | sal_uInt32 nLabels = scanDomain(rText, &i, rEnd); |
680 | 0 | if (nLabels >= 1 && i != rEnd && rText[i] == '\\') |
681 | 0 | { |
682 | 0 | sal_Int32 nUriEnd = ++i; |
683 | 0 | while (i != rEnd |
684 | 0 | && checkWChar(rCharClass, rText, &i, &nUriEnd, |
685 | 0 | nullptr, true)) ; |
686 | 0 | if (isBoundary1(rCharClass, rText, nUriEnd, rEnd)) |
687 | 0 | { |
688 | 0 | INetURLObject aUri(rText.subView(nPos, nUriEnd - nPos), |
689 | 0 | INetProtocol::File, |
690 | 0 | INetURLObject::EncodeMechanism::All, |
691 | 0 | RTL_TEXTENCODING_UTF8, |
692 | 0 | FSysStyle::Dos); |
693 | 0 | if (!aUri.HasError()) |
694 | 0 | { |
695 | 0 | rBegin = nPos; |
696 | 0 | rEnd = nUriEnd; |
697 | 0 | return |
698 | 0 | aUri.GetMainURL(INetURLObject::DecodeMechanism::ToIUri); |
699 | 0 | } |
700 | 0 | } |
701 | 0 | } |
702 | 0 | } |
703 | 0 | } |
704 | 0 | if (bBoundary2 && INetMIME::isAtomChar(c)) // 5th |
705 | 0 | { |
706 | 0 | bool bDot = false; |
707 | 0 | for (sal_Int32 i = nPos + 1; i != rEnd; ++i) |
708 | 0 | { |
709 | 0 | sal_Unicode c2 = rText[i]; |
710 | 0 | if (INetMIME::isAtomChar(c2)) |
711 | 0 | bDot = false; |
712 | 0 | else if (bDot) |
713 | 0 | break; |
714 | 0 | else if (c2 == '.') |
715 | 0 | bDot = true; |
716 | 0 | else |
717 | 0 | { |
718 | 0 | if (c2 == '@') |
719 | 0 | { |
720 | 0 | ++i; |
721 | 0 | sal_uInt32 nLabels = scanDomain(rText, &i, rEnd); |
722 | 0 | if (nLabels >= 1 |
723 | 0 | && isBoundary1(rCharClass, rText, i, rEnd)) |
724 | 0 | { |
725 | 0 | INetURLObject aUri(rText.subView(nPos, i - nPos), |
726 | 0 | INetProtocol::Mailto, |
727 | 0 | INetURLObject::EncodeMechanism::All); |
728 | 0 | if (!aUri.HasError()) |
729 | 0 | { |
730 | 0 | rBegin = nPos; |
731 | 0 | rEnd = i; |
732 | 0 | return aUri.GetMainURL( |
733 | 0 | INetURLObject::DecodeMechanism::ToIUri); |
734 | 0 | } |
735 | 0 | } |
736 | 0 | } |
737 | 0 | break; |
738 | 0 | } |
739 | 0 | } |
740 | 0 | } |
741 | 0 | bBoundary1 = isBoundary1(rCharClass, rText, nPos, rEnd); |
742 | 0 | bBoundary2 = isBoundary2(rCharClass, rText, nPos, rEnd); |
743 | 0 | } |
744 | 0 | rBegin = rEnd; |
745 | 0 | return OUString(); |
746 | 0 | } |
747 | | |
748 | | OUString URIHelper::FindFirstDOIInText(std::u16string_view rText, |
749 | | sal_Int32 & rBegin, |
750 | | sal_Int32 nEnd, |
751 | | CharClass const & rCharClass) |
752 | 0 | { |
753 | 0 | if (rBegin > nEnd || nEnd > static_cast<sal_Int32>(rText.size())) |
754 | 0 | return OUString(); |
755 | | |
756 | 0 | sal_Int32 start = 7; |
757 | 0 | sal_Int32 count = nEnd-rBegin; |
758 | 0 | OUString candidate(rText.substr(rBegin, count)); |
759 | | // Match with regex "doi:10\.\d{4,9}\/[-._;()\/:a-zA-Z0-9]+" |
760 | 0 | if (candidate.startsWithIgnoreAsciiCase("doi:10.")) |
761 | 0 | { |
762 | 0 | bool flag = true; |
763 | 0 | sal_Int32 digit = 0; |
764 | 0 | for (sal_Int32 i=start; i<count; i++) |
765 | 0 | { |
766 | 0 | sal_Unicode c = candidate[i]; |
767 | | // Match 4 to 9 digits before slash |
768 | 0 | if (digit >= 0) |
769 | 0 | { |
770 | 0 | if (digit>9) |
771 | 0 | { |
772 | 0 | flag = false; |
773 | 0 | break; |
774 | 0 | } |
775 | | |
776 | 0 | if ( rCharClass.isDigit(candidate,i) ) |
777 | 0 | { |
778 | 0 | digit++; |
779 | 0 | } |
780 | 0 | else if (c=='/' && digit>=4 && i<count-1) |
781 | 0 | { |
782 | 0 | digit=-1; |
783 | 0 | } |
784 | 0 | else |
785 | 0 | { |
786 | 0 | flag = false; |
787 | 0 | break; |
788 | 0 | } |
789 | 0 | } |
790 | | // Match [-._;()\/:a-zA-Z0-9] after slash |
791 | 0 | else if (!( rCharClass.isAlphaNumeric(candidate, i) || c == '.' || c == '-' || c=='_' || |
792 | 0 | c==';' || c=='(' || c==')' || c=='\\' || (c=='/' && i<count-1) || c==':')) |
793 | 0 | { |
794 | 0 | flag = false; |
795 | 0 | break; |
796 | 0 | } |
797 | 0 | } |
798 | 0 | if (flag && digit==-1) |
799 | 0 | { |
800 | 0 | return OUString::Concat("https://doi.org/")+candidate.subView(4); |
801 | 0 | } |
802 | 0 | } |
803 | 0 | rBegin = nEnd; |
804 | 0 | return OUString(); |
805 | 0 | } |
806 | | |
807 | | OUString URIHelper::removePassword(OUString const & rURI, |
808 | | INetURLObject::EncodeMechanism eEncodeMechanism, |
809 | | INetURLObject::DecodeMechanism eDecodeMechanism, |
810 | | rtl_TextEncoding eCharset) |
811 | 0 | { |
812 | 0 | INetURLObject aObj(rURI, eEncodeMechanism, eCharset); |
813 | 0 | return aObj.HasError() ? |
814 | 0 | rURI : |
815 | 0 | aObj.GetURLNoPass(eDecodeMechanism, eCharset); |
816 | 0 | } |
817 | | |
818 | 531 | OUString URIHelper::resolveIdnaHost(OUString const & url) { |
819 | 531 | css::uno::Reference<css::uri::XUriReference> uri( |
820 | 531 | css::uri::UriReferenceFactory::create( |
821 | 531 | comphelper::getProcessComponentContext()) |
822 | 531 | ->parse(url)); |
823 | 531 | if (!(uri.is() && uri->hasAuthority())) { |
824 | 29 | return url; |
825 | 29 | } |
826 | 502 | auto auth(uri->getAuthority()); |
827 | 502 | if (auth.isEmpty()) |
828 | 0 | return url; |
829 | 502 | sal_Int32 hostStart = auth.indexOf('@') + 1; |
830 | 502 | sal_Int32 hostEnd = auth.getLength(); |
831 | 502 | while (hostEnd > hostStart && rtl::isAsciiDigit(auth[hostEnd - 1])) { |
832 | 0 | --hostEnd; |
833 | 0 | } |
834 | 502 | if (hostEnd > hostStart && auth[hostEnd - 1] == ':') { |
835 | 4 | --hostEnd; |
836 | 498 | } else { |
837 | 498 | hostEnd = auth.getLength(); |
838 | 498 | } |
839 | 502 | auto asciiOnly = true; |
840 | 10.5k | for (auto i = hostStart; i != hostEnd; ++i) { |
841 | 10.0k | if (!rtl::isAscii(auth[i])) { |
842 | 6 | asciiOnly = false; |
843 | 6 | break; |
844 | 6 | } |
845 | 10.0k | } |
846 | 502 | if (asciiOnly) { |
847 | | // Avoid icu::IDNA case normalization in purely non-IDNA domain names: |
848 | 496 | return url; |
849 | 496 | } |
850 | 6 | UErrorCode e = U_ZERO_ERROR; |
851 | 6 | std::unique_ptr<icu::IDNA> idna( |
852 | 6 | icu::IDNA::createUTS46Instance( |
853 | 6 | (UIDNA_USE_STD3_RULES | UIDNA_CHECK_BIDI | UIDNA_CHECK_CONTEXTJ | UIDNA_CHECK_CONTEXTO), |
854 | 6 | e)); |
855 | 6 | if (U_FAILURE(e)) { |
856 | 0 | SAL_WARN("vcl.gdi", "icu::IDNA::createUTS46Instance " << e); |
857 | 0 | return url; |
858 | 0 | } |
859 | 6 | icu::UnicodeString ascii; |
860 | 6 | icu::IDNAInfo info; |
861 | 6 | idna->nameToASCII( |
862 | 6 | icu::UnicodeString( |
863 | 6 | reinterpret_cast<UChar const *>(auth.getStr() + hostStart), |
864 | 6 | hostEnd - hostStart), |
865 | 6 | ascii, info, e); |
866 | 6 | if (U_FAILURE(e) || info.hasErrors()) { |
867 | 0 | return url; |
868 | 0 | } |
869 | 6 | OUStringBuffer buf(uri->getScheme()); |
870 | 6 | buf.append(OUString::Concat("://") + auth.subView(0, hostStart)); |
871 | 6 | buf.append( |
872 | 6 | reinterpret_cast<sal_Unicode const *>(ascii.getBuffer()), |
873 | 6 | ascii.length()); |
874 | 6 | buf.append(auth.subView(hostEnd) + uri->getPath()); |
875 | 6 | if (uri->hasQuery()) { |
876 | 0 | buf.append("?" + uri->getQuery()); |
877 | 0 | } |
878 | 6 | if (uri->hasFragment()) { |
879 | 0 | buf.append("#" + uri->getFragment()); |
880 | 0 | } |
881 | 6 | return buf.makeStringAndClear(); |
882 | 6 | } |
883 | | |
884 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |