Coverage Report

Created: 2026-09-28 10:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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: */