tdf#158198 sw: prevent xBookmark.getAnchor().setString("") from deleting
[LibreOffice.git] / svtools / source / control / inettbc.cxx
blob27d423c738ec86912695a3618b9cdadfb05b6056
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #ifdef UNX
21 #include <pwd.h>
22 #endif
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>
54 #include <mutex>
55 #include <utility>
56 #include <vector>
57 #include <algorithm>
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;
68 class SvtURLBox_Impl
70 public:
71 std::vector<OUString> aURLs;
72 std::vector<OUString> aCompletions;
73 std::vector<WildCard> m_aFilters;
75 static bool TildeParsing( OUString& aText, OUString& aBaseUrl );
77 SvtURLBox_Impl( )
79 FilterMatch::createWildCardFilterList(u"",m_aFilters);
83 class SvtMatchContext_Impl: public salhelper::Thread
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;
94 std::mutex mutex_;
95 bool stopped_;
96 css::uno::Reference< css::ucb::XCommandProcessor > processor_;
97 sal_Int32 commandId_;
99 DECL_LINK( Select_Impl, void*, void );
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);
108 public:
109 SvtMatchContext_Impl( SvtURLBox* pBoxP, OUString aText );
110 void Stop();
114 namespace
116 ::osl::Mutex& theSvtMatchContextMutex()
118 static ::osl::Mutex SINGLETON;
119 return SINGLETON;
123 SvtMatchContext_Impl::SvtMatchContext_Impl(SvtURLBox* pBoxP, OUString _aText)
124 : Thread( "MatchContext_Impl" )
125 , aLink( LINK( this, SvtMatchContext_Impl, Select_Impl ) )
126 , aText(std::move( _aText ))
127 , pBox( pBoxP )
128 , bOnlyDirectories( pBoxP->bOnlyDirectories )
129 , bNoSelection( pBoxP->bNoSelection )
130 , stopped_(false)
131 , commandId_(0)
133 FillPicklist( aPickList );
136 SvtMatchContext_Impl::~SvtMatchContext_Impl()
138 aLink.ClearPendingCall();
141 void SvtMatchContext_Impl::FillPicklist(std::vector<OUString>& rPickList)
143 // Read the history of picks
144 std::vector< SvtHistoryOptions::HistoryItem > seqPicklist = SvtHistoryOptions::GetList( EHistoryType::PickList );
145 sal_uInt32 nCount = seqPicklist.size();
147 for( sal_uInt32 nItem=0; nItem < nCount; nItem++ )
149 INetURLObject aURL;
150 aURL.SetURL( seqPicklist[nItem].sTitle );
151 rPickList.insert(rPickList.begin() + nItem, aURL.GetMainURL(INetURLObject::DecodeMechanism::WithCharset));
155 void SvtMatchContext_Impl::Stop()
157 css::uno::Reference< css::ucb::XCommandProcessor > proc;
158 sal_Int32 id(0);
160 std::scoped_lock g(mutex_);
161 if (!stopped_) {
162 stopped_ = true;
163 proc = processor_;
164 id = commandId_;
167 if (proc.is()) {
168 proc->abort(id);
170 terminate();
173 void SvtMatchContext_Impl::execute( )
175 doExecute();
176 aLink.Call( this );
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.
186 IMPL_LINK_NOARG( SvtMatchContext_Impl, Select_Impl, void*, void )
188 // avoid recursion through cancel button
190 std::scoped_lock g(mutex_);
191 if (stopped_) {
192 // Completion was stopped, no display:
193 return;
197 // insert all completed strings into the listbox
198 pBox->clear();
200 for (auto const& completion : aCompletions)
202 // convert the file into a URL
203 OUString sURL;
204 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
208 if ( !sURL.isEmpty() && !sURL.endsWith("/") )
210 OUString sUpperURL( sURL.toAsciiUpperCase() );
212 if ( ::std::none_of( pBox->pImpl->m_aFilters.begin(),
213 pBox->pImpl->m_aFilters.end(),
214 FilterMatch( sUpperURL ) ) )
215 { // this URL is not allowed
216 continue;
220 pBox->append_text(completion);
223 pBox->EnableAutocomplete(!bNoSelection);
225 // transfer string lists to listbox and forget them
226 pBox->pImpl->aURLs = aURLs;
227 pBox->pImpl->aCompletions = aCompletions;
228 aURLs.clear();
229 aCompletions.clear();
231 // the box has this control as a member so we have to set that member
232 // to zero before deleting ourself.
233 pBox->pCtx.clear();
236 void SvtMatchContext_Impl::Insert( const OUString& rCompletion,
237 const OUString& rURL,
238 bool bForce )
240 if( !bForce )
242 // avoid doubles
243 if(find(aCompletions.begin(), aCompletions.end(), rCompletion) != aCompletions.end())
244 return;
247 aCompletions.push_back(rCompletion);
248 aURLs.push_back(rURL);
252 void SvtMatchContext_Impl::ReadFolder( const OUString& rURL,
253 const OUString& rMatch,
254 bool bSmart )
256 // check folder to scan
257 if( !UCBContentHelper::IsFolder( rURL ) )
258 return;
260 bool bPureHomePath = false;
261 #ifdef UNX
262 bPureHomePath = aText.startsWith( "~" ) && aText.indexOf( '/' ) == -1;
263 #endif
265 bool bExectMatch = bPureHomePath
266 || aText == "."
267 || aText.endsWith("/.")
268 || aText.endsWith("/..");
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 if ( bPureHomePath && rMatch == "file:///." )
275 // a home that refers to /
277 OUString aNewText = aText + "/";
278 Insert( aNewText, rURL, true );
280 return;
283 // string to match with
284 INetURLObject aMatchObj( rMatch );
285 OUString aMatchName;
287 if ( rURL != aMatchObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) )
289 aMatchName = aMatchObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
291 // matching is always done case insensitive, but completion will be case sensitive and case preserving
292 aMatchName = aMatchName.toAsciiLowerCase();
294 // if the matchstring ends with a slash, we must search for this also
295 if ( rMatch.endsWith("/") )
296 aMatchName += "/";
299 sal_Int32 nMatchLen = aMatchName.getLength();
301 INetURLObject aFolderObj( rURL );
302 DBG_ASSERT( aFolderObj.GetProtocol() != INetProtocol::NotValid, "Invalid URL!" );
306 Content aCnt( aFolderObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ),
307 new ::ucbhelper::CommandEnvironment( uno::Reference< XInteractionHandler >(),
308 uno::Reference< XProgressHandler >() ),
309 comphelper::getProcessComponentContext() );
310 uno::Reference< XResultSet > xResultSet;
314 ResultSetInclude eInclude = INCLUDE_FOLDERS_AND_DOCUMENTS;
315 if ( bOnlyDirectories )
316 eInclude = INCLUDE_FOLDERS_ONLY;
317 uno::Reference< XDynamicResultSet > xDynResultSet = aCnt.createDynamicCursor( { u"Title"_ustr, u"IsFolder"_ustr }, eInclude );
319 uno::Reference < XAnyCompareFactory > xCompare;
320 uno::Reference < XSortedDynamicResultSetFactory > xSRSFac =
321 SortedDynamicResultSetFactory::create( ::comphelper::getProcessComponentContext() );
323 uno::Reference< XDynamicResultSet > xDynamicResultSet =
324 xSRSFac->createSortedDynamicResultSet( xDynResultSet, { { 2, false }, { 1, true } }, xCompare );
326 if ( xDynamicResultSet.is() )
328 xResultSet = xDynamicResultSet->getStaticResultSet();
331 catch( css::uno::Exception& ) {}
333 if ( xResultSet.is() )
335 uno::Reference< XRow > xRow( xResultSet, UNO_QUERY );
336 uno::Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY );
340 while ( schedule() && xResultSet->next() )
342 OUString aURL = xContentAccess->queryContentIdentifierString();
343 OUString aTitle = xRow->getString(1);
344 bool bIsFolder = xRow->getBoolean(2);
346 // matching is always done case insensitive, but completion will be case sensitive and case preserving
347 aTitle = aTitle.toAsciiLowerCase();
349 if (
350 !nMatchLen ||
351 (bExectMatch && aMatchName == aTitle) ||
352 (!bExectMatch && aTitle.startsWith(aMatchName))
355 // all names fit if matchstring is empty
356 INetURLObject aObj( aURL );
357 sal_Unicode aDelimiter = '/';
358 if ( bSmart )
359 // when parsing is done "smart", the delimiter must be "guessed"
360 aObj.getFSysPath( static_cast<FSysStyle>(FSysStyle::Detect & ~FSysStyle::Vos), &aDelimiter );
362 if ( bIsFolder )
363 aObj.setFinalSlash();
365 // get the last name of the URL
366 OUString aMatch = aObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
367 OUString aInput( aText );
368 if ( nMatchLen )
370 if (aText.endsWith(".") || bPureHomePath)
372 // if a "special folder" URL was typed, don't touch the user input
373 aMatch = aMatch.copy( nMatchLen );
375 else
377 // make the user input case preserving
378 DBG_ASSERT( aInput.getLength() >= nMatchLen, "Suspicious Matching!" );
379 aInput = aInput.copy( 0, aInput.getLength() - nMatchLen );
383 aInput += aMatch;
385 // folders should get a final slash automatically
386 if ( bIsFolder )
387 aInput += OUStringChar(aDelimiter);
389 Insert( aInput, aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), true );
393 catch( css::uno::Exception& )
398 catch( css::uno::Exception& )
403 void SvtMatchContext_Impl::doExecute()
405 ::osl::MutexGuard aGuard( theSvtMatchContextMutex() );
407 // have we been stopped while we were waiting for the mutex?
408 std::scoped_lock g(mutex_);
409 if (stopped_) {
410 return;
414 // Reset match lists
415 aCompletions.clear();
416 aURLs.clear();
418 // check for input
419 if ( aText.isEmpty() )
420 return;
422 if( aText.indexOf( '*' ) != -1 || aText.indexOf( '?' ) != -1 )
423 // no autocompletion for wildcards
424 return;
426 OUString aMatch;
427 INetProtocol eProt = INetURLObject::CompareProtocolScheme( aText );
428 INetProtocol eBaseProt = INetURLObject::CompareProtocolScheme( pBox->aBaseURL );
429 if ( pBox->aBaseURL.isEmpty() )
430 eBaseProt = INetURLObject::CompareProtocolScheme( SvtPathOptions().GetWorkPath() );
431 INetProtocol eSmartProt = pBox->GetSmartProtocol();
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 if( eProt == INetProtocol::NotValid || eProt == eSmartProt || (eSmartProt == INetProtocol::NotValid && eProt == eBaseProt) )
438 // not stopped yet ?
439 if( schedule() )
441 if ( eProt == INetProtocol::NotValid )
442 aMatch = SvtURLBox::ParseSmart( aText, pBox->aBaseURL );
443 else
444 aMatch = aText;
445 if ( !aMatch.isEmpty() )
447 INetURLObject aURLObject( aMatch );
448 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 if ( !aMainURL.isEmpty()
456 && aURLObject.GetProtocol() == INetProtocol::File )
458 // if text input is a directory, it must be part of the match list! Until then it is scanned
459 bool folder = false;
460 if (aURLObject.hasFinalSlash()) {
461 try {
462 const css::uno::Reference< css::uno::XComponentContext >&
463 ctx(comphelper::getProcessComponentContext());
464 css::uno::Reference<
465 css::ucb::XUniversalContentBroker > ucb(
466 css::ucb::UniversalContentBroker::create(
467 ctx));
468 css::uno::Sequence< css::beans::Property > prop{
469 { /* Name */ u"IsFolder"_ustr,
470 /* Handle */ -1,
471 /* Type */ cppu::UnoType< bool >::get(),
472 /* Attributes */ {} }
474 css::uno::Any res;
475 css::uno::Reference< css::ucb::XCommandProcessor >
476 proc(
477 ucb->queryContent(
478 ucb->createContentIdentifier(aMainURL)),
479 css::uno::UNO_QUERY_THROW);
480 css::uno::Reference< css::ucb::XCommandProcessor2 >
481 proc2(proc, css::uno::UNO_QUERY);
482 sal_Int32 id = proc->createCommandIdentifier();
483 try {
485 std::scoped_lock g(mutex_);
486 processor_ = proc;
487 commandId_ = id;
489 res = proc->execute(
490 css::ucb::Command(
491 u"getPropertyValues"_ustr, -1,
492 css::uno::Any(prop)),
494 css::uno::Reference<
495 css::ucb::XCommandEnvironment >());
496 } catch (...) {
497 if (proc2.is()) {
498 try {
499 proc2->releaseCommandIdentifier(id);
500 } catch (css::uno::RuntimeException &) {
501 TOOLS_WARN_EXCEPTION("svtools.control", "ignoring");
504 throw;
506 if (proc2.is()) {
507 proc2->releaseCommandIdentifier(id);
510 std::scoped_lock g(mutex_);
511 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 if (stopped_) {
518 return;
521 css::uno::Reference< css::sdbc::XRow > row(
522 res, css::uno::UNO_QUERY_THROW);
523 folder = row->getBoolean(1) && !row->wasNull();
524 } catch (css::uno::Exception &) {
525 TOOLS_WARN_EXCEPTION("svtools.control", "ignoring");
526 return;
529 if (folder)
530 Insert( aText, aMatch );
531 else
532 // otherwise the parent folder will be taken
533 aURLObject.removeSegment();
535 // scan directory and insert all matches
536 ReadFolder( aURLObject.GetMainURL( INetURLObject::DecodeMechanism::NONE ), aMatch, eProt == INetProtocol::NotValid );
542 if ( bOnlyDirectories )
543 // don't scan history picklist if only directories are allowed, picklist contains only files
544 return;
546 bool bFull = false;
548 INetURLObject aCurObj;
549 OUString aCurString, aCurMainURL;
550 INetURLObject aObj;
551 aObj.SetSmartProtocol( eSmartProt == INetProtocol::NotValid ? INetProtocol::Http : eSmartProt );
552 for( ;; )
554 for(const auto& rPick : aPickList)
556 if (!schedule())
557 break;
559 aCurObj.SetURL(rPick);
560 aCurObj.SetSmartURL( aCurObj.GetURLNoPass());
561 aCurMainURL = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
563 if( eProt != INetProtocol::NotValid && aCurObj.GetProtocol() != eProt )
564 continue;
566 if( eSmartProt != INetProtocol::NotValid && aCurObj.GetProtocol() != eSmartProt )
567 continue;
569 switch( aCurObj.GetProtocol() )
571 case INetProtocol::Http:
572 case INetProtocol::Https:
573 case INetProtocol::Ftp:
575 if( eProt == INetProtocol::NotValid && !bFull )
577 aObj.SetSmartURL( aText );
578 if( aObj.GetURLPath().getLength() > 1 )
579 continue;
582 aCurString = aCurMainURL;
583 if( eProt == INetProtocol::NotValid )
585 // try if text matches the scheme
586 OUString aScheme( INetURLObject::GetScheme( aCurObj.GetProtocol() ) );
587 if ( aScheme.startsWithIgnoreAsciiCase( aText ) && aText.getLength() < aScheme.getLength() )
589 if( bFull )
590 aMatch = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
591 else
593 aCurObj.SetMark( u"" );
594 aCurObj.SetParam( u"" );
595 aCurObj.SetURLPath( u"" );
596 aMatch = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
599 Insert( aMatch, aMatch );
602 // now try smart matching
603 aCurString = aCurString.copy( aScheme.getLength() );
606 if( aCurString.startsWithIgnoreAsciiCase( aText ) )
608 if( bFull )
609 aMatch = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
610 else
612 aCurObj.SetMark( u"" );
613 aCurObj.SetParam( u"" );
614 aCurObj.SetURLPath( u"" );
615 aMatch = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
618 OUString aURL( aMatch );
619 if( eProt == INetProtocol::NotValid )
620 aMatch = aMatch.copy( INetURLObject::GetScheme( aCurObj.GetProtocol() ).getLength() );
622 if( aText.getLength() < aMatch.getLength() )
623 Insert( aMatch, aURL );
625 continue;
627 break;
629 default:
631 if( bFull )
632 continue;
634 if( aCurMainURL.startsWith(aText) )
636 if( aText.getLength() < aCurMainURL.getLength() )
637 Insert( aCurMainURL, aCurMainURL );
639 continue;
641 break;
646 if( !bFull )
647 bFull = true;
648 else
649 break;
653 /** Parse leading ~ for Unix systems,
654 does nothing for Windows
656 bool SvtURLBox_Impl::TildeParsing(
657 OUString&
658 #ifdef UNX
659 aText
660 #endif
661 , OUString&
662 #ifdef UNX
663 aBaseURL
664 #endif
667 #ifdef UNX
668 if( aText.startsWith( "~" ) )
670 OUString aParseTilde;
671 bool bTrailingSlash = true; // use trailing slash
673 if( aText.getLength() == 1 || aText[ 1 ] == '/' )
675 // covers "~" or "~/..." cases
676 const char* aHomeLocation = getenv( "HOME" );
677 if( !aHomeLocation )
678 aHomeLocation = "";
680 aParseTilde = OUString::createFromAscii(aHomeLocation);
682 // in case the whole path is just "~" then there should
683 // be no trailing slash at the end
684 if( aText.getLength() == 1 )
685 bTrailingSlash = false;
687 else
689 // covers "~username" and "~username/..." cases
690 sal_Int32 nNameEnd = aText.indexOf( '/' );
691 OUString aUserName = aText.copy( 1, ( nNameEnd != -1 ) ? nNameEnd : ( aText.getLength() - 1 ) );
693 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 pPasswd = getpwnam( OUStringToOString( aUserName, RTL_TEXTENCODING_ASCII_US ).getStr() );
708 if( pPasswd )
709 aParseTilde = OUString::createFromAscii(pPasswd->pw_dir);
710 else
711 return false; // no such user
712 #endif
714 // in case the path is "~username" then there should
715 // be no trailing slash at the end
716 if( nNameEnd == -1 )
717 bTrailingSlash = false;
720 if( !bTrailingSlash )
722 if( aParseTilde.isEmpty() || aParseTilde == "/" )
724 // "/" path should be converted to "/."
725 aParseTilde = "/.";
727 else
729 // "blabla/" path should be converted to "blabla"
730 aParseTilde = comphelper::string::stripEnd(aParseTilde, '/');
733 else
735 if( !aParseTilde.endsWith("/") )
736 aParseTilde += "/";
737 if( aText.getLength() > 2 )
738 aParseTilde += aText.subView( 2 );
741 aText = aParseTilde;
742 aBaseURL.clear(); // tilde provide absolute path
744 #endif
746 return true;
749 //--
751 OUString SvtURLBox::ParseSmart( const OUString& _aText, const OUString& _aBaseURL )
753 OUString aMatch;
754 OUString aText = _aText;
755 OUString aBaseURL = _aBaseURL;
757 // parse ~ for Unix systems
758 // does nothing for Windows
759 if( !SvtURLBox_Impl::TildeParsing( aText, aBaseURL ) )
760 return OUString();
762 if( !aBaseURL.isEmpty() )
764 INetProtocol eBaseProt = INetURLObject::CompareProtocolScheme( aBaseURL );
766 // if a base URL is set the string may be parsed relative
767 if( aText.startsWith( "/" ) )
769 // text starting with slashes means absolute file URLs
770 OUString aTemp = INetURLObject::GetScheme( eBaseProt );
772 // file URL must be correctly encoded!
773 OUString aTextURL = INetURLObject::encode( aText, INetURLObject::PART_FPATH,
774 INetURLObject::EncodeMechanism::All );
775 aTemp += aTextURL;
777 INetURLObject aTmp( aTemp );
778 if ( !aTmp.HasError() && aTmp.GetProtocol() != INetProtocol::NotValid )
779 aMatch = aTmp.GetMainURL( INetURLObject::DecodeMechanism::NONE );
781 else
783 OUString aSmart( aText );
784 INetURLObject aObj( aBaseURL );
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 !
791 if( aText.startsWith("\\") && (aText.getLength() < 2 || aText[ 1 ] != '\\') )
793 // cut to first segment
794 OUString aTmp = INetURLObject::GetScheme( eBaseProt ) + "/";
795 aTmp += aObj.getName( 0, true, INetURLObject::DecodeMechanism::WithCharset );
796 aObj.SetURL( aTmp );
798 aSmart = aSmart.copy(1);
800 #endif
801 // base URL must be a directory !
802 aObj.setFinalSlash();
804 // take base URL and append current input
805 bool bWasAbsolute = false;
806 #ifdef UNX
807 // encode file URL correctly
808 aSmart = INetURLObject::encode( aSmart, INetURLObject::PART_FPATH, INetURLObject::EncodeMechanism::All );
809 #endif
810 INetURLObject aTmp( aObj.smartRel2Abs( aSmart, bWasAbsolute ) );
812 if ( aText.endsWith(".") )
813 // INetURLObject appends a final slash for the directories "." and "..", this is a bug!
814 // Remove it as a workaround
815 aTmp.removeFinalSlash();
816 if ( !aTmp.HasError() && aTmp.GetProtocol() != INetProtocol::NotValid )
817 aMatch = aTmp.GetMainURL( INetURLObject::DecodeMechanism::NONE );
820 else
822 OUString aTmpMatch;
823 osl::FileBase::getFileURLFromSystemPath( aText, aTmpMatch );
824 aMatch = aTmpMatch;
827 return aMatch;
830 IMPL_LINK_NOARG(SvtURLBox, TryAutoComplete, Timer *, void)
832 OUString aCurText = m_xWidget->get_active_text();
833 int nStartPos, nEndPos;
834 m_xWidget->get_entry_selection_bounds(nStartPos, nEndPos);
835 if (std::max(nStartPos, nEndPos) != aCurText.getLength())
836 return;
838 auto nLen = std::min(nStartPos, nEndPos);
839 aCurText = aCurText.copy( 0, nLen );
840 if (!aCurText.isEmpty())
842 if (pCtx.is())
844 pCtx->Stop();
845 pCtx->join();
846 pCtx.clear();
848 pCtx = new SvtMatchContext_Impl(this, aCurText);
849 pCtx->launch();
851 else
852 m_xWidget->clear();
855 SvtURLBox::SvtURLBox(std::unique_ptr<weld::ComboBox> pWidget)
856 : aChangedIdle("svtools::URLBox aChangedIdle")
857 , eSmartProtocol(INetProtocol::NotValid)
858 , bOnlyDirectories( false )
859 , bHistoryDisabled( false )
860 , bNoSelection( false )
861 , m_xWidget(std::move(pWidget))
863 //don't grow to fix mega-long urls
864 Size aSize(m_xWidget->get_preferred_size());
865 m_xWidget->set_size_request(aSize.Width(), -1);
867 Init();
869 m_xWidget->connect_focus_in(LINK(this, SvtURLBox, FocusInHdl));
870 m_xWidget->connect_focus_out(LINK(this, SvtURLBox, FocusOutHdl));
871 m_xWidget->connect_changed(LINK(this, SvtURLBox, ChangedHdl));
873 aChangedIdle.SetInvokeHandler(LINK(this, SvtURLBox, TryAutoComplete));
876 void SvtURLBox::Init()
878 pImpl.reset( new SvtURLBox_Impl );
880 m_xWidget->set_entry_completion(false);
882 UpdatePicklistForSmartProtocol_Impl();
885 SvtURLBox::~SvtURLBox()
887 if (pCtx.is())
889 pCtx->Stop();
890 pCtx->join();
894 void SvtURLBox::SetSmartProtocol(INetProtocol eProt)
896 if ( eSmartProtocol != eProt )
898 eSmartProtocol = eProt;
899 UpdatePicklistForSmartProtocol_Impl();
903 void SvtURLBox::UpdatePicklistForSmartProtocol_Impl()
905 m_xWidget->clear();
906 if ( bHistoryDisabled )
907 return;
909 if (bHistoryDisabled)
910 return;
912 // read history pick list
913 const std::vector< SvtHistoryOptions::HistoryItem > seqPicklist = SvtHistoryOptions::GetList( EHistoryType::PickList );
914 INetURLObject aCurObj;
916 for( const SvtHistoryOptions::HistoryItem& rPropertySet : seqPicklist )
918 aCurObj.SetURL( rPropertySet.sURL );
920 if ( !rPropertySet.sURL.isEmpty() && ( eSmartProtocol != INetProtocol::NotValid ) )
922 if( aCurObj.GetProtocol() != eSmartProtocol )
923 continue;
926 OUString aURL( aCurObj.GetMainURL( INetURLObject::DecodeMechanism::WithCharset ) );
928 if ( !aURL.isEmpty() )
930 bool bFound = aURL.endsWith("/");
931 if ( !bFound )
933 OUString aUpperURL = aURL.toAsciiUpperCase();
935 bFound = ::std::any_of(pImpl->m_aFilters.begin(),
936 pImpl->m_aFilters.end(),
937 FilterMatch( aUpperURL ) );
939 if ( bFound )
941 OUString aFile;
942 if (osl::FileBase::getSystemPathFromFileURL(aURL, aFile) == osl::FileBase::E_None)
943 m_xWidget->append_text(aFile);
944 else
945 m_xWidget->append_text(aURL);
951 IMPL_LINK_NOARG(SvtURLBox, ChangedHdl, weld::ComboBox&, void)
953 aChangeHdl.Call(*m_xWidget);
954 aChangedIdle.Start(); //launch this to happen on idle after cursor position will have been set
957 IMPL_LINK_NOARG(SvtURLBox, FocusInHdl, weld::Widget&, void)
959 #ifndef UNX
960 // pb: don't select automatically on unix #93251#
961 m_xWidget->select_entry_region(0, -1);
962 #endif
963 aFocusInHdl.Call(*m_xWidget);
966 IMPL_LINK_NOARG(SvtURLBox, FocusOutHdl, weld::Widget&, void)
968 if (pCtx.is())
970 pCtx->Stop();
971 pCtx->join();
972 pCtx.clear();
974 aFocusOutHdl.Call(*m_xWidget);
977 void SvtURLBox::SetOnlyDirectories( bool bDir )
979 bOnlyDirectories = bDir;
980 if ( bOnlyDirectories )
981 m_xWidget->clear();
984 void SvtURLBox::SetNoURLSelection( bool bSet )
986 bNoSelection = bSet;
989 OUString SvtURLBox::GetURL()
991 // wait for end of autocompletion
992 ::osl::MutexGuard aGuard( theSvtMatchContextMutex() );
994 OUString aText(m_xWidget->get_active_text());
995 if (MatchesPlaceHolder(aText))
996 return aPlaceHolder;
998 // try to get the right case preserving URL from the list of URLs
999 for(std::vector<OUString>::iterator i = pImpl->aCompletions.begin(), j = pImpl->aURLs.begin(); i != pImpl->aCompletions.end() && j != pImpl->aURLs.end(); ++i, ++j)
1001 if((*i) == aText)
1002 return *j;
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
1014 INetURLObject aObj( aText );
1015 if( aText.indexOf( '*' ) != -1 || aText.indexOf( '?' ) != -1 )
1017 // no autocompletion for wildcards
1018 INetURLObject aTempObj;
1019 if ( eSmartProtocol != INetProtocol::NotValid )
1020 aTempObj.SetSmartProtocol( eSmartProtocol );
1021 if ( aTempObj.SetSmartURL( aText ) )
1022 return aTempObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
1023 else
1024 return aText;
1027 if ( aObj.GetProtocol() == INetProtocol::NotValid )
1029 OUString aName = ParseSmart( aText, aBaseURL );
1030 aObj.SetURL(aName);
1031 OUString aURL( aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
1032 if ( aURL.isEmpty() )
1033 // aText itself is invalid, and even together with aBaseURL, it could not
1034 // made valid -> no chance
1035 return aText;
1037 bool bSlash = aObj.hasFinalSlash();
1039 OUString aFileURL;
1041 Any aAny = UCBContentHelper::GetProperty(aURL, u"CasePreservingURL"_ustr);
1042 bool success = (aAny >>= aFileURL);
1043 OUString aTitle;
1044 if(success)
1045 aTitle = INetURLObject(aFileURL).getName(
1046 INetURLObject::LAST_SEGMENT,
1047 true,
1048 INetURLObject::DecodeMechanism::WithCharset );
1049 else
1050 success =
1051 UCBContentHelper::GetTitle(aURL,&aTitle);
1053 if( success && aTitle != "/" && aTitle != "." )
1055 aObj.setName( aTitle );
1056 if ( bSlash )
1057 aObj.setFinalSlash();
1062 return aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
1065 void SvtURLBox::SetBaseURL( const OUString& rURL )
1067 ::osl::MutexGuard aGuard( theSvtMatchContextMutex() );
1069 // Reset match lists
1070 pImpl->aCompletions.clear();
1071 pImpl->aURLs.clear();
1073 aBaseURL = rURL;
1076 void SvtURLBox::DisableHistory()
1078 bHistoryDisabled = true;
1079 UpdatePicklistForSmartProtocol_Impl();
1082 void SvtURLBox::SetFilter(std::u16string_view _sFilter)
1084 pImpl->m_aFilters.clear();
1085 FilterMatch::createWildCardFilterList(_sFilter,pImpl->m_aFilters);
1088 void FilterMatch::createWildCardFilterList(std::u16string_view _rFilterList,::std::vector< WildCard >& _rFilters)
1090 if( !_rFilterList.empty() )
1092 // filter is given
1093 sal_Int32 nIndex = 0;
1094 OUString sToken;
1097 sToken = o3tl::getToken(_rFilterList, 0, ';', nIndex );
1098 if ( !sToken.isEmpty() )
1100 _rFilters.emplace_back( sToken.toAsciiUpperCase() );
1103 while ( nIndex >= 0 );
1105 else
1107 // no filter is given -> match all
1108 _rFilters.emplace_back(u"*" );
1112 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */