Coverage Report

Created: 2025-07-07 10:01

/src/libreoffice/svtools/source/control/inettbc.cxx
Line
Count
Source (jump to first uncovered line)
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
#ifdef UNX
21
#include <pwd.h>
22
#endif
23
24
#include <svtools/inettbc.hxx>
25
#include <comphelper/diagnose_ex.hxx>
26
#include <com/sun/star/uno/Any.hxx>
27
#include <com/sun/star/uno/Reference.hxx>
28
#include <com/sun/star/beans/Property.hpp>
29
#include <com/sun/star/sdbc/XResultSet.hpp>
30
#include <com/sun/star/sdbc/XRow.hpp>
31
#include <com/sun/star/task/XInteractionHandler.hpp>
32
#include <com/sun/star/ucb/NumberedSortingInfo.hpp>
33
#include <com/sun/star/ucb/UniversalContentBroker.hpp>
34
#include <com/sun/star/ucb/XAnyCompareFactory.hpp>
35
#include <com/sun/star/ucb/XCommandProcessor2.hpp>
36
#include <com/sun/star/ucb/XProgressHandler.hpp>
37
#include <com/sun/star/ucb/XContentAccess.hpp>
38
#include <com/sun/star/ucb/SortedDynamicResultSetFactory.hpp>
39
#include <comphelper/processfactory.hxx>
40
#include <comphelper/string.hxx>
41
#include <salhelper/thread.hxx>
42
#include <tools/debug.hxx>
43
#include <o3tl/string_view.hxx>
44
#include <osl/file.hxx>
45
#include <osl/mutex.hxx>
46
#include <unotools/historyoptions.hxx>
47
#include <unotools/pathoptions.hxx>
48
#include <ucbhelper/commandenvironment.hxx>
49
#include <ucbhelper/content.hxx>
50
#include <unotools/ucbhelper.hxx>
51
#include <svtools/asynclink.hxx>
52
#include <svtools/urlfilter.hxx>
53
54
#include <mutex>
55
#include <utility>
56
#include <vector>
57
#include <algorithm>
58
59
using namespace ::ucbhelper;
60
using namespace ::utl;
61
using namespace ::com::sun::star;
62
using namespace ::com::sun::star::beans;
63
using namespace ::com::sun::star::sdbc;
64
using namespace ::com::sun::star::task;
65
using namespace ::com::sun::star::ucb;
66
using namespace ::com::sun::star::uno;
67
68
class SvtURLBox_Impl
69
{
70
public:
71
    std::vector<OUString>      aURLs;
72
    std::vector<OUString>      aCompletions;
73
    std::vector<WildCard>      m_aFilters;
74
75
    static bool TildeParsing( OUString& aText, OUString& aBaseUrl );
76
77
    SvtURLBox_Impl( )
78
0
    {
79
0
        FilterMatch::createWildCardFilterList(u"",m_aFilters);
80
0
    }
81
};
82
83
class SvtMatchContext_Impl: public salhelper::Thread
84
{
85
    std::vector<OUString>           aPickList;
86
    std::vector<OUString>           aCompletions;
87
    std::vector<OUString>           aURLs;
88
    svtools::AsynchronLink          aLink;
89
    OUString                        aText;
90
    SvtURLBox*                      pBox;
91
    bool                            bOnlyDirectories;
92
    bool                            bNoSelection;
93
94
    std::mutex mutex_;
95
    bool stopped_;
96
    css::uno::Reference< css::ucb::XCommandProcessor > processor_;
97
    sal_Int32 commandId_;
98
99
    DECL_LINK(                Select_Impl, void*, void );
100
101
    virtual                         ~SvtMatchContext_Impl() override;
102
    virtual void                    execute() override;
103
    void                            doExecute();
104
    void                            Insert( const OUString& rCompletion, const OUString& rURL, bool bForce = false);
105
    void                            ReadFolder( const OUString& rURL, const OUString& rMatch, bool bSmart );
106
    static void                     FillPicklist(std::vector<OUString>& rPickList);
107
108
public:
109
                                    SvtMatchContext_Impl( SvtURLBox* pBoxP, OUString aText );
110
    void                            Stop();
111
};
112
113
114
namespace
115
{
116
    ::osl::Mutex& theSvtMatchContextMutex()
117
0
    {
118
0
        static ::osl::Mutex SINGLETON;
119
0
        return SINGLETON;
120
0
    }
121
}
122
123
SvtMatchContext_Impl::SvtMatchContext_Impl(SvtURLBox* pBoxP, OUString _aText)
124
0
    : Thread( "MatchContext_Impl" )
125
0
    , aLink( LINK( this, SvtMatchContext_Impl, Select_Impl ) )
126
0
    , aText(std::move( _aText ))
127
0
    , pBox( pBoxP )
128
0
    , bOnlyDirectories( pBoxP->bOnlyDirectories )
129
0
    , bNoSelection( pBoxP->bNoSelection )
130
0
    , stopped_(false)
131
0
    , commandId_(0)
