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 <tools/diagnose_ex.h>
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/beans/PropertyValue.hpp>
30 #include <com/sun/star/sdbc/XResultSet.hpp>
31 #include <com/sun/star/sdbc/XRow.hpp>
32 #include <com/sun/star/task/XInteractionHandler.hpp>
33 #include <com/sun/star/ucb/NumberedSortingInfo.hpp>
34 #include <com/sun/star/ucb/UniversalContentBroker.hpp>
35 #include <com/sun/star/ucb/XAnyCompareFactory.hpp>
36 #include <com/sun/star/ucb/XCommandProcessor2.hpp>
37 #include <com/sun/star/ucb/XProgressHandler.hpp>
38 #include <com/sun/star/ucb/XContentAccess.hpp>
39 #include <com/sun/star/ucb/SortedDynamicResultSetFactory.hpp>
40 #include <comphelper/processfactory.hxx>
41 #include <comphelper/string.hxx>
42 #include <rtl/instance.hxx>
43 #include <salhelper/thread.hxx>
44 #include <tools/debug.hxx>
45 #include <osl/file.hxx>
46 #include <osl/mutex.hxx>
47 #include <vcl/builder.hxx>
48 #include <vcl/event.hxx>
49 #include <vcl/svapp.hxx>
50 #include <unotools/historyoptions.hxx>
51 #include <unotools/pathoptions.hxx>
52 #include <ucbhelper/commandenvironment.hxx>
53 #include <ucbhelper/content.hxx>
54 #include <unotools/ucbhelper.hxx>
55 #include <svtools/asynclink.hxx>
56 #include <svtools/urlfilter.hxx>
57 
58 #include <vector>
59 #include <algorithm>
60 
61 using namespace ::ucbhelper;
62 using namespace ::utl;
63 using namespace ::com::sun::star;
64 using namespace ::com::sun::star::beans;
65 using namespace ::com::sun::star::lang;
66 using namespace ::com::sun::star::sdbc;
67 using namespace ::com::sun::star::task;
68 using namespace ::com::sun::star::ucb;
69 using namespace ::com::sun::star::uno;
70 
71 class SvtURLBox_Impl
72 {
73 public:
74     std::vector<OUString>      aURLs;
75     std::vector<OUString>      aCompletions;
76     std::vector<WildCard>      m_aFilters;
77 
78     static bool TildeParsing( OUString& aText, OUString& aBaseUrl );
79 
SvtURLBox_Impl()80     SvtURLBox_Impl( )
81     {
82         FilterMatch::createWildCardFilterList(OUString(),m_aFilters);
83     }
84 };
85 
86 class SvtMatchContext_Impl: public salhelper::Thread
87 {
88     static ::osl::Mutex*            pDirMutex;
89 
90     std::vector<OUString>           aPickList;
91     std::vector<OUString>           aCompletions;
92     std::vector<OUString>           aURLs;
93     svtools::AsynchronLink          aLink;
94     OUString const                  aBaseURL;
95     OUString const                  aText;
96     VclPtr<SvtURLBox>               pBox;
97     bool const                      bOnlyDirectories;
98     bool const                      bNoSelection;
99 
100     osl::Mutex mutex_;
101     bool stopped_;
102     css::uno::Reference< css::ucb::XCommandProcessor > processor_;
103     sal_Int32 commandId_;
104 
105     DECL_LINK(                Select_Impl, void*, void );
106 
107     virtual                         ~SvtMatchContext_Impl() override;
108     virtual void                    execute() override;
109     void                            doExecute();
110     void                            Insert( const OUString& rCompletion, const OUString& rURL, bool bForce = false);
111     void                            ReadFolder( const OUString& rURL, const OUString& rMatch, bool bSmart );
112     static void                     FillPicklist(std::vector<OUString>& rPickList);
113 
114 public:
115                                     SvtMatchContext_Impl( SvtURLBox* pBoxP, const OUString& rText );
116     void                            Stop();
117 };
118 
119 class MatchContext_Impl: public salhelper::Thread
120 {
121     static ::osl::Mutex*            pDirMutex;
122 
123     std::vector<OUString>           aPickList;
124     std::vector<OUString>           aCompletions;
125     std::vector<OUString>           aURLs;
126     svtools::AsynchronLink          aLink;
127     OUString const                  aText;
128     URLBox*                         pBox;
129     bool const                      bOnlyDirectories;
130     bool const                      bNoSelection;
131 
132     osl::Mutex mutex_;
133     bool stopped_;
134     css::uno::Reference< css::ucb::XCommandProcessor > processor_;
135     sal_Int32 commandId_;
136 
137     DECL_LINK(                Select_Impl, void*, void );
138 
139     virtual                         ~MatchContext_Impl() override;
140     virtual void                    execute() override;
141     void                            doExecute();
142     void                            Insert( const OUString& rCompletion, const OUString& rURL, bool bForce = false);
143     void                            ReadFolder( const OUString& rURL, const OUString& rMatch, bool bSmart );
144     static void                     FillPicklist(std::vector<OUString>& rPickList);
145 
146 public:
147                                     MatchContext_Impl( URLBox* pBoxP, const OUString& rText );
148     void                            Stop();
149 };
150 
151 
152 namespace
153 {
154     struct theSvtMatchContextMutex
155         : public rtl::Static< ::osl::Mutex, theSvtMatchContextMutex > {};
156 }
157 
SvtMatchContext_Impl(SvtURLBox * pBoxP,const OUString & rText)158 SvtMatchContext_Impl::SvtMatchContext_Impl(
159     SvtURLBox* pBoxP, const OUString& rText )
160     : Thread( "SvtMatchContext_Impl" )
161     , aLink( LINK( this, SvtMatchContext_Impl, Select_Impl ) )
162     , aBaseURL( pBoxP->aBaseURL )
163     , aText( rText )
164     , pBox( pBoxP )
165     , bOnlyDirectories( pBoxP->bOnlyDirectories )
166     , bNoSelection( pBoxP->bNoSelection )
167     , stopped_(false)
168     , commandId_(0)
169 {
170     aLink.CreateMutex();
171 
172     FillPicklist( aPickList );
173 }
174 
~SvtMatchContext_Impl()175 SvtMatchContext_Impl::~SvtMatchContext_Impl()
176 {
177     aLink.ClearPendingCall();
178 }
179 
FillPicklist(std::vector<OUString> & rPickList)180 void SvtMatchContext_Impl::FillPicklist(std::vector<OUString>& rPickList)
181 {
182     // Read the history of picks
183     Sequence< Sequence< PropertyValue > > seqPicklist = SvtHistoryOptions().GetList( ePICKLIST );
184     sal_uInt32 nCount = seqPicklist.getLength();
185 
186     for( sal_uInt32 nItem=0; nItem < nCount; nItem++ )
187     {
188         Sequence< PropertyValue > seqPropertySet = seqPicklist[ nItem ];
189 
190         auto pProperty = std::find_if(seqPropertySet.begin(), seqPropertySet.end(),
191             [](const PropertyValue& rProperty) { return rProperty.Name == HISTORY_PROPERTYNAME_TITLE; });
192         if (pProperty != seqPropertySet.end())
193         {
194             OUString sTitle;
195             INetURLObject aURL;
196 
197             pProperty->Value >>= sTitle;
198             aURL.SetURL( sTitle );
199             rPickList.insert(rPickList.begin() + nItem, aURL.GetMainURL(INetURLObject::DecodeMechanism::WithCharset));
200         }
201     }
202 }
203 
Stop()204 void SvtMatchContext_Impl::Stop()
205 {
206     css::uno::Reference< css::ucb::XCommandProcessor > proc;
207     sal_Int32 id(0);
208     {
209         osl::MutexGuard g(mutex_);
210         if (!stopped_) {
211             stopped_ = true;
212             proc = processor_;
213             id = commandId_;
214         }
215     }
216     if (proc.is()) {
217         proc->abort(id);
218     }
219     terminate();
220 }
221 
execute()222 void SvtMatchContext_Impl::execute( )
223 {
224     doExecute();
225     aLink.Call( this );
226 }
227 
228 
229 // This method is called via AsynchronLink, so it has the SolarMutex and
230 // calling solar code ( VCL ... ) is safe. It is called when the thread is
231 // terminated ( finished work or stopped ). Cancelling the thread via
232 // Cancellable does not discard the information gained so far, it
233 // inserts all collected completions into the listbox.
234 
IMPL_LINK_NOARG(SvtMatchContext_Impl,Select_Impl,void *,void)235 IMPL_LINK_NOARG( SvtMatchContext_Impl, Select_Impl, void*, void )
236 {
237     // avoid recursion through cancel button
238     {
239         osl::MutexGuard g(mutex_);
240         if (stopped_) {
241             // Completion was stopped, no display:
242             return;
243         }
244     }
245 
246     pBox->bAutoCompleteMode = true;
247 
248     // did we filter completions which otherwise would have been valid?
249     // (to be filled below)
250     bool bValidCompletionsFiltered = false;
251 
252     // insert all completed strings into the listbox
253     pBox->Clear();
254 
255     for (auto const& completion : aCompletions)
256     {
257         // convert the file into a URL
258         OUString sURL;
259         osl::FileBase::getFileURLFromSystemPath(completion, sURL);
260             // note: if this doesn't work, we're not interested in: we're checking the
261             // untouched sCompletion then
262 
263         if ( !sURL.isEmpty() && !sURL.endsWith("/") )
264         {
265             OUString sUpperURL( sURL.toAsciiUpperCase() );
266 
267             if ( ::std::none_of( pBox->pImpl->m_aFilters.begin(),
268                                  pBox->pImpl->m_aFilters.end(),
269                                  FilterMatch( sUpperURL ) ) )
270             {   // this URL is not allowed
271                 bValidCompletionsFiltered = true;
272                 continue;
273             }
274         }
275 
276         pBox->InsertEntry(completion);
277     }
278 
279     if( !bNoSelection && !aCompletions.empty() && !bValidCompletionsFiltered )
280     {
281         // select the first one
282         OUString aTmp( pBox->GetEntry(0) );
283         pBox->SetText( aTmp );
284         pBox->SetSelection( Selection( aText.getLength(), aTmp.getLength() ) );
285     }
286 
287     // transfer string lists to listbox and forget them
288     pBox->pImpl->aURLs = aURLs;
289     pBox->pImpl->aCompletions = aCompletions;
290     aURLs.clear();
291     aCompletions.clear();
292 
293     // force listbox to resize ( it may be open )
294     pBox->Resize();
295 
296     // the box has this control as a member so we have to set that member
297     // to zero before deleting ourself.
298     pBox->pCtx.clear();
299 }
300 
301 
Insert(const OUString & rCompletion,const OUString & rURL,bool bForce)302 void SvtMatchContext_Impl::Insert( const OUString& rCompletion,
303                                    const OUString& rURL,
304                                    bool bForce )
305 {
306     if( !bForce )
307     {
308         // avoid doubles
309         if(find(aCompletions.begin(), aCompletions.end(), rCompletion) != aCompletions.end())
310             return;
311     }
312 
313     aCompletions.push_back(rCompletion);
314     aURLs.push_back(rURL);
315 }
316 
317 
ReadFolder(const OUString & rURL,const OUString & rMatch,bool bSmart)318 void SvtMatchContext_Impl::ReadFolder( const OUString& rURL,
319                                        const OUString& rMatch,
320                                        bool bSmart )
321 {
322     // check folder to scan
323     if( !UCBContentHelper::IsFolder( rURL ) )
324         return;
325 
326     bool bPureHomePath = false;
327 #ifdef UNX
328     bPureHomePath = aText.startsWith( "~" ) && aText.indexOf( '/' ) == -1;
329 #endif
330 
331     bool bExectMatch = bPureHomePath
332                 || aText == "."
333                 || aText.endsWith("/.")
334                 || aText.endsWith("/..");
335 
336     // for pure home paths ( ~username ) the '.' at the end of rMatch
337     // means that it points to root catalog
338     // this is done only for file contents since home paths parsing is useful only for them
339     if ( bPureHomePath && rMatch == "file:///." )
340     {
341         // a home that refers to /
342 
343         OUString aNewText = aText + "/";
344         Insert( aNewText, rURL, true );
345 
346         return;
347     }
348 
349     // string to match with
350     INetURLObject aMatchObj( rMatch );
351     OUString aMatchName;
352 
353     if ( rURL != aMatchObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) )
354     {
355         aMatchName = aMatchObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
356 
357         // matching is always done case insensitive, but completion will be case sensitive and case preserving
358         aMatchName = aMatchName.toAsciiLowerCase();
359 
360         // if the matchstring ends with a slash, we must search for this also
361         if ( rMatch.endsWith("/") )
362             aMatchName += "/";
363     }
364 
365     sal_Int32 nMatchLen = aMatchName.getLength();
366 
367     INetURLObject aFolderObj( rURL );
368     DBG_ASSERT( aFolderObj.GetProtocol() != INetProtocol::NotValid, "Invalid URL!" );
369 
370     try
371     {
372         Content aCnt( aFolderObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ),
373                       new ::ucbhelper::CommandEnvironment( uno::Reference< XInteractionHandler >(),
374                                                      uno::Reference< XProgressHandler >() ),
375                       comphelper::getProcessComponentContext() );
376         uno::Reference< XResultSet > xResultSet;
377         Sequence< OUString > aProps(2);
378         OUString* pProps = aProps.getArray();
379         pProps[0] = "Title";
380         pProps[1] = "IsFolder";
381 
382         try
383         {
384             uno::Reference< XDynamicResultSet > xDynResultSet;
385             ResultSetInclude eInclude = INCLUDE_FOLDERS_AND_DOCUMENTS;
386             if ( bOnlyDirectories )
387                 eInclude = INCLUDE_FOLDERS_ONLY;
388 
389             xDynResultSet = aCnt.createDynamicCursor( aProps, eInclude );
390 
391             uno::Reference < XAnyCompareFactory > xCompare;
392             uno::Reference < XSortedDynamicResultSetFactory > xSRSFac =
393                 SortedDynamicResultSetFactory::create( ::comphelper::getProcessComponentContext() );
394 
395             Sequence< NumberedSortingInfo > aSortInfo( 2 );
396             NumberedSortingInfo* pInfo = aSortInfo.getArray();
397             pInfo[ 0 ].ColumnIndex = 2;
398             pInfo[ 0 ].Ascending   = false;
399             pInfo[ 1 ].ColumnIndex = 1;
400             pInfo[ 1 ].Ascending   = true;
401 
402             uno::Reference< XDynamicResultSet > xDynamicResultSet =
403                 xSRSFac->createSortedDynamicResultSet( xDynResultSet, aSortInfo, xCompare );
404 
405             if ( xDynamicResultSet.is() )
406             {
407                 xResultSet = xDynamicResultSet->getStaticResultSet();
408             }
409         }
410         catch( css::uno::Exception& ) {}
411 
412         if ( xResultSet.is() )
413         {
414             uno::Reference< XRow > xRow( xResultSet, UNO_QUERY );
415             uno::Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY );
416 
417             try
418             {
419                 while ( schedule() && xResultSet->next() )
420                 {
421                     OUString   aURL      = xContentAccess->queryContentIdentifierString();
422                     OUString   aTitle    = xRow->getString(1);
423                     bool   bIsFolder = xRow->getBoolean(2);
424 
425                     // matching is always done case insensitive, but completion will be case sensitive and case preserving
426                     aTitle = aTitle.toAsciiLowerCase();
427 
428                     if (
429                         !nMatchLen ||
430                         (bExectMatch && aMatchName == aTitle) ||
431                         (!bExectMatch && aTitle.startsWith(aMatchName))
432                        )
433                     {
434                         // all names fit if matchstring is empty
435                         INetURLObject aObj( aURL );
436                         sal_Unicode aDelimiter = '/';
437                         if ( bSmart )
438                             // when parsing is done "smart", the delimiter must be "guessed"
439                             aObj.getFSysPath( static_cast<FSysStyle>(FSysStyle::Detect & ~FSysStyle::Vos), &aDelimiter );
440 
441                         if ( bIsFolder )
442                             aObj.setFinalSlash();
443 
444                         // get the last name of the URL
445                         OUString aMatch = aObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
446                         OUString aInput( aText );
447                         if ( nMatchLen )
448                         {
449                             if (aText.endsWith(".") || bPureHomePath)
450                             {
451                                 // if a "special folder" URL was typed, don't touch the user input
452                                 aMatch = aMatch.copy( nMatchLen );
453                             }
454                             else
455                             {
456                                 // make the user input case preserving
457                                 DBG_ASSERT( aInput.getLength() >= nMatchLen, "Suspicious Matching!" );
458                                 aInput = aInput.copy( 0, aInput.getLength() - nMatchLen );
459                             }
460                         }
461 
462                         aInput += aMatch;
463 
464                         // folders should get a final slash automatically
465                         if ( bIsFolder )
466                             aInput += OUStringChar(aDelimiter);
467 
468                         Insert( aInput, aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), true );
469                     }
470                 }
471             }
472             catch( css::uno::Exception& )
473             {
474             }
475         }
476     }
477     catch( css::uno::Exception& )
478     {
479     }
480 }
481 
MatchContext_Impl(URLBox * pBoxP,const OUString & rText)482 MatchContext_Impl::MatchContext_Impl(URLBox* pBoxP, const OUString& rText)
483     : Thread( "MatchContext_Impl" )
484     , aLink( LINK( this, MatchContext_Impl, Select_Impl ) )
485     , aText( rText )
486     , pBox( pBoxP )
487     , bOnlyDirectories( pBoxP->bOnlyDirectories )
488     , bNoSelection( pBoxP->bNoSelection )
489     , stopped_(false)
490     , commandId_(0)
491 {
492     aLink.CreateMutex();
493 
494     FillPicklist( aPickList );
495 }
496 
~MatchContext_Impl()497 MatchContext_Impl::~MatchContext_Impl()
498 {
499     aLink.ClearPendingCall();
500 }
501 
FillPicklist(std::vector<OUString> & rPickList)502 void MatchContext_Impl::FillPicklist(std::vector<OUString>& rPickList)
503 {
504     // Read the history of picks
505     Sequence< Sequence< PropertyValue > > seqPicklist = SvtHistoryOptions().GetList( ePICKLIST );
506     sal_uInt32 nCount = seqPicklist.getLength();
507 
508     for( sal_uInt32 nItem=0; nItem < nCount; nItem++ )
509     {
510         Sequence< PropertyValue > seqPropertySet = seqPicklist[ nItem ];
511 
512         auto pProperty = std::find_if(seqPropertySet.begin(), seqPropertySet.end(),
513             [](const PropertyValue& rProperty) { return rProperty.Name == HISTORY_PROPERTYNAME_TITLE; });
514         if (pProperty != seqPropertySet.end())
515         {
516             OUString sTitle;
517             INetURLObject aURL;
518 
519             pProperty->Value >>= sTitle;
520             aURL.SetURL( sTitle );
521             rPickList.insert(rPickList.begin() + nItem, aURL.GetMainURL(INetURLObject::DecodeMechanism::WithCharset));
522         }
523     }
524 }
525 
Stop()526 void MatchContext_Impl::Stop()
527 {
528     css::uno::Reference< css::ucb::XCommandProcessor > proc;
529     sal_Int32 id(0);
530     {
531         osl::MutexGuard g(mutex_);
532         if (!stopped_) {
533             stopped_ = true;
534             proc = processor_;
535             id = commandId_;
536         }
537     }
538     if (proc.is()) {
539         proc->abort(id);
540     }
541     terminate();
542 }
543 
execute()544 void MatchContext_Impl::execute( )
545 {
546     doExecute();
547     aLink.Call( this );
548 }
549 
550 
551 // This method is called via AsynchronLink, so it has the SolarMutex and
552 // calling solar code ( VCL ... ) is safe. It is called when the thread is
553 // terminated ( finished work or stopped ). Cancelling the thread via
554 // Cancellable does not discard the information gained so far, it
555 // inserts all collected completions into the listbox.
556 
IMPL_LINK_NOARG(MatchContext_Impl,Select_Impl,void *,void)557 IMPL_LINK_NOARG( MatchContext_Impl, Select_Impl, void*, void )
558 {
559     // avoid recursion through cancel button
560     {
561         osl::MutexGuard g(mutex_);
562         if (stopped_) {
563             // Completion was stopped, no display:
564             return;
565         }
566     }
567 
568     // insert all completed strings into the listbox
569     pBox->clear();
570 
571     for (auto const& completion : aCompletions)
572     {
573         // convert the file into a URL
574         OUString sURL;
575         osl::FileBase::getFileURLFromSystemPath(completion, sURL);
576             // note: if this doesn't work, we're not interested in: we're checking the
577             // untouched sCompletion then
578 
579         if ( !sURL.isEmpty() && !sURL.endsWith("/") )
580         {
581             OUString sUpperURL( sURL.toAsciiUpperCase() );
582 
583             if ( ::std::none_of( pBox->pImpl->m_aFilters.begin(),
584                                  pBox->pImpl->m_aFilters.end(),
585                                  FilterMatch( sUpperURL ) ) )
586             {   // this URL is not allowed
587                 continue;
588             }
589         }
590 
591         pBox->append_text(completion);
592     }
593 
594     pBox->EnableAutocomplete(!bNoSelection);
595 
596     // transfer string lists to listbox and forget them
597     pBox->pImpl->aURLs = aURLs;
598     pBox->pImpl->aCompletions = aCompletions;
599     aURLs.clear();
600     aCompletions.clear();
601 
602     // the box has this control as a member so we have to set that member
603     // to zero before deleting ourself.
604     pBox->pCtx.clear();
605 }
606 
Insert(const OUString & rCompletion,const OUString & rURL,bool bForce)607 void MatchContext_Impl::Insert( const OUString& rCompletion,
608                                    const OUString& rURL,
609                                    bool bForce )
610 {
611     if( !bForce )
612     {
613         // avoid doubles
614         if(find(aCompletions.begin(), aCompletions.end(), rCompletion) != aCompletions.end())
615             return;
616     }
617 
618     aCompletions.push_back(rCompletion);
619     aURLs.push_back(rURL);
620 }
621 
622 
ReadFolder(const OUString & rURL,const OUString & rMatch,bool bSmart)623 void MatchContext_Impl::ReadFolder( const OUString& rURL,
624                                        const OUString& rMatch,
625                                        bool bSmart )
626 {
627     // check folder to scan
628     if( !UCBContentHelper::IsFolder( rURL ) )
629         return;
630 
631     bool bPureHomePath = false;
632 #ifdef UNX
633     bPureHomePath = aText.startsWith( "~" ) && aText.indexOf( '/' ) == -1;
634 #endif
635 
636     bool bExectMatch = bPureHomePath
637                 || aText == "."
638                 || aText.endsWith("/.")
639                 || aText.endsWith("/..");
640 
641     // for pure home paths ( ~username ) the '.' at the end of rMatch
642     // means that it points to root catalog
643     // this is done only for file contents since home paths parsing is useful only for them
644     if ( bPureHomePath && rMatch == "file:///." )
645     {
646         // a home that refers to /
647 
648         OUString aNewText = aText + "/";
649         Insert( aNewText, rURL, true );
650 
651         return;
652     }
653 
654     // string to match with
655     INetURLObject aMatchObj( rMatch );
656     OUString aMatchName;
657 
658     if ( rURL != aMatchObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) )
659     {
660         aMatchName = aMatchObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
661 
662         // matching is always done case insensitive, but completion will be case sensitive and case preserving
663         aMatchName = aMatchName.toAsciiLowerCase();
664 
665         // if the matchstring ends with a slash, we must search for this also
666         if ( rMatch.endsWith("/") )
667             aMatchName += "/";
668     }
669 
670     sal_Int32 nMatchLen = aMatchName.getLength();
671 
672     INetURLObject aFolderObj( rURL );
673     DBG_ASSERT( aFolderObj.GetProtocol() != INetProtocol::NotValid, "Invalid URL!" );
674 
675     try
676     {
677         Content aCnt( aFolderObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ),
678                       new ::ucbhelper::CommandEnvironment( uno::Reference< XInteractionHandler >(),
679                                                      uno::Reference< XProgressHandler >() ),
680                       comphelper::getProcessComponentContext() );
681         uno::Reference< XResultSet > xResultSet;
682         Sequence< OUString > aProps(2);
683         OUString* pProps = aProps.getArray();
684         pProps[0] = "Title";
685         pProps[1] = "IsFolder";
686 
687         try
688         {
689             ResultSetInclude eInclude = INCLUDE_FOLDERS_AND_DOCUMENTS;
690             if ( bOnlyDirectories )
691                 eInclude = INCLUDE_FOLDERS_ONLY;
692             uno::Reference< XDynamicResultSet > xDynResultSet = aCnt.createDynamicCursor( aProps, eInclude );
693 
694             uno::Reference < XAnyCompareFactory > xCompare;
695             uno::Reference < XSortedDynamicResultSetFactory > xSRSFac =
696                 SortedDynamicResultSetFactory::create( ::comphelper::getProcessComponentContext() );
697 
698             Sequence< NumberedSortingInfo > aSortInfo( 2 );
699             NumberedSortingInfo* pInfo = aSortInfo.getArray();
700             pInfo[ 0 ].ColumnIndex = 2;
701             pInfo[ 0 ].Ascending   = false;
702             pInfo[ 1 ].ColumnIndex = 1;
703             pInfo[ 1 ].Ascending   = true;
704 
705             uno::Reference< XDynamicResultSet > xDynamicResultSet =
706                 xSRSFac->createSortedDynamicResultSet( xDynResultSet, aSortInfo, xCompare );
707 
708             if ( xDynamicResultSet.is() )
709             {
710                 xResultSet = xDynamicResultSet->getStaticResultSet();
711             }
712         }
713         catch( css::uno::Exception& ) {}
714 
715         if ( xResultSet.is() )
716         {
717             uno::Reference< XRow > xRow( xResultSet, UNO_QUERY );
718             uno::Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY );
719 
720             try
721             {
722                 while ( schedule() && xResultSet->next() )
723                 {
724                     OUString   aURL      = xContentAccess->queryContentIdentifierString();
725                     OUString   aTitle    = xRow->getString(1);
726                     bool   bIsFolder = xRow->getBoolean(2);
727 
728                     // matching is always done case insensitive, but completion will be case sensitive and case preserving
729                     aTitle = aTitle.toAsciiLowerCase();
730 
731                     if (
732                         !nMatchLen ||
733                         (bExectMatch && aMatchName == aTitle) ||
734                         (!bExectMatch && aTitle.startsWith(aMatchName))
735                        )
736                     {
737                         // all names fit if matchstring is empty
738                         INetURLObject aObj( aURL );
739                         sal_Unicode aDelimiter = '/';
740                         if ( bSmart )
741                             // when parsing is done "smart", the delimiter must be "guessed"
742                             aObj.getFSysPath( static_cast<FSysStyle>(FSysStyle::Detect & ~FSysStyle::Vos), &aDelimiter );
743 
744                         if ( bIsFolder )
745                             aObj.setFinalSlash();
746 
747                         // get the last name of the URL
748                         OUString aMatch = aObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
749                         OUString aInput( aText );
750                         if ( nMatchLen )
751                         {
752                             if (aText.endsWith(".") || bPureHomePath)
753                             {
754                                 // if a "special folder" URL was typed, don't touch the user input
755                                 aMatch = aMatch.copy( nMatchLen );
756                             }
757                             else
758                             {
759                                 // make the user input case preserving
760                                 DBG_ASSERT( aInput.getLength() >= nMatchLen, "Suspicious Matching!" );
761                                 aInput = aInput.copy( 0, aInput.getLength() - nMatchLen );
762                             }
763                         }
764 
765                         aInput += aMatch;
766 
767                         // folders should get a final slash automatically
768                         if ( bIsFolder )
769                             aInput += OUStringChar(aDelimiter);
770 
771                         Insert( aInput, aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), true );
772                     }
773                 }
774             }
775             catch( css::uno::Exception& )
776             {
777             }
778         }
779     }
780     catch( css::uno::Exception& )
781     {
782     }
783 }
784 
ParseSmart(const OUString & _aText,const OUString & _aBaseURL)785 OUString SvtURLBox::ParseSmart( const OUString& _aText, const OUString& _aBaseURL )
786 {
787     OUString aMatch;
788     OUString aText = _aText;
789     OUString aBaseURL = _aBaseURL;
790 
791     // parse ~ for Unix systems
792     // does nothing for Windows
793     if( !SvtURLBox_Impl::TildeParsing( aText, aBaseURL ) )
794         return OUString();
795 
796     if( !aBaseURL.isEmpty() )
797     {
798         INetProtocol eBaseProt = INetURLObject::CompareProtocolScheme( aBaseURL );
799 
800         // if a base URL is set the string may be parsed relative
801         if( aText.startsWith( "/" ) )
802         {
803             // text starting with slashes means absolute file URLs
804             OUString aTemp = INetURLObject::GetScheme( eBaseProt );
805 
806             // file URL must be correctly encoded!
807             OUString aTextURL = INetURLObject::encode( aText, INetURLObject::PART_FPATH,
808                                                      INetURLObject::EncodeMechanism::All );
809             aTemp += aTextURL;
810 
811             INetURLObject aTmp( aTemp );
812             if ( !aTmp.HasError() && aTmp.GetProtocol() != INetProtocol::NotValid )
813                 aMatch = aTmp.GetMainURL( INetURLObject::DecodeMechanism::NONE );
814         }
815         else
816         {
817             OUString aSmart( aText );
818             INetURLObject aObj( aBaseURL );
819 
820             // HRO: I suppose this hack should only be done for Windows !!!???
821 #ifdef _WIN32
822             // HRO: INetURLObject::smatRel2Abs does not recognize '\\' as a relative path
823             //      but in case of "\\\\" INetURLObject is right - this is an absolute path !
824 
825             if( aText.startsWith("\\") && (aText.getLength() < 2 || aText[ 1 ] != '\\') )
826             {
827                 // cut to first segment
828                 OUString aTmp = INetURLObject::GetScheme( eBaseProt ) + "/";
829                 aTmp += aObj.getName( 0, true, INetURLObject::DecodeMechanism::WithCharset );
830                 aObj.SetURL( aTmp );
831 
832                 aSmart = aSmart.copy(1);
833             }
834 #endif
835             // base URL must be a directory !
836             aObj.setFinalSlash();
837 
838             // take base URL and append current input
839             bool bWasAbsolute = false;
840 #ifdef UNX
841             // encode file URL correctly
842             aSmart = INetURLObject::encode( aSmart, INetURLObject::PART_FPATH, INetURLObject::EncodeMechanism::All );
843 #endif
844             INetURLObject aTmp( aObj.smartRel2Abs( aSmart, bWasAbsolute ) );
845 
846             if ( aText.endsWith(".") )
847                 // INetURLObject appends a final slash for the directories "." and "..", this is a bug!
848                 // Remove it as a workaround
849                 aTmp.removeFinalSlash();
850             if ( !aTmp.HasError() && aTmp.GetProtocol() != INetProtocol::NotValid )
851                 aMatch = aTmp.GetMainURL( INetURLObject::DecodeMechanism::NONE );
852         }
853     }
854     else
855     {
856         OUString aTmpMatch;
857         osl::FileBase::getFileURLFromSystemPath( aText, aTmpMatch );
858         aMatch = aTmpMatch;
859     }
860 
861     return aMatch;
862 }
863 
doExecute()864 void SvtMatchContext_Impl::doExecute()
865 {
866     ::osl::MutexGuard aGuard( theSvtMatchContextMutex::get() );
867     {
868         // have we been stopped while we were waiting for the mutex?
869         osl::MutexGuard g(mutex_);
870         if (stopped_) {
871             return;
872         }
873     }
874 
875     // Reset match lists
876     aCompletions.clear();
877     aURLs.clear();
878 
879     // check for input
880     if ( aText.isEmpty() )
881         return;
882 
883     if( aText.indexOf( '*' ) != -1 || aText.indexOf( '?' ) != -1 )
884         // no autocompletion for wildcards
885         return;
886 
887     OUString aMatch;
888     INetProtocol eProt = INetURLObject::CompareProtocolScheme( aText );
889     INetProtocol eBaseProt = INetURLObject::CompareProtocolScheme( aBaseURL );
890     if ( aBaseURL.isEmpty() )
891         eBaseProt = INetURLObject::CompareProtocolScheme( SvtPathOptions().GetWorkPath() );
892     INetProtocol eSmartProt = pBox->GetSmartProtocol();
893 
894     // if the user input is a valid URL, go on with it
895     // otherwise it could be parsed smart with a predefined smart protocol
896     // ( or if this is not set with the protocol of a predefined base URL )
897     if( eProt == INetProtocol::NotValid || eProt == eSmartProt || (eSmartProt == INetProtocol::NotValid && eProt == eBaseProt) )
898     {
899         // not stopped yet ?
900         if( schedule() )
901         {
902             if ( eProt == INetProtocol::NotValid )
903                 aMatch = SvtURLBox::ParseSmart( aText, aBaseURL );
904             else
905                 aMatch = aText;
906             if ( !aMatch.isEmpty() )
907             {
908                 INetURLObject aURLObject( aMatch );
909                 OUString aMainURL( aURLObject.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
910                 // Disable autocompletion for anything but the (local) file
911                 // system (for which access is hopefully fast), as the logic of
912                 // how MatchContext_Impl is used requires this code to run to
913                 // completion before further user input is processed, and even
914                 // MatchContext_Impl::Stop does not guarantee a speedy
915                 // return:
916                 if ( !aMainURL.isEmpty()
917                      && aURLObject.GetProtocol() == INetProtocol::File )
918                 {
919                     // if text input is a directory, it must be part of the match list! Until then it is scanned
920                     bool folder = false;
921                     if (aURLObject.hasFinalSlash()) {
922                         try {
923                             css::uno::Reference< css::uno::XComponentContext >
924                                 ctx(comphelper::getProcessComponentContext());
925                             css::uno::Reference<
926                                 css::ucb::XUniversalContentBroker > ucb(
927                                     css::ucb::UniversalContentBroker::create(
928                                         ctx));
929                             css::uno::Sequence< css::beans::Property > prop(1);
930                             prop[0].Name = "IsFolder";
931                             prop[0].Handle = -1;
932                             prop[0].Type = cppu::UnoType< bool >::get();
933                             css::uno::Any res;
934                             css::uno::Reference< css::ucb::XCommandProcessor >
935                                 proc(
936                                     ucb->queryContent(
937                                         ucb->createContentIdentifier(aMainURL)),
938                                     css::uno::UNO_QUERY_THROW);
939                             css::uno::Reference< css::ucb::XCommandProcessor2 >
940                                 proc2(proc, css::uno::UNO_QUERY);
941                             sal_Int32 id = proc->createCommandIdentifier();
942                             try {
943                                 {
944                                     osl::MutexGuard g(mutex_);
945                                     processor_ = proc;
946                                     commandId_ = id;
947                                 }
948                                 res = proc->execute(
949                                     css::ucb::Command(
950                                         "getPropertyValues", -1,
951                                         css::uno::makeAny(prop)),
952                                     id,
953                                     css::uno::Reference<
954                                         css::ucb::XCommandEnvironment >());
955                             } catch (...) {
956                                 if (proc2.is()) {
957                                     try {
958                                         proc2->releaseCommandIdentifier(id);
959                                     } catch (css::uno::RuntimeException &) {
960                                         TOOLS_WARN_EXCEPTION("svtools.control", "ignoring");
961                                     }
962                                 }
963                                 throw;
964                             }
965                             if (proc2.is()) {
966                                 proc2->releaseCommandIdentifier(id);
967                             }
968                             {
969                                 osl::MutexGuard g(mutex_);
970                                 processor_.clear();
971                                 // At least the neon-based WebDAV UCP does not
972                                 // properly support aborting commands, so return
973                                 // anyway now if an abort request had been
974                                 // ignored and the command execution only
975                                 // returned "successfully" after some timeout:
976                                 if (stopped_) {
977                                     return;
978                                 }
979                             }
980                             css::uno::Reference< css::sdbc::XRow > row(
981                                 res, css::uno::UNO_QUERY_THROW);
982                             folder = row->getBoolean(1) && !row->wasNull();
983                         } catch (css::uno::Exception &) {
984                             TOOLS_WARN_EXCEPTION("svtools.control", "ignoring");
985                             return;
986                         }
987                     }
988                     if ( folder )
989                         Insert( aText, aMatch );
990                     else
991                         // otherwise the parent folder will be taken
992                         aURLObject.removeSegment();
993 
994                     // scan directory and insert all matches
995                     ReadFolder( aURLObject.GetMainURL( INetURLObject::DecodeMechanism::NONE ), aMatch, eProt == INetProtocol::NotValid );
996                 }
997             }
998         }
999     }
1000 
1001     if ( bOnlyDirectories )
1002         // don't scan history picklist if only directories are allowed, picklist contains only files
1003         return;
1004 
1005     bool bFull = false;
1006 
1007     INetURLObject aCurObj;
1008     OUString aCurString, aCurMainURL;
1009     INetURLObject aObj;
1010     aObj.SetSmartProtocol( eSmartProt == INetProtocol::NotValid ? INetProtocol::Http : eSmartProt );
1011     for( ;; )
1012     {
1013         for(const auto& rPick : aPickList)
1014         {
1015             if (!schedule())
1016                 break;
1017 
1018             aCurObj.SetURL(rPick);
1019             aCurObj.SetSmartURL( aCurObj.GetURLNoPass());
1020             aCurMainURL = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
1021 
1022             if( eProt != INetProtocol::NotValid && aCurObj.GetProtocol() != eProt )
1023                 continue;
1024 
1025             if( eSmartProt != INetProtocol::NotValid && aCurObj.GetProtocol() != eSmartProt )
1026                 continue;
1027 
1028             switch( aCurObj.GetProtocol() )
1029             {
1030                 case INetProtocol::Http:
1031                 case INetProtocol::Https:
1032                 case INetProtocol::Ftp:
1033                 {
1034                     if( eProt == INetProtocol::NotValid && !bFull )
1035                     {
1036                         aObj.SetSmartURL( aText );
1037                         if( aObj.GetURLPath().getLength() > 1 )
1038                             continue;
1039                     }
1040 
1041                     aCurString = aCurMainURL;
1042                     if( eProt == INetProtocol::NotValid )
1043                     {
1044                         // try if text matches the scheme
1045                         OUString aScheme( INetURLObject::GetScheme( aCurObj.GetProtocol() ) );
1046                         if ( aScheme.startsWithIgnoreAsciiCase( aText ) && aText.getLength() < aScheme.getLength() )
1047                         {
1048                             if( bFull )
1049                                 aMatch = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
1050                             else
1051                             {
1052                                 aCurObj.SetMark( "" );
1053                                 aCurObj.SetParam( "" );
1054                                 aCurObj.SetURLPath( "" );
1055                                 aMatch = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
1056                             }
1057 
1058                             Insert( aMatch, aMatch );
1059                         }
1060 
1061                         // now try smart matching
1062                         aCurString = aCurString.copy( aScheme.getLength() );
1063                     }
1064 
1065                     if( aCurString.startsWithIgnoreAsciiCase( aText ) )
1066                     {
1067                         if( bFull )
1068                             aMatch = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
1069                         else
1070                         {
1071                             aCurObj.SetMark( "" );
1072                             aCurObj.SetParam( "" );
1073                             aCurObj.SetURLPath( "" );
1074                             aMatch = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
1075                         }
1076 
1077                         OUString aURL( aMatch );
1078                         if( eProt == INetProtocol::NotValid )
1079                             aMatch = aMatch.copy( INetURLObject::GetScheme( aCurObj.GetProtocol() ).getLength() );
1080 
1081                         if( aText.getLength() < aMatch.getLength() )
1082                             Insert( aMatch, aURL );
1083 
1084                         continue;
1085                     }
1086                     break;
1087                 }
1088                 default:
1089                 {
1090                     if( bFull )
1091                         continue;
1092 
1093                     if( aCurMainURL.startsWith(aText) )
1094                     {
1095                         if( aText.getLength() < aCurMainURL.getLength() )
1096                             Insert( aCurMainURL, aCurMainURL );
1097 
1098                         continue;
1099                     }
1100                     break;
1101                 }
1102             }
1103         }
1104 
1105         if( !bFull )
1106             bFull = true;
1107         else
1108             break;
1109     }
1110 }
1111 
doExecute()1112 void MatchContext_Impl::doExecute()
1113 {
1114     ::osl::MutexGuard aGuard( theSvtMatchContextMutex::get() );
1115     {
1116         // have we been stopped while we were waiting for the mutex?
1117         osl::MutexGuard g(mutex_);
1118         if (stopped_) {
1119             return;
1120         }
1121     }
1122 
1123     // Reset match lists
1124     aCompletions.clear();
1125     aURLs.clear();
1126 
1127     // check for input
1128     if ( aText.isEmpty() )
1129         return;
1130 
1131     if( aText.indexOf( '*' ) != -1 || aText.indexOf( '?' ) != -1 )
1132         // no autocompletion for wildcards
1133         return;
1134 
1135     OUString aMatch;
1136     INetProtocol eProt = INetURLObject::CompareProtocolScheme( aText );
1137     INetProtocol eBaseProt = INetURLObject::CompareProtocolScheme( pBox->aBaseURL );
1138     if ( pBox->aBaseURL.isEmpty() )
1139         eBaseProt = INetURLObject::CompareProtocolScheme( SvtPathOptions().GetWorkPath() );
1140     INetProtocol eSmartProt = pBox->GetSmartProtocol();
1141 
1142     // if the user input is a valid URL, go on with it
1143     // otherwise it could be parsed smart with a predefined smart protocol
1144     // ( or if this is not set with the protocol of a predefined base URL )
1145     if( eProt == INetProtocol::NotValid || eProt == eSmartProt || (eSmartProt == INetProtocol::NotValid && eProt == eBaseProt) )
1146     {
1147         // not stopped yet ?
1148         if( schedule() )
1149         {
1150             if ( eProt == INetProtocol::NotValid )
1151                 aMatch = SvtURLBox::ParseSmart( aText, pBox->aBaseURL );
1152             else
1153                 aMatch = aText;
1154             if ( !aMatch.isEmpty() )
1155             {
1156                 INetURLObject aURLObject( aMatch );
1157                 OUString aMainURL( aURLObject.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
1158                 // Disable autocompletion for anything but the (local) file
1159                 // system (for which access is hopefully fast), as the logic of
1160                 // how MatchContext_Impl is used requires this code to run to
1161                 // completion before further user input is processed, and even
1162                 // MatchContext_Impl::Stop does not guarantee a speedy
1163                 // return:
1164                 if ( !aMainURL.isEmpty()
1165                      && aURLObject.GetProtocol() == INetProtocol::File )
1166                 {
1167                     // if text input is a directory, it must be part of the match list! Until then it is scanned
1168                     bool folder = false;
1169                     if (aURLObject.hasFinalSlash()) {
1170                         try {
1171                             css::uno::Reference< css::uno::XComponentContext >
1172                                 ctx(comphelper::getProcessComponentContext());
1173                             css::uno::Reference<
1174                                 css::ucb::XUniversalContentBroker > ucb(
1175                                     css::ucb::UniversalContentBroker::create(
1176                                         ctx));
1177                             css::uno::Sequence< css::beans::Property > prop(1);
1178                             prop[0].Name = "IsFolder";
1179                             prop[0].Handle = -1;
1180                             prop[0].Type = cppu::UnoType< bool >::get();
1181                             css::uno::Any res;
1182                             css::uno::Reference< css::ucb::XCommandProcessor >
1183                                 proc(
1184                                     ucb->queryContent(
1185                                         ucb->createContentIdentifier(aMainURL)),
1186                                     css::uno::UNO_QUERY_THROW);
1187                             css::uno::Reference< css::ucb::XCommandProcessor2 >
1188                                 proc2(proc, css::uno::UNO_QUERY);
1189                             sal_Int32 id = proc->createCommandIdentifier();
1190                             try {
1191                                 {
1192                                     osl::MutexGuard g(mutex_);
1193                                     processor_ = proc;
1194                                     commandId_ = id;
1195                                 }
1196                                 res = proc->execute(
1197                                     css::ucb::Command(
1198                                         "getPropertyValues", -1,
1199                                         css::uno::makeAny(prop)),
1200                                     id,
1201                                     css::uno::Reference<
1202                                         css::ucb::XCommandEnvironment >());
1203                             } catch (...) {
1204                                 if (proc2.is()) {
1205                                     try {
1206                                         proc2->releaseCommandIdentifier(id);
1207                                     } catch (css::uno::RuntimeException &) {
1208                                         TOOLS_WARN_EXCEPTION("svtools.control", "ignoring");
1209                                     }
1210                                 }
1211                                 throw;
1212                             }
1213                             if (proc2.is()) {
1214                                 proc2->releaseCommandIdentifier(id);
1215                             }
1216                             {
1217                                 osl::MutexGuard g(mutex_);
1218                                 processor_.clear();
1219                                 // At least the neon-based WebDAV UCP does not
1220                                 // properly support aborting commands, so return
1221                                 // anyway now if an abort request had been
1222                                 // ignored and the command execution only
1223                                 // returned "successfully" after some timeout:
1224                                 if (stopped_) {
1225                                     return;
1226                                 }
1227                             }
1228                             css::uno::Reference< css::sdbc::XRow > row(
1229                                 res, css::uno::UNO_QUERY_THROW);
1230                             folder = row->getBoolean(1) && !row->wasNull();
1231                         } catch (css::uno::Exception &) {
1232                             TOOLS_WARN_EXCEPTION("svtools.control", "ignoring");
1233                             return;
1234                         }
1235                     }
1236                     if (folder)
1237                         Insert( aText, aMatch );
1238                     else
1239                         // otherwise the parent folder will be taken
1240                         aURLObject.removeSegment();
1241 
1242                     // scan directory and insert all matches
1243                     ReadFolder( aURLObject.GetMainURL( INetURLObject::DecodeMechanism::NONE ), aMatch, eProt == INetProtocol::NotValid );
1244                 }
1245             }
1246         }
1247     }
1248 
1249     if ( bOnlyDirectories )
1250         // don't scan history picklist if only directories are allowed, picklist contains only files
1251         return;
1252 
1253     bool bFull = false;
1254 
1255     INetURLObject aCurObj;
1256     OUString aCurString, aCurMainURL;
1257     INetURLObject aObj;
1258     aObj.SetSmartProtocol( eSmartProt == INetProtocol::NotValid ? INetProtocol::Http : eSmartProt );
1259     for( ;; )
1260     {
1261         for(const auto& rPick : aPickList)
1262         {
1263             if (!schedule())
1264                 break;
1265 
1266             aCurObj.SetURL(rPick);
1267             aCurObj.SetSmartURL( aCurObj.GetURLNoPass());
1268             aCurMainURL = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
1269 
1270             if( eProt != INetProtocol::NotValid && aCurObj.GetProtocol() != eProt )
1271                 continue;
1272 
1273             if( eSmartProt != INetProtocol::NotValid && aCurObj.GetProtocol() != eSmartProt )
1274                 continue;
1275 
1276             switch( aCurObj.GetProtocol() )
1277             {
1278                 case INetProtocol::Http:
1279                 case INetProtocol::Https:
1280                 case INetProtocol::Ftp:
1281                 {
1282                     if( eProt == INetProtocol::NotValid && !bFull )
1283                     {
1284                         aObj.SetSmartURL( aText );
1285                         if( aObj.GetURLPath().getLength() > 1 )
1286                             continue;
1287                     }
1288 
1289                     aCurString = aCurMainURL;
1290                     if( eProt == INetProtocol::NotValid )
1291                     {
1292                         // try if text matches the scheme
1293                         OUString aScheme( INetURLObject::GetScheme( aCurObj.GetProtocol() ) );
1294                         if ( aScheme.startsWithIgnoreAsciiCase( aText ) && aText.getLength() < aScheme.getLength() )
1295                         {
1296                             if( bFull )
1297                                 aMatch = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
1298                             else
1299                             {
1300                                 aCurObj.SetMark( "" );
1301                                 aCurObj.SetParam( "" );
1302                                 aCurObj.SetURLPath( "" );
1303                                 aMatch = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
1304                             }
1305 
1306                             Insert( aMatch, aMatch );
1307                         }
1308 
1309                         // now try smart matching
1310                         aCurString = aCurString.copy( aScheme.getLength() );
1311                     }
1312 
1313                     if( aCurString.startsWithIgnoreAsciiCase( aText ) )
1314                     {
1315                         if( bFull )
1316                             aMatch = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
1317                         else
1318                         {
1319                             aCurObj.SetMark( "" );
1320                             aCurObj.SetParam( "" );
1321                             aCurObj.SetURLPath( "" );
1322                             aMatch = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
1323                         }
1324 
1325                         OUString aURL( aMatch );
1326                         if( eProt == INetProtocol::NotValid )
1327                             aMatch = aMatch.copy( INetURLObject::GetScheme( aCurObj.GetProtocol() ).getLength() );
1328 
1329                         if( aText.getLength() < aMatch.getLength() )
1330                             Insert( aMatch, aURL );
1331 
1332                         continue;
1333                     }
1334                     break;
1335                 }
1336                 default:
1337                 {
1338                     if( bFull )
1339                         continue;
1340 
1341                     if( aCurMainURL.startsWith(aText) )
1342                     {
1343                         if( aText.getLength() < aCurMainURL.getLength() )
1344                             Insert( aCurMainURL, aCurMainURL );
1345 
1346                         continue;
1347                     }
1348                     break;
1349                 }
1350             }
1351         }
1352 
1353         if( !bFull )
1354             bFull = true;
1355         else
1356             break;
1357     }
1358 }
1359 
TryAutoComplete()1360 void SvtURLBox::TryAutoComplete()
1361 {
1362     if( Application::AnyInput( VclInputFlags::KEYBOARD ) ) return;
1363 
1364     OUString aCurText = GetText();
1365     Selection aSelection( GetSelection() );
1366     if( aSelection.Max() != aCurText.getLength() )
1367         return;
1368     sal_uInt16 nLen = static_cast<sal_uInt16>(aSelection.Min());
1369     aCurText = aCurText.copy( 0, nLen );
1370     if( !aCurText.isEmpty() && bIsAutoCompleteEnabled )
1371     {
1372         if ( pCtx.is() )
1373         {
1374             pCtx->Stop();
1375             pCtx->join();
1376             pCtx.clear();
1377         }
1378         pCtx = new SvtMatchContext_Impl( this, aCurText );
1379         pCtx->launch();
1380     }
1381 }
1382 
1383 
SvtURLBox(vcl::Window * pParent,INetProtocol eSmart,bool bSetDefaultHelpID)1384 SvtURLBox::SvtURLBox( vcl::Window* pParent, INetProtocol eSmart, bool bSetDefaultHelpID )
1385     :   ComboBox( pParent , WB_DROPDOWN | WB_AUTOHSCROLL ),
1386         eSmartProtocol( eSmart ),
1387         bAutoCompleteMode( false ),
1388         bOnlyDirectories( false ),
1389         bHistoryDisabled( false ),
1390         bNoSelection( false ),
1391         bIsAutoCompleteEnabled( true )
1392 {
1393     Init(bSetDefaultHelpID);
1394 
1395     if ( GetDesktopRectPixel().GetWidth() > 800 )
1396         SetSizePixel( Size( 300, 240 ) );
1397     else
1398         SetSizePixel( Size( 225, 240 ) );
1399 }
1400 
1401 
SvtURLBox(vcl::Window * pParent,WinBits _nStyle,INetProtocol eSmart,bool bSetDefaultHelpID)1402 SvtURLBox::SvtURLBox( vcl::Window* pParent, WinBits _nStyle, INetProtocol eSmart,
1403     bool bSetDefaultHelpID )
1404     :   ComboBox( pParent, _nStyle ),
1405         eSmartProtocol( eSmart ),
1406         bAutoCompleteMode( false ),
1407         bOnlyDirectories( false ),
1408         bHistoryDisabled( false ),
1409         bNoSelection( false ),
1410         bIsAutoCompleteEnabled( true )
1411 {
1412     Init(bSetDefaultHelpID);
1413 }
1414 
Init(bool bSetDefaultHelpID)1415 void SvtURLBox::Init(bool bSetDefaultHelpID)
1416 {
1417     pImpl.reset( new SvtURLBox_Impl );
1418 
1419     if (bSetDefaultHelpID && GetHelpId().isEmpty())
1420         SetHelpId( ".uno:OpenURL" );
1421     EnableAutocomplete( false );
1422 
1423     SetText( OUString() );
1424 
1425     GetSubEdit()->SetAutocompleteHdl(LINK(this, SvtURLBox, AutoCompleteHdl_Impl));
1426     UpdatePicklistForSmartProtocol_Impl();
1427 
1428     EnableAutoSize(true);
1429 }
1430 
~SvtURLBox()1431 SvtURLBox::~SvtURLBox()
1432 {
1433     disposeOnce();
1434 }
1435 
dispose()1436 void SvtURLBox::dispose()
1437 {
1438     if( pCtx.is() )
1439     {
1440         pCtx->Stop();
1441         pCtx->join();
1442     }
1443 
1444     pImpl.reset();
1445     ComboBox::dispose();
1446 }
1447 
UpdatePickList()1448 void SvtURLBox::UpdatePickList( )
1449 {
1450     if( pCtx.is() )
1451     {
1452         pCtx->Stop();
1453         pCtx->join();
1454         pCtx.clear();
1455     }
1456 
1457     OUString sText = GetText();
1458     if ( !sText.isEmpty() && bIsAutoCompleteEnabled )
1459     {
1460         pCtx = new SvtMatchContext_Impl( this, sText );
1461         pCtx->launch();
1462     }
1463 }
1464 
UpdatePicklistForSmartProtocol_Impl()1465 void SvtURLBox::UpdatePicklistForSmartProtocol_Impl()
1466 {
1467     Clear();
1468     if ( bHistoryDisabled )
1469         return;
1470 
1471     // read history pick list
1472     const Sequence< Sequence< PropertyValue > > seqPicklist = SvtHistoryOptions().GetList( ePICKLIST );
1473     INetURLObject aCurObj;
1474 
1475     for( const Sequence< PropertyValue >& rPropertySet : seqPicklist )
1476     {
1477         auto pProperty = std::find_if(rPropertySet.begin(), rPropertySet.end(),
1478             [](const PropertyValue& rProperty) { return rProperty.Name == HISTORY_PROPERTYNAME_URL; });
1479         if (pProperty != rPropertySet.end())
1480         {
1481             OUString sURL;
1482 
1483             pProperty->Value >>= sURL;
1484             aCurObj.SetURL( sURL );
1485 
1486             if ( !sURL.isEmpty() && ( eSmartProtocol != INetProtocol::NotValid ) )
1487             {
1488                 if( aCurObj.GetProtocol() != eSmartProtocol )
1489                     continue;
1490             }
1491 
1492             OUString aURL( aCurObj.GetMainURL( INetURLObject::DecodeMechanism::WithCharset ) );
1493 
1494             if ( !aURL.isEmpty() )
1495             {
1496                 bool bFound = aURL.endsWith("/");
1497                 if ( !bFound )
1498                 {
1499                     OUString aUpperURL = aURL.toAsciiUpperCase();
1500 
1501                     bFound = ::std::any_of(pImpl->m_aFilters.begin(),
1502                                            pImpl->m_aFilters.end(),
1503                                            FilterMatch( aUpperURL ) );
1504                 }
1505                 if ( bFound )
1506                 {
1507                     OUString aFile;
1508                     if (osl::FileBase::getSystemPathFromFileURL(aURL, aFile) == osl::FileBase::E_None)
1509                         InsertEntry(aFile);
1510                     else
1511                         InsertEntry(aURL);
1512                 }
1513             }
1514         }
1515     }
1516 }
1517 
1518 
ProcessKey(const vcl::KeyCode & rKey)1519 bool SvtURLBox::ProcessKey( const vcl::KeyCode& rKey )
1520 {
1521     // every key input stops the current matching thread
1522     if( pCtx.is() )
1523     {
1524         pCtx->Stop();
1525         pCtx->join();
1526         pCtx.clear();
1527     }
1528 
1529     vcl::KeyCode aCode( rKey.GetCode() );
1530     if ( aCode == KEY_RETURN && !GetText().isEmpty() )
1531     {
1532         // wait for completion of matching thread
1533         ::osl::MutexGuard aGuard( theSvtMatchContextMutex::get() );
1534 
1535         if ( bAutoCompleteMode )
1536         {
1537             // reset picklist
1538             bAutoCompleteMode = false;
1539             Selection aSelection( GetSelection() );
1540             SetSelection( Selection( aSelection.Min(), aSelection.Min() ) );
1541             if ( bOnlyDirectories )
1542                 Clear();
1543             else
1544                 UpdatePicklistForSmartProtocol_Impl();
1545             Resize();
1546         }
1547 
1548         bool bHandled = false;
1549         if ( GetOpenHdl().IsSet() )
1550         {
1551             bHandled = true;
1552             GetOpenHdl().Call(this);
1553         }
1554         else if ( GetSelectHdl().IsSet() )
1555         {
1556             bHandled = true;
1557             GetSelectHdl().Call(*this);
1558         }
1559 
1560         ClearModifyFlag();
1561         return bHandled;
1562     }
1563     else if ( aCode == KEY_RETURN && GetText().isEmpty() && GetOpenHdl().IsSet() )
1564     {
1565         // for file dialog
1566         bAutoCompleteMode = false;
1567         GetOpenHdl().Call(this);
1568         return true;
1569     }
1570     else if( aCode == KEY_ESCAPE )
1571     {
1572         Selection aSelection( GetSelection() );
1573         if ( bAutoCompleteMode || aSelection.Min() != aSelection.Max() )
1574         {
1575             SetSelection( Selection( aSelection.Min(), aSelection.Min() ) );
1576             if ( bOnlyDirectories )
1577                 Clear();
1578             else
1579                 UpdatePicklistForSmartProtocol_Impl();
1580             Resize();
1581         }
1582         else
1583         {
1584            return false;
1585         }
1586 
1587         bAutoCompleteMode = false;
1588         return true;
1589     }
1590     else
1591     {
1592         return false;
1593     }
1594 }
1595 
1596 
PreNotify(NotifyEvent & rNEvt)1597 bool SvtURLBox::PreNotify( NotifyEvent& rNEvt )
1598 {
1599     if( rNEvt.GetWindow() == GetSubEdit() && rNEvt.GetType() == MouseNotifyEvent::KEYINPUT )
1600     {
1601 
1602         const KeyEvent& rEvent = *rNEvt.GetKeyEvent();
1603         const vcl::KeyCode& rKey = rEvent.GetKeyCode();
1604         vcl::KeyCode aCode( rKey.GetCode() );
1605         if( ProcessKey( rKey ) )
1606         {
1607             return true;
1608         }
1609         else if( ( aCode == KEY_UP || aCode == KEY_DOWN ) && !rKey.IsMod2() )
1610         {
1611             Selection aSelection( GetSelection() );
1612             sal_uInt16 nLen = static_cast<sal_uInt16>(aSelection.Min());
1613             GetSubEdit()->KeyInput( rEvent );
1614             SetSelection( Selection( nLen, GetText().getLength() ) );
1615             return true;
1616         }
1617 
1618         if ( MatchesPlaceHolder( GetText() ) )
1619         {
1620             // set the selection so a key stroke will overwrite
1621             // the placeholder rather than edit it
1622             SetSelection( Selection( 0, GetText().getLength() ) );
1623         }
1624     }
1625 
1626     return ComboBox::PreNotify( rNEvt );
1627 }
1628 
IMPL_LINK_NOARG(SvtURLBox,AutoCompleteHdl_Impl,Edit &,void)1629 IMPL_LINK_NOARG(SvtURLBox, AutoCompleteHdl_Impl, Edit&, void)
1630 {
1631     TryAutoComplete();
1632 }
1633 
EventNotify(NotifyEvent & rEvt)1634 bool SvtURLBox::EventNotify( NotifyEvent &rEvt )
1635 {
1636     if ( MouseNotifyEvent::GETFOCUS == rEvt.GetType() )
1637     {
1638 #ifndef UNX
1639         // pb: don't select automatically on unix #93251#
1640         SetSelection( Selection( 0, GetText().getLength() ) );
1641 #endif
1642     }
1643     else if ( MouseNotifyEvent::LOSEFOCUS == rEvt.GetType() )
1644     {
1645         if( GetText().isEmpty() )
1646             ClearModifyFlag();
1647         if ( pCtx.is() )
1648         {
1649             pCtx->Stop();
1650             pCtx->join();
1651             pCtx.clear();
1652         }
1653     }
1654 
1655     return ComboBox::EventNotify( rEvt );
1656 }
1657 
1658 
Select()1659 void SvtURLBox::Select()
1660 {
1661     ComboBox::Select();
1662     ClearModifyFlag();
1663 }
1664 
1665 
GetURL()1666 OUString SvtURLBox::GetURL()
1667 {
1668     // wait for end of autocompletion
1669     ::osl::MutexGuard aGuard( theSvtMatchContextMutex::get() );
1670 
1671     OUString aText( GetText() );
1672     if ( MatchesPlaceHolder( aText ) )
1673         return aPlaceHolder;
1674 
1675     // try to get the right case preserving URL from the list of URLs
1676     for(std::vector<OUString>::iterator i = pImpl->aCompletions.begin(), j = pImpl->aURLs.begin(); i != pImpl->aCompletions.end() && j != pImpl->aURLs.end(); ++i, ++j)
1677     {
1678         if((*i) == aText)
1679             return *j;
1680     }
1681 
1682 #ifdef _WIN32
1683     // erase trailing spaces on Windows since they are invalid on this OS and
1684     // most of the time they are inserted by accident via copy / paste
1685     aText = comphelper::string::stripEnd(aText, ' ');
1686     if ( aText.isEmpty() )
1687         return aText;
1688     // #i9739#
1689 #endif
1690 
1691     INetURLObject aObj( aText );
1692     if( aText.indexOf( '*' ) != -1 || aText.indexOf( '?' ) != -1 )
1693     {
1694         // no autocompletion for wildcards
1695         INetURLObject aTempObj;
1696         if ( eSmartProtocol != INetProtocol::NotValid )
1697             aTempObj.SetSmartProtocol( eSmartProtocol );
1698         if ( aTempObj.SetSmartURL( aText ) )
1699             return aTempObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
1700         else
1701             return aText;
1702     }
1703 
1704     if ( aObj.GetProtocol() == INetProtocol::NotValid )
1705     {
1706         OUString aName = ParseSmart( aText, aBaseURL );
1707         aObj.SetURL(aName);
1708         OUString aURL( aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
1709         if ( aURL.isEmpty() )
1710             // aText itself is invalid, and even together with aBaseURL, it could not
1711             // made valid -> no chance
1712             return aText;
1713 
1714         bool bSlash = aObj.hasFinalSlash();
1715         {
1716             const OUString aPropName("CasePreservingURL");
1717 
1718             OUString aFileURL;
1719 
1720             Any aAny = UCBContentHelper::GetProperty(aURL, aPropName);
1721             bool success = (aAny >>= aFileURL);
1722             OUString aTitle;
1723             if(success)
1724                 aTitle = INetURLObject(aFileURL).getName(
1725                              INetURLObject::LAST_SEGMENT,
1726                              true,
1727                              INetURLObject::DecodeMechanism::WithCharset );
1728             else
1729                 success =
1730                     UCBContentHelper::GetTitle(aURL,&aTitle);
1731 
1732             if( success && aTitle != "/" && aTitle != "." )
1733             {
1734                     aObj.setName( aTitle );
1735                     if ( bSlash )
1736                         aObj.setFinalSlash();
1737             }
1738         }
1739     }
1740 
1741     return aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
1742 }
1743 
DisableHistory()1744 void SvtURLBox::DisableHistory()
1745 {
1746     bHistoryDisabled = true;
1747     UpdatePicklistForSmartProtocol_Impl();
1748 }
1749 
1750 /** Parse leading ~ for Unix systems,
1751     does nothing for Windows
1752  */
TildeParsing(OUString & aText,OUString & aBaseURL)1753 bool SvtURLBox_Impl::TildeParsing(
1754     OUString&
1755 #ifdef UNX
1756     aText
1757 #endif
1758     , OUString&
1759 #ifdef UNX
1760     aBaseURL
1761 #endif
1762 )
1763 {
1764 #ifdef UNX
1765     if( aText.startsWith( "~" ) )
1766     {
1767         OUString aParseTilde;
1768         bool bTrailingSlash = true; // use trailing slash
1769 
1770         if( aText.getLength() == 1 || aText[ 1 ] == '/' )
1771         {
1772             // covers "~" or "~/..." cases
1773             const char* aHomeLocation = getenv( "HOME" );
1774             if( !aHomeLocation )
1775                 aHomeLocation = "";
1776 
1777             aParseTilde = OUString::createFromAscii(aHomeLocation);
1778 
1779             // in case the whole path is just "~" then there should
1780             // be no trailing slash at the end
1781             if( aText.getLength() == 1 )
1782                 bTrailingSlash = false;
1783         }
1784         else
1785         {
1786             // covers "~username" and "~username/..." cases
1787             sal_Int32 nNameEnd = aText.indexOf( '/' );
1788             OUString aUserName = aText.copy( 1, ( nNameEnd != -1 ) ? nNameEnd : ( aText.getLength() - 1 ) );
1789 
1790             struct passwd* pPasswd = nullptr;
1791 #ifdef __sun
1792             Sequence< sal_Int8 > sBuf( 1024 );
1793             struct passwd aTmp;
1794             sal_Int32 nRes = getpwnam_r( OUStringToOString( aUserName, RTL_TEXTENCODING_ASCII_US ).getStr(),
1795                                   &aTmp,
1796                                   (char*)sBuf.getArray(),
1797                                   1024,
1798                                   &pPasswd );
1799             if( !nRes && pPasswd )
1800                 aParseTilde = OUString::createFromAscii(pPasswd->pw_dir);
1801             else
1802                 return false; // no such user
1803 #else
1804             pPasswd = getpwnam( OUStringToOString( aUserName, RTL_TEXTENCODING_ASCII_US ).getStr() );
1805             if( pPasswd )
1806                 aParseTilde = OUString::createFromAscii(pPasswd->pw_dir);
1807             else
1808                 return false; // no such user
1809 #endif
1810 
1811             // in case the path is "~username" then there should
1812             // be no trailing slash at the end
1813             if( nNameEnd == -1 )
1814                 bTrailingSlash = false;
1815         }
1816 
1817         if( !bTrailingSlash )
1818         {
1819             if( aParseTilde.isEmpty() || aParseTilde == "/" )
1820             {
1821                 // "/" path should be converted to "/."
1822                 aParseTilde = "/.";
1823             }
1824             else
1825             {
1826                 // "blabla/" path should be converted to "blabla"
1827                 aParseTilde = comphelper::string::stripEnd(aParseTilde, '/');
1828             }
1829         }
1830         else
1831         {
1832             if( !aParseTilde.endsWith("/") )
1833                 aParseTilde += "/";
1834             if( aText.getLength() > 2 )
1835                 aParseTilde += aText.copy( 2 );
1836         }
1837 
1838         aText = aParseTilde;
1839         aBaseURL.clear(); // tilde provide absolute path
1840     }
1841 #endif
1842 
1843     return true;
1844 }
1845 
1846 //--
1847 
ParseSmart(const OUString & _aText,const OUString & _aBaseURL)1848 OUString URLBox::ParseSmart( const OUString& _aText, const OUString& _aBaseURL )
1849 {
1850     OUString aMatch;
1851     OUString aText = _aText;
1852     OUString aBaseURL = _aBaseURL;
1853 
1854     // parse ~ for Unix systems
1855     // does nothing for Windows
1856     if( !SvtURLBox_Impl::TildeParsing( aText, aBaseURL ) )
1857         return OUString();
1858 
1859     if( !aBaseURL.isEmpty() )
1860     {
1861         INetProtocol eBaseProt = INetURLObject::CompareProtocolScheme( aBaseURL );
1862 
1863         // if a base URL is set the string may be parsed relative
1864         if( aText.startsWith( "/" ) )
1865         {
1866             // text starting with slashes means absolute file URLs
1867             OUString aTemp = INetURLObject::GetScheme( eBaseProt );
1868 
1869             // file URL must be correctly encoded!
1870             OUString aTextURL = INetURLObject::encode( aText, INetURLObject::PART_FPATH,
1871                                                      INetURLObject::EncodeMechanism::All );
1872             aTemp += aTextURL;
1873 
1874             INetURLObject aTmp( aTemp );
1875             if ( !aTmp.HasError() && aTmp.GetProtocol() != INetProtocol::NotValid )
1876                 aMatch = aTmp.GetMainURL( INetURLObject::DecodeMechanism::NONE );
1877         }
1878         else
1879         {
1880             OUString aSmart( aText );
1881             INetURLObject aObj( aBaseURL );
1882 
1883             // HRO: I suppose this hack should only be done for Windows !!!???
1884 #ifdef _WIN32
1885             // HRO: INetURLObject::smatRel2Abs does not recognize '\\' as a relative path
1886             //      but in case of "\\\\" INetURLObject is right - this is an absolute path !
1887 
1888             if( aText.startsWith("\\") && (aText.getLength() < 2 || aText[ 1 ] != '\\') )
1889             {
1890                 // cut to first segment
1891                 OUString aTmp = INetURLObject::GetScheme( eBaseProt ) + "/";
1892                 aTmp += aObj.getName( 0, true, INetURLObject::DecodeMechanism::WithCharset );
1893                 aObj.SetURL( aTmp );
1894 
1895                 aSmart = aSmart.copy(1);
1896             }
1897 #endif
1898             // base URL must be a directory !
1899             aObj.setFinalSlash();
1900 
1901             // take base URL and append current input
1902             bool bWasAbsolute = false;
1903 #ifdef UNX
1904             // encode file URL correctly
1905             aSmart = INetURLObject::encode( aSmart, INetURLObject::PART_FPATH, INetURLObject::EncodeMechanism::All );
1906 #endif
1907             INetURLObject aTmp( aObj.smartRel2Abs( aSmart, bWasAbsolute ) );
1908 
1909             if ( aText.endsWith(".") )
1910                 // INetURLObject appends a final slash for the directories "." and "..", this is a bug!
1911                 // Remove it as a workaround
1912                 aTmp.removeFinalSlash();
1913             if ( !aTmp.HasError() && aTmp.GetProtocol() != INetProtocol::NotValid )
1914                 aMatch = aTmp.GetMainURL( INetURLObject::DecodeMechanism::NONE );
1915         }
1916     }
1917     else
1918     {
1919         OUString aTmpMatch;
1920         osl::FileBase::getFileURLFromSystemPath( aText, aTmpMatch );
1921         aMatch = aTmpMatch;
1922     }
1923 
1924     return aMatch;
1925 }
1926 
IMPL_LINK_NOARG(URLBox,TryAutoComplete,Timer *,void)1927 IMPL_LINK_NOARG(URLBox, TryAutoComplete, Timer *, void)
1928 {
1929     OUString aCurText = m_xWidget->get_active_text();
1930     int nStartPos, nEndPos;
1931     m_xWidget->get_entry_selection_bounds(nStartPos, nEndPos);
1932     if (std::max(nStartPos, nEndPos) != aCurText.getLength())
1933         return;
1934 
1935     auto nLen = std::min(nStartPos, nEndPos);
1936     aCurText = aCurText.copy( 0, nLen );
1937     if (!aCurText.isEmpty())
1938     {
1939         if (pCtx.is())
1940         {
1941             pCtx->Stop();
1942             pCtx->join();
1943             pCtx.clear();
1944         }
1945         pCtx = new MatchContext_Impl(this, aCurText);
1946         pCtx->launch();
1947     }
1948     else
1949         m_xWidget->clear();
1950 }
1951 
URLBox(std::unique_ptr<weld::ComboBox> pWidget)1952 URLBox::URLBox(std::unique_ptr<weld::ComboBox> pWidget)
1953     : eSmartProtocol(INetProtocol::NotValid)
1954     , bOnlyDirectories( false )
1955     , bHistoryDisabled( false )
1956     , bNoSelection( false )
1957     , m_xWidget(std::move(pWidget))
1958 {
1959     //don't grow to fix mega-long urls
1960     Size aSize(m_xWidget->get_preferred_size());
1961     m_xWidget->set_size_request(aSize.Width(), -1);
1962 
1963     Init();
1964 
1965     m_xWidget->connect_focus_in(LINK(this, URLBox, FocusInHdl));
1966     m_xWidget->connect_focus_out(LINK(this, URLBox, FocusOutHdl));
1967     m_xWidget->connect_changed(LINK(this, URLBox, ChangedHdl));
1968 
1969     aChangedIdle.SetInvokeHandler(LINK(this, URLBox, TryAutoComplete));
1970     aChangedIdle.SetDebugName("svtools::URLBox aChangedIdle");
1971 }
1972 
Init()1973 void URLBox::Init()
1974 {
1975     pImpl.reset( new SvtURLBox_Impl );
1976 
1977     m_xWidget->set_entry_completion(false);
1978 
1979     UpdatePicklistForSmartProtocol_Impl();
1980 }
1981 
~URLBox()1982 URLBox::~URLBox()
1983 {
1984     if (pCtx.is())
1985     {
1986         pCtx->Stop();
1987         pCtx->join();
1988     }
1989 }
1990 
SetSmartProtocol(INetProtocol eProt)1991 void URLBox::SetSmartProtocol(INetProtocol eProt)
1992 {
1993     if ( eSmartProtocol != eProt )
1994     {
1995         eSmartProtocol = eProt;
1996         UpdatePicklistForSmartProtocol_Impl();
1997     }
1998 }
1999 
UpdatePicklistForSmartProtocol_Impl()2000 void URLBox::UpdatePicklistForSmartProtocol_Impl()
2001 {
2002     m_xWidget->clear();
2003     if ( bHistoryDisabled )
2004         return;
2005 
2006     if (bHistoryDisabled)
2007         return;
2008 
2009     // read history pick list
2010     const Sequence< Sequence< PropertyValue > > seqPicklist = SvtHistoryOptions().GetList( ePICKLIST );
2011     INetURLObject aCurObj;
2012 
2013     for( const Sequence< PropertyValue >& rPropertySet : seqPicklist )
2014     {
2015         auto pProperty = std::find_if(rPropertySet.begin(), rPropertySet.end(),
2016             [](const PropertyValue& rProperty) { return rProperty.Name == HISTORY_PROPERTYNAME_URL; });
2017         if (pProperty != rPropertySet.end())
2018         {
2019             OUString sURL;
2020 
2021             pProperty->Value >>= sURL;
2022             aCurObj.SetURL( sURL );
2023 
2024             if ( !sURL.isEmpty() && ( eSmartProtocol != INetProtocol::NotValid ) )
2025             {
2026                 if( aCurObj.GetProtocol() != eSmartProtocol )
2027                     continue;
2028             }
2029 
2030             OUString aURL( aCurObj.GetMainURL( INetURLObject::DecodeMechanism::WithCharset ) );
2031 
2032             if ( !aURL.isEmpty() )
2033             {
2034                 bool bFound = aURL.endsWith("/");
2035                 if ( !bFound )
2036                 {
2037                     OUString aUpperURL = aURL.toAsciiUpperCase();
2038 
2039                     bFound = ::std::any_of(pImpl->m_aFilters.begin(),
2040                                            pImpl->m_aFilters.end(),
2041                                            FilterMatch( aUpperURL ) );
2042                 }
2043                 if ( bFound )
2044                 {
2045                     OUString aFile;
2046                     if (osl::FileBase::getSystemPathFromFileURL(aURL, aFile) == osl::FileBase::E_None)
2047                         m_xWidget->append_text(aFile);
2048                     else
2049                         m_xWidget->append_text(aURL);
2050                 }
2051             }
2052         }
2053     }
2054 }
2055 
IMPL_LINK_NOARG(URLBox,ChangedHdl,weld::ComboBox &,void)2056 IMPL_LINK_NOARG(URLBox, ChangedHdl, weld::ComboBox&, void)
2057 {
2058     aChangeHdl.Call(*m_xWidget);
2059     aChangedIdle.Start(); //launch this to happen on idle after cursor position will have been set
2060 }
2061 
IMPL_LINK_NOARG(URLBox,FocusInHdl,weld::Widget &,void)2062 IMPL_LINK_NOARG(URLBox, FocusInHdl, weld::Widget&, void)
2063 {
2064 #ifndef UNX
2065     // pb: don't select automatically on unix #93251#
2066     m_xWidget->select_entry_region(0, -1);
2067 #endif
2068     aFocusInHdl.Call(*m_xWidget);
2069 }
2070 
IMPL_LINK_NOARG(URLBox,FocusOutHdl,weld::Widget &,void)2071 IMPL_LINK_NOARG(URLBox, FocusOutHdl, weld::Widget&, void)
2072 {
2073     if (pCtx.is())
2074     {
2075         pCtx->Stop();
2076         pCtx->join();
2077         pCtx.clear();
2078     }
2079     aFocusOutHdl.Call(*m_xWidget);
2080 }
2081 
SetOnlyDirectories(bool bDir)2082 void URLBox::SetOnlyDirectories( bool bDir )
2083 {
2084     bOnlyDirectories = bDir;
2085     if ( bOnlyDirectories )
2086         m_xWidget->clear();
2087 }
2088 
SetNoURLSelection(bool bSet)2089 void URLBox::SetNoURLSelection( bool bSet )
2090 {
2091     bNoSelection = bSet;
2092 }
2093 
GetURL()2094 OUString URLBox::GetURL()
2095 {
2096     // wait for end of autocompletion
2097     ::osl::MutexGuard aGuard( theSvtMatchContextMutex::get() );
2098 
2099     OUString aText(m_xWidget->get_active_text());
2100 
2101     // try to get the right case preserving URL from the list of URLs
2102     for(std::vector<OUString>::iterator i = pImpl->aCompletions.begin(), j = pImpl->aURLs.begin(); i != pImpl->aCompletions.end() && j != pImpl->aURLs.end(); ++i, ++j)
2103     {
2104         if((*i) == aText)
2105             return *j;
2106     }
2107 
2108 #ifdef _WIN32
2109     // erase trailing spaces on Windows since they are invalid on this OS and
2110     // most of the time they are inserted by accident via copy / paste
2111     aText = comphelper::string::stripEnd(aText, ' ');
2112     if ( aText.isEmpty() )
2113         return aText;
2114     // #i9739#
2115 #endif
2116 
2117     INetURLObject aObj( aText );
2118     if( aText.indexOf( '*' ) != -1 || aText.indexOf( '?' ) != -1 )
2119     {
2120         // no autocompletion for wildcards
2121         INetURLObject aTempObj;
2122         if ( eSmartProtocol != INetProtocol::NotValid )
2123             aTempObj.SetSmartProtocol( eSmartProtocol );
2124         if ( aTempObj.SetSmartURL( aText ) )
2125             return aTempObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
2126         else
2127             return aText;
2128     }
2129 
2130     if ( aObj.GetProtocol() == INetProtocol::NotValid )
2131     {
2132         OUString aName = ParseSmart( aText, aBaseURL );
2133         aObj.SetURL(aName);
2134         OUString aURL( aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
2135         if ( aURL.isEmpty() )
2136             // aText itself is invalid, and even together with aBaseURL, it could not
2137             // made valid -> no chance
2138             return aText;
2139 
2140         bool bSlash = aObj.hasFinalSlash();
2141         {
2142             const OUString aPropName("CasePreservingURL");
2143 
2144             OUString aFileURL;
2145 
2146             Any aAny = UCBContentHelper::GetProperty(aURL, aPropName);
2147             bool success = (aAny >>= aFileURL);
2148             OUString aTitle;
2149             if(success)
2150                 aTitle = INetURLObject(aFileURL).getName(
2151                              INetURLObject::LAST_SEGMENT,
2152                              true,
2153                              INetURLObject::DecodeMechanism::WithCharset );
2154             else
2155                 success =
2156                     UCBContentHelper::GetTitle(aURL,&aTitle);
2157 
2158             if( success && aTitle != "/" && aTitle != "." )
2159             {
2160                     aObj.setName( aTitle );
2161                     if ( bSlash )
2162                         aObj.setFinalSlash();
2163             }
2164         }
2165     }
2166 
2167     return aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
2168 }
2169 
SetBaseURL(const OUString & rURL)2170 void URLBox::SetBaseURL( const OUString& rURL )
2171 {
2172     ::osl::MutexGuard aGuard( theSvtMatchContextMutex::get() );
2173 
2174     // Reset match lists
2175     pImpl->aCompletions.clear();
2176     pImpl->aURLs.clear();
2177 
2178     aBaseURL = rURL;
2179 }
2180 
DisableHistory()2181 void URLBox::DisableHistory()
2182 {
2183     bHistoryDisabled = true;
2184     UpdatePicklistForSmartProtocol_Impl();
2185 }
2186 
SetFilter(const OUString & _sFilter)2187 void URLBox::SetFilter(const OUString& _sFilter)
2188 {
2189     pImpl->m_aFilters.clear();
2190     FilterMatch::createWildCardFilterList(_sFilter,pImpl->m_aFilters);
2191 }
2192 
createWildCardFilterList(const OUString & _rFilterList,::std::vector<WildCard> & _rFilters)2193 void FilterMatch::createWildCardFilterList(const OUString& _rFilterList,::std::vector< WildCard >& _rFilters)
2194 {
2195     if( _rFilterList.getLength() )
2196     {
2197         // filter is given
2198         sal_Int32 nIndex = 0;
2199         OUString sToken;
2200         do
2201         {
2202             sToken = _rFilterList.getToken( 0, ';', nIndex );
2203             if ( !sToken.isEmpty() )
2204             {
2205                 _rFilters.emplace_back( sToken.toAsciiUpperCase() );
2206             }
2207         }
2208         while ( nIndex >= 0 );
2209     }
2210     else
2211     {
2212         // no filter is given -> match all
2213         _rFilters.emplace_back("*" );
2214     }
2215 }
2216 
2217 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2218