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 
21 #include <sfx2/DocumentMetadataAccess.hxx>
22 
23 #include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
24 #include <com/sun/star/beans/XPropertySet.hpp>
25 #include <com/sun/star/embed/ElementModes.hpp>
26 #include <com/sun/star/embed/XStorage.hpp>
27 #include <com/sun/star/embed/XTransactedObject.hpp>
28 #include <com/sun/star/frame/XTransientDocumentsDocumentContentIdentifierFactory.hpp>
29 #include <com/sun/star/task/ErrorCodeIOException.hpp>
30 #include <com/sun/star/ucb/InteractiveAugmentedIOException.hpp>
31 #include <com/sun/star/rdf/FileFormat.hpp>
32 #include <com/sun/star/rdf/ParseException.hpp>
33 #include <com/sun/star/rdf/RepositoryException.hpp>
34 #include <com/sun/star/rdf/URIs.hpp>
35 #include <com/sun/star/rdf/Statement.hpp>
36 #include <com/sun/star/rdf/URI.hpp>
37 #include <com/sun/star/rdf/Repository.hpp>
38 
39 #include <rtl/ustrbuf.hxx>
40 #include <rtl/uri.hxx>
41 #include <rtl/bootstrap.hxx>
42 #include <sal/log.hxx>
43 
44 #include <comphelper/interaction.hxx>
45 #include <unotools/mediadescriptor.hxx>
46 #include <comphelper/sequence.hxx>
47 #include <comphelper/storagehelper.hxx>
48 #include <cppuhelper/exc_hlp.hxx>
49 
50 #include <sfx2/docfile.hxx>
51 #include <sfx2/XmlIdRegistry.hxx>
52 #include <sfx2/objsh.hxx>
53 #include <tools/diagnose_ex.h>
54 
55 #include <libxml/tree.h>
56 
57 #include <vector>
58 #include <set>
59 #include <string_view>
60 
61 #include <com/sun/star/uri/XUriReference.hpp>
62 #include <com/sun/star/uri/UriReferenceFactory.hpp>
63 
64 
65 /*
66  Note: in the context of this implementation, all rdf.QueryExceptions and
67  rdf.RepositoryExceptions are RuntimeExceptions, and will be reported as such.
68 
69  This implementation assumes that it is only used with ODF documents, not mere
70  ODF packages. In other words, we enforce that metadata files must not be
71  called reserved names.
72  */
73 
74 using namespace ::com::sun::star;
75 
76 namespace sfx2 {
77 
78 
isValidNCName(std::u16string_view i_rIdref)79 bool isValidNCName(std::u16string_view i_rIdref)
80 {
81     const OString id(
82         OUStringToOString(i_rIdref, RTL_TEXTENCODING_UTF8) );
83     return !(xmlValidateNCName(
84         reinterpret_cast<const unsigned char*>(id.getStr()), 0));
85 }
86 
87 
88 constexpr OUStringLiteral s_content = u"content.xml";
89 constexpr OUStringLiteral s_styles = u"styles.xml";
90 constexpr OUStringLiteral s_manifest = u"manifest.rdf";
91 const char s_odfmime [] = "application/vnd.oasis.opendocument.";
92 
93 
isContentFile(std::u16string_view i_rPath)94 static bool isContentFile(std::u16string_view i_rPath)
95 {
96     return i_rPath == s_content;
97 }
98 
isStylesFile(std::u16string_view i_rPath)99 static bool isStylesFile (std::u16string_view i_rPath)
100 {
101     return i_rPath == s_styles;
102 }
103 
isValidXmlId(std::u16string_view i_rStreamName,std::u16string_view i_rIdref)104 bool isValidXmlId(std::u16string_view i_rStreamName,
105     std::u16string_view i_rIdref)
106 {
107     return isValidNCName(i_rIdref)
108         && (isContentFile(i_rStreamName) || isStylesFile(i_rStreamName));
109 }
110 
isReservedFile(std::u16string_view i_rPath)111 static bool isReservedFile(std::u16string_view i_rPath)
112 {
113     return isContentFile(i_rPath) || isStylesFile(i_rPath) || i_rPath == u"meta.xml" || i_rPath == u"settings.xml";
114 }
115 
116 
createBaseURI(uno::Reference<uno::XComponentContext> const & i_xContext,uno::Reference<frame::XModel> const & i_xModel,OUString const & i_rPkgURI,std::u16string_view i_rSubDocument)117 uno::Reference<rdf::XURI> createBaseURI(
118     uno::Reference<uno::XComponentContext> const & i_xContext,
119     uno::Reference<frame::XModel> const & i_xModel,
120     OUString const & i_rPkgURI, std::u16string_view i_rSubDocument)
121 {
122     if (!i_xContext.is() || (!i_xModel.is() && i_rPkgURI.isEmpty())) {
123         throw uno::RuntimeException();
124     }
125 
126     OUString pkgURI(i_rPkgURI);
127 
128     // tdf#123293 chicken/egg problem when loading from stream: there is no URI,
129     // and also the model doesn't have a storage yet, so we need to get the
130     // tdoc URI without a storage...
131     if (pkgURI.isEmpty())
132     {
133         assert(i_xModel.is());
134         uno::Reference<frame::XTransientDocumentsDocumentContentIdentifierFactory>
135             const xTDDCIF(
136                     i_xContext->getServiceManager()->createInstanceWithContext(
137                         "com.sun.star.ucb.TransientDocumentsContentProvider",
138                         i_xContext),
139                 uno::UNO_QUERY_THROW);
140         uno::Reference<ucb::XContentIdentifier> const xContentId(
141             xTDDCIF->createDocumentContentIdentifier(i_xModel));
142         SAL_WARN_IF(!xContentId.is(), "sfx", "createBaseURI: cannot create ContentIdentifier");
143         if (!xContentId.is())
144         {
145             throw uno::RuntimeException("createBaseURI: cannot create ContentIdentifier");
146         }
147         pkgURI = xContentId->getContentIdentifier();
148         assert(!pkgURI.isEmpty());
149         if (!pkgURI.isEmpty() && !pkgURI.endsWith("/"))
150         {
151             pkgURI += "/";
152         }
153     }
154 
155     // #i108078# workaround non-hierarchical vnd.sun.star.expand URIs
156     // this really should be done somewhere else, not here.
157     if (pkgURI.matchIgnoreAsciiCase("vnd.sun.star.expand:"))
158     {
159         // expand it here (makeAbsolute requires hierarchical URI)
160         pkgURI = pkgURI.copy( RTL_CONSTASCII_LENGTH("vnd.sun.star.expand:") );
161         if (!pkgURI.isEmpty()) {
162             pkgURI = ::rtl::Uri::decode(
163                     pkgURI, rtl_UriDecodeStrict, RTL_TEXTENCODING_UTF8);
164             if (pkgURI.isEmpty()) {
165                 throw uno::RuntimeException();
166             }
167             ::rtl::Bootstrap::expandMacros(pkgURI);
168         }
169     }
170 
171     const uno::Reference<uri::XUriReferenceFactory> xUriFactory =
172         uri::UriReferenceFactory::create( i_xContext);
173     uno::Reference< uri::XUriReference > xBaseURI;
174 
175     const uno::Reference< uri::XUriReference > xPkgURI(
176         xUriFactory->parse(pkgURI), uno::UNO_SET_THROW );
177     xPkgURI->clearFragment();
178 
179     // need to know whether the storage is a FileSystemStorage
180     // XServiceInfo would be better, but it is not implemented
181 //    if ( pkgURI.getLength() && ::utl::UCBContentHelper::IsFolder(pkgURI) )
182     if (true) {
183         xBaseURI.set( xPkgURI, uno::UNO_SET_THROW );
184     }
185     OUStringBuffer buf(64);
186     if (!xBaseURI->getUriReference().endsWith("/"))
187     {
188         const sal_Int32 count( xBaseURI->getPathSegmentCount() );
189         if (count > 0)
190         {
191             buf.append(xBaseURI->getPathSegment(count - 1));
192         }
193         buf.append('/');
194     }
195     if (!i_rSubDocument.empty())
196     {
197         buf.append(i_rSubDocument);
198         buf.append('/');
199     }
200     if (!buf.isEmpty())
201     {
202         const uno::Reference< uri::XUriReference > xPathURI(
203             xUriFactory->parse(buf.makeStringAndClear()), uno::UNO_SET_THROW );
204         xBaseURI.set(
205             xUriFactory->makeAbsolute(xBaseURI, xPathURI,
206                 true, uri::RelativeUriExcessParentSegments_ERROR),
207             uno::UNO_SET_THROW);
208     }
209 
210     return rdf::URI::create(i_xContext, xBaseURI->getUriReference());
211 }
212 
213 
214 struct DocumentMetadataAccess_Impl
215 {
216     // note: these are all initialized in constructor, and loadFromStorage
217     const uno::Reference<uno::XComponentContext> m_xContext;
218     const SfxObjectShell & m_rXmlIdRegistrySupplier;
219     uno::Reference<rdf::XURI> m_xBaseURI;
220     uno::Reference<rdf::XRepository> m_xRepository;
221     uno::Reference<rdf::XNamedGraph> m_xManifest;
DocumentMetadataAccess_Implsfx2::DocumentMetadataAccess_Impl222     DocumentMetadataAccess_Impl(
223             uno::Reference<uno::XComponentContext> const& i_xContext,
224             SfxObjectShell const & i_rRegistrySupplier)
225       : m_xContext(i_xContext)
226       , m_rXmlIdRegistrySupplier(i_rRegistrySupplier)
227       , m_xBaseURI()
228       , m_xRepository()
229       , m_xManifest()
230     {
231         OSL_ENSURE(m_xContext.is(), "context null");
232     }
233 };
234 
235 // this is... a hack.
236 template<sal_Int16 Constant>
237 static uno::Reference<rdf::XURI> const &
getURI(uno::Reference<uno::XComponentContext> const & i_xContext)238 getURI(uno::Reference< uno::XComponentContext > const & i_xContext)
239 {
240     static uno::Reference< rdf::XURI > xURI(
241         rdf::URI::createKnown(i_xContext, Constant), uno::UNO_SET_THROW);
242     return xURI;
243 }
244 
245 
246 /** would storing the file to a XStorage succeed? */
isFileNameValid(const OUString & i_rFileName)247 static bool isFileNameValid(const OUString & i_rFileName)
248 {
249     if (i_rFileName.isEmpty()) return false;
250     if (i_rFileName[0] == '/')        return false; // no absolute paths!
251     sal_Int32 idx(0);
252     do {
253       const OUString segment(
254         i_rFileName.getToken(0, u'/', idx) );
255       if (segment.isEmpty()      ||  // no empty segments
256           segment == "."         ||  // no . segments
257           segment == ".."        ||  // no .. segments
258           !::comphelper::OStorageHelper::IsValidZipEntryFileName(
259               segment, false))      // no invalid characters
260                                       return false;
261     } while (idx >= 0);
262     return true;
263 }
264 
265 /** split a uri hierarchy into first segment and rest */
266 static bool
splitPath(OUString const & i_rPath,OUString & o_rDir,OUString & o_rRest)267 splitPath(OUString const & i_rPath,
268     OUString & o_rDir, OUString& o_rRest)
269 {
270     const sal_Int32 idx(i_rPath.indexOf(u'/'));
271     if (idx < 0 || idx >= i_rPath.getLength()) {
272         o_rDir.clear();
273         o_rRest = i_rPath;
274         return true;
275     } else if (idx == 0 || idx == i_rPath.getLength() - 1) {
276         // input must not start or end with '/'
277         return false;
278     } else {
279         o_rDir  = i_rPath.copy(0, idx);
280         o_rRest = i_rPath.copy(idx+1);
281         return true;
282     }
283 }
284 
285 static bool
splitXmlId(OUString const & i_XmlId,OUString & o_StreamName,OUString & o_Idref)286 splitXmlId(OUString const & i_XmlId,
287     OUString & o_StreamName, OUString& o_Idref )
288 {
289     const sal_Int32 idx(i_XmlId.indexOf(u'#'));
290     if ((idx <= 0) || (idx >= i_XmlId.getLength() - 1)) {
291         return false;
292     } else {
293         o_StreamName = i_XmlId.copy(0, idx);
294         o_Idref      = i_XmlId.copy(idx+1);
295         return isValidXmlId(o_StreamName, o_Idref);
296     }
297 }
298 
299 
300 static uno::Reference<rdf::XURI>
getURIForStream(struct DocumentMetadataAccess_Impl const & i_rImpl,OUString const & i_rPath)301 getURIForStream(struct DocumentMetadataAccess_Impl const & i_rImpl,
302     OUString const& i_rPath)
303 {
304     const uno::Reference<rdf::XURI> xURI(
305         rdf::URI::createNS( i_rImpl.m_xContext,
306             i_rImpl.m_xBaseURI->getStringValue(), i_rPath),
307         uno::UNO_SET_THROW);
308     return xURI;
309 }
310 
311 /** add statements declaring i_xResource to be a file of type i_xType with
312     path i_rPath to manifest, with optional additional types i_pTypes */
313 static void
addFile(struct DocumentMetadataAccess_Impl const & i_rImpl,uno::Reference<rdf::XURI> const & i_xType,OUString const & i_rPath,const uno::Sequence<uno::Reference<rdf::XURI>> * i_pTypes)314 addFile(struct DocumentMetadataAccess_Impl const & i_rImpl,
315     uno::Reference<rdf::XURI> const& i_xType,
316     OUString const & i_rPath,
317     const uno::Sequence < uno::Reference< rdf::XURI > > * i_pTypes)
318 {
319     try {
320         const uno::Reference<rdf::XURI> xURI( getURIForStream(
321             i_rImpl, i_rPath) );
322 
323         i_rImpl.m_xManifest->addStatement(i_rImpl.m_xBaseURI,
324             getURI<rdf::URIs::PKG_HASPART>(i_rImpl.m_xContext),
325             xURI);
326         i_rImpl.m_xManifest->addStatement(xURI,
327             getURI<rdf::URIs::RDF_TYPE>(i_rImpl.m_xContext),
328             i_xType);
329         if (i_pTypes) {
330             for (const auto& rType : *i_pTypes) {
331                 i_rImpl.m_xManifest->addStatement(xURI,
332                     getURI<rdf::URIs::RDF_TYPE>(i_rImpl.m_xContext),
333                     rType);
334             }
335         }
336     } catch (const uno::RuntimeException &) {
337         throw;
338     } catch (const uno::Exception &) {
339         css::uno::Any anyEx = cppu::getCaughtException();
340         throw lang::WrappedTargetRuntimeException(
341             "addFile: exception", /*this*/nullptr, anyEx);
342     }
343 }
344 
345 /** add content.xml or styles.xml to manifest */
346 static bool
addContentOrStylesFileImpl(struct DocumentMetadataAccess_Impl const & i_rImpl,const OUString & i_rPath)347 addContentOrStylesFileImpl(struct DocumentMetadataAccess_Impl const & i_rImpl,
348     const OUString & i_rPath)
349 {
350     uno::Reference<rdf::XURI> xType;
351     if (isContentFile(i_rPath)) {
352         xType.set(getURI<rdf::URIs::ODF_CONTENTFILE>(i_rImpl.m_xContext));
353     } else if (isStylesFile(i_rPath)) {
354         xType.set(getURI<rdf::URIs::ODF_STYLESFILE>(i_rImpl.m_xContext));
355     } else {
356         return false;
357     }
358     addFile(i_rImpl, xType, i_rPath, nullptr);
359     return true;
360 }
361 
362 /** add metadata file to manifest */
363 static void
addMetadataFileImpl(struct DocumentMetadataAccess_Impl const & i_rImpl,const OUString & i_rPath,const uno::Sequence<uno::Reference<rdf::XURI>> & i_rTypes)364 addMetadataFileImpl(struct DocumentMetadataAccess_Impl const & i_rImpl,
365     const OUString & i_rPath,
366     const uno::Sequence < uno::Reference< rdf::XURI > > & i_rTypes)
367 {
368     addFile(i_rImpl,
369             getURI<rdf::URIs::PKG_METADATAFILE>(i_rImpl.m_xContext),
370             i_rPath, &i_rTypes);
371 }
372 
373 /** remove a file from the manifest */
374 static void
removeFile(struct DocumentMetadataAccess_Impl const & i_rImpl,uno::Reference<rdf::XURI> const & i_xPart)375 removeFile(struct DocumentMetadataAccess_Impl const & i_rImpl,
376     uno::Reference<rdf::XURI> const& i_xPart)
377 {
378     if (!i_xPart.is()) throw uno::RuntimeException();
379     try {
380         i_rImpl.m_xManifest->removeStatements(i_rImpl.m_xBaseURI,
381             getURI<rdf::URIs::PKG_HASPART>(i_rImpl.m_xContext),
382             i_xPart);
383         i_rImpl.m_xManifest->removeStatements(i_xPart,
384             getURI<rdf::URIs::RDF_TYPE>(i_rImpl.m_xContext), nullptr);
385     } catch (const uno::RuntimeException &) {
386         throw;
387     } catch (const uno::Exception &) {
388         css::uno::Any anyEx = cppu::getCaughtException();
389         throw lang::WrappedTargetRuntimeException(
390             "removeFile: exception",
391             nullptr, anyEx);
392     }
393 }
394 
395 static ::std::vector< uno::Reference< rdf::XURI > >
getAllParts(struct DocumentMetadataAccess_Impl const & i_rImpl)396 getAllParts(struct DocumentMetadataAccess_Impl const & i_rImpl)
397 {
398     ::std::vector< uno::Reference< rdf::XURI > > ret;
399     try {
400         const uno::Reference<container::XEnumeration> xEnum(
401             i_rImpl.m_xManifest->getStatements( i_rImpl.m_xBaseURI,
402                 getURI<rdf::URIs::PKG_HASPART>(i_rImpl.m_xContext), nullptr),
403             uno::UNO_SET_THROW);
404         while (xEnum->hasMoreElements()) {
405             rdf::Statement stmt;
406             if (!(xEnum->nextElement() >>= stmt)) {
407                 throw uno::RuntimeException();
408             }
409             const uno::Reference<rdf::XURI> xPart(stmt.Object,
410                 uno::UNO_QUERY);
411             if (!xPart.is()) continue;
412             ret.push_back(xPart);
413         }
414         return ret;
415     } catch (const uno::RuntimeException &) {
416         throw;
417     } catch (const uno::Exception &) {
418         css::uno::Any anyEx = cppu::getCaughtException();
419         throw lang::WrappedTargetRuntimeException(
420             "getAllParts: exception",
421             nullptr, anyEx);
422     }
423 }
424 
425 static bool
isPartOfType(struct DocumentMetadataAccess_Impl const & i_rImpl,uno::Reference<rdf::XURI> const & i_xPart,uno::Reference<rdf::XURI> const & i_xType)426 isPartOfType(struct DocumentMetadataAccess_Impl const & i_rImpl,
427     uno::Reference<rdf::XURI> const & i_xPart,
428     uno::Reference<rdf::XURI> const & i_xType)
429 {
430     if (!i_xPart.is() || !i_xType.is()) throw uno::RuntimeException();
431     try {
432         const uno::Reference<container::XEnumeration> xEnum(
433             i_rImpl.m_xManifest->getStatements(i_xPart,
434                 getURI<rdf::URIs::RDF_TYPE>(i_rImpl.m_xContext),
435                 i_xType),
436             uno::UNO_SET_THROW);
437         return xEnum->hasMoreElements();
438     } catch (const uno::RuntimeException &) {
439         throw;
440     } catch (const uno::Exception &) {
441         css::uno::Any anyEx = cppu::getCaughtException();
442         throw lang::WrappedTargetRuntimeException(
443             "isPartOfType: exception",
444             nullptr, anyEx);
445     }
446 }
447 
448 static ::std::vector<uno::Reference<rdf::XURI>>
getAllParts(struct DocumentMetadataAccess_Impl const & i_rImpl,const uno::Reference<rdf::XURI> & i_xType)449 getAllParts(struct DocumentMetadataAccess_Impl const& i_rImpl,
450             const uno::Reference<rdf::XURI>& i_xType)
451 {
452     ::std::vector<uno::Reference<rdf::XURI>> ret;
453     try
454     {
455         const uno::Reference<container::XEnumeration> xEnum(
456             i_rImpl.m_xManifest->getStatements(i_rImpl.m_xBaseURI,
457                                                getURI<rdf::URIs::PKG_HASPART>(i_rImpl.m_xContext),
458                                                nullptr),
459             uno::UNO_SET_THROW);
460         while (xEnum->hasMoreElements())
461         {
462             rdf::Statement stmt;
463             if (!(xEnum->nextElement() >>= stmt))
464             {
465                 throw uno::RuntimeException();
466             }
467             const uno::Reference<rdf::XURI> xPart(stmt.Object, uno::UNO_QUERY);
468             if (!xPart.is())
469                 continue;
470 
471             const uno::Reference<container::XEnumeration> xEnum2(
472                 i_rImpl.m_xManifest->getStatements(
473                     xPart, getURI<rdf::URIs::RDF_TYPE>(i_rImpl.m_xContext), i_xType),
474                 uno::UNO_SET_THROW);
475             if (xEnum2->hasMoreElements())
476                 ret.emplace_back(xPart);
477         }
478         return ret;
479     }
480     catch (const uno::RuntimeException&)
481     {
482         throw;
483     }
484     catch (const uno::Exception& e)
485     {
486         throw lang::WrappedTargetRuntimeException("getAllParts: exception", nullptr,
487                                                   uno::makeAny(e));
488     }
489 }
490 
491 static ucb::InteractiveAugmentedIOException
mkException(OUString const & i_rMessage,ucb::IOErrorCode const i_ErrorCode,OUString const & i_rUri,OUString const & i_rResource)492 mkException( OUString const & i_rMessage,
493     ucb::IOErrorCode const i_ErrorCode,
494     OUString const & i_rUri, OUString const & i_rResource)
495 {
496     ucb::InteractiveAugmentedIOException iaioe;
497     iaioe.Message = i_rMessage;
498     iaioe.Classification = task::InteractionClassification_ERROR;
499     iaioe.Code = i_ErrorCode;
500 
501     const beans::PropertyValue uriProp("Uri",
502         -1, uno::makeAny(i_rUri), static_cast<beans::PropertyState>(0));
503     const beans::PropertyValue rnProp(
504         "ResourceName",
505         -1, uno::makeAny(i_rResource), static_cast<beans::PropertyState>(0));
506     iaioe.Arguments = { uno::makeAny(uriProp), uno::makeAny(rnProp) };
507     return iaioe;
508 }
509 
510 /** error handling policy.
511     <p>If a handler is given, ask it how to proceed:
512     <ul><li>(default:) cancel import, raise exception</li>
513         <li>ignore the error and continue</li>
514         <li>retry the action that led to the error</li></ul></p>
515     N.B.: must not be called before DMA is fully initialized!
516     @returns true iff caller should retry
517  */
518 static bool
handleError(ucb::InteractiveAugmentedIOException const & i_rException,const uno::Reference<task::XInteractionHandler> & i_xHandler)519 handleError( ucb::InteractiveAugmentedIOException const & i_rException,
520     const uno::Reference<task::XInteractionHandler> & i_xHandler)
521 {
522     if (!i_xHandler.is()) {
523         throw lang::WrappedTargetException(
524             "DocumentMetadataAccess::loadMetadataFromStorage: exception",
525             /* *this*/ nullptr, uno::makeAny(i_rException));
526     }
527 
528     ::rtl::Reference< ::comphelper::OInteractionRequest > pRequest(
529         new ::comphelper::OInteractionRequest(uno::makeAny(i_rException)) );
530     ::rtl::Reference< ::comphelper::OInteractionRetry > pRetry(
531         new ::comphelper::OInteractionRetry );
532     ::rtl::Reference< ::comphelper::OInteractionApprove > pApprove(
533         new ::comphelper::OInteractionApprove );
534     ::rtl::Reference< ::comphelper::OInteractionAbort > pAbort(
535         new ::comphelper::OInteractionAbort );
536 
537     pRequest->addContinuation( pApprove );
538     pRequest->addContinuation( pAbort );
539     // actually call the handler
540     i_xHandler->handle( pRequest );
541     if (pRetry->wasSelected()) {
542         return true;
543     } else if (pApprove->wasSelected()) {
544         return false;
545     } else {
546         OSL_ENSURE(pAbort->wasSelected(), "no continuation selected?");
547         throw lang::WrappedTargetException(
548             "DocumentMetadataAccess::loadMetadataFromStorage: exception",
549             /* *this*/ nullptr, uno::makeAny(i_rException));
550     }
551 }
552 
553 /** check if storage has content.xml/styles.xml;
554     e.g. ODB files seem to only have content.xml */
555 static void
collectFilesFromStorage(uno::Reference<embed::XStorage> const & i_xStorage,std::set<OUString> & o_rFiles)556 collectFilesFromStorage(uno::Reference<embed::XStorage> const& i_xStorage,
557     std::set< OUString > & o_rFiles)
558 {
559     try {
560         if (i_xStorage->hasByName(s_content) &&
561             i_xStorage->isStreamElement(s_content))
562         {
563             o_rFiles.insert(s_content);
564         }
565         if (i_xStorage->hasByName(s_styles) &&
566             i_xStorage->isStreamElement(s_styles))
567         {
568             o_rFiles.insert(s_styles);
569         }
570     } catch (const uno::Exception &) {
571         TOOLS_WARN_EXCEPTION("sfx", "collectFilesFromStorage");
572     }
573 }
574 
575 /** import a metadata file into repository */
576 static void
readStream(struct DocumentMetadataAccess_Impl & i_rImpl,uno::Reference<embed::XStorage> const & i_xStorage,OUString const & i_rPath,OUString const & i_rBaseURI)577 readStream(struct DocumentMetadataAccess_Impl & i_rImpl,
578     uno::Reference< embed::XStorage > const & i_xStorage,
579     OUString const & i_rPath,
580     OUString const & i_rBaseURI)
581 {
582     try {
583         OUString dir;
584         OUString rest;
585         if (!splitPath(i_rPath, dir, rest)) throw uno::RuntimeException();
586         if (dir.isEmpty()) {
587             if (!i_xStorage->isStreamElement(i_rPath)) {
588                 throw mkException(
589                     "readStream: is not a stream",
590                     ucb::IOErrorCode_NO_FILE, i_rBaseURI + i_rPath, i_rPath);
591             }
592             const uno::Reference<io::XStream> xStream(
593                 i_xStorage->openStreamElement(i_rPath,
594                     embed::ElementModes::READ), uno::UNO_SET_THROW);
595             const uno::Reference<io::XInputStream> xInStream(
596                 xStream->getInputStream(), uno::UNO_SET_THROW );
597             const uno::Reference<rdf::XURI> xBaseURI(
598                 rdf::URI::create(i_rImpl.m_xContext, i_rBaseURI));
599             const uno::Reference<rdf::XURI> xURI(
600                 rdf::URI::createNS(i_rImpl.m_xContext,
601                     i_rBaseURI, i_rPath));
602             i_rImpl.m_xRepository->importGraph(rdf::FileFormat::RDF_XML,
603                 xInStream, xURI, xBaseURI);
604         } else {
605             if (!i_xStorage->isStorageElement(dir)) {
606                 throw mkException(
607                     "readStream: is not a directory",
608                     ucb::IOErrorCode_NO_DIRECTORY, i_rBaseURI + dir, dir);
609             }
610             const uno::Reference<embed::XStorage> xDir(
611                 i_xStorage->openStorageElement(dir,
612                     embed::ElementModes::READ));
613             const uno::Reference< beans::XPropertySet > xDirProps(xDir,
614                 uno::UNO_QUERY_THROW);
615             try {
616                 OUString mimeType;
617                 xDirProps->getPropertyValue(
618                         utl::MediaDescriptor::PROP_MEDIATYPE() )
619                     >>= mimeType;
620                 if (mimeType.startsWith(s_odfmime)) {
621                     SAL_WARN("sfx", "readStream: refusing to recurse into embedded document");
622                     return;
623                 }
624             } catch (const uno::Exception &) { }
625             readStream(i_rImpl, xDir, rest, i_rBaseURI+dir+"/" );
626         }
627     } catch (const container::NoSuchElementException & e) {
628         throw mkException(e.Message, ucb::IOErrorCode_NOT_EXISTING_PATH,
629             i_rBaseURI + i_rPath, i_rPath);
630     } catch (const io::IOException & e) {
631         throw mkException(e.Message, ucb::IOErrorCode_CANT_READ,
632             i_rBaseURI + i_rPath, i_rPath);
633     } catch (const rdf::ParseException & e) {
634         throw mkException(e.Message, ucb::IOErrorCode_WRONG_FORMAT,
635             i_rBaseURI + i_rPath, i_rPath);
636     }
637 }
638 
639 /** import a metadata file into repository */
640 static void
importFile(struct DocumentMetadataAccess_Impl & i_rImpl,uno::Reference<embed::XStorage> const & i_xStorage,OUString const & i_rBaseURI,uno::Reference<task::XInteractionHandler> const & i_xHandler,const OUString & i_rPath)641 importFile(struct DocumentMetadataAccess_Impl & i_rImpl,
642     uno::Reference<embed::XStorage> const & i_xStorage,
643     OUString const & i_rBaseURI,
644     uno::Reference<task::XInteractionHandler> const & i_xHandler,
645     const OUString& i_rPath)
646 {
647 retry:
648     try {
649         readStream(i_rImpl, i_xStorage, i_rPath, i_rBaseURI);
650     } catch (const ucb::InteractiveAugmentedIOException & e) {
651         if (handleError(e, i_xHandler)) goto retry;
652     } catch (const uno::RuntimeException &) {
653         throw;
654     } catch (const uno::Exception &) {
655         css::uno::Any anyEx = cppu::getCaughtException();
656         throw lang::WrappedTargetRuntimeException(
657             "importFile: exception",
658             nullptr, anyEx);
659     }
660 }
661 
662 /** actually write a metadata file to the storage */
663 static void
exportStream(struct DocumentMetadataAccess_Impl const & i_rImpl,uno::Reference<embed::XStorage> const & i_xStorage,uno::Reference<rdf::XURI> const & i_xGraphName,OUString const & i_rFileName,OUString const & i_rBaseURI)664 exportStream(struct DocumentMetadataAccess_Impl const & i_rImpl,
665     uno::Reference< embed::XStorage > const & i_xStorage,
666     uno::Reference<rdf::XURI> const & i_xGraphName,
667     OUString const & i_rFileName,
668     OUString const & i_rBaseURI)
669 {
670     const uno::Reference<io::XStream> xStream(
671         i_xStorage->openStreamElement(i_rFileName,
672             embed::ElementModes::WRITE | embed::ElementModes::TRUNCATE),
673         uno::UNO_SET_THROW);
674     const uno::Reference< beans::XPropertySet > xStreamProps(xStream,
675         uno::UNO_QUERY);
676     if (xStreamProps.is()) { // this is NOT supported in FileSystemStorage
677         xStreamProps->setPropertyValue(
678             "MediaType",
679             uno::makeAny(OUString("application/rdf+xml")));
680     }
681     const uno::Reference<io::XOutputStream> xOutStream(
682         xStream->getOutputStream(), uno::UNO_SET_THROW );
683     const uno::Reference<rdf::XURI> xBaseURI(
684         rdf::URI::create(i_rImpl.m_xContext, i_rBaseURI));
685     i_rImpl.m_xRepository->exportGraph(rdf::FileFormat::RDF_XML,
686         xOutStream, i_xGraphName, xBaseURI);
687 }
688 
689 /** write a metadata file to the storage */
690 static void
writeStream(struct DocumentMetadataAccess_Impl & i_rImpl,uno::Reference<embed::XStorage> const & i_xStorage,uno::Reference<rdf::XURI> const & i_xGraphName,OUString const & i_rPath,OUString const & i_rBaseURI)691 writeStream(struct DocumentMetadataAccess_Impl & i_rImpl,
692     uno::Reference< embed::XStorage > const & i_xStorage,
693     uno::Reference<rdf::XURI> const & i_xGraphName,
694     OUString const & i_rPath,
695     OUString const & i_rBaseURI)
696 {
697     OUString dir;
698     OUString rest;
699     if (!splitPath(i_rPath, dir, rest)) throw uno::RuntimeException();
700     try {
701         if (dir.isEmpty()) {
702             exportStream(i_rImpl, i_xStorage, i_xGraphName, i_rPath,
703                 i_rBaseURI);
704         } else {
705             const uno::Reference<embed::XStorage> xDir(
706                 i_xStorage->openStorageElement(dir,
707                     embed::ElementModes::WRITE));
708             const uno::Reference< beans::XPropertySet > xDirProps(xDir,
709                 uno::UNO_QUERY_THROW);
710             try {
711                 OUString mimeType;
712                 xDirProps->getPropertyValue(
713                         utl::MediaDescriptor::PROP_MEDIATYPE() )
714                     >>= mimeType;
715                 if (mimeType.startsWith(s_odfmime)) {
716                     SAL_WARN("sfx", "writeStream: refusing to recurse into embedded document");
717                     return;
718                 }
719             } catch (const uno::Exception &) { }
720             writeStream(i_rImpl, xDir, i_xGraphName, rest, i_rBaseURI+dir+"/");
721             uno::Reference<embed::XTransactedObject> const xTransaction(
722                 xDir, uno::UNO_QUERY);
723             if (xTransaction.is()) {
724                 xTransaction->commit();
725             }
726         }
727     } catch (const uno::RuntimeException &) {
728         throw;
729     } catch (const io::IOException &) {
730         throw;
731     }
732 }
733 
734 static void
initLoading(struct DocumentMetadataAccess_Impl & i_rImpl,const uno::Reference<embed::XStorage> & i_xStorage,const uno::Reference<rdf::XURI> & i_xBaseURI,const uno::Reference<task::XInteractionHandler> & i_xHandler)735 initLoading(struct DocumentMetadataAccess_Impl & i_rImpl,
736     const uno::Reference< embed::XStorage > & i_xStorage,
737     const uno::Reference<rdf::XURI> & i_xBaseURI,
738     const uno::Reference<task::XInteractionHandler> & i_xHandler)
739 {
740 retry:
741     // clear old data
742     i_rImpl.m_xManifest.clear();
743     // init BaseURI
744     i_rImpl.m_xBaseURI = i_xBaseURI;
745 
746     // create repository
747     i_rImpl.m_xRepository.clear();
748     i_rImpl.m_xRepository.set(rdf::Repository::create(i_rImpl.m_xContext),
749             uno::UNO_SET_THROW);
750 
751     // try to delay raising errors until after initialization is done
752     uno::Any rterr;
753     ucb::InteractiveAugmentedIOException iaioe;
754     bool err(false);
755 
756     const uno::Reference <rdf::XURI> xManifest(
757         getURIForStream(i_rImpl, s_manifest));
758     try {
759         readStream(i_rImpl, i_xStorage, s_manifest, i_xBaseURI->getStringValue());
760     } catch (const ucb::InteractiveAugmentedIOException & e) {
761         // no manifest.rdf: this is not an error in ODF < 1.2
762         if (ucb::IOErrorCode_NOT_EXISTING_PATH != e.Code) {
763             iaioe = e;
764             err = true;
765         }
766     } catch (const uno::Exception & e) {
767         rterr <<= e;
768     }
769 
770     // init manifest graph
771     const uno::Reference<rdf::XNamedGraph> xManifestGraph(
772         i_rImpl.m_xRepository->getGraph(xManifest));
773     i_rImpl.m_xManifest.set(xManifestGraph.is() ? xManifestGraph :
774         i_rImpl.m_xRepository->createGraph(xManifest), uno::UNO_SET_THROW);
775 
776     // document statement
777     i_rImpl.m_xManifest->addStatement(i_rImpl.m_xBaseURI,
778         getURI<rdf::URIs::RDF_TYPE>(i_rImpl.m_xContext),
779         getURI<rdf::URIs::PKG_DOCUMENT>(i_rImpl.m_xContext));
780 
781     OSL_ENSURE(i_rImpl.m_xBaseURI.is(), "base URI is null");
782     OSL_ENSURE(i_rImpl.m_xRepository.is(), "repository is null");
783     OSL_ENSURE(i_rImpl.m_xManifest.is(), "manifest is null");
784 
785     if (rterr.hasValue()) {
786         throw lang::WrappedTargetRuntimeException(
787             "DocumentMetadataAccess::loadMetadataFromStorage: "
788             "exception", nullptr, rterr);
789     }
790 
791     if (err && handleError(iaioe, i_xHandler))
792         goto retry;
793 }
794 
795 /** init Impl struct */
init(struct DocumentMetadataAccess_Impl & i_rImpl)796 static void init(struct DocumentMetadataAccess_Impl & i_rImpl)
797 {
798     try {
799 
800         i_rImpl.m_xManifest.set(i_rImpl.m_xRepository->createGraph(
801             getURIForStream(i_rImpl, s_manifest)),
802             uno::UNO_SET_THROW);
803 
804         // insert the document statement
805         i_rImpl.m_xManifest->addStatement(i_rImpl.m_xBaseURI,
806             getURI<rdf::URIs::RDF_TYPE>(i_rImpl.m_xContext),
807             getURI<rdf::URIs::PKG_DOCUMENT>(i_rImpl.m_xContext));
808     } catch (const uno::Exception &) {
809         css::uno::Any anyEx = cppu::getCaughtException();
810         throw lang::WrappedTargetRuntimeException(
811             "init: unexpected exception", nullptr,
812             anyEx);
813     }
814 
815     // add top-level content files
816     if (!addContentOrStylesFileImpl(i_rImpl, s_content)) {
817         throw uno::RuntimeException();
818     }
819     if (!addContentOrStylesFileImpl(i_rImpl, s_styles)) {
820         throw uno::RuntimeException();
821     }
822 }
823 
824 
DocumentMetadataAccess(uno::Reference<uno::XComponentContext> const & i_xContext,const SfxObjectShell & i_rRegistrySupplier)825 DocumentMetadataAccess::DocumentMetadataAccess(
826         uno::Reference< uno::XComponentContext > const & i_xContext,
827         const SfxObjectShell & i_rRegistrySupplier)
828     : m_pImpl(new DocumentMetadataAccess_Impl(i_xContext, i_rRegistrySupplier))
829 {
830     // no initialization: must call loadFrom...
831 }
832 
DocumentMetadataAccess(uno::Reference<uno::XComponentContext> const & i_xContext,const SfxObjectShell & i_rRegistrySupplier,OUString const & i_rURI)833 DocumentMetadataAccess::DocumentMetadataAccess(
834         uno::Reference< uno::XComponentContext > const & i_xContext,
835         const SfxObjectShell & i_rRegistrySupplier,
836         OUString const & i_rURI)
837     : m_pImpl(new DocumentMetadataAccess_Impl(i_xContext, i_rRegistrySupplier))
838 {
839     OSL_ENSURE(!i_rURI.isEmpty(), "DMA::DMA: no URI given!");
840     OSL_ENSURE(i_rURI.endsWith("/"), "DMA::DMA: URI without / given!");
841     if (!i_rURI.endsWith("/")) throw uno::RuntimeException();
842     m_pImpl->m_xBaseURI.set(rdf::URI::create(m_pImpl->m_xContext, i_rURI));
843     m_pImpl->m_xRepository.set(rdf::Repository::create(m_pImpl->m_xContext),
844             uno::UNO_SET_THROW);
845 
846     // init repository
847     init(*m_pImpl);
848 
849     OSL_ENSURE(m_pImpl->m_xBaseURI.is(), "base URI is null");
850     OSL_ENSURE(m_pImpl->m_xRepository.is(), "repository is null");
851     OSL_ENSURE(m_pImpl->m_xManifest.is(), "manifest is null");
852 }
853 
~DocumentMetadataAccess()854 DocumentMetadataAccess::~DocumentMetadataAccess()
855 {
856 }
857 
858 // css::rdf::XRepositorySupplier:
859 uno::Reference< rdf::XRepository > SAL_CALL
getRDFRepository()860 DocumentMetadataAccess::getRDFRepository()
861 {
862     OSL_ENSURE(m_pImpl->m_xRepository.is(), "repository not initialized");
863     return m_pImpl->m_xRepository;
864 }
865 
866 // css::rdf::XNode:
867 OUString SAL_CALL
getStringValue()868 DocumentMetadataAccess::getStringValue()
869 {
870     return m_pImpl->m_xBaseURI->getStringValue();
871 }
872 
873 // css::rdf::XURI:
874 OUString SAL_CALL
getNamespace()875 DocumentMetadataAccess::getNamespace()
876 {
877     return m_pImpl->m_xBaseURI->getNamespace();
878 }
879 
880 OUString SAL_CALL
getLocalName()881 DocumentMetadataAccess::getLocalName()
882 {
883     return m_pImpl->m_xBaseURI->getLocalName();
884 }
885 
886 // css::rdf::XDocumentMetadataAccess:
887 uno::Reference< rdf::XMetadatable > SAL_CALL
getElementByMetadataReference(const css::beans::StringPair & i_rReference)888 DocumentMetadataAccess::getElementByMetadataReference(
889     const css::beans::StringPair & i_rReference)
890 {
891     const IXmlIdRegistry * pReg(
892         m_pImpl->m_rXmlIdRegistrySupplier.GetXmlIdRegistry() );
893     if (!pReg) {
894         throw uno::RuntimeException(
895             "DocumentMetadataAccess::getElementByXmlId: no registry", *this);
896     }
897     return pReg->GetElementByMetadataReference(i_rReference);
898 }
899 
900 uno::Reference< rdf::XMetadatable > SAL_CALL
getElementByURI(const uno::Reference<rdf::XURI> & i_xURI)901 DocumentMetadataAccess::getElementByURI(
902     const uno::Reference< rdf::XURI > & i_xURI )
903 {
904     if (!i_xURI.is()) {
905         throw lang::IllegalArgumentException(
906             "DocumentMetadataAccess::getElementByURI: URI is null", *this, 0);
907     }
908 
909     const OUString baseURI( m_pImpl->m_xBaseURI->getStringValue() );
910     const OUString name( i_xURI->getStringValue() );
911     if (!name.match(baseURI)) {
912         return nullptr;
913     }
914     OUString path;
915     OUString idref;
916     if (!splitXmlId(name.copy(baseURI.getLength()), path, idref)) {
917         return nullptr;
918     }
919 
920     return getElementByMetadataReference( beans::StringPair(path, idref) );
921 }
922 
923 uno::Sequence<uno::Reference<rdf::XURI>> SAL_CALL
getMetadataGraphsWithType(const uno::Reference<rdf::XURI> & i_xType)924 DocumentMetadataAccess::getMetadataGraphsWithType(const uno::Reference<rdf::XURI>& i_xType)
925 {
926     if (!i_xType.is())
927     {
928         throw lang::IllegalArgumentException("DocumentMetadataAccess::getMetadataGraphsWithType: "
929                                              "type is null",
930                                              *this, 0);
931     }
932 
933     return ::comphelper::containerToSequence(getAllParts(*m_pImpl, i_xType));
934 }
935 
936 uno::Reference<rdf::XURI> SAL_CALL
addMetadataFile(const OUString & i_rFileName,const uno::Sequence<uno::Reference<rdf::XURI>> & i_rTypes)937 DocumentMetadataAccess::addMetadataFile(const OUString & i_rFileName,
938     const uno::Sequence < uno::Reference< rdf::XURI > > & i_rTypes)
939 {
940     if (!isFileNameValid(i_rFileName)) {
941         throw lang::IllegalArgumentException(
942             "DocumentMetadataAccess::addMetadataFile: invalid FileName",
943             *this, 0);
944     }
945     if (isReservedFile(i_rFileName)) {
946         throw lang::IllegalArgumentException(
947             "DocumentMetadataAccess::addMetadataFile:"
948             "invalid FileName: reserved", *this, 0);
949     }
950     if (std::any_of(i_rTypes.begin(), i_rTypes.end(),
951             [](const uno::Reference< rdf::XURI >& rType) { return !rType.is(); })) {
952         throw lang::IllegalArgumentException(
953                 "DocumentMetadataAccess::addMetadataFile: "
954                 "null type", *this, 2);
955     }
956 
957     const uno::Reference<rdf::XURI> xGraphName(
958         getURIForStream(*m_pImpl, i_rFileName) );
959 
960     try {
961         m_pImpl->m_xRepository->createGraph(xGraphName);
962     } catch (const rdf::RepositoryException &) {
963         css::uno::Any anyEx = cppu::getCaughtException();
964         throw lang::WrappedTargetRuntimeException(
965             "DocumentMetadataAccess::addMetadataFile: exception",
966             *this, anyEx);
967         // note: all other exceptions are propagated
968     }
969 
970     addMetadataFileImpl(*m_pImpl, i_rFileName, i_rTypes);
971     return xGraphName;
972 }
973 
974 uno::Reference<rdf::XURI> SAL_CALL
importMetadataFile(::sal_Int16 i_Format,const uno::Reference<io::XInputStream> & i_xInStream,const OUString & i_rFileName,const uno::Reference<rdf::XURI> & i_xBaseURI,const uno::Sequence<uno::Reference<rdf::XURI>> & i_rTypes)975 DocumentMetadataAccess::importMetadataFile(::sal_Int16 i_Format,
976     const uno::Reference< io::XInputStream > & i_xInStream,
977     const OUString & i_rFileName,
978     const uno::Reference< rdf::XURI > & i_xBaseURI,
979     const uno::Sequence < uno::Reference< rdf::XURI > > & i_rTypes)
980 {
981     if (!isFileNameValid(i_rFileName)) {
982         throw lang::IllegalArgumentException(
983             "DocumentMetadataAccess::importMetadataFile: invalid FileName",
984             *this, 0);
985     }
986     if (isReservedFile(i_rFileName)) {
987         throw lang::IllegalArgumentException(
988             "DocumentMetadataAccess::importMetadataFile:"
989             "invalid FileName: reserved", *this, 0);
990     }
991     if (std::any_of(i_rTypes.begin(), i_rTypes.end(),
992             [](const uno::Reference< rdf::XURI >& rType) { return !rType.is(); })) {
993         throw lang::IllegalArgumentException(
994             "DocumentMetadataAccess::importMetadataFile: null type",
995             *this, 5);
996     }
997 
998     const uno::Reference<rdf::XURI> xGraphName(
999         getURIForStream(*m_pImpl, i_rFileName) );
1000 
1001     try {
1002         m_pImpl->m_xRepository->importGraph(
1003             i_Format, i_xInStream, xGraphName, i_xBaseURI);
1004     } catch (const rdf::RepositoryException &) {
1005         css::uno::Any anyEx = cppu::getCaughtException();
1006         throw lang::WrappedTargetRuntimeException(
1007                 "DocumentMetadataAccess::importMetadataFile: "
1008                 "RepositoryException", *this, anyEx);
1009         // note: all other exceptions are propagated
1010     }
1011 
1012     // add to manifest
1013     addMetadataFileImpl(*m_pImpl, i_rFileName, i_rTypes);
1014     return xGraphName;
1015 }
1016 
1017 void SAL_CALL
removeMetadataFile(const uno::Reference<rdf::XURI> & i_xGraphName)1018 DocumentMetadataAccess::removeMetadataFile(
1019     const uno::Reference< rdf::XURI > & i_xGraphName)
1020 {
1021     try {
1022         m_pImpl->m_xRepository->destroyGraph(i_xGraphName);
1023     } catch (const rdf::RepositoryException &) {
1024         css::uno::Any anyEx = cppu::getCaughtException();
1025         throw lang::WrappedTargetRuntimeException(
1026                 "DocumentMetadataAccess::removeMetadataFile: "
1027                 "RepositoryException", *this, anyEx);
1028         // note: all other exceptions are propagated
1029     }
1030 
1031     // remove file from manifest
1032     removeFile(*m_pImpl, i_xGraphName);
1033 }
1034 
1035 void SAL_CALL
addContentOrStylesFile(const OUString & i_rFileName)1036 DocumentMetadataAccess::addContentOrStylesFile(
1037     const OUString & i_rFileName)
1038 {
1039     if (!isFileNameValid(i_rFileName)) {
1040         throw lang::IllegalArgumentException(
1041             "DocumentMetadataAccess::addContentOrStylesFile: "
1042             "invalid FileName", *this, 0);
1043     }
1044 
1045     if (!addContentOrStylesFileImpl(*m_pImpl, i_rFileName)) {
1046         throw lang::IllegalArgumentException(
1047             "DocumentMetadataAccess::addContentOrStylesFile: "
1048             "invalid FileName: must end with content.xml or styles.xml",
1049             *this, 0);
1050     }
1051 }
1052 
1053 void SAL_CALL
removeContentOrStylesFile(const OUString & i_rFileName)1054 DocumentMetadataAccess::removeContentOrStylesFile(
1055     const OUString & i_rFileName)
1056 {
1057     if (!isFileNameValid(i_rFileName)) {
1058         throw lang::IllegalArgumentException(
1059             "DocumentMetadataAccess::removeContentOrStylesFile: "
1060             "invalid FileName", *this, 0);
1061     }
1062 
1063     try {
1064         const uno::Reference<rdf::XURI> xPart(
1065             getURIForStream(*m_pImpl, i_rFileName) );
1066         const uno::Reference<container::XEnumeration> xEnum(
1067             m_pImpl->m_xManifest->getStatements( m_pImpl->m_xBaseURI,
1068                 getURI<rdf::URIs::PKG_HASPART>(m_pImpl->m_xContext),
1069                 xPart),
1070             uno::UNO_SET_THROW);
1071         if (!xEnum->hasMoreElements()) {
1072             throw container::NoSuchElementException(
1073                 "DocumentMetadataAccess::removeContentOrStylesFile: "
1074                 "cannot find stream in manifest graph: " + i_rFileName,
1075                 *this);
1076         }
1077 
1078         // remove file from manifest
1079         removeFile(*m_pImpl, xPart);
1080 
1081     } catch (const uno::RuntimeException &) {
1082         throw;
1083     } catch (const uno::Exception &) {
1084         css::uno::Any anyEx = cppu::getCaughtException();
1085         throw lang::WrappedTargetRuntimeException(
1086             "DocumentMetadataAccess::removeContentOrStylesFile: exception",
1087             *this, anyEx);
1088     }
1089 }
1090 
loadMetadataFromStorage(const uno::Reference<embed::XStorage> & i_xStorage,const uno::Reference<rdf::XURI> & i_xBaseURI,const uno::Reference<task::XInteractionHandler> & i_xHandler)1091 void SAL_CALL DocumentMetadataAccess::loadMetadataFromStorage(
1092     const uno::Reference< embed::XStorage > & i_xStorage,
1093     const uno::Reference<rdf::XURI> & i_xBaseURI,
1094     const uno::Reference<task::XInteractionHandler> & i_xHandler)
1095 {
1096     if (!i_xStorage.is()) {
1097         throw lang::IllegalArgumentException(
1098             "DocumentMetadataAccess::loadMetadataFromStorage: "
1099             "storage is null", *this, 0);
1100     }
1101     if (!i_xBaseURI.is()) {
1102         throw lang::IllegalArgumentException(
1103             "DocumentMetadataAccess::loadMetadataFromStorage: "
1104             "base URI is null", *this, 1);
1105     }
1106     const OUString baseURI( i_xBaseURI->getStringValue());
1107     if (baseURI.indexOf('#') >= 0) {
1108         throw lang::IllegalArgumentException(
1109             "DocumentMetadataAccess::loadMetadataFromStorage: "
1110             "base URI not absolute", *this, 1);
1111     }
1112     if (!baseURI.endsWith("/")) {
1113         throw lang::IllegalArgumentException(
1114             "DocumentMetadataAccess::loadMetadataFromStorage: "
1115             "base URI does not end with slash", *this, 1);
1116     }
1117 
1118     initLoading(*m_pImpl, i_xStorage, i_xBaseURI, i_xHandler);
1119 
1120     std::set< OUString > StgFiles;
1121     collectFilesFromStorage(i_xStorage, StgFiles);
1122 
1123     std::vector< OUString > MfstMetadataFiles;
1124 
1125     try {
1126         const ::std::vector< uno::Reference< rdf::XURI > > parts(
1127             getAllParts(*m_pImpl) );
1128         const uno::Reference<rdf::XURI>& xContentFile(
1129             getURI<rdf::URIs::ODF_CONTENTFILE>(m_pImpl->m_xContext));
1130         const uno::Reference<rdf::XURI>& xStylesFile(
1131             getURI<rdf::URIs::ODF_STYLESFILE>(m_pImpl->m_xContext));
1132         const uno::Reference<rdf::XURI>& xMetadataFile(
1133             getURI<rdf::URIs::PKG_METADATAFILE>(m_pImpl->m_xContext));
1134         const sal_Int32 len( baseURI.getLength() );
1135         for (const auto& rxPart : parts) {
1136             const OUString name(rxPart->getStringValue());
1137             if (!name.match(baseURI)) {
1138                 SAL_WARN("sfx", "loadMetadataFromStorage: graph not in document: " << name);
1139                 continue;
1140             }
1141             const OUString relName( name.copy(len) );
1142             if (relName == s_manifest) {
1143                 SAL_WARN("sfx", "loadMetadataFromStorage: found ourselves a recursive manifest!");
1144                 continue;
1145             }
1146             // remove found items from StgFiles
1147             StgFiles.erase(relName);
1148             if (isContentFile(relName)) {
1149                 if (!isPartOfType(*m_pImpl, rxPart, xContentFile)) {
1150                     const uno::Reference <rdf::XURI> xName(
1151                         getURIForStream(*m_pImpl, relName) );
1152                     // add missing type statement
1153                     m_pImpl->m_xManifest->addStatement(xName,
1154                         getURI<rdf::URIs::RDF_TYPE>(m_pImpl->m_xContext),
1155                         xContentFile);
1156                 }
1157             } else if (isStylesFile(relName)) {
1158                 if (!isPartOfType(*m_pImpl, rxPart, xStylesFile)) {
1159                     const uno::Reference <rdf::XURI> xName(
1160                         getURIForStream(*m_pImpl, relName) );
1161                     // add missing type statement
1162                     m_pImpl->m_xManifest->addStatement(xName,
1163                         getURI<rdf::URIs::RDF_TYPE>(m_pImpl->m_xContext),
1164                         xStylesFile);
1165                 }
1166             } else if (isReservedFile(relName)) {
1167                 SAL_WARN("sfx", "loadMetadataFromStorage: reserved file name in manifest");
1168             } else {
1169                 if (isPartOfType(*m_pImpl, rxPart, xMetadataFile)) {
1170                     MfstMetadataFiles.push_back(relName);
1171                 }
1172                 // do not add statement for MetadataFile; it could be
1173                 // something else! just ignore it...
1174             }
1175         }
1176     } catch (const uno::RuntimeException &) {
1177         throw;
1178     } catch (const uno::Exception &) {
1179         css::uno::Any anyEx = cppu::getCaughtException();
1180         throw lang::WrappedTargetRuntimeException(
1181                 "DocumentMetadataAccess::loadMetadataFromStorage: "
1182                 "exception", *this, anyEx);
1183     }
1184 
1185     for (const auto& aStgFile : StgFiles)
1186         addContentOrStylesFileImpl(*m_pImpl, aStgFile);
1187 
1188     for (const auto& aMfstMetadataFile : MfstMetadataFiles)
1189         importFile(*m_pImpl, i_xStorage, baseURI, i_xHandler, aMfstMetadataFile);
1190 }
1191 
storeMetadataToStorage(const uno::Reference<embed::XStorage> & i_xStorage)1192 void SAL_CALL DocumentMetadataAccess::storeMetadataToStorage(
1193     const uno::Reference< embed::XStorage > & i_xStorage)
1194 {
1195     if (!i_xStorage.is()) {
1196         throw lang::IllegalArgumentException(
1197             "DocumentMetadataAccess::storeMetadataToStorage: "
1198             "storage is null", *this, 0);
1199     }
1200 
1201     // export manifest
1202     const uno::Reference <rdf::XURI> xManifest(
1203         getURIForStream(*m_pImpl, s_manifest) );
1204     const OUString baseURI( m_pImpl->m_xBaseURI->getStringValue() );
1205     try {
1206         writeStream(*m_pImpl, i_xStorage, xManifest, s_manifest, baseURI);
1207     } catch (const uno::RuntimeException &) {
1208         throw;
1209     } catch (const io::IOException &) {
1210         css::uno::Any anyEx = cppu::getCaughtException();
1211         throw lang::WrappedTargetException(
1212             "storeMetadataToStorage: IO exception", *this, anyEx);
1213     } catch (const uno::Exception &) {
1214         css::uno::Any anyEx = cppu::getCaughtException();
1215         throw lang::WrappedTargetRuntimeException(
1216                 "storeMetadataToStorage: exception", *this, anyEx);
1217     }
1218 
1219     // export metadata streams
1220     try {
1221         const uno::Sequence<uno::Reference<rdf::XURI> > graphs(
1222             m_pImpl->m_xRepository->getGraphNames());
1223         const sal_Int32 len( baseURI.getLength() );
1224         for (const uno::Reference<rdf::XURI>& xName : graphs) {
1225             const OUString name(xName->getStringValue());
1226             if (!name.match(baseURI)) {
1227                 SAL_WARN("sfx", "storeMetadataToStorage: graph not in document: " << name);
1228                 continue;
1229             }
1230             const OUString relName( name.copy(len) );
1231             if (relName == s_manifest) {
1232                 continue;
1233             }
1234             if (!isFileNameValid(relName) || isReservedFile(relName)) {
1235                 SAL_WARN("sfx", "storeMetadataToStorage: invalid file name: " << relName);
1236                 continue;
1237             }
1238             try {
1239                 writeStream(*m_pImpl, i_xStorage, xName, relName, baseURI);
1240             } catch (const uno::RuntimeException &) {
1241                 throw;
1242             } catch (const io::IOException &) {
1243                 css::uno::Any anyEx = cppu::getCaughtException();
1244                 throw lang::WrappedTargetException(
1245                     "storeMetadataToStorage: IO exception",
1246                     *this, anyEx);
1247             } catch (const uno::Exception &) {
1248                 css::uno::Any anyEx = cppu::getCaughtException();
1249                 throw lang::WrappedTargetRuntimeException(
1250                     "storeMetadataToStorage: exception",
1251                     *this, anyEx);
1252             }
1253         }
1254     } catch (const rdf::RepositoryException &) {
1255         css::uno::Any anyEx = cppu::getCaughtException();
1256         throw lang::WrappedTargetRuntimeException(
1257                 "storeMetadataToStorage: exception", *this, anyEx);
1258     }
1259 }
1260 
1261 void SAL_CALL
loadMetadataFromMedium(const uno::Sequence<beans::PropertyValue> & i_rMedium)1262 DocumentMetadataAccess::loadMetadataFromMedium(
1263     const uno::Sequence< beans::PropertyValue > & i_rMedium)
1264 {
1265     uno::Reference<io::XInputStream> xIn;
1266     utl::MediaDescriptor md(i_rMedium);
1267     OUString URL;
1268     md[ utl::MediaDescriptor::PROP_URL() ] >>= URL;
1269     OUString BaseURL;
1270     md[ utl::MediaDescriptor::PROP_DOCUMENTBASEURL() ] >>= BaseURL;
1271     if (md.addInputStream()) {
1272         md[ utl::MediaDescriptor::PROP_INPUTSTREAM() ] >>= xIn;
1273     }
1274     if (!xIn.is() && URL.isEmpty()) {
1275         throw lang::IllegalArgumentException(
1276             "DocumentMetadataAccess::loadMetadataFromMedium: "
1277             "invalid medium: no URL, no input stream", *this, 0);
1278     }
1279     uno::Reference<embed::XStorage> xStorage;
1280     try {
1281         if (xIn.is()) {
1282             xStorage = ::comphelper::OStorageHelper::GetStorageFromInputStream(
1283                             xIn, m_pImpl->m_xContext);
1284         } else { // fallback to url
1285             xStorage = ::comphelper::OStorageHelper::GetStorageFromURL2(
1286                             URL, embed::ElementModes::READ, m_pImpl->m_xContext);
1287         }
1288     } catch (const uno::RuntimeException &) {
1289         throw;
1290     } catch (const io::IOException &) {
1291         throw;
1292     } catch (const uno::Exception &) {
1293         css::uno::Any anyEx = cppu::getCaughtException();
1294         throw lang::WrappedTargetException(
1295                     "DocumentMetadataAccess::loadMetadataFromMedium: "
1296                     "exception", *this, anyEx);
1297     }
1298     if (!xStorage.is()) {
1299         throw uno::RuntimeException(
1300             "DocumentMetadataAccess::loadMetadataFromMedium: "
1301             "cannot get Storage", *this);
1302     }
1303     uno::Reference<rdf::XURI> xBaseURI;
1304     try {
1305         xBaseURI = createBaseURI(m_pImpl->m_xContext, nullptr, BaseURL);
1306     } catch (const uno::Exception &) {
1307         // fall back to URL
1308         try {
1309             xBaseURI = createBaseURI(m_pImpl->m_xContext, nullptr, URL);
1310         } catch (const uno::Exception &) {
1311             OSL_FAIL("cannot create base URI");
1312         }
1313     }
1314     uno::Reference<task::XInteractionHandler> xIH;
1315     md[ utl::MediaDescriptor::PROP_INTERACTIONHANDLER() ] >>= xIH;
1316     loadMetadataFromStorage(xStorage, xBaseURI, xIH);
1317 }
1318 
1319 void SAL_CALL
storeMetadataToMedium(const uno::Sequence<beans::PropertyValue> & i_rMedium)1320 DocumentMetadataAccess::storeMetadataToMedium(
1321     const uno::Sequence< beans::PropertyValue > & i_rMedium)
1322 {
1323     utl::MediaDescriptor md(i_rMedium);
1324     OUString URL;
1325     md[ utl::MediaDescriptor::PROP_URL() ] >>= URL;
1326     if (URL.isEmpty()) {
1327         throw lang::IllegalArgumentException(
1328             "DocumentMetadataAccess::storeMetadataToMedium: "
1329             "invalid medium: no URL", *this, 0);
1330     }
1331 
1332     SfxMedium aMedium(i_rMedium);
1333     uno::Reference<embed::XStorage> xStorage(aMedium.GetOutputStorage());
1334 
1335     bool sfx(false);
1336     if (xStorage.is()) {
1337         sfx = true;
1338     } else {
1339         xStorage = ::comphelper::OStorageHelper::GetStorageFromURL2(
1340                         URL, embed::ElementModes::WRITE, m_pImpl->m_xContext);
1341     }
1342 
1343     if (!xStorage.is()) {
1344         throw uno::RuntimeException(
1345             "DocumentMetadataAccess::storeMetadataToMedium: "
1346             "cannot get Storage", *this);
1347     }
1348     // set MIME type of the storage
1349     utl::MediaDescriptor::const_iterator iter
1350         = md.find(utl::MediaDescriptor::PROP_MEDIATYPE());
1351     if (iter != md.end()) {
1352         uno::Reference< beans::XPropertySet > xProps(xStorage,
1353             uno::UNO_QUERY_THROW);
1354         try {
1355             // this is NOT supported in FileSystemStorage
1356             xProps->setPropertyValue(
1357                 utl::MediaDescriptor::PROP_MEDIATYPE(),
1358                 iter->second);
1359         } catch (const uno::Exception &) { }
1360     }
1361     storeMetadataToStorage(xStorage);
1362 
1363     if (!sfx)
1364         return;
1365 
1366     const bool bOk = aMedium.Commit();
1367     aMedium.Close();
1368     if ( !bOk ) {
1369         ErrCode nError = aMedium.GetError();
1370         if ( nError == ERRCODE_NONE ) {
1371             nError = ERRCODE_IO_GENERAL;
1372         }
1373         task::ErrorCodeIOException ex(
1374             "DocumentMetadataAccess::storeMetadataToMedium Commit failed: " + nError.toHexString(),
1375             uno::Reference< uno::XInterface >(), sal_uInt32(nError));
1376         throw lang::WrappedTargetException(OUString(), *this,
1377                 uno::makeAny(ex));
1378     }
1379 }
1380 
1381 } // namespace sfx2
1382 
1383 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1384