132
0
{
133
0
    FillPicklist( aPickList );
134
0
}
135
136
SvtMatchContext_Impl::~SvtMatchContext_Impl()
137
0
{
138
0
    aLink.ClearPendingCall();
139
0
}
140
141
void SvtMatchContext_Impl::FillPicklist(std::vector<OUString>& rPickList)
142
0
{
143
    // Read the history of picks
144
0
    std::vector< SvtHistoryOptions::HistoryItem > seqPicklist = SvtHistoryOptions::GetList( EHistoryType::PickList );
145
0
    sal_uInt32 nCount = seqPicklist.size();
146
147
0
    for( sal_uInt32 nItem=0; nItem < nCount; nItem++ )
148
0
    {
149
0
        INetURLObject aURL;
150
0
        aURL.SetURL( seqPicklist[nItem].sTitle );
151
0
        rPickList.insert(rPickList.begin() + nItem, aURL.GetMainURL(INetURLObject::DecodeMechanism::WithCharset));
152
0
    }
153
0
}
154
155
void SvtMatchContext_Impl::Stop()
156
0
{
157
0
    css::uno::Reference< css::ucb::XCommandProcessor > proc;
158
0
    sal_Int32 id(0);
159
0
    {
160
0
        std::scoped_lock g(mutex_);
161
0
        if (!stopped_) {
162
0
            stopped_ = true;
163
0
            proc = processor_;
164
0
            id = commandId_;
165
0
        }
166
0
    }
167
0
    if (proc.is()) {
168
0
        proc->abort(id);
169
0
    }
170
0
    terminate();
171
0
}
172
173
void SvtMatchContext_Impl::execute( )
174
0
{
175
0
    doExecute();
176
0
    aLink.Call( this );
177
0
}
178
179
180
// This method is called via AsynchronLink, so it has the SolarMutex and
181
// calling solar code ( VCL ... ) is safe. It is called when the thread is
182
// terminated ( finished work or stopped ). Cancelling the thread via
183
// Cancellable does not discard the information gained so far, it
184
// inserts all collected completions into the listbox.
185
186
IMPL_LINK_NOARG( SvtMatchContext_Impl, Select_Impl, void*, void )
187
0
{
188
    // avoid recursion through cancel button
189
0
    {
190
0
        std::scoped_lock g(mutex_);
191
0
        if (stopped_) {
192
            // Completion was stopped, no display:
193
0
            return;
194
0
        }
195
0
    }
196
197
    // insert all completed strings into the listbox
198
0
    pBox->clear();
199
200
0
    for (auto const& completion : aCompletions)
201
0
    {
202
        // convert the file into a URL
203
0
        OUString sURL;
204
0
        osl::FileBase::getFileURLFromSystemPath(completion, sURL);
205
            // note: if this doesn't work, we're not interested in: we're checking the
206
            // untouched sCompletion then
207
208
0
        if ( !sURL.isEmpty() && !sURL.endsWith("/") )
209
0
        {
210
0
            OUString sUpperURL( sURL.toAsciiUpperCase() );
211
212
0
            if ( ::std::none_of( pBox->pImpl->m_aFilters.begin(),
213
0
                                 pBox->pImpl->m_aFilters.end(),
214
0
                                 FilterMatch( sUpperURL ) ) )
215
0
            {   // this URL is not allowed
216
0
                continue;
217
0
            }
218
0
        }
219
220
0
        pBox->append_text(completion);
221
0
    }
222
223
0
    pBox->EnableAutocomplete(!bNoSelection);
224
225
    // transfer string lists to listbox and forget them
226
0
    pBox->pImpl->aURLs = aURLs;
227
0
    pBox->pImpl->aCompletions = aCompletions;
228
0
    aURLs.clear();
229
0
    aCompletions.clear();
230
231
    // the box has this control as a member so we have to set that member
232
    // to zero before deleting ourself.
233
0
    pBox->pCtx.clear();
234
0
}
235
236
void SvtMatchContext_Impl::Insert( const OUString& rCompletion,
237
                                   const OUString& rURL,
238
                                   bool bForce )
239
0
{
240
0
    if( !bForce )
241
0
    {
242
        // avoid doubles
243
0
        if(find(aCompletions.begin(), aCompletions.end(), rCompletion) != aCompletions.end())
244
0
            return;
245
0
    }
246
247
0
    aCompletions.push_back(rCompletion);
248
0
    aURLs.push_back(rURL);
249
0
}
250
251
252
void SvtMatchContext_Impl::ReadFolder( const OUString& rURL,
253
                                       const OUString& rMatch,
254
                                       bool bSmart )
255
0
{
256
    // check folder to scan
257
0
    if( !UCBContentHelper::IsFolder( rURL ) )
258
0
        return;
259
260
0
    bool bPureHomePath = false;
261
0
#ifdef UNX
262
0
    bPureHomePath = aText.startsWith( "~" ) && aText.indexOf( '/' ) == -1;
263
0
#endif
264
265
0
    bool bExectMatch = bPureHomePath
266
0
                || aText == "."
267
0
                || aText.endsWith("/.")
268
0
                || aText.endsWith("/..");
269
270
    // for pure home paths ( ~username ) the '.' at the end of rMatch
271
    // means that it points to root catalog
272
    // this is done only for file contents since home paths parsing is useful only for them
273
0
    if ( bPureHomePath && rMatch == "file:///." )
274
0
    {
275
        // a home that refers to /
276
277
0
        OUString aNewText = aText + "/";
278
0
        Insert( aNewText, rURL, true );
279
280
0
        return;
281
0
    }
282
283
    // string to match with
284
0
    INetURLObject aMatchObj( rMatch );
285
0
    OUString aMatchName;
286
287
0
    if ( rURL != aMatchObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) )
288
0
    {
289
0
        aMatchName = aMatchObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
290
291
        // matching is always done case insensitive, but completion will be case sensitive and case preserving
292
0
        aMatchName = aMatchName.toAsciiLowerCase();
293
294
        // if the matchstring ends with a slash, we must search for this also
295
0
        if ( rMatch.endsWith("/") )
296
0
            aMatchName += "/";
297
0
    }
298
299
0
    sal_Int32 nMatchLen = aMatchName.getLength();
300
301
0
    INetURLObject aFolderObj( rURL );
