/src/libreoffice/desktop/source/deployment/misc/dp_descriptioninfoset.cxx
Line | Count | Source |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* |
3 | | * This file is part of the LibreOffice project. |
4 | | * |
5 | | * This Source Code Form is subject to the terms of the Mozilla Public |
6 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
7 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
8 | | * |
9 | | * This file incorporates work covered by the following license notice: |
10 | | * |
11 | | * Licensed to the Apache Software Foundation (ASF) under one or more |
12 | | * contributor license agreements. See the NOTICE file distributed |
13 | | * with this work for additional information regarding copyright |
14 | | * ownership. The ASF licenses this file to you under the Apache |
15 | | * License, Version 2.0 (the "License"); you may not use this file |
16 | | * except in compliance with the License. You may obtain a copy of |
17 | | * the License at http://www.apache.org/licenses/LICENSE-2.0 . |
18 | | */ |
19 | | |
20 | | #include <sal/config.h> |
21 | | |
22 | | #include <string_view> |
23 | | |
24 | | #include <dp_descriptioninfoset.hxx> |
25 | | |
26 | | #include <dp_resource.h> |
27 | | |
28 | | #include <comphelper/sequence.hxx> |
29 | | #include <comphelper/processfactory.hxx> |
30 | | #include <comphelper/propertysequence.hxx> |
31 | | #include <optional> |
32 | | #include <com/sun/star/configuration/theDefaultProvider.hpp> |
33 | | #include <com/sun/star/container/XNameAccess.hpp> |
34 | | #include <com/sun/star/deployment/DeploymentException.hpp> |
35 | | #include <com/sun/star/beans/XPropertySet.hpp> |
36 | | #include <com/sun/star/io/SequenceInputStream.hpp> |
37 | | #include <com/sun/star/lang/XMultiComponentFactory.hpp> |
38 | | #include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> |
39 | | #include <com/sun/star/task/XInteractionHandler.hpp> |
40 | | #include <com/sun/star/ucb/XCommandEnvironment.hpp> |
41 | | #include <com/sun/star/uno/Reference.hxx> |
42 | | #include <com/sun/star/uno/RuntimeException.hpp> |
43 | | #include <com/sun/star/uno/Sequence.hxx> |
44 | | #include <com/sun/star/uno/XInterface.hpp> |
45 | | #include <com/sun/star/xml/dom/DOMException.hpp> |
46 | | #include <com/sun/star/xml/dom/XNode.hpp> |
47 | | #include <com/sun/star/xml/dom/XNodeList.hpp> |
48 | | #include <com/sun/star/xml/dom/DocumentBuilder.hpp> |
49 | | #include <com/sun/star/xml/xpath/XPathAPI.hpp> |
50 | | #include <com/sun/star/xml/xpath/XPathException.hpp> |
51 | | #include <com/sun/star/ucb/InteractiveIOException.hpp> |
52 | | #include <cppuhelper/implbase.hxx> |
53 | | #include <cppuhelper/weak.hxx> |
54 | | #include <cppuhelper/exc_hlp.hxx> |
55 | | #include <rtl/ustring.hxx> |
56 | | #include <sal/types.h> |
57 | | #include <ucbhelper/content.hxx> |
58 | | #include <o3tl/string_view.hxx> |
59 | | |
60 | | namespace { |
61 | | |
62 | | using css::uno::Reference; |
63 | | |
64 | | class EmptyNodeList: |
65 | | public cppu::WeakImplHelper<css::xml::dom::XNodeList> |
66 | | { |
67 | | public: |
68 | | EmptyNodeList(); |
69 | | |
70 | | EmptyNodeList(const EmptyNodeList&) = delete; |
71 | | const EmptyNodeList& operator=(const EmptyNodeList&) = delete; |
72 | | |
73 | | virtual ::sal_Int32 SAL_CALL getLength() override; |
74 | | |
75 | | virtual css::uno::Reference< css::xml::dom::XNode > SAL_CALL |
76 | | item(::sal_Int32 index) override; |
77 | | }; |
78 | | |
79 | 0 | EmptyNodeList::EmptyNodeList() {} |
80 | | |
81 | 0 | ::sal_Int32 EmptyNodeList::getLength() { |
82 | 0 | return 0; |
83 | 0 | } |
84 | | |
85 | | css::uno::Reference< css::xml::dom::XNode > EmptyNodeList::item(::sal_Int32) |
86 | 0 | { |
87 | 0 | throw css::uno::RuntimeException(u"bad EmptyNodeList com.sun.star.xml.dom.XNodeList.item call"_ustr, |
88 | 0 | static_cast< ::cppu::OWeakObject * >(this)); |
89 | 0 | } |
90 | | |
91 | | OUString getNodeValue( |
92 | | css::uno::Reference< css::xml::dom::XNode > const & node) |
93 | 0 | { |
94 | 0 | OSL_ASSERT(node.is()); |
95 | 0 | try { |
96 | 0 | return node->getNodeValue(); |
97 | 0 | } catch (const css::xml::dom::DOMException & e) { |
98 | 0 | css::uno::Any anyEx = cppu::getCaughtException(); |
99 | 0 | throw css::lang::WrappedTargetRuntimeException( |
100 | 0 | "com.sun.star.xml.dom.DOMException: " + e.Message, |
101 | 0 | nullptr, anyEx ); |
102 | 0 | } |
103 | 0 | } |
104 | | |
105 | | /**The class uses the UCB to access the description.xml file in an |
106 | | extension. The UCB must have been initialized already. It also |
107 | | requires that the extension has already be unzipped to a particular |
108 | | location. |
109 | | */ |
110 | | class ExtensionDescription |
111 | | { |
112 | | public: |
113 | | /**throws an exception if the description.xml is not |
114 | | available, cannot be read, does not contain the expected data, |
115 | | or any other error occurred. Therefore it should only be used with |
116 | | new extensions. |
117 | | |
118 | | Throws css::uno::RuntimeException, |
119 | | css::deployment::DeploymentException, |
120 | | dp_registry::backend::bundle::NoDescriptionException. |
121 | | */ |
122 | | ExtensionDescription( |
123 | | const css::uno::Reference<css::uno::XComponentContext>& xContext, |
124 | | std::u16string_view installDir, |
125 | | const css::uno::Reference< css::ucb::XCommandEnvironment >& xCmdEnv); |
126 | | |
127 | | const css::uno::Reference<css::xml::dom::XNode>& getRootElement() const |
128 | 0 | { |
129 | 0 | return m_xRoot; |
130 | 0 | } |
131 | | |
132 | | private: |
133 | | css::uno::Reference<css::xml::dom::XNode> m_xRoot; |
134 | | }; |
135 | | |
136 | | class NoDescriptionException |
137 | | { |
138 | | }; |
139 | | |
140 | | class FileDoesNotExistFilter |
141 | | : public ::cppu::WeakImplHelper< css::ucb::XCommandEnvironment, |
142 | | css::task::XInteractionHandler > |
143 | | |
144 | | { |
145 | | bool m_bExist; |
146 | | css::uno::Reference< css::ucb::XCommandEnvironment > m_xCommandEnv; |
147 | | |
148 | | public: |
149 | | explicit FileDoesNotExistFilter( |
150 | | const css::uno::Reference< css::ucb::XCommandEnvironment >& xCmdEnv); |
151 | | |
152 | 0 | bool exist() { return m_bExist;} |
153 | | // XCommandEnvironment |
154 | | virtual css::uno::Reference<css::task::XInteractionHandler > SAL_CALL |
155 | | getInteractionHandler() override; |
156 | | virtual css::uno::Reference<css::ucb::XProgressHandler > |
157 | | SAL_CALL getProgressHandler() override; |
158 | | |
159 | | // XInteractionHandler |
160 | | virtual void SAL_CALL handle( |
161 | | css::uno::Reference<css::task::XInteractionRequest > const & xRequest ) override; |
162 | | }; |
163 | | |
164 | | ExtensionDescription::ExtensionDescription( |
165 | | const Reference<css::uno::XComponentContext>& xContext, |
166 | | std::u16string_view installDir, |
167 | | const Reference< css::ucb::XCommandEnvironment >& xCmdEnv) |
168 | 0 | { |
169 | 0 | try { |
170 | | //may throw css::ucb::ContentCreationException |
171 | | //If there is no description.xml then ucb will start an interaction which |
172 | | //brings up a dialog.We want to prevent this. Therefore we wrap the xCmdEnv |
173 | | //and filter the respective exception out. |
174 | 0 | OUString sDescriptionUri(OUString::Concat(installDir) + "/description.xml"); |
175 | 0 | Reference<css::ucb::XCommandEnvironment> xFilter = new FileDoesNotExistFilter(xCmdEnv); |
176 | 0 | ::ucbhelper::Content descContent(sDescriptionUri, xFilter, xContext); |
177 | | |
178 | | //throws a css::uno::Exception if the file is not available |
179 | 0 | Reference<css::io::XInputStream> xIn; |
180 | 0 | try |
181 | 0 | { //throws com.sun.star.ucb.InteractiveIOException |
182 | 0 | xIn = descContent.openStream(); |
183 | 0 | } |
184 | 0 | catch ( const css::uno::Exception& ) |
185 | 0 | { |
186 | 0 | if ( ! static_cast<FileDoesNotExistFilter*>(xFilter.get())->exist()) |
187 | 0 | throw NoDescriptionException(); |
188 | 0 | throw; |
189 | 0 | } |
190 | 0 | if (!xIn.is()) |
191 | 0 | { |
192 | 0 | throw css::uno::Exception( |
193 | 0 | "Could not get XInputStream for description.xml of extension " + |
194 | 0 | sDescriptionUri, nullptr); |
195 | 0 | } |
196 | | |
197 | | //get root node of description.xml |
198 | 0 | Reference<css::xml::dom::XDocumentBuilder> xDocBuilder( |
199 | 0 | css::xml::dom::DocumentBuilder::create(xContext) ); |
200 | |
|
201 | 0 | if (!xDocBuilder->isNamespaceAware()) |
202 | 0 | { |
203 | 0 | throw css::uno::Exception( |
204 | 0 | u"Service com.sun.star.xml.dom.DocumentBuilder is not namespace aware."_ustr, nullptr); |
205 | 0 | } |
206 | | |
207 | 0 | Reference<css::xml::dom::XDocument> xDoc = xDocBuilder->parse(xIn); |
208 | 0 | if (!xDoc.is()) |
209 | 0 | { |
210 | 0 | throw css::uno::Exception(sDescriptionUri + " contains data which cannot be parsed. ", nullptr); |
211 | 0 | } |
212 | | |
213 | | //check for proper root element and namespace |
214 | 0 | Reference<css::xml::dom::XElement> xRoot = xDoc->getDocumentElement(); |
215 | 0 | if (!xRoot.is()) |
216 | 0 | { |
217 | 0 | throw css::uno::Exception( |
218 | 0 | sDescriptionUri + " contains no root element.", nullptr); |
219 | 0 | } |
220 | | |
221 | 0 | if ( xRoot->getTagName() != "description") |
222 | 0 | { |
223 | 0 | throw css::uno::Exception( |
224 | 0 | sDescriptionUri + " does not contain the root element <description>.", nullptr); |
225 | 0 | } |
226 | | |
227 | 0 | m_xRoot.set(xRoot, css::uno::UNO_QUERY_THROW); |
228 | 0 | OUString nsDescription = xRoot->getNamespaceURI(); |
229 | | |
230 | | //check if this namespace is supported |
231 | 0 | if ( nsDescription != "http://openoffice.org/extensions/description/2006") |
232 | 0 | { |
233 | 0 | throw css::uno::Exception(sDescriptionUri + " contains a root element with an unsupported namespace. ", nullptr); |
234 | 0 | } |
235 | 0 | } catch (const css::uno::RuntimeException &) { |
236 | 0 | throw; |
237 | 0 | } catch (const css::deployment::DeploymentException &) { |
238 | 0 | throw; |
239 | 0 | } catch (const css::uno::Exception & e) { |
240 | 0 | css::uno::Any a(cppu::getCaughtException()); |
241 | 0 | throw css::deployment::DeploymentException( |
242 | 0 | e.Message, Reference< css::uno::XInterface >(), a); |
243 | 0 | } |
244 | 0 | } |
245 | | |
246 | | FileDoesNotExistFilter::FileDoesNotExistFilter( |
247 | | const Reference< css::ucb::XCommandEnvironment >& xCmdEnv): |
248 | 0 | m_bExist(true), m_xCommandEnv(xCmdEnv) |
249 | 0 | {} |
250 | | |
251 | | // XCommandEnvironment |
252 | | Reference<css::task::XInteractionHandler > |
253 | | FileDoesNotExistFilter::getInteractionHandler() |
254 | 0 | { |
255 | 0 | return static_cast<css::task::XInteractionHandler*>(this); |
256 | 0 | } |
257 | | |
258 | | Reference<css::ucb::XProgressHandler > |
259 | | FileDoesNotExistFilter::getProgressHandler() |
260 | 0 | { |
261 | 0 | return m_xCommandEnv.is() |
262 | 0 | ? m_xCommandEnv->getProgressHandler() |
263 | 0 | : Reference<css::ucb::XProgressHandler>(); |
264 | 0 | } |
265 | | |
266 | | // XInteractionHandler |
267 | | //If the interaction was caused by a non-existing file which is specified in the ctor |
268 | | //of FileDoesNotExistFilter, then we do nothing |
269 | | void FileDoesNotExistFilter::handle( |
270 | | Reference<css::task::XInteractionRequest > const & xRequest ) |
271 | 0 | { |
272 | 0 | css::uno::Any request( xRequest->getRequest() ); |
273 | |
|
274 | 0 | css::ucb::InteractiveIOException ioexc; |
275 | 0 | if ((request>>= ioexc) |
276 | 0 | && (ioexc.Code == css::ucb::IOErrorCode_NOT_EXISTING |
277 | 0 | || ioexc.Code == css::ucb::IOErrorCode_NOT_EXISTING_PATH)) |
278 | 0 | { |
279 | 0 | m_bExist = false; |
280 | 0 | return; |
281 | 0 | } |
282 | 0 | Reference<css::task::XInteractionHandler> xInteraction; |
283 | 0 | if (m_xCommandEnv.is()) { |
284 | 0 | xInteraction = m_xCommandEnv->getInteractionHandler(); |
285 | 0 | } |
286 | 0 | if (xInteraction.is()) { |
287 | 0 | xInteraction->handle(xRequest); |
288 | 0 | } |
289 | 0 | } |
290 | | |
291 | | } |
292 | | |
293 | | namespace dp_misc { |
294 | | |
295 | | DescriptionInfoset getDescriptionInfoset(std::u16string_view sExtensionFolderURL) |
296 | 0 | { |
297 | 0 | Reference< css::xml::dom::XNode > root; |
298 | 0 | const Reference<css::uno::XComponentContext>& context( |
299 | 0 | comphelper::getProcessComponentContext()); |
300 | 0 | try { |
301 | 0 | root = |
302 | 0 | ExtensionDescription( |
303 | 0 | context, sExtensionFolderURL, |
304 | 0 | Reference< css::ucb::XCommandEnvironment >()). |
305 | 0 | getRootElement(); |
306 | 0 | } catch (const NoDescriptionException &) { |
307 | 0 | } catch (const css::deployment::DeploymentException & e) { |
308 | 0 | css::uno::Any anyEx = cppu::getCaughtException(); |
309 | 0 | throw css::lang::WrappedTargetRuntimeException( |
310 | 0 | "com.sun.star.deployment.DeploymentException: " + e.Message, |
311 | 0 | nullptr, anyEx ); |
312 | 0 | } |
313 | 0 | return DescriptionInfoset(context, root); |
314 | 0 | } |
315 | | |
316 | | DescriptionInfoset::DescriptionInfoset( |
317 | | css::uno::Reference< css::uno::XComponentContext > const & context, |
318 | | css::uno::Reference< css::xml::dom::XNode > const & element): |
319 | 0 | m_context(context), |
320 | 0 | m_element(element) |
321 | 0 | { |
322 | 0 | if (m_element.is()) { |
323 | 0 | m_xpath = css::xml::xpath::XPathAPI::create(context); |
324 | 0 | m_xpath->registerNS(u"desc"_ustr, element->getNamespaceURI()); |
325 | 0 | m_xpath->registerNS(u"xlink"_ustr, u"http://www.w3.org/1999/xlink"_ustr); |
326 | 0 | } |
327 | 0 | } |
328 | | |
329 | 0 | DescriptionInfoset::~DescriptionInfoset() {} |
330 | | |
331 | 0 | ::std::optional< OUString > DescriptionInfoset::getIdentifier() const { |
332 | 0 | return getOptionalValue(u"desc:identifier/@value"_ustr); |
333 | 0 | } |
334 | | |
335 | | OUString DescriptionInfoset::getNodeValueFromExpression(OUString const & expression) const |
336 | 0 | { |
337 | 0 | css::uno::Reference< css::xml::dom::XNode > n; |
338 | 0 | if (m_element.is()) { |
339 | 0 | try { |
340 | 0 | n = m_xpath->selectSingleNode(m_element, expression); |
341 | 0 | } catch (const css::xml::xpath::XPathException &) { |
342 | | // ignore |
343 | 0 | } |
344 | 0 | } |
345 | 0 | return n.is() ? getNodeValue(n) : OUString(); |
346 | 0 | } |
347 | | |
348 | | void DescriptionInfoset::checkDenylist() const |
349 | 0 | { |
350 | 0 | if (!m_element.is()) |
351 | 0 | return; |
352 | | |
353 | 0 | std::optional< OUString > id(getIdentifier()); |
354 | 0 | if (!id) |
355 | 0 | return; // nothing to check |
356 | 0 | OUString currentversion(getVersion()); |
357 | 0 | if (currentversion.getLength() == 0) |
358 | 0 | return; // nothing to check |
359 | | |
360 | 0 | css::uno::Sequence<css::uno::Any> args(comphelper::InitAnyPropertySequence( |
361 | 0 | { |
362 | 0 | {"nodepath", css::uno::Any(u"/org.openoffice.Office.ExtensionDependencies/Extensions"_ustr)} |
363 | 0 | })); |
364 | 0 | css::uno::Reference< css::container::XNameAccess > denylist( |
365 | 0 | (css::configuration::theDefaultProvider::get(m_context) |
366 | 0 | ->createInstanceWithArguments( |
367 | 0 | u"com.sun.star.configuration.ConfigurationAccess"_ustr, args)), |
368 | 0 | css::uno::UNO_QUERY_THROW); |
369 | | |
370 | | // check first if a denylist entry is available |
371 | 0 | if (!(denylist.is() && denylist->hasByName(*id))) return; |
372 | | |
373 | 0 | css::uno::Reference< css::beans::XPropertySet > extProps( |
374 | 0 | denylist->getByName(*id), css::uno::UNO_QUERY_THROW); |
375 | |
|
376 | 0 | css::uno::Any anyValue = extProps->getPropertyValue(u"Versions"_ustr); |
377 | |
|
378 | 0 | css::uno::Sequence< OUString > blversions; |
379 | 0 | anyValue >>= blversions; |
380 | | |
381 | | // check if the current version requires further dependency checks from the denylist |
382 | 0 | if (!checkDenylistVersion(currentversion, blversions)) return; |
383 | | |
384 | 0 | anyValue = extProps->getPropertyValue(u"Dependencies"_ustr); |
385 | 0 | OUString udeps; |
386 | 0 | anyValue >>= udeps; |
387 | |
|
388 | 0 | if (udeps.getLength() == 0) |
389 | 0 | return; // nothing todo |
390 | | |
391 | 0 | OString xmlDependencies = OUStringToOString(udeps, RTL_TEXTENCODING_UNICODE); |
392 | |
|
393 | 0 | css::uno::Reference< css::xml::dom::XDocumentBuilder> docbuilder( |
394 | 0 | m_context->getServiceManager()->createInstanceWithContext(u"com.sun.star.xml.dom.DocumentBuilder"_ustr, m_context), |
395 | 0 | css::uno::UNO_QUERY_THROW); |
396 | |
|
397 | 0 | css::uno::Sequence< sal_Int8 > byteSeq(reinterpret_cast<const sal_Int8*>(xmlDependencies.getStr()), xmlDependencies.getLength()); |
398 | |
|
399 | 0 | css::uno::Reference< css::io::XInputStream> inputstream( css::io::SequenceInputStream::createStreamFromSequence(m_context, byteSeq), |
400 | 0 | css::uno::UNO_QUERY_THROW); |
401 | |
|
402 | 0 | css::uno::Reference< css::xml::dom::XDocument > xDocument(docbuilder->parse(inputstream)); |
403 | 0 | css::uno::Reference< css::xml::dom::XElement > xElement(xDocument->getDocumentElement()); |
404 | 0 | css::uno::Reference< css::xml::dom::XNodeList > xDeps(xElement->getChildNodes()); |
405 | 0 | sal_Int32 nLen = xDeps->getLength(); |
406 | | |
407 | | // get the parent xml document of current description info for the import |
408 | 0 | css::uno::Reference< css::xml::dom::XDocument > xCurrentDescInfo(m_element->getOwnerDocument()); |
409 | | |
410 | | // get dependency node of current description info to merge the new dependencies from the denylist |
411 | 0 | css::uno::Reference< css::xml::dom::XNode > xCurrentDeps( |
412 | 0 | m_xpath->selectSingleNode(m_element, u"desc:dependencies"_ustr)); |
413 | | |
414 | | // if no dependency node exists, create a new one in the current description info |
415 | 0 | if (!xCurrentDeps.is()) { |
416 | 0 | css::uno::Reference< css::xml::dom::XNode > xNewDepNode( |
417 | 0 | xCurrentDescInfo->createElementNS( |
418 | 0 | u"http://openoffice.org/extensions/description/2006"_ustr, |
419 | 0 | u"dependencies"_ustr), css::uno::UNO_QUERY_THROW); |
420 | 0 | m_element->appendChild(xNewDepNode); |
421 | 0 | xCurrentDeps = m_xpath->selectSingleNode(m_element, u"desc:dependencies"_ustr); |
422 | 0 | } |
423 | |
|
424 | 0 | for (sal_Int32 i=0; i<nLen; i++) { |
425 | 0 | css::uno::Reference< css::xml::dom::XNode > xNode(xDeps->item(i)); |
426 | 0 | css::uno::Reference< css::xml::dom::XElement > xDep(xNode, css::uno::UNO_QUERY); |
427 | 0 | if (xDep.is()) { |
428 | | // found valid denylist dependency, import the node first and append it to the existing dependency node |
429 | 0 | css::uno::Reference< css::xml::dom::XNode > importedNode = xCurrentDescInfo->importNode(xNode, true); |
430 | 0 | xCurrentDeps->appendChild(importedNode); |
431 | 0 | } |
432 | 0 | } |
433 | 0 | } |
434 | | |
435 | | bool DescriptionInfoset::checkDenylistVersion( |
436 | | std::u16string_view currentversion, |
437 | | css::uno::Sequence< OUString > const & versions) |
438 | 0 | { |
439 | 0 | sal_Int32 nLen = versions.getLength(); |
440 | 0 | for (sal_Int32 i=0; i<nLen; i++) { |
441 | 0 | if (currentversion == versions[i]) |
442 | 0 | return true; |
443 | 0 | } |
444 | | |
445 | 0 | return false; |
446 | 0 | } |
447 | | |
448 | | OUString DescriptionInfoset::getVersion() const |
449 | 0 | { |
450 | 0 | return getNodeValueFromExpression( u"desc:version/@value"_ustr ); |
451 | 0 | } |
452 | | |
453 | | css::uno::Sequence< OUString > DescriptionInfoset::getSupportedPlatforms() const |
454 | 0 | { |
455 | | //When there is no description.xml then we assume that we support all platforms |
456 | 0 | if (! m_element.is()) |
457 | 0 | { |
458 | 0 | return { u"all"_ustr }; |
459 | 0 | } |
460 | | |
461 | | //Check if the <platform> element was provided. If not the default is "all" platforms |
462 | 0 | css::uno::Reference< css::xml::dom::XNode > nodePlatform( |
463 | 0 | m_xpath->selectSingleNode(m_element, u"desc:platform"_ustr)); |
464 | 0 | if (!nodePlatform.is()) |
465 | 0 | { |
466 | 0 | return { u"all"_ustr }; |
467 | 0 | } |
468 | | |
469 | | //There is a platform element. |
470 | 0 | const OUString value = getNodeValueFromExpression(u"desc:platform/@value"_ustr); |
471 | | //parse the string, it can contained multiple strings separated by commas |
472 | 0 | std::vector< OUString> vec; |
473 | 0 | sal_Int32 nIndex = 0; |
474 | 0 | do |
475 | 0 | { |
476 | 0 | const OUString aToken( o3tl::trim(o3tl::getToken(value, 0, ',', nIndex )) ); |
477 | 0 | if (!aToken.isEmpty()) |
478 | 0 | vec.push_back(aToken); |
479 | |
|
480 | 0 | } |
481 | 0 | while (nIndex >= 0); |
482 | |
|
483 | 0 | return comphelper::containerToSequence(vec); |
484 | 0 | } |
485 | | |
486 | | css::uno::Reference< css::xml::dom::XNodeList > |
487 | 0 | DescriptionInfoset::getDependencies() const { |
488 | 0 | if (m_element.is()) { |
489 | 0 | try { |
490 | | // check the extension denylist first and expand the dependencies if applicable |
491 | 0 | checkDenylist(); |
492 | |
|
493 | 0 | return m_xpath->selectNodeList(m_element, u"desc:dependencies/*"_ustr); |
494 | 0 | } catch (const css::xml::xpath::XPathException &) { |
495 | | // ignore |
496 | 0 | } |
497 | 0 | } |
498 | 0 | return new EmptyNodeList; |
499 | 0 | } |
500 | | |
501 | | css::uno::Sequence< OUString > |
502 | 0 | DescriptionInfoset::getUpdateInformationUrls() const { |
503 | 0 | return getUrls(u"desc:update-information/desc:src/@xlink:href"_ustr); |
504 | 0 | } |
505 | | |
506 | | css::uno::Sequence< OUString > |
507 | | DescriptionInfoset::getUpdateDownloadUrls() const |
508 | 0 | { |
509 | 0 | return getUrls(u"desc:update-download/desc:src/@xlink:href"_ustr); |
510 | 0 | } |
511 | | |
512 | | OUString DescriptionInfoset::getIconURL( bool bHighContrast ) const |
513 | 0 | { |
514 | 0 | css::uno::Sequence< OUString > aStrList = getUrls( u"desc:icon/desc:default/@xlink:href"_ustr ); |
515 | 0 | css::uno::Sequence< OUString > aStrListHC = getUrls( u"desc:icon/desc:high-contrast/@xlink:href"_ustr ); |
516 | |
|
517 | 0 | if ( bHighContrast && aStrListHC.hasElements() && !aStrListHC[0].isEmpty() ) |
518 | 0 | return aStrListHC[0]; |
519 | | |
520 | 0 | if ( aStrList.hasElements() && !aStrList[0].isEmpty() ) |
521 | 0 | return aStrList[0]; |
522 | | |
523 | 0 | return OUString(); |
524 | 0 | } |
525 | | |
526 | | ::std::optional< OUString > DescriptionInfoset::getLocalizedUpdateWebsiteURL() |
527 | | const |
528 | 0 | { |
529 | 0 | bool bParentExists = false; |
530 | 0 | const OUString sURL (getLocalizedHREFAttrFromChild(u"/desc:description/desc:update-website"_ustr, &bParentExists )); |
531 | |
|
532 | 0 | if (!sURL.isEmpty()) |
533 | 0 | return ::std::optional< OUString >(sURL); |
534 | 0 | else |
535 | 0 | return bParentExists ? ::std::optional< OUString >(OUString()) : |
536 | 0 | ::std::optional< OUString >(); |
537 | 0 | } |
538 | | |
539 | | ::std::optional< OUString > DescriptionInfoset::getOptionalValue( |
540 | | OUString const & expression) const |
541 | 0 | { |
542 | 0 | css::uno::Reference< css::xml::dom::XNode > n; |
543 | 0 | if (m_element.is()) { |
544 | 0 | try { |
545 | 0 | n = m_xpath->selectSingleNode(m_element, expression); |
546 | 0 | } catch (const css::xml::xpath::XPathException &) { |
547 | | // ignore |
548 | 0 | } |
549 | 0 | } |
550 | 0 | return n.is() |
551 | 0 | ? ::std::optional< OUString >(getNodeValue(n)) |
552 | 0 | : ::std::optional< OUString >(); |
553 | 0 | } |
554 | | |
555 | | css::uno::Sequence< OUString > DescriptionInfoset::getUrls( |
556 | | OUString const & expression) const |
557 | 0 | { |
558 | 0 | css::uno::Reference< css::xml::dom::XNodeList > ns; |
559 | 0 | if (m_element.is()) { |
560 | 0 | try { |
561 | 0 | ns = m_xpath->selectNodeList(m_element, expression); |
562 | 0 | } catch (const css::xml::xpath::XPathException &) { |
563 | | // ignore |
564 | 0 | } |
565 | 0 | } |
566 | 0 | css::uno::Sequence< OUString > urls(ns.is() ? ns->getLength() : 0); |
567 | 0 | auto urlsRange = asNonConstRange(urls); |
568 | 0 | for (::sal_Int32 i = 0; i < urls.getLength(); ++i) { |
569 | 0 | urlsRange[i] = getNodeValue(ns->item(i)); |
570 | 0 | } |
571 | 0 | return urls; |
572 | 0 | } |
573 | | |
574 | | std::pair< OUString, OUString > DescriptionInfoset::getLocalizedPublisherNameAndURL() const |
575 | 0 | { |
576 | 0 | css::uno::Reference< css::xml::dom::XNode > node = |
577 | 0 | getLocalizedChild(u"desc:publisher"_ustr); |
578 | |
|
579 | 0 | OUString sPublisherName; |
580 | 0 | OUString sURL; |
581 | 0 | if (node.is()) |
582 | 0 | { |
583 | 0 | css::uno::Reference< css::xml::dom::XNode > xPathName; |
584 | 0 | try { |
585 | 0 | xPathName = m_xpath->selectSingleNode(node, u"text()"_ustr); |
586 | 0 | } catch (const css::xml::xpath::XPathException &) { |
587 | | // ignore |
588 | 0 | } |
589 | 0 | OSL_ASSERT(xPathName.is()); |
590 | 0 | if (xPathName.is()) |
591 | 0 | sPublisherName = xPathName->getNodeValue(); |
592 | |
|
593 | 0 | css::uno::Reference< css::xml::dom::XNode > xURL; |
594 | 0 | try { |
595 | 0 | xURL = m_xpath->selectSingleNode(node, u"@xlink:href"_ustr); |
596 | 0 | } catch (const css::xml::xpath::XPathException &) { |
597 | | // ignore |
598 | 0 | } |
599 | 0 | OSL_ASSERT(xURL.is()); |
600 | 0 | if (xURL.is()) |
601 | 0 | sURL = xURL->getNodeValue(); |
602 | 0 | } |
603 | 0 | return std::make_pair(sPublisherName, sURL); |
604 | 0 | } |
605 | | |
606 | | OUString DescriptionInfoset::getLocalizedReleaseNotesURL() const |
607 | 0 | { |
608 | 0 | return getLocalizedHREFAttrFromChild(u"/desc:description/desc:release-notes"_ustr, nullptr); |
609 | 0 | } |
610 | | |
611 | | OUString DescriptionInfoset::getLocalizedDisplayName() const |
612 | 0 | { |
613 | 0 | css::uno::Reference< css::xml::dom::XNode > node = |
614 | 0 | getLocalizedChild(u"desc:display-name"_ustr); |
615 | 0 | if (node.is()) |
616 | 0 | { |
617 | 0 | css::uno::Reference< css::xml::dom::XNode > xtext; |
618 | 0 | try { |
619 | 0 | xtext = m_xpath->selectSingleNode(node, u"text()"_ustr); |
620 | 0 | } catch (const css::xml::xpath::XPathException &) { |
621 | | // ignore |
622 | 0 | } |
623 | 0 | if (xtext.is()) |
624 | 0 | return xtext->getNodeValue(); |
625 | 0 | } |
626 | 0 | return OUString(); |
627 | 0 | } |
628 | | |
629 | | OUString DescriptionInfoset::getLocalizedLicenseURL() const |
630 | 0 | { |
631 | 0 | return getLocalizedHREFAttrFromChild(u"/desc:description/desc:registration/desc:simple-license"_ustr, nullptr); |
632 | |
|
633 | 0 | } |
634 | | |
635 | | ::std::optional<SimpleLicenseAttributes> |
636 | | DescriptionInfoset::getSimpleLicenseAttributes() const |
637 | 0 | { |
638 | | //Check if the node exist |
639 | 0 | css::uno::Reference< css::xml::dom::XNode > n; |
640 | 0 | if (m_element.is()) { |
641 | 0 | try { |
642 | 0 | n = m_xpath->selectSingleNode(m_element, u"/desc:description/desc:registration/desc:simple-license/@accept-by"_ustr); |
643 | 0 | } catch (const css::xml::xpath::XPathException &) { |
644 | | // ignore |
645 | 0 | } |
646 | 0 | if (n.is()) |
647 | 0 | { |
648 | 0 | SimpleLicenseAttributes attributes; |
649 | 0 | attributes.acceptBy = |
650 | 0 | getNodeValueFromExpression(u"/desc:description/desc:registration/desc:simple-license/@accept-by"_ustr); |
651 | |
|
652 | 0 | ::std::optional< OUString > suppressOnUpdate = getOptionalValue(u"/desc:description/desc:registration/desc:simple-license/@suppress-on-update"_ustr); |
653 | 0 | if (suppressOnUpdate) |
654 | 0 | attributes.suppressOnUpdate = o3tl::equalsIgnoreAsciiCase(o3tl::trim(*suppressOnUpdate), u"true"); |
655 | 0 | else |
656 | 0 | attributes.suppressOnUpdate = false; |
657 | |
|
658 | 0 | ::std::optional< OUString > suppressIfRequired = getOptionalValue(u"/desc:description/desc:registration/desc:simple-license/@suppress-if-required"_ustr); |
659 | 0 | if (suppressIfRequired) |
660 | 0 | attributes.suppressIfRequired = o3tl::equalsIgnoreAsciiCase(o3tl::trim(*suppressIfRequired), u"true"); |
661 | 0 | else |
662 | 0 | attributes.suppressIfRequired = false; |
663 | |
|
664 | 0 | return ::std::optional<SimpleLicenseAttributes>(attributes); |
665 | 0 | } |
666 | 0 | } |
667 | 0 | return ::std::optional<SimpleLicenseAttributes>(); |
668 | 0 | } |
669 | | |
670 | | OUString DescriptionInfoset::getLocalizedDescriptionURL() const |
671 | 0 | { |
672 | 0 | return getLocalizedHREFAttrFromChild(u"/desc:description/desc:extension-description"_ustr, nullptr); |
673 | 0 | } |
674 | | |
675 | | css::uno::Reference< css::xml::dom::XNode > |
676 | | DescriptionInfoset::getLocalizedChild( const OUString & sParent) const |
677 | 0 | { |
678 | 0 | if ( ! m_element.is() || sParent.isEmpty()) |
679 | 0 | return css::uno::Reference< css::xml::dom::XNode > (); |
680 | | |
681 | 0 | css::uno::Reference< css::xml::dom::XNode > xParent; |
682 | 0 | try { |
683 | 0 | xParent = m_xpath->selectSingleNode(m_element, sParent); |
684 | 0 | } catch (const css::xml::xpath::XPathException &) { |
685 | | // ignore |
686 | 0 | } |
687 | 0 | css::uno::Reference<css::xml::dom::XNode> nodeMatch; |
688 | 0 | if (xParent.is()) |
689 | 0 | { |
690 | 0 | nodeMatch = matchLanguageTag(xParent, getOfficeLanguageTag().getBcp47()); |
691 | | |
692 | | //office: en-DE, en, en-DE-altmark |
693 | 0 | if (! nodeMatch.is()) |
694 | 0 | { |
695 | | // Already tried full tag, continue with first fallback. |
696 | 0 | const std::vector< OUString > aFallbacks( getOfficeLanguageTag().getFallbackStrings( false)); |
697 | 0 | for (auto const& fallback : aFallbacks) |
698 | 0 | { |
699 | 0 | nodeMatch = matchLanguageTag(xParent, fallback); |
700 | 0 | if (nodeMatch.is()) |
701 | 0 | break; |
702 | 0 | } |
703 | 0 | if (! nodeMatch.is()) |
704 | 0 | nodeMatch = getChildWithDefaultLocale(xParent); |
705 | 0 | } |
706 | 0 | } |
707 | |
|
708 | 0 | return nodeMatch; |
709 | 0 | } |
710 | | |
711 | | css::uno::Reference<css::xml::dom::XNode> |
712 | | DescriptionInfoset::matchLanguageTag( |
713 | | css::uno::Reference< css::xml::dom::XNode > const & xParent, std::u16string_view rTag) const |
714 | 0 | { |
715 | 0 | OSL_ASSERT(xParent.is()); |
716 | 0 | css::uno::Reference<css::xml::dom::XNode> nodeMatch; |
717 | | |
718 | | //first try exact match for lang |
719 | 0 | const OUString exp1(OUString::Concat("*[@lang=\"") + rTag + "\"]"); |
720 | 0 | try { |
721 | 0 | nodeMatch = m_xpath->selectSingleNode(xParent, exp1); |
722 | 0 | } catch (const css::xml::xpath::XPathException &) { |
723 | | // ignore |
724 | 0 | } |
725 | | |
726 | | //try to match in strings that also have a country and/or variant, for |
727 | | //example en matches in en-US-montana, en-US, en-montana |
728 | 0 | if (!nodeMatch.is()) |
729 | 0 | { |
730 | 0 | const OUString exp2( |
731 | 0 | OUString::Concat("*[starts-with(@lang,\"") + rTag + "-\")]"); |
732 | 0 | try { |
733 | 0 | nodeMatch = m_xpath->selectSingleNode(xParent, exp2); |
734 | 0 | } catch (const css::xml::xpath::XPathException &) { |
735 | | // ignore |
736 | 0 | } |
737 | 0 | } |
738 | 0 | return nodeMatch; |
739 | 0 | } |
740 | | |
741 | | css::uno::Reference<css::xml::dom::XNode> |
742 | | DescriptionInfoset::getChildWithDefaultLocale(css::uno::Reference< css::xml::dom::XNode > |
743 | | const & xParent) const |
744 | 0 | { |
745 | 0 | OSL_ASSERT(xParent.is()); |
746 | 0 | if ( xParent->getNodeName() == "simple-license" ) |
747 | 0 | { |
748 | 0 | css::uno::Reference<css::xml::dom::XNode> nodeDefault; |
749 | 0 | try { |
750 | 0 | nodeDefault = m_xpath->selectSingleNode(xParent, u"@default-license-id"_ustr); |
751 | 0 | } catch (const css::xml::xpath::XPathException &) { |
752 | | // ignore |
753 | 0 | } |
754 | 0 | if (nodeDefault.is()) |
755 | 0 | { |
756 | | //The old way |
757 | 0 | const OUString exp1("desc:license-text[@license-id = \"" |
758 | 0 | + nodeDefault->getNodeValue() |
759 | 0 | + "\"]"); |
760 | 0 | try { |
761 | 0 | return m_xpath->selectSingleNode(xParent, exp1); |
762 | 0 | } catch (const css::xml::xpath::XPathException &) { |
763 | | // ignore |
764 | 0 | } |
765 | 0 | } |
766 | 0 | } |
767 | | |
768 | 0 | try { |
769 | 0 | return m_xpath->selectSingleNode(xParent, u"*[1]"_ustr); |
770 | 0 | } catch (const css::xml::xpath::XPathException &) { |
771 | | // ignore |
772 | 0 | return nullptr; |
773 | 0 | } |
774 | 0 | } |
775 | | |
776 | | OUString DescriptionInfoset::getLocalizedHREFAttrFromChild( |
777 | | OUString const & sXPathParent, bool * out_bParentExists) |
778 | | const |
779 | 0 | { |
780 | 0 | css::uno::Reference< css::xml::dom::XNode > node = |
781 | 0 | getLocalizedChild(sXPathParent); |
782 | |
|
783 | 0 | OUString sURL; |
784 | 0 | if (node.is()) |
785 | 0 | { |
786 | 0 | if (out_bParentExists) |
787 | 0 | *out_bParentExists = true; |
788 | 0 | css::uno::Reference< css::xml::dom::XNode > xURL; |
789 | 0 | try { |
790 | 0 | xURL = m_xpath->selectSingleNode(node, u"@xlink:href"_ustr); |
791 | 0 | } catch (const css::xml::xpath::XPathException &) { |
792 | | // ignore |
793 | 0 | } |
794 | 0 | OSL_ASSERT(xURL.is()); |
795 | 0 | if (xURL.is()) |
796 | 0 | sURL = xURL->getNodeValue(); |
797 | 0 | } |
798 | 0 | else |
799 | 0 | { |
800 | 0 | if (out_bParentExists) |
801 | 0 | *out_bParentExists = false; |
802 | 0 | } |
803 | 0 | return sURL; |
804 | 0 | } |
805 | | |
806 | | } |
807 | | |
808 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |