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