302
0
    DBG_ASSERT( aFolderObj.GetProtocol() != INetProtocol::NotValid, "Invalid URL!" );
303
304
0
    try
305
0
    {
306
0
        Content aCnt( aFolderObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ),
307
0
                      new ::ucbhelper::CommandEnvironment( uno::Reference< XInteractionHandler >(),
308
0
                                                     uno::Reference< XProgressHandler >() ),
309
0
                      comphelper::getProcessComponentContext() );
310
0
        uno::Reference< XResultSet > xResultSet;
311
312
0
        try
313
0
        {
314
0
            ResultSetInclude eInclude = INCLUDE_FOLDERS_AND_DOCUMENTS;
315
0
            if ( bOnlyDirectories )
316
0
                eInclude = INCLUDE_FOLDERS_ONLY;
317
0
            uno::Reference< XDynamicResultSet > xDynResultSet = aCnt.createDynamicCursor( { u"Title"_ustr, u"IsFolder"_ustr }, eInclude );
318
319
0
            uno::Reference < XAnyCompareFactory > xCompare;
320
0
            uno::Reference < XSortedDynamicResultSetFactory > xSRSFac =
321
0
                SortedDynamicResultSetFactory::create( ::comphelper::getProcessComponentContext() );
322
323
0
            uno::Reference< XDynamicResultSet > xDynamicResultSet =
324
0
                xSRSFac->createSortedDynamicResultSet( xDynResultSet, { { 2, false }, { 1, true } }, xCompare );
325
326
0
            if ( xDynamicResultSet.is() )
327
0
            {
328
0
                xResultSet = xDynamicResultSet->getStaticResultSet();
329
0
            }
330
0
        }
331
0
        catch( css::uno::Exception& ) {}
332
333
0
        if ( xResultSet.is() )
334
0
        {
335
0
            uno::Reference< XRow > xRow( xResultSet, UNO_QUERY );
336
0
            uno::Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY );
337
338
0
            try
339
0
            {
340
0
                while ( schedule() && xResultSet->next() )
341
0
                {
342
0
                    OUString   aURL      = xContentAccess->queryContentIdentifierString();
343
0
                    OUString   aTitle    = xRow->getString(1);
344
0
                    bool   bIsFolder = xRow->getBoolean(2);
345
346
                    // matching is always done case insensitive, but completion will be case sensitive and case preserving
347
0
                    aTitle = aTitle.toAsciiLowerCase();
348
349
0
                    if (
350
0
                        !nMatchLen ||
351
0
                        (bExectMatch && aMatchName == aTitle) ||
352
0
                        (!bExectMatch && aTitle.startsWith(aMatchName))
353
0
                       )
354
0
                    {
355
                        // all names fit if matchstring is empty
356
0
                        INetURLObject aObj( aURL );
357
0
                        sal_Unicode aDelimiter = '/';
358
0
                        if ( bSmart )
359
                            // when parsing is done "smart", the delimiter must be "guessed"
360
0
                            aObj.getFSysPath( static_cast<FSysStyle>(FSysStyle::Detect & ~FSysStyle::Vos), &aDelimiter );
361
362
0
                        if ( bIsFolder )
363
0
                            aObj.setFinalSlash();
364
365
                        // get the last name of the URL
366
0
                        OUString aMatch = aObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
367
0
                        OUString aInput( aText );
368
0
                        if ( nMatchLen )
369
0
                        {
370
0
                            if (aText.endsWith(".") || bPureHomePath)
371
0
                            {
372
                                // if a "special folder" URL was typed, don't touch the user input
373
0
                                aMatch = aMatch.copy( nMatchLen );
374
0
                            }
375
0
                            else
376
0
                            {
377
                                // make the user input case preserving
378
0
                                DBG_ASSERT( aInput.getLength() >= nMatchLen, "Suspicious Matching!" );
379
0
                                aInput = aInput.copy( 0, aInput.getLength() - nMatchLen );
380
0
                            }
381
0
                        }
382
383
0
                        aInput += aMatch;
384
385
                        // folders should get a final slash automatically
386
0
                        if ( bIsFolder )
387
0
                            aInput += OUStringChar(aDelimiter);
388
389
0
                        Insert( aInput, aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), true );
390
0
                    }
391
0
                }
392
0
            }
393
0
            catch( css::uno::Exception& )
394
0
            {
395
0
            }
396
0
        }
397
0
    }
398
0
    catch( css::uno::Exception& )
399
0
    {
400
0
    }
