nss: upgrade to release 3.73
[LibreOffice.git] / ucb / source / core / ucb.cxx
blobf49dd87a4939b573d4b2e2ff665e8a44fe11ce2f
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 .
21 /**************************************************************************
22 TODO
23 **************************************************************************
25 *************************************************************************/
26 #include <osl/diagnose.h>
27 #include <sal/log.hxx>
28 #include <rtl/ustrbuf.hxx>
29 #include <comphelper/processfactory.hxx>
30 #include <comphelper/interfacecontainer2.hxx>
31 #include <comphelper/propertysequence.hxx>
32 #include <com/sun/star/lang/IllegalArgumentException.hpp>
33 #include <com/sun/star/ucb/DuplicateProviderException.hpp>
34 #include <com/sun/star/ucb/GlobalTransferCommandArgument2.hpp>
35 #include <com/sun/star/ucb/UnsupportedCommandException.hpp>
36 #include <com/sun/star/ucb/XCommandInfo.hpp>
37 #include <com/sun/star/ucb/XContentProviderSupplier.hpp>
38 #include <com/sun/star/configuration/theDefaultProvider.hpp>
39 #include <com/sun/star/container/XHierarchicalNameAccess.hpp>
40 #include <com/sun/star/container/XNameAccess.hpp>
41 #include <com/sun/star/uno/Any.hxx>
42 #include <cppuhelper/weak.hxx>
43 #include <ucbhelper/cancelcommandexecution.hxx>
44 #include <ucbhelper/macros.hxx>
45 #include <tools/diagnose_ex.h>
46 #include "identify.hxx"
47 #include "ucbcmds.hxx"
49 #include "ucb.hxx"
51 using namespace comphelper;
52 using namespace com::sun::star::uno;
53 using namespace com::sun::star::lang;
54 using namespace com::sun::star::ucb;
55 using namespace ucb_impl;
56 using namespace com::sun::star;
57 using namespace ucbhelper;
59 namespace {
61 bool fillPlaceholders(OUString const & rInput,
62 uno::Sequence< uno::Any > const & rReplacements,
63 OUString * pOutput)
65 sal_Unicode const * p = rInput.getStr();
66 sal_Unicode const * pEnd = p + rInput.getLength();
67 sal_Unicode const * pCopy = p;
68 OUStringBuffer aBuffer;
69 while (p != pEnd)
70 switch (*p++)
72 case '&':
73 if (pEnd - p >= 4
74 && p[0] == 'a' && p[1] == 'm' && p[2] == 'p'
75 && p[3] == ';')
77 aBuffer.append(pCopy, p - 1 - pCopy);
78 aBuffer.append('&');
79 p += 4;
80 pCopy = p;
82 else if (pEnd - p >= 3
83 && p[0] == 'l' && p[1] == 't' && p[2] == ';')
85 aBuffer.append(pCopy, p - 1 - pCopy);
86 aBuffer.append('<');
87 p += 3;
88 pCopy = p;
90 else if (pEnd - p >= 3
91 && p[0] == 'g' && p[1] == 't' && p[2] == ';')
93 aBuffer.append(pCopy, p - 1 - pCopy);
94 aBuffer.append('>');
95 p += 3;
96 pCopy = p;
98 break;
100 case '<':
101 sal_Unicode const * q = p;
102 while (q != pEnd && *q != '>')
103 ++q;
104 if (q == pEnd)
105 break;
106 OUString aKey(p, q - p);
107 OUString aValue;
108 bool bFound = false;
109 for (sal_Int32 i = 2; i + 1 < rReplacements.getLength();
110 i += 2)
112 OUString aReplaceKey;
113 if ((rReplacements[i] >>= aReplaceKey)
114 && aReplaceKey == aKey
115 && (rReplacements[i + 1] >>= aValue))
117 bFound = true;
118 break;
121 if (!bFound)
122 return false;
123 aBuffer.append(pCopy, p - 1 - pCopy);
124 aBuffer.append(aValue);
125 p = q + 1;
126 pCopy = p;
127 break;
129 aBuffer.append(pCopy, pEnd - pCopy);
130 *pOutput = aBuffer.makeStringAndClear();
131 return true;
134 void makeAndAppendXMLName(
135 OUStringBuffer & rBuffer, const OUString & rIn )
137 sal_Int32 nCount = rIn.getLength();
138 for ( sal_Int32 n = 0; n < nCount; ++n )
140 const sal_Unicode c = rIn[ n ];
141 switch ( c )
143 case '&':
144 rBuffer.append( "&amp;" );
145 break;
147 case '"':
148 rBuffer.append( "&quot;" );
149 break;
151 case '\'':
152 rBuffer.append( "&apos;" );
153 break;
155 case '<':
156 rBuffer.append( "&lt;" );
157 break;
159 case '>':
160 rBuffer.append( "&gt;" );
161 break;
163 default:
164 rBuffer.append( c );
165 break;
170 bool createContentProviderData(
171 const OUString & rProvider,
172 const uno::Reference< container::XHierarchicalNameAccess >& rxHierNameAccess,
173 ContentProviderData & rInfo)
175 // Obtain service name.
177 OUString aValue;
180 if ( !( rxHierNameAccess->getByHierarchicalName(
181 rProvider + "/ServiceName" ) >>= aValue ) )
183 OSL_FAIL( "UniversalContentBroker::getContentProviderData - "
184 "Error getting item value!" );
187 catch (const container::NoSuchElementException&)
189 return false;
192 rInfo.ServiceName = aValue;
194 // Obtain URL Template.
196 if ( !( rxHierNameAccess->getByHierarchicalName(
197 rProvider + "/URLTemplate" ) >>= aValue ) )
199 OSL_FAIL( "UniversalContentBroker::getContentProviderData - "
200 "Error getting item value!" );
203 rInfo.URLTemplate = aValue;
205 // Obtain Arguments.
207 if ( !( rxHierNameAccess->getByHierarchicalName(
208 rProvider + "/Arguments" ) >>= aValue ) )
210 OSL_FAIL( "UniversalContentBroker::getContentProviderData - "
211 "Error getting item value!" );
214 rInfo.Arguments = aValue;
215 return true;
221 // UniversalContentBroker Implementation.
224 UniversalContentBroker::UniversalContentBroker(
225 const Reference< css::uno::XComponentContext >& xContext )
226 : m_xContext( xContext ),
227 m_nCommandId( 0 )
229 OSL_ENSURE( m_xContext.is(),
230 "UniversalContentBroker ctor: No service manager" );
234 // virtual
235 UniversalContentBroker::~UniversalContentBroker()
240 // XComponent methods.
243 // virtual
244 void SAL_CALL UniversalContentBroker::dispose()
246 if ( m_pDisposeEventListeners && m_pDisposeEventListeners->getLength() )
248 EventObject aEvt;
249 aEvt.Source = static_cast< XComponent* >(this);
250 m_pDisposeEventListeners->disposeAndClear( aEvt );
253 if ( m_xNotifier.is() )
254 m_xNotifier->removeChangesListener( this );
258 // virtual
259 void SAL_CALL UniversalContentBroker::addEventListener(
260 const Reference< XEventListener >& Listener )
262 if ( !m_pDisposeEventListeners )
263 m_pDisposeEventListeners.reset( new OInterfaceContainerHelper2( m_aMutex ) );
265 m_pDisposeEventListeners->addInterface( Listener );
269 // virtual
270 void SAL_CALL UniversalContentBroker::removeEventListener(
271 const Reference< XEventListener >& Listener )
273 if ( m_pDisposeEventListeners )
274 m_pDisposeEventListeners->removeInterface( Listener );
276 // Note: Don't want to delete empty container here -> performance.
280 // XServiceInfo methods.
282 OUString SAL_CALL UniversalContentBroker::getImplementationName()
284 return "com.sun.star.comp.ucb.UniversalContentBroker";
286 sal_Bool SAL_CALL UniversalContentBroker::supportsService( const OUString& ServiceName )
288 return cppu::supportsService( this, ServiceName );
290 css::uno::Sequence< OUString > SAL_CALL UniversalContentBroker::getSupportedServiceNames()
292 return { "com.sun.star.ucb.UniversalContentBroker" };
296 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
297 ucb_UniversalContentBroker_get_implementation(
298 css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&)
300 return cppu::acquire(static_cast<cppu::OWeakObject*>(new UniversalContentBroker(context)));
304 // XInitialization methods.
307 // virtual
308 void SAL_CALL UniversalContentBroker::initialize( const css::uno::Sequence< Any >& aArguments )
311 osl::MutexGuard aGuard(m_aMutex);
312 if (m_aArguments.hasElements())
314 if (aArguments.hasElements()
315 && !(m_aArguments.getLength() == 2
316 && aArguments.getLength() == 2
317 && m_aArguments[0] == aArguments[0]
318 && m_aArguments[1] == aArguments[1]))
320 throw IllegalArgumentException(
321 "UCB reinitialized with different arguments",
322 static_cast< cppu::OWeakObject * >(this), 0);
324 return;
326 if (!aArguments.hasElements())
328 m_aArguments.realloc(2);
329 m_aArguments[0] <<= OUString("Local");
330 m_aArguments[1] <<= OUString("Office");
332 else
334 m_aArguments = aArguments;
337 configureUcb();
341 // XContentProviderManager methods.
344 // virtual
345 Reference< XContentProvider > SAL_CALL
346 UniversalContentBroker::registerContentProvider(
347 const Reference< XContentProvider >& Provider,
348 const OUString& Scheme,
349 sal_Bool ReplaceExisting )
351 osl::MutexGuard aGuard(m_aMutex);
353 ProviderMap_Impl::iterator aIt;
356 aIt = m_aProviders.find(Scheme);
358 catch (const IllegalArgumentException&)
360 return nullptr; //@@@
363 Reference< XContentProvider > xPrevious;
364 if (aIt == m_aProviders.end())
366 ProviderList_Impl aList;
367 aList.push_front( ProviderListEntry_Impl(Provider) );
370 m_aProviders.add(Scheme, aList);
372 catch (const IllegalArgumentException&)
374 return nullptr; //@@@
377 else
379 if (!ReplaceExisting)
380 throw DuplicateProviderException();
382 ProviderList_Impl & rList = aIt->getValue();
383 xPrevious = rList.front().getProvider();
384 rList.push_front( ProviderListEntry_Impl(Provider) );
387 return xPrevious;
391 // virtual
392 void SAL_CALL UniversalContentBroker::deregisterContentProvider(
393 const Reference< XContentProvider >& Provider,
394 const OUString& Scheme )
396 osl::MutexGuard aGuard(m_aMutex);
398 ProviderMap_Impl::iterator aMapIt;
401 aMapIt = m_aProviders.find(Scheme);
403 catch (const IllegalArgumentException&)
405 return; //@@@
408 if (aMapIt != m_aProviders.end())
410 ProviderList_Impl & rList = aMapIt->getValue();
412 auto aListIt = std::find_if(rList.begin(), rList.end(),
413 [&Provider](const ProviderListEntry_Impl& rEntry) { return rEntry.getProvider() == Provider; });
414 if (aListIt != rList.end())
415 rList.erase(aListIt);
417 if (rList.empty())
418 m_aProviders.erase(aMapIt);
423 // virtual
424 css::uno::Sequence< ContentProviderInfo > SAL_CALL
425 UniversalContentBroker::queryContentProviders()
427 // Return a list with information about active(!) content providers.
429 osl::MutexGuard aGuard(m_aMutex);
431 css::uno::Sequence< ContentProviderInfo > aSeq( m_aProviders.size() );
432 ContentProviderInfo* pInfo = aSeq.getArray();
434 ProviderMap_Impl::const_iterator end = m_aProviders.end();
435 for (ProviderMap_Impl::const_iterator it(m_aProviders.begin()); it != end;
436 ++it)
438 // Note: Active provider is always the first list element.
439 pInfo->ContentProvider = it->getValue().front().getProvider();
440 pInfo->Scheme = it->getRegexp();
441 ++pInfo;
444 return aSeq;
448 // virtual
449 Reference< XContentProvider > SAL_CALL
450 UniversalContentBroker::queryContentProvider( const OUString&
451 Identifier )
453 return queryContentProvider( Identifier, false );
457 // XContentProvider methods.
460 // virtual
461 Reference< XContent > SAL_CALL UniversalContentBroker::queryContent(
462 const Reference< XContentIdentifier >& Identifier )
465 // Let the content provider for the scheme given with the content
466 // identifier create the XContent instance.
469 if ( !Identifier.is() )
470 return Reference< XContent >();
472 Reference< XContentProvider > xProv =
473 queryContentProvider( Identifier->getContentIdentifier(), true );
474 if ( xProv.is() )
475 return xProv->queryContent( Identifier );
477 return Reference< XContent >();
481 // virtual
482 sal_Int32 SAL_CALL UniversalContentBroker::compareContentIds(
483 const Reference< XContentIdentifier >& Id1,
484 const Reference< XContentIdentifier >& Id2 )
486 OUString aURI1( Id1->getContentIdentifier() );
487 OUString aURI2( Id2->getContentIdentifier() );
489 Reference< XContentProvider > xProv1
490 = queryContentProvider( aURI1, true );
491 Reference< XContentProvider > xProv2
492 = queryContentProvider( aURI2, true );
494 // When both identifiers belong to the same provider, let that provider
495 // compare them; otherwise, simply compare the URI strings (which must
496 // be different):
497 if ( xProv1.is() && ( xProv1 == xProv2 ) )
498 return xProv1->compareContentIds( Id1, Id2 );
499 else
500 return aURI1.compareTo( aURI2 );
504 // XContentIdentifierFactory methods.
507 // virtual
508 Reference< XContentIdentifier > SAL_CALL
509 UniversalContentBroker::createContentIdentifier(
510 const OUString& ContentId )
513 // Let the content provider for the scheme given with content
514 // identifier create the XContentIdentifier instance, if he supports
515 // the XContentIdentifierFactory interface. Otherwise create standard
516 // implementation object for XContentIdentifier.
519 Reference< XContentIdentifier > xIdentifier;
521 Reference< XContentProvider > xProv
522 = queryContentProvider( ContentId, true );
523 if ( xProv.is() )
525 Reference< XContentIdentifierFactory > xFac( xProv, UNO_QUERY );
526 if ( xFac.is() )
527 xIdentifier = xFac->createContentIdentifier( ContentId );
530 if ( !xIdentifier.is() )
531 xIdentifier = new ContentIdentifier( ContentId );
533 return xIdentifier;
537 // XCommandProcessor methods.
540 // virtual
541 sal_Int32 SAL_CALL UniversalContentBroker::createCommandIdentifier()
543 osl::MutexGuard aGuard( m_aMutex );
545 // Just increase counter on every call to generate an identifier.
546 return ++m_nCommandId;
550 // virtual
551 Any SAL_CALL UniversalContentBroker::execute(
552 const Command& aCommand,
553 sal_Int32,
554 const Reference< XCommandEnvironment >& Environment )
556 Any aRet;
559 // Note: Don't forget to adapt ucb_commands::CommandProcessorInfo
560 // ctor in ucbcmds.cxx when adding new commands!
563 if ( ( aCommand.Handle == GETCOMMANDINFO_HANDLE ) || aCommand.Name == GETCOMMANDINFO_NAME )
566 // getCommandInfo
569 aRet <<= getCommandInfo();
571 else if ( ( aCommand.Handle == GLOBALTRANSFER_HANDLE ) || aCommand.Name == GLOBALTRANSFER_NAME )
574 // globalTransfer
577 GlobalTransferCommandArgument2 aTransferArg;
578 if ( !( aCommand.Argument >>= aTransferArg ) )
580 GlobalTransferCommandArgument aArg;
581 if ( !( aCommand.Argument >>= aArg ) )
583 ucbhelper::cancelCommandExecution(
584 makeAny( IllegalArgumentException(
585 "Wrong argument type!",
586 static_cast< cppu::OWeakObject * >( this ),
587 -1 ) ),
588 Environment );
589 // Unreachable
592 // Copy infos into the new structure
593 aTransferArg.Operation = aArg.Operation;
594 aTransferArg.SourceURL = aArg.SourceURL;
595 aTransferArg.TargetURL = aArg.TargetURL;
596 aTransferArg.NewTitle = aArg.NewTitle;
597 aTransferArg.NameClash = aArg.NameClash;
600 globalTransfer( aTransferArg, Environment );
602 else if ( ( aCommand.Handle == CHECKIN_HANDLE ) || aCommand.Name == CHECKIN_NAME )
604 ucb::CheckinArgument aCheckinArg;
605 if ( !( aCommand.Argument >>= aCheckinArg ) )
607 ucbhelper::cancelCommandExecution(
608 makeAny( IllegalArgumentException(
609 "Wrong argument type!",
610 static_cast< cppu::OWeakObject * >( this ),
611 -1 ) ),
612 Environment );
613 // Unreachable
615 aRet = checkIn( aCheckinArg, Environment );
617 else
620 // Unknown command
623 ucbhelper::cancelCommandExecution(
624 makeAny( UnsupportedCommandException(
625 OUString(),
626 static_cast< cppu::OWeakObject * >( this ) ) ),
627 Environment );
628 // Unreachable
631 return aRet;
635 // XCommandProcessor2 methods.
638 // virtual
639 void SAL_CALL UniversalContentBroker::releaseCommandIdentifier(sal_Int32 /*aCommandId*/)
641 // @@@ Not implemented ( yet).
645 // virtual
646 void SAL_CALL UniversalContentBroker::abort( sal_Int32 )
648 // @@@ Not implemented ( yet).
652 // XChangesListener methods
655 // virtual
656 void SAL_CALL UniversalContentBroker::changesOccurred( const util::ChangesEvent& Event )
658 if ( !Event.Changes.hasElements() )
659 return;
661 uno::Reference< container::XHierarchicalNameAccess > xHierNameAccess;
662 Event.Base >>= xHierNameAccess;
664 OSL_ASSERT( xHierNameAccess.is() );
666 ContentProviderDataList aData;
667 for ( const util::ElementChange& rElem : Event.Changes )
669 OUString aKey;
670 rElem.Accessor >>= aKey;
672 ContentProviderData aInfo;
674 // Removal of UCPs from the configuration leads to changesOccurred
675 // notifications, too, but it is hard to tell for a given
676 // ElementChange whether it is an addition or a removal, so as a
677 // heuristic consider as removals those that cause a
678 // NoSuchElementException in createContentProviderData.
680 // For now, removal of UCPs from the configuration is simply ignored
681 // (and not reflected in the UCB's data structures):
682 if (createContentProviderData(aKey, xHierNameAccess, aInfo))
684 aData.push_back(aInfo);
688 prepareAndRegister(aData);
692 // XEventListener methods
695 // virtual
696 void SAL_CALL UniversalContentBroker::disposing(const lang::EventObject&)
698 if ( m_xNotifier.is() )
700 osl::Guard< osl::Mutex > aGuard( m_aMutex );
702 if ( m_xNotifier.is() )
703 m_xNotifier.clear();
708 // Non-interface methods
711 Reference< XContentProvider > UniversalContentBroker::queryContentProvider(
712 const OUString& Identifier,
713 bool bResolved )
715 osl::MutexGuard aGuard( m_aMutex );
717 ProviderList_Impl const * pList = m_aProviders.map( Identifier );
718 return pList ? bResolved ? pList->front().getResolvedProvider()
719 : pList->front().getProvider()
720 : Reference< XContentProvider >();
723 void UniversalContentBroker::configureUcb()
725 OUString aKey1;
726 OUString aKey2;
727 if (m_aArguments.getLength() < 2
728 || !(m_aArguments[0] >>= aKey1) || !(m_aArguments[1] >>= aKey2))
730 OSL_FAIL("UniversalContentBroker::configureUcb(): Bad arguments");
731 return;
734 ContentProviderDataList aData;
735 if (!getContentProviderData(aKey1, aKey2, aData))
737 SAL_WARN( "ucb", "No configuration");
738 return;
741 prepareAndRegister(aData);
744 void UniversalContentBroker::prepareAndRegister(
745 const ContentProviderDataList& rData)
747 for (const auto& rContentProviderData : rData)
749 OUString aProviderArguments;
750 if (fillPlaceholders(rContentProviderData.Arguments,
751 m_aArguments,
752 &aProviderArguments))
754 registerAtUcb(this,
755 m_xContext,
756 rContentProviderData.ServiceName,
757 aProviderArguments,
758 rContentProviderData.URLTemplate);
761 else
762 OSL_FAIL("UniversalContentBroker::prepareAndRegister(): Bad argument placeholders");
767 bool UniversalContentBroker::getContentProviderData(
768 const OUString & rKey1,
769 const OUString & rKey2,
770 ContentProviderDataList & rListToFill )
772 if ( !m_xContext.is() || rKey1.isEmpty() || rKey2.isEmpty() )
774 OSL_FAIL( "UniversalContentBroker::getContentProviderData - Invalid argument!" );
775 return false;
780 uno::Reference< lang::XMultiServiceFactory > xConfigProv =
781 configuration::theDefaultProvider::get( m_xContext );
783 OUStringBuffer aFullPath(128);
784 aFullPath.append(
785 "/org.openoffice.ucb.Configuration/ContentProviders"
786 "/['" );
787 makeAndAppendXMLName( aFullPath, rKey1 );
788 aFullPath.append( "']/SecondaryKeys/['" );
789 makeAndAppendXMLName( aFullPath, rKey2 );
790 aFullPath.append( "']/ProviderData" );
792 uno::Sequence<uno::Any> aArguments(comphelper::InitAnyPropertySequence(
794 {"nodepath", uno::Any(aFullPath.makeStringAndClear())}
795 }));
797 uno::Reference< uno::XInterface > xInterface(
798 xConfigProv->createInstanceWithArguments(
799 "com.sun.star.configuration.ConfigurationAccess",
800 aArguments ) );
802 if ( !m_xNotifier.is() )
804 m_xNotifier.set( xInterface, uno::UNO_QUERY_THROW );
806 m_xNotifier->addChangesListener( this );
809 uno::Reference< container::XNameAccess > xNameAccess(
810 xInterface, uno::UNO_QUERY_THROW );
812 const uno::Sequence< OUString > aElems = xNameAccess->getElementNames();
814 if ( aElems.hasElements() )
816 uno::Reference< container::XHierarchicalNameAccess >
817 xHierNameAccess( xInterface, uno::UNO_QUERY_THROW );
819 // Iterate over children.
820 for ( const auto& rElem : aElems )
826 ContentProviderData aInfo;
828 OUStringBuffer aElemBuffer;
829 aElemBuffer.append( "['" );
830 makeAndAppendXMLName( aElemBuffer, rElem );
831 aElemBuffer.append( "']" );
833 OSL_VERIFY(
834 createContentProviderData(
835 aElemBuffer.makeStringAndClear(), xHierNameAccess,
836 aInfo));
838 rListToFill.push_back( aInfo );
840 catch (const container::NoSuchElementException&)
842 // getByHierarchicalName
843 OSL_FAIL( "UniversalContentBroker::getContentProviderData - "
844 "caught NoSuchElementException!" );
849 catch (const uno::RuntimeException&)
851 TOOLS_WARN_EXCEPTION( "ucb", "" );
852 return false;
854 catch (const uno::Exception&)
856 // createInstance, createInstanceWithArguments
858 TOOLS_WARN_EXCEPTION( "ucb", "" );
859 return false;
862 return true;
866 // ProviderListEntry_Impl implementation.
869 Reference< XContentProvider > const & ProviderListEntry_Impl::resolveProvider() const
871 if ( !m_xResolvedProvider.is() )
873 Reference< XContentProviderSupplier > xSupplier(
874 m_xProvider, UNO_QUERY );
875 if ( xSupplier.is() )
876 m_xResolvedProvider = xSupplier->getContentProvider();
878 if ( !m_xResolvedProvider.is() )
879 m_xResolvedProvider = m_xProvider;
882 return m_xResolvedProvider;
885 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */