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