401
0
}
402
403
void SvtMatchContext_Impl::doExecute()
404
0
{
405
0
    ::osl::MutexGuard aGuard( theSvtMatchContextMutex() );
406
0
    {
407
        // have we been stopped while we were waiting for the mutex?
408
0
        std::scoped_lock g(mutex_);
409
0
        if (stopped_) {
410
0
            return;
411
0
        }
412
0
    }
413
414
    // Reset match lists
415
0
    aCompletions.clear();
416
0
    aURLs.clear();
417
418
    // check for input
419
0
    if ( aText.isEmpty() )
420
0
        return;
421
422
0
    if( aText.indexOf( '*' ) != -1 || aText.indexOf( '?' ) != -1 )
423
        // no autocompletion for wildcards
424
0
        return;
425
426
0
    OUString aMatch;
427
0
    INetProtocol eProt = INetURLObject::CompareProtocolScheme( aText );
428
0
    INetProtocol eBaseProt = INetURLObject::CompareProtocolScheme( pBox->aBaseURL );
429
0
    if ( pBox->aBaseURL.isEmpty() )
430
0
        eBaseProt = INetURLObject::CompareProtocolScheme( SvtPathOptions().GetWorkPath() );
431
0
    INetProtocol eSmartProt = pBox->GetSmartProtocol();
432
433
    // if the user input is a valid URL, go on with it
434
    // otherwise it could be parsed smart with a predefined smart protocol
435
    // ( or if this is not set with the protocol of a predefined base URL )
436
0
    if( eProt == INetProtocol::NotValid || eProt == eSmartProt || (eSmartProt == INetProtocol::NotValid && eProt == eBaseProt) )
437
0
    {
438
        // not stopped yet ?
439
0
        if( schedule() )
440
0
        {
441
0
            if ( eProt == INetProtocol::NotValid )
442
0
                aMatch = SvtURLBox::ParseSmart( aText, pBox->aBaseURL );
443
0
            else
444
0
                aMatch = aText;
445
0
            if ( !aMatch.isEmpty() )
446
0
            {
447
0
                INetURLObject aURLObject( aMatch );
448
0
                OUString aMainURL( aURLObject.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
449
                // Disable autocompletion for anything but the (local) file
450
                // system (for which access is hopefully fast), as the logic of
451
                // how SvtMatchContext_Impl is used requires this code to run to
452
                // completion before further user input is processed, and even
453
                // SvtMatchContext_Impl::Stop does not guarantee a speedy
454
                // return:
455
0
                if ( !aMainURL.isEmpty()
456
0
                     && aURLObject.GetProtocol() == INetProtocol::File )
457
0
                {
458
                    // if text input is a directory, it must be part of the match list! Until then it is scanned
459
0
                    bool folder = false;
460
0
                    if (aURLObject.hasFinalSlash()) {
461
0
                        try {
462
0
                            const css::uno::Reference< css::uno::XComponentContext >&
463
0
                                ctx(comphelper::getProcessComponentContext());
464
0
                            css::uno::Reference<
465
0
                                css::ucb::XUniversalContentBroker > ucb(
466
0
                                    css::ucb::UniversalContentBroker::create(
467
0
                                        ctx));
468
0
                            css::uno::Sequence< css::beans::Property > prop{
469
0
                                { /* Name       */ u"IsFolder"_ustr,
470
0
                                  /* Handle     */ -1,
471
0
                                  /* Type       */ cppu::UnoType< bool >::get(),
472
0
                                  /* Attributes */ {} }
473
0
                            };
474
0
                            css::uno::Any res;
475
0
                            css::uno::Reference< css::ucb::XCommandProcessor >
476
0
                                proc(
477
0
                                    ucb->queryContent(
478
0
                                        ucb->createContentIdentifier(aMainURL)),
479
0
                                    css::uno::UNO_QUERY_THROW);
480
0
                            css::uno::Reference< css::ucb::XCommandProcessor2 >
481
0
                                proc2(proc, css::uno::UNO_QUERY);
482
0
                            sal_Int32 id = proc->createCommandIdentifier();
483
0
                            try {
484
0
                                {
485
0
                                    std::scoped_lock g(mutex_);
486
0
                                    processor_ = proc;
487
0
                                    commandId_ = id;
488
0
                                }
489
0
                                res = proc->execute(
490
0
                                    css::ucb::Command(
491
0
                                        u"getPropertyValues"_ustr, -1,
492
0
                                        css::uno::Any(prop)),
493
0
                                    id,
494
0
                                    css::uno::Reference<
495
0
                                        css::ucb::XCommandEnvironment >());
496
0
                            } catch (...) {
497
0
                                if (proc2.is()) {
498
0
                                    try {
499
0
                                        proc2->releaseCommandIdentifier(id);
500
0
                                    } catch (css::uno::RuntimeException &) {
501
0
                                        TOOLS_WARN_EXCEPTION("svtools.control", "ignoring");
502
0
                                    }
503
0
                                }
504
0
                                throw;
505
0
                            }
506
0
                            if (proc2.is()) {
507
0
                                proc2->releaseCommandIdentifier(id);
508
0
                            }
509
0
                            {
510
0
                                std::scoped_lock g(mutex_);
511
0
                                processor_.clear();
512
                                // At least the neon-based WebDAV UCP does not
513
                                // properly support aborting commands, so return
514
                                // anyway now if an abort request had been
515
                                // ignored and the command execution only
516
                                // returned "successfully" after some timeout:
517
0
                                if (stopped_) {
518
0
                                    return;
519
0
                                }
520
0
                            }
521
0
                            css::uno::Reference< css::sdbc::XRow > row(
522
0
                                res, css::uno::UNO_QUERY_THROW);
523
0
                            folder = row->getBoolean(1) && !row->wasNull();
524
0
                        } catch (css::uno::Exception &) {
525
0
                            TOOLS_WARN_EXCEPTION("svtools.control", "ignoring");
526
0
                            return;
527
0
                        }
528
0
                    }
529
0
                    if (folder)
530
0
                        Insert( aText, aMatch );
531
0
                    else
532
                        // otherwise the parent folder will be taken
533
0
                        aURLObject.removeSegment();
534
535
                    // scan directory and insert all matches
536
0
                    ReadFolder( aURLObject.GetMainURL( INetURLObject::DecodeMechanism::NONE ), aMatch, eProt == INetProtocol::NotValid );
537
0
                }
538
0
            }
539
0
        }
540
0
    }
541
542
0
    if ( bOnlyDirectories )
543
        // don't scan history picklist if only directories are allowed, picklist contains only files
544
0
        return;
545
546
0
    bool bFull = false;
547
548
0
    INetURLObject aCurObj;
549
0
    OUString aCurString, aCurMainURL;
550
0
    INetURLObject aObj;
551
0
    aObj.SetSmartProtocol( eSmartProt == INetProtocol::NotValid ? INetProtocol::Http : eSmartProt );
552
0
    for( ;; )
553
0
    {
554
0
        for(const auto& rPick : aPickList)
555
0
        {
556
0
            if (!schedule())
557
0
                break;
558
559
0
            aCurObj.SetURL(rPick);
560
0
            aCurObj.SetSmartURL( aCurObj.GetURLNoPass());
561
0
            aCurMainURL = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
562
563
0
            if( eProt != INetProtocol::NotValid && aCurObj.GetProtocol() != eProt )
564
0
                continue;
565
566
0
            if( eSmartProt != INetProtocol::NotValid && aCurObj.GetProtocol() != eSmartProt )
567
0
                continue;
568
569
0
            switch( aCurObj.GetProtocol() )
570
0
            {
571
0
                case INetProtocol::Http:
572
0
                case INetProtocol::Https:
573
0
                case INetProtocol::Ftp:
574
0
                {
575
0
                    if( eProt == INetProtocol::NotValid && !bFull )
576
0
                    {
577
0
                        aObj.SetSmartURL( aText );
578
0
                        if( aObj.GetURLPath().getLength() > 1 )
579
0
                            continue;
580
0
                    }
581
582
0
                    aCurString = aCurMainURL;
583
0
                    if( eProt == INetProtocol::NotValid )
584
0
                    {
585
                        // try if text matches the scheme
586
0
                        OUString aScheme( INetURLObject::GetScheme( aCurObj.GetProtocol() ) );
587
0
                        if ( aScheme.startsWithIgnoreAsciiCase( aText ) && aText.getLength() < aScheme.getLength() )
588
0
                        {
589
0
                            if( bFull )
590
0
                                aMatch = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
591
0
                            else
592
0
                            {
593
0
                                aCurObj.SetMark( u"" );
594
0
                                aCurObj.SetParam( u"" );
595
0
                                aCurObj.SetURLPath( u"" );
596
0
                                aMatch = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
597
0
                            }
598
599
0
                            Insert( aMatch, aMatch );
600
0
                        }
601
602
                        // now try smart matching
603
0
                        aCurString = aCurString.copy( aScheme.getLength() );
604
0
                    }
605
606
0
                    if( aCurString.startsWithIgnoreAsciiCase( aText ) )
607
0
                    {
608
0
                        if( bFull )
609
0
                            aMatch = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
610
0
                        else
611
0
                        {
612
0
                            aCurObj.SetMark( u"" );
613
0
                            aCurObj.SetParam( u"" );
614
0
                            aCurObj.SetURLPath( u"" );
615
0
                            aMatch = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
616
0
                        }
617
618
0
                        OUString aURL( aMatch );
619
0
                        if( eProt == INetProtocol::NotValid )
620
0
                            aMatch = aMatch.copy( INetURLObject::GetScheme( aCurObj.GetProtocol() ).getLength() );
621
622
0
                        if( aText.getLength() < aMatch.getLength() )
623
0
                            Insert( aMatch, aURL );
624
625
0
                        continue;
626
0
                    }
627
0
                    break;
628
0
                }
629
0
                default:
630
0
                {
631
0
                    if( bFull )
632
0
                        continue;
633
634
0
                    if( aCurMainURL.startsWith(aText) )
635
0
                    {
636
0
                        if( aText.getLength() < aCurMainURL.getLength() )
637
0
                            Insert( aCurMainURL, aCurMainURL );
638
639
0
                        continue;
640
0
                    }
641
0
                    break;
642
0
                }
643
0
            }
644
0
        }
645
646
0
        if( !bFull )
647
0
            bFull = true;
648
0
        else
649
0
            break;
650
0
    }
651
0
}
652
653
/** Parse leading ~ for Unix systems,
654
    does nothing for Windows
655
 */
656
bool SvtURLBox_Impl::TildeParsing(
657
    OUString&
658
#ifdef UNX
659
    aText
660
#endif
661
    , OUString&
662
#ifdef UNX
663
    aBaseURL
664
#endif
665
)
666
0
{
667
0
#ifdef UNX
668
0
    if( aText.startsWith( "~" ) )
669
0
    {
670
0
        OUString aParseTilde;
671
0
        bool bTrailingSlash = true; // use trailing slash
672
673
0
        if( aText.getLength() == 1 || aText[ 1 ] == '/' )
674
0
        {
675
            // covers "~" or "~/..." cases
676
0
            const char* aHomeLocation = getenv( "HOME" );
677
0
            if( !aHomeLocation )
678
0
                aHomeLocation = "";
679
680
0
            aParseTilde = OUString::createFromAscii(aHomeLocation);
681
682
            // in case the whole path is just "~" then there should
683
            // be no trailing slash at the end
684
0
            if( aText.getLength() == 1 )
685
0
                bTrailingSlash = false;
686
0
        }
687
0
        else
688
0
        {
689
            // covers "~username" and "~username/..." cases
690
0
            sal_Int32 nNameEnd = aText.indexOf( '/' );
691
0
            OUString aUserName = aText.copy( 1, ( nNameEnd != -1 ) ? nNameEnd : ( aText.getLength() - 1 ) );
692
693
0
            struct passwd* pPasswd = nullptr;
694
#ifdef __sun
695
            Sequence< sal_Int8 > sBuf( 1024 );
696
            struct passwd aTmp;
697
            sal_Int32 nRes = getpwnam_r( OUStringToOString( aUserName, RTL_TEXTENCODING_ASCII_US ).getStr(),
698
                                  &aTmp,
699
                                  (char*)sBuf.getArray(),
700
                                  1024,
701
                                  &pPasswd );
702
            if( !nRes && pPasswd )
703
                aParseTilde = OUString::createFromAscii(pPasswd->pw_dir);
704
            else
705
                return false; // no such user
706
#else
707
0
            pPasswd = getpwnam( OUStringToOString( aUserName, RTL_TEXTENCODING_ASCII_US ).getStr() );
708
0
            if( pPasswd )
709
0
                aParseTilde = OUString::createFromAscii(pPasswd->pw_dir);
710
0
            else
711
0
                return false; // no such user
712
0
#endif
713
714
            // in case the path is "~username" then there should
715
            // be no trailing slash at the end
716
0
            if( nNameEnd == -1 )
717
0
                bTrailingSlash = false;
718
0
        }
719
720
0
        if( !bTrailingSlash )
721
0
        {
722
0
            if( aParseTilde.isEmpty() || aParseTilde == "/" )
723
0
            {
724
                // "/" path should be converted to "/."
725
0
                aParseTilde = "/.";
726
0
            }
727
0
            else
728
0
            {
729
                // "blabla/" path should be converted to "blabla"
730
0
                aParseTilde = comphelper::string::stripEnd(aParseTilde, '/');
731
0
            }
732
0
        }
733
0
        else
734
0
        {
735
0
            if( !aParseTilde.endsWith("/") )
736
0
                aParseTilde += "/";
737
0
            if( aText.getLength() > 2 )
738
0
                aParseTilde += aText.subView( 2 );
739
0
        }
740
741
0
        aText = aParseTilde;
742
0
        aBaseURL.clear(); // tilde provide absolute path
743
0
    }
744
0
#endif
745
746
0
    return true;
747
0
}
748
749
//--
750
751
OUString SvtURLBox::ParseSmart( const OUString& _aText, const OUString& _aBaseURL )
752
0
{
753
0
    OUString aMatch;
754
0
    OUString aText = _aText;
755
0
    OUString aBaseURL = _aBaseURL;
756
757
    // parse ~ for Unix systems
758
    // does nothing for Windows
759
0
    if( !SvtURLBox_Impl::TildeParsing( aText, aBaseURL ) )
760
0
        return OUString();
761
762
0
    if( !aBaseURL.isEmpty() )
763
0
    {
764
0
        INetProtocol eBaseProt = INetURLObject::CompareProtocolScheme( aBaseURL );
765
766
        // if a base URL is set the string may be parsed relative
767
0
        if( aText.startsWith( "/" ) )
768
0
        {
769
            // text starting with slashes means absolute file URLs
770
0
            OUString aTemp = INetURLObject::GetScheme( eBaseProt );
771
772
            // file URL must be correctly encoded!
773
0
            OUString aTextURL = INetURLObject::encode( aText, INetURLObject::PART_FPATH,
774
0
                                                     INetURLObject::EncodeMechanism::All );
775
0
            aTemp += aTextURL;
776
777
0
            INetURLObject aTmp( aTemp );
778
0
            if ( !aTmp.HasError() && aTmp.GetProtocol() != INetProtocol::NotValid )
779
0
                aMatch = aTmp.GetMainURL( INetURLObject::DecodeMechanism::NONE );
780
0
        }
781
0
        else
782
0
        {
783
0
            OUString aSmart( aText );
784
0
            INetURLObject aObj( aBaseURL );
785
786
            // HRO: I suppose this hack should only be done for Windows !!!???
787
#ifdef _WIN32
788
            // HRO: INetURLObject::smatRel2Abs does not recognize '\\' as a relative path
789
            //      but in case of "\\\\" INetURLObject is right - this is an absolute path !
790
791
            if( aText.startsWith("\\") && (aText.getLength() < 2 || aText[ 1 ] != '\\') )
792
            {
793
                // cut to first segment
794
                OUString aTmp = INetURLObject::GetScheme( eBaseProt ) + "/";
795
                aTmp += aObj.getName( 0, true, INetURLObject::DecodeMechanism::WithCharset );
796
                aObj.SetURL( aTmp );
797
798
                aSmart = aSmart.copy(1);
799
            }
800
#endif
801
            // base URL must be a directory !
802
0
            aObj.setFinalSlash();
803
804
            // take base URL and append current input
805
0
            bool bWasAbsolute = false;
806
0
#ifdef UNX
807
            // encode file URL correctly
808
0
            aSmart = INetURLObject::encode( aSmart, INetURLObject::PART_FPATH, INetURLObject::EncodeMechanism::All );
809
0
#endif
810
0
            INetURLObject aTmp( aObj.smartRel2Abs( aSmart, bWasAbsolute ) );
811
812
0
            if ( aText.endsWith(".") )
813
                // INetURLObject appends a final slash for the directories "." and "..", this is a bug!
814
                // Remove it as a workaround
815
0
                aTmp.removeFinalSlash();
816
0
            if ( !aTmp.HasError() && aTmp.GetProtocol() != INetProtocol::NotValid )
817
0
                aMatch = aTmp.GetMainURL( INetURLObject::DecodeMechanism::NONE );
818
0
        }
819
0
    }
820
0
    else
821
0
    {
822
0
        OUString aTmpMatch;
823
0
        osl::FileBase::getFileURLFromSystemPath( aText, aTmpMatch );
824
0
        aMatch = aTmpMatch;
825
0
    }
826
827
0
    return aMatch;
828
0
}
829
830
IMPL_LINK_NOARG(SvtURLBox, TryAutoComplete, Timer *, void)
831
0
{
832
0
    OUString aCurText = m_xWidget->get_active_text();
833
0
    int nStartPos, nEndPos;
834
0
    m_xWidget->get_entry_selection_bounds(nStartPos, nEndPos);
835
0
    if (std::max(nStartPos, nEndPos) != aCurText.getLength())
836
0
        return;
837
838
0
    auto nLen = std::min(nStartPos, nEndPos);
839
0
    aCurText = aCurText.copy( 0, nLen );
840
0
    if (!aCurText.isEmpty())
841
0
    {
842
0
        if (pCtx.is())
843
0
        {
844
0
            pCtx->Stop();
845
0
            pCtx->join();
846
0
            pCtx.clear();
847
0
        }
848
0
        pCtx = new SvtMatchContext_Impl(this, aCurText);
849
0
        pCtx->launch();
850
0
    }
851
0
    else
852
0
        m_xWidget->clear();
853
0
}
854
855
SvtURLBox::SvtURLBox(std::unique_ptr<weld::ComboBox> pWidget)
856
0
    : aChangedIdle("svtools::URLBox aChangedIdle")
857
0
    , eSmartProtocol(INetProtocol::NotValid)
858
0
    , bOnlyDirectories( false )
859
0
    , bHistoryDisabled( false )
860
0
    , bNoSelection( false )
861
0
    , m_xWidget(std::move(pWidget))
862
0
{
863
    //don't grow to fix mega-long urls
864
0
    Size aSize(m_xWidget->get_preferred_size());
865
0
    m_xWidget->set_size_request(aSize.Width(), -1);
866
867
0
    Init();
868
869
0
    m_xWidget->connect_focus_in(LINK(this, SvtURLBox, FocusInHdl));
870
0
    m_xWidget->connect_focus_out(LINK(this, SvtURLBox, FocusOutHdl));
871
0
    m_xWidget->connect_changed(LINK(this, SvtURLBox, ChangedHdl));
872
873
0
    aChangedIdle.SetInvokeHandler(LINK(this, SvtURLBox, TryAutoComplete));
874
0
}
875
876
void SvtURLBox::Init()
877
0
{
878
0
    pImpl.reset( new SvtURLBox_Impl );
879
880
0
    m_xWidget->set_entry_completion(false);
881
882
0
    UpdatePicklistForSmartProtocol_Impl();
883
0
}
884
885
SvtURLBox::~SvtURLBox()
886
0
{
887
0
    if (pCtx.is())
888
0
    {
889
0
        pCtx->Stop();
890
0
        pCtx->join();
891
0
    }
892
0
}
893
894
void SvtURLBox::SetSmartProtocol(INetProtocol eProt)
895
0
{
896
0
    if ( eSmartProtocol != eProt )
897
0
    {
898
0
        eSmartProtocol = eProt;
899
0
        UpdatePicklistForSmartProtocol_Impl();
900
0
    }
901
0
}
902
903
void SvtURLBox::UpdatePicklistForSmartProtocol_Impl()
904
0
{
905
0
    m_xWidget->clear();
906
0
    if ( bHistoryDisabled )
907
0
        return;
908
909
0
    if (bHistoryDisabled)
910
0
        return;
911
912
    // read history pick list
913
0
    const std::vector< SvtHistoryOptions::HistoryItem > seqPicklist = SvtHistoryOptions::GetList( EHistoryType::PickList );
914
0
    INetURLObject aCurObj;
915
916
0
    for( const SvtHistoryOptions::HistoryItem& rPropertySet : seqPicklist )
917
0
    {
918
0
        aCurObj.SetURL( rPropertySet.sURL );
919
920
0
        if ( !rPropertySet.sURL.isEmpty() && ( eSmartProtocol != INetProtocol::NotValid ) )
921
0
        {
922
0
            if( aCurObj.GetProtocol() != eSmartProtocol )
923
0
                continue;
924
0
        }
925
926
0
        OUString aURL( aCurObj.GetMainURL( INetURLObject::DecodeMechanism::WithCharset ) );
927
928
0
        if ( !aURL.isEmpty() )
929
0
        {
930
0
            bool bFound = aURL.endsWith("/");
931
0
            if ( !bFound )
932
0
            {
933
0
                OUString aUpperURL = aURL.toAsciiUpperCase();
934
935
0
                bFound = ::std::any_of(pImpl->m_aFilters.begin(),
936
0
                                       pImpl->m_aFilters.end(),
937
0
                                       FilterMatch( aUpperURL ) );
938
0
            }
939
0
            if ( bFound )
940
0
            {
941
0
                OUString aFile;
942
0
                if (osl::FileBase::getSystemPathFromFileURL(aURL, aFile) == osl::FileBase::E_None)
943
0
                    m_xWidget->append_text(aFile);
944
0
                else
945
0
                    m_xWidget->append_text(aURL);
946
0
            }
947
0
        }
948
0
    }
949
0
}
950
951
IMPL_LINK_NOARG(SvtURLBox, ChangedHdl, weld::ComboBox&, void)
952
0
{
953
0
    aChangeHdl.Call(*m_xWidget);
954
0
    aChangedIdle.Start(); //launch this to happen on idle after cursor position will have been set
955
0
}
956
957
IMPL_LINK_NOARG(SvtURLBox, FocusInHdl, weld::Widget&, void)
958
0
{
959
#ifndef UNX
960
    // pb: don't select automatically on unix #93251#
961
    m_xWidget->select_entry_region(0, -1);
962
#endif
963
0
    aFocusInHdl.Call(*m_xWidget);
964
0
}
965
966
IMPL_LINK_NOARG(SvtURLBox, FocusOutHdl, weld::Widget&, void)
967
0
{
968
0
    if (pCtx.is())
969
0
    {
970
0
        pCtx->Stop();
971
0
        pCtx->join();
972
0
        pCtx.clear();
973
0
    }
974
0
    aFocusOutHdl.Call(*m_xWidget);
975
0
}
976
977
void SvtURLBox::SetOnlyDirectories( bool bDir )
978
0
{
979
0
    bOnlyDirectories = bDir;
980
0
    if ( bOnlyDirectories )
981
0
        m_xWidget->clear();
982
0
}
983
984
void SvtURLBox::SetNoURLSelection( bool bSet )
985
0
{
986
0
    bNoSelection = bSet;
987
0
}
988
989
OUString SvtURLBox::GetURL()
990
0
{
991
    // wait for end of autocompletion
992
0
    ::osl::MutexGuard aGuard( theSvtMatchContextMutex() );
993
994
0
    OUString aText(m_xWidget->get_active_text());
995
0
    if (MatchesPlaceHolder(aText))
996
0
        return aPlaceHolder;
997
998
    // try to get the right case preserving URL from the list of URLs
999
0
    for(std::vector<OUString>::iterator i = pImpl->aCompletions.begin(), j = pImpl->aURLs.begin(); i != pImpl->aCompletions.end() && j != pImpl->aURLs.end(); ++i, ++j)
1000
0
    {
1001
0
        if((*i) == aText)
1002
0
            return *j;
1003
0
    }
1004
1005
#ifdef _WIN32
1006
    // erase trailing spaces on Windows since they are invalid on this OS and
1007
    // most of the time they are inserted by accident via copy / paste
1008
    aText = comphelper::string::stripEnd(aText, ' ');
1009
    if ( aText.isEmpty() )
1010
        return aText;
1011
    // #i9739#
1012
#endif
1013
1014
0
    INetURLObject aObj( aText );
1015
0
    if( aText.indexOf( '*' ) != -1 || aText.indexOf( '?' ) != -1 )
1016
0
    {
1017
        // no autocompletion for wildcards
1018
0
        INetURLObject aTempObj;
1019
0
        if ( eSmartProtocol != INetProtocol::NotValid )
1020
0
            aTempObj.SetSmartProtocol( eSmartProtocol );
1021
0
        if ( aTempObj.SetSmartURL( aText ) )
1022
0
            return aTempObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
1023
0
        else
1024
0
            return aText;
1025
0
    }
1026
1027
0
    if ( aObj.GetProtocol() == INetProtocol::NotValid )
1028
0
    {
1029
0
        OUString aName = ParseSmart( aText, aBaseURL );
1030
0
        aObj.SetURL(aName);
1031
0
        OUString aURL( aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
1032
0
        if ( aURL.isEmpty() )
1033
            // aText itself is invalid, and even together with aBaseURL, it could not
1034
            // made valid -> no chance
1035
0
            return aText;
1036
1037
0
        bool bSlash = aObj.hasFinalSlash();
1038
0
        {
1039
0
            OUString aFileURL;
1040
1041
0
            Any aAny = UCBContentHelper::GetProperty(aURL, u"CasePreservingURL"_ustr);
1042
0
            bool success = (aAny >>= aFileURL);
1043
0
            OUString aTitle;
1044
0
            if(success)
1045
0
                aTitle = INetURLObject(aFileURL).getName(
1046
0
                             INetURLObject::LAST_SEGMENT,
1047
0
                             true,
1048
0
                             INetURLObject::DecodeMechanism::WithCharset );
1049
0
            else
1050
0
                success =
1051
0
                    UCBContentHelper::GetTitle(aURL,&aTitle);
1052
1053
0
            if( success && aTitle != "/" && aTitle != "." )
1054
0
            {
1055
0
                    aObj.setName( aTitle );
1056
0
                    if ( bSlash )
1057
0
                        aObj.setFinalSlash();
1058
0
            }
1059
0
        }
1060
0
    }
1061
1062
0
    return aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
1063
0
}
1064
1065
void SvtURLBox::SetBaseURL( const OUString& rURL )
1066
0
{
1067
0
    ::osl::MutexGuard aGuard( theSvtMatchContextMutex() );
1068
1069
    // Reset match lists
1070
0
    pImpl->aCompletions.clear();
1071
0
    pImpl->aURLs.clear();
1072
1073
0
    aBaseURL = rURL;
1074
0
}
1075
1076
void SvtURLBox::DisableHistory()
1077
0
{
1078
0
    bHistoryDisabled = true;
1079
0
    UpdatePicklistForSmartProtocol_Impl();
1080
0
}
1081
1082
void SvtURLBox::SetFilter(std::u16string_view _sFilter)
1083
0
{
1084
0
    pImpl->m_aFilters.clear();
1085
0
    FilterMatch::createWildCardFilterList(_sFilter,pImpl->m_aFilters);
1086
0
}
1087
1088
void FilterMatch::createWildCardFilterList(std::u16string_view _rFilterList,::std::vector< WildCard >& _rFilters)
1089
0
{
1090
0
    if( !_rFilterList.empty() )
1091
0
    {
1092
        // filter is given
1093
0
        sal_Int32 nIndex = 0;
1094
0
        OUString sToken;
1095
0
        do
1096
0
        {
1097
0
            sToken = o3tl::getToken(_rFilterList, 0, ';', nIndex );
1098
0
            if ( !sToken.isEmpty() )
1099
0
            {
1100
0
                _rFilters.emplace_back( sToken.toAsciiUpperCase() );
1101
0
            }
1102
0
        }
1103
0
        while ( nIndex >= 0 );
1104
0
    }
1105
0
    else
1106
0
    {
1107
        // no filter is given -> match all
1108
0
        _rFilters.emplace_back(u"*" );
1109
0
    }
1110
0
}
1111
1112
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */