diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /package | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'package')
150 files changed, 39099 insertions, 0 deletions
diff --git a/package/CppunitTest_package2_test.mk b/package/CppunitTest_package2_test.mk new file mode 100644 index 0000000000..546da10ded --- /dev/null +++ b/package/CppunitTest_package2_test.mk @@ -0,0 +1,40 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +#************************************************************************* +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +#************************************************************************* + +$(eval $(call gb_CppunitTest_CppunitTest,package2_test)) + +$(eval $(call gb_CppunitTest_add_exception_objects,package2_test, \ + package/qa/cppunit/test_package \ +)) + +$(eval $(call gb_CppunitTest_use_libraries,package2_test, \ + comphelper \ + cppu \ + cppuhelper \ + sal \ + tl \ + unotest \ +)) + +$(eval $(call gb_CppunitTest_use_sdk_api,package2_test)) + +$(eval $(call gb_CppunitTest_use_components,package2_test,\ + configmgr/source/configmgr \ + package/util/package2 \ + ucb/source/core/ucb1 \ + ucb/source/ucp/file/ucpfile1 \ +)) + +$(eval $(call gb_CppunitTest_use_ure,package2_test)) + +$(eval $(call gb_CppunitTest_use_configuration,package2_test)) + +# vim: set noet sw=4 ts=4: diff --git a/package/IwyuFilter_package.yaml b/package/IwyuFilter_package.yaml new file mode 100644 index 0000000000..cd456c41cd --- /dev/null +++ b/package/IwyuFilter_package.yaml @@ -0,0 +1,24 @@ +--- +assumeFilename: package/source/zippackage/ZipPackage.cxx +excludelist: + package/source/manifest/ManifestExport.cxx: + # Actually used + - com/sun/star/beans/PropertyValue.hpp + package/source/manifest/ManifestImport.cxx: + # Actually used + - com/sun/star/xml/sax/XAttributeList.hpp + package/source/xstor/owriteablestream.cxx: + # Actually used + - com/sun/star/uno/XComponentContext.hpp + package/source/zippackage/ZipPackage.cxx: + # Actually used + - com/sun/star/beans/PropertyValue.hpp + package/source/zippackage/ZipPackageFolder.cxx: + # Actually used + - com/sun/star/beans/PropertyValue.hpp + package/source/zippackage/ZipPackageStream.cxx: + # Actually used + - com/sun/star/beans/PropertyValue.hpp + package/source/xstor/xstorage.cxx: + # Actually used + - com/sun/star/beans/PropertyValue.hpp diff --git a/package/Library_package2.mk b/package/Library_package2.mk new file mode 100644 index 0000000000..2ddbc31caf --- /dev/null +++ b/package/Library_package2.mk @@ -0,0 +1,85 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_Library_Library,package2)) + +$(eval $(call gb_Library_set_componentfile,package2,package/util/package2,services)) + +$(eval $(call gb_Library_set_include,package2,\ + $$(INCLUDE) \ + -I$(SRCDIR)/package/inc \ +)) + +$(eval $(call gb_Library_use_sdk_api,package2)) + +$(eval $(call gb_Library_add_defs,package2,\ + -DDLLIMPLEMENTATION_PACKAGE \ +)) + +$(eval $(call gb_Library_set_precompiled_header,package2,package/inc/pch/precompiled_package2)) + +$(eval $(call gb_Library_use_custom_headers,package2,\ + officecfg/registry \ +)) + +$(eval $(call gb_Library_use_libraries,package2,\ + comphelper \ + cppu \ + cppuhelper \ + sal \ + sax \ + salhelper \ + ucbhelper \ + tl \ + utl \ +)) + +$(eval $(call gb_Library_use_externals,package2,\ + boost_headers \ + argon2 \ + zlib \ +)) + +$(eval $(call gb_Library_add_exception_objects,package2,\ + package/source/manifest/ManifestExport \ + package/source/manifest/ManifestImport \ + package/source/manifest/ManifestReader \ + package/source/manifest/ManifestWriter \ + package/source/zipapi/blowfishcontext \ + package/source/zipapi/ByteChucker \ + package/source/zipapi/ByteGrabber \ + package/source/zipapi/CRC32 \ + package/source/zipapi/Deflater \ + package/source/zipapi/Inflater \ + package/source/zipapi/sha1context \ + package/source/zipapi/ThreadedDeflater \ + package/source/zipapi/XBufferedThreadedStream \ + package/source/zipapi/XUnbufferedStream \ + package/source/zipapi/ZipEnumeration \ + package/source/zipapi/ZipFile \ + package/source/zipapi/ZipOutputEntry \ + package/source/zipapi/ZipOutputStream \ + package/source/zippackage/wrapstreamforshare \ + package/source/zippackage/zipfileaccess \ + package/source/zippackage/ZipPackageBuffer \ + package/source/zippackage/ZipPackage \ + package/source/zippackage/ZipPackageEntry \ + package/source/zippackage/ZipPackageFolder \ + package/source/zippackage/ZipPackageFolderEnumeration \ + package/source/zippackage/ZipPackageSink \ + package/source/zippackage/ZipPackageStream \ +)) + +ifneq ($(SYSTEM_ZLIB),) +$(eval $(call gb_Library_add_defs,package2,\ + -DSYSTEM_ZLIB \ +)) +endif + +# vim: set noet sw=4 ts=4: diff --git a/package/Library_xstor.mk b/package/Library_xstor.mk new file mode 100644 index 0000000000..ea6b503e13 --- /dev/null +++ b/package/Library_xstor.mk @@ -0,0 +1,45 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_Library_Library,xstor)) + +$(eval $(call gb_Library_set_componentfile,xstor,package/source/xstor/xstor,services)) + +$(eval $(call gb_Library_set_include,xstor,\ + $$(INCLUDE) \ + -I$(SRCDIR)/package/inc \ +)) + +$(eval $(call gb_Library_set_precompiled_header,xstor,package/inc/pch/precompiled_xstor)) + +$(eval $(call gb_Library_use_sdk_api,xstor)) + +$(eval $(call gb_Library_use_libraries,xstor,\ + comphelper \ + cppu \ + cppuhelper \ + sal \ + salhelper \ + tl \ + utl \ +)) + +$(eval $(call gb_Library_add_exception_objects,xstor,\ + package/source/xstor/disposelistener \ + package/source/xstor/ocompinstream \ + package/source/xstor/ohierarchyholder \ + package/source/xstor/oseekinstream \ + package/source/xstor/owriteablestream \ + package/source/xstor/selfterminatefilestream \ + package/source/xstor/switchpersistencestream \ + package/source/xstor/xfactory \ + package/source/xstor/xstorage \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/package/Makefile b/package/Makefile new file mode 100644 index 0000000000..ccb1c85a04 --- /dev/null +++ b/package/Makefile @@ -0,0 +1,7 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- + +module_directory:=$(dir $(realpath $(firstword $(MAKEFILE_LIST)))) + +include $(module_directory)/../solenv/gbuild/partial_build.mk + +# vim: set noet sw=4 ts=4: diff --git a/package/Module_package.mk b/package/Module_package.mk new file mode 100644 index 0000000000..ba9c09b5a4 --- /dev/null +++ b/package/Module_package.mk @@ -0,0 +1,22 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_Module_Module,package)) + +$(eval $(call gb_Module_add_targets,package,\ + Library_package2 \ + Library_xstor \ + Package_dtd \ +)) + +$(eval $(call gb_Module_add_check_targets,package,\ + CppunitTest_package2_test \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/package/Package_dtd.mk b/package/Package_dtd.mk new file mode 100644 index 0000000000..bfed3d60b5 --- /dev/null +++ b/package/Package_dtd.mk @@ -0,0 +1,14 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_Package_Package,package_dtd,$(SRCDIR)/package/dtd)) + +$(eval $(call gb_Package_add_file,package_dtd,$(LIBO_SHARE_FOLDER)/dtd/officedocument/1_0/Manifest.dtd,Manifest.dtd)) + +# vim: set noet sw=4 ts=4: diff --git a/package/README.md b/package/README.md new file mode 100644 index 0000000000..c85ce97904 --- /dev/null +++ b/package/README.md @@ -0,0 +1,3 @@ +# ZIP Support + +Reading and writing ZIP files. diff --git a/package/dtd/Manifest.dtd b/package/dtd/Manifest.dtd new file mode 100644 index 0000000000..9e16a007a7 --- /dev/null +++ b/package/dtd/Manifest.dtd @@ -0,0 +1,48 @@ +<!-- + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + --> +<!ELEMENT manifest:manifest (manifest:file-entry+)> +<!ATTLIST manifest:manifest xmlns:manifest CDATA #FIXED "http://openoffice.org/2001/manifest"> + +<!ELEMENT manifest:file-entry (manifest:encryption-data?)> +<!-- manifest:size is usually only specified for encrypted entries --> +<!ATTLIST manifest:file-entry + manifest:full-path CDATA #REQUIRED + manifest:size CDATA #IMPLIED + manifest:media-type CDATA #REQUIRED +> + +<!ELEMENT manifest:encryption-data (manifest:algorithm,manifest:key-derivation)> +<!ATTLIST manifest:encryption-data + manifest:checksum-type CDATA #REQUIRED + manifest:checksum CDATA #REQUIRED > +<!-- algorithm-name specifies the name of the algorithm used to encrypt + the stream, for example Blowfish + manifest:initialisation-vector is stored encoded in Base64 --> +<!ELEMENT manifest:algorithm EMPTY> +<!ATTLIST manifest:algorithm + manifest:algorithm-name CDATA #REQUIRED + manifest:initialisation-vector CDATA #REQUIRED> + +<!ELEMENT manifest:key-derivation EMPTY> +<!-- manifest:key-derivation-name specifies the name of the algorithm used to derive + the key, for example PBKDF2 (see rfc 2898 ) + manifest:salt is stored encoded in Base64 --> +<!ATTLIST manifest:key-derivation + manifest:key-derivation-name CDATA #REQUIRED + manifest:salt CDATA #REQUIRED + manifest:iteration-count CDATA #REQUIRED> diff --git a/package/inc/ByteChucker.hxx b/package/inc/ByteChucker.hxx new file mode 100644 index 0000000000..f5d0202050 --- /dev/null +++ b/package/inc/ByteChucker.hxx @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_PACKAGE_INC_BYTECHUCKER_HXX +#define INCLUDED_PACKAGE_INC_BYTECHUCKER_HXX + +#include <com/sun/star/uno/Sequence.h> +#include <com/sun/star/uno/Reference.h> + +namespace com::sun::star { + namespace io { class XSeekable; class XOutputStream; } +} +class ByteChucker final +{ + css::uno::Reference < css::io::XOutputStream > xStream; + css::uno::Reference < css::io::XSeekable > xSeek; + css::uno::Sequence < sal_Int8 > a2Sequence, a4Sequence, a8Sequence; + sal_Int8 * const p2Sequence, * const p4Sequence, * const p8Sequence; + +public: + ByteChucker (css::uno::Reference<css::io::XOutputStream> const & xOstream); + ~ByteChucker(); + + /// @throws css::io::NotConnectedException + /// @throws css::io::BufferSizeExceededException + /// @throws css::io::IOException + /// @throws css::uno::RuntimeException + void WriteBytes( const css::uno::Sequence< sal_Int8 >& aData ); + + /// @throws css::io::IOException + /// @throws css::uno::RuntimeException + sal_Int64 GetPosition(); + + void WriteInt16(sal_Int16 nInt16) + { + p2Sequence[0] = static_cast< sal_Int8 >((nInt16 >> 0 ) & 0xFF); + p2Sequence[1] = static_cast< sal_Int8 >((nInt16 >> 8 ) & 0xFF); + WriteBytes( a2Sequence ); + } + + void WriteInt32(sal_Int32 nInt32) + { + p4Sequence[0] = static_cast< sal_Int8 >((nInt32 >> 0 ) & 0xFF); + p4Sequence[1] = static_cast< sal_Int8 >((nInt32 >> 8 ) & 0xFF); + p4Sequence[2] = static_cast< sal_Int8 >((nInt32 >> 16 ) & 0xFF); + p4Sequence[3] = static_cast< sal_Int8 >((nInt32 >> 24 ) & 0xFF); + WriteBytes( a4Sequence ); + } + + void WriteUInt32(sal_uInt32 nuInt32) + { + p4Sequence[0] = static_cast < sal_Int8 > ((nuInt32 >> 0 ) & 0xFF); + p4Sequence[1] = static_cast < sal_Int8 > ((nuInt32 >> 8 ) & 0xFF); + p4Sequence[2] = static_cast < sal_Int8 > ((nuInt32 >> 16 ) & 0xFF); + p4Sequence[3] = static_cast < sal_Int8 > ((nuInt32 >> 24 ) & 0xFF); + WriteBytes( a4Sequence ); + } + + void WriteUInt64(sal_uInt64 nuInt64) + { + p8Sequence[0] = static_cast<sal_Int8>((nuInt64 >> 0) & 0xFF); + p8Sequence[1] = static_cast<sal_Int8>((nuInt64 >> 8) & 0xFF); + p8Sequence[2] = static_cast<sal_Int8>((nuInt64 >> 16) & 0xFF); + p8Sequence[3] = static_cast<sal_Int8>((nuInt64 >> 24) & 0xFF); + p8Sequence[4] = static_cast<sal_Int8>((nuInt64 >> 32) & 0xFF); + p8Sequence[5] = static_cast<sal_Int8>((nuInt64 >> 40) & 0xFF); + p8Sequence[6] = static_cast<sal_Int8>((nuInt64 >> 48) & 0xFF); + p8Sequence[7] = static_cast<sal_Int8>((nuInt64 >> 56) & 0xFF); + WriteBytes( a8Sequence ); + } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/inc/ByteGrabber.hxx b/package/inc/ByteGrabber.hxx new file mode 100644 index 0000000000..ba1512cf51 --- /dev/null +++ b/package/inc/ByteGrabber.hxx @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_PACKAGE_INC_BYTEGRABBER_HXX +#define INCLUDED_PACKAGE_INC_BYTEGRABBER_HXX + +#include <com/sun/star/uno/Sequence.h> +#include <com/sun/star/uno/Reference.h> + +#include <mutex> + +namespace com::sun::star { + namespace io { class XSeekable; class XInputStream; } +} +class ByteGrabber final +{ + std::mutex m_aMutex; + + css::uno::Reference < css::io::XInputStream > xStream; + css::uno::Reference < css::io::XSeekable > xSeek; + css::uno::Sequence < sal_Int8 > aSequence; + const sal_Int8 *pSequence; + +public: + ByteGrabber (css::uno::Reference < css::io::XInputStream > const & xIstream); + ~ByteGrabber(); + + void setInputStream (const css::uno::Reference < css::io::XInputStream >& xNewStream); + // XInputStream + /// @throws css::io::NotConnectedException + /// @throws css::io::BufferSizeExceededException + /// @throws css::io::IOException + /// @throws css::uno::RuntimeException + sal_Int32 readBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ); + // XSeekable + /// @throws css::lang::IllegalArgumentException + /// @throws css::io::IOException + /// @throws css::uno::RuntimeException + void seek( sal_Int64 location ); + /// @throws css::io::IOException + /// @throws css::uno::RuntimeException + sal_Int64 getPosition( ); + /// @throws css::io::IOException + /// @throws css::uno::RuntimeException + sal_Int64 getLength( ); + + sal_uInt16 ReadUInt16(); + sal_uInt32 ReadUInt32(); + sal_Int16 ReadInt16() + { + return static_cast<sal_Int16>(ReadUInt16()); + } + sal_Int32 ReadInt32() + { + return static_cast<sal_Int32>(ReadUInt32()); + } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/inc/CRC32.hxx b/package/inc/CRC32.hxx new file mode 100644 index 0000000000..a2ca1b435e --- /dev/null +++ b/package/inc/CRC32.hxx @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_PACKAGE_INC_CRC32_HXX +#define INCLUDED_PACKAGE_INC_CRC32_HXX + +#include <com/sun/star/uno/Sequence.h> +#include <com/sun/star/uno/Reference.h> + +namespace com::sun::star { + namespace io { class XInputStream; } +} +class CRC32 final +{ + sal_uInt32 nCRC; +public: + CRC32(); + + /// @throws css::uno::RuntimeException + sal_Int64 updateStream (css::uno::Reference < css::io::XInputStream > const & xStream); + /// @throws css::uno::RuntimeException + void updateSegment(const css::uno::Sequence< sal_Int8 > &b, sal_Int32 len); + /// @throws css::uno::RuntimeException + void update(const css::uno::Sequence< sal_Int8 > &b); + /// @throws css::uno::RuntimeException + sal_Int32 getValue() const; + /// @throws css::uno::RuntimeException + void reset(); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/inc/EncryptedDataHeader.hxx b/package/inc/EncryptedDataHeader.hxx new file mode 100644 index 0000000000..2f92dea60a --- /dev/null +++ b/package/inc/EncryptedDataHeader.hxx @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_PACKAGE_INC_ENCRYPTEDDATAHEADER_HXX +#define INCLUDED_PACKAGE_INC_ENCRYPTEDDATAHEADER_HXX + +#include <sal/types.h> + +/* The structure of this header is as follows: + + Header signature 4 bytes + Version number 2 bytes + PBKDF2 Iteration count 4 bytes + Argon2 t_cost 4 bytes + Argon2 m_cost 4 bytes + Argon2 lanes 4 bytes + Size 4 bytes + EncAlgorithm 4 bytes + DigestAlgorithm 4 bytes + DerivedKeySize 4 bytes + StartKeyAlgorithm 4 bytes + Salt length 2 bytes + IV length 2 bytes + Digest length 2 bytes + MediaType length 2 bytes + Salt content X bytes + IV content X bytes + digest content X bytes + MediaType X bytes + +*/ +const sal_uInt32 n_ConstHeader = 0x05024d4dL; // "MM\002\005" +const sal_Int32 n_ConstHeaderSize + = 50; // + salt length + iv length + digest length + mediatype length +const sal_Int16 n_ConstCurrentVersion = 2; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/inc/EncryptionData.hxx b/package/inc/EncryptionData.hxx new file mode 100644 index 0000000000..b505b9c227 --- /dev/null +++ b/package/inc/EncryptionData.hxx @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_PACKAGE_INC_ENCRYPTIONDATA_HXX +#define INCLUDED_PACKAGE_INC_ENCRYPTIONDATA_HXX + +#include <com/sun/star/uno/Sequence.hxx> +#include <cppuhelper/weak.hxx> + +#include <optional> +#include <tuple> + +class BaseEncryptionData : public cppu::OWeakObject +{ +public: + css::uno::Sequence< sal_Int8 > m_aSalt; + css::uno::Sequence< sal_Int8 > m_aInitVector; + css::uno::Sequence< sal_Int8 > m_aDigest; + ::std::optional<sal_Int32> m_oPBKDFIterationCount; + ::std::optional<::std::tuple<sal_Int32, sal_Int32, sal_Int32>> m_oArgon2Args; + + BaseEncryptionData() + { + } + + BaseEncryptionData( const BaseEncryptionData& aData ) + : cppu::OWeakObject() + , m_aSalt( aData.m_aSalt ) + , m_aInitVector( aData.m_aInitVector ) + , m_aDigest( aData.m_aDigest ) + , m_oPBKDFIterationCount(aData.m_oPBKDFIterationCount) + , m_oArgon2Args(aData.m_oArgon2Args) + {} +}; + +class EncryptionData final : public BaseEncryptionData +{ +public: + css::uno::Sequence < sal_Int8 > m_aKey; + sal_Int32 m_nEncAlg; + ::std::optional<sal_Int32> m_oCheckAlg; + sal_Int32 m_nDerivedKeySize; + sal_Int32 m_nStartKeyGenID; + bool m_bTryWrongSHA1; + + EncryptionData(const BaseEncryptionData& aData, + const css::uno::Sequence<sal_Int8>& aKey, sal_Int32 const nEncAlg, + ::std::optional<sal_Int32> const oCheckAlg, + sal_Int32 const nDerivedKeySize, sal_Int32 const nStartKeyGenID, + bool const bTryWrongSHA1) + : BaseEncryptionData( aData ) + , m_aKey( aKey ) + , m_nEncAlg( nEncAlg ) + , m_oCheckAlg( oCheckAlg ) + , m_nDerivedKeySize( nDerivedKeySize ) + , m_nStartKeyGenID( nStartKeyGenID ) + , m_bTryWrongSHA1(bTryWrongSHA1) + {} + + EncryptionData( const EncryptionData& aData ) + : BaseEncryptionData( aData ) + , m_aKey( aData.m_aKey ) + , m_nEncAlg( aData.m_nEncAlg ) + , m_oCheckAlg( aData.m_oCheckAlg ) + , m_nDerivedKeySize( aData.m_nDerivedKeySize ) + , m_nStartKeyGenID( aData.m_nStartKeyGenID ) + , m_bTryWrongSHA1(aData.m_bTryWrongSHA1) + {} +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/inc/HashMaps.hxx b/package/inc/HashMaps.hxx new file mode 100644 index 0000000000..000afa9e3b --- /dev/null +++ b/package/inc/HashMaps.hxx @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_PACKAGE_INC_HASHMAPS_HXX +#define INCLUDED_PACKAGE_INC_HASHMAPS_HXX + +#include "ZipEntry.hxx" +#include <unordered_map> + +class ZipPackageFolder; +struct ZipContentInfo; + +typedef std::unordered_map < OUString, + ZipPackageFolder * > FolderHash; + +typedef std::unordered_map < OUString, + ZipEntry > EntryHash; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/inc/PackageConstants.hxx b/package/inc/PackageConstants.hxx new file mode 100644 index 0000000000..15cc4d3ee9 --- /dev/null +++ b/package/inc/PackageConstants.hxx @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_PACKAGE_INC_PACKAGECONSTANTS_HXX +#define INCLUDED_PACKAGE_INC_PACKAGECONSTANTS_HXX + +#include <sal/types.h> +#include <rtl/ustring.hxx> + +const sal_Int32 n_ConstBufferSize = 32768; + +// by calculation of the digest we read 32 bytes more ( if available ) +// it allows to ignore the padding if the stream is longer than n_ConstDigestDecrypt since we read at least two blocks more; +// if the stream is shorter or equal the padding will be done successfully +const sal_Int32 n_ConstDigestLength = 1024; +const sal_Int32 n_ConstDigestDecrypt = 1056; // 1024 + 32 + +// the constants related to the manifest.xml entries +// these primarily exist so that ManifestImport can directly write into Sequence +#define PKG_MNFST_FULLPATH 0 //FullPath (Put full-path property first for MBA) +#define PKG_MNFST_VERSION 1 //Version +#define PKG_MNFST_MEDIATYPE 2 //MediaType + +#define PKG_MNFST_INIVECTOR 3 //InitialisationVector +#define PKG_MNFST_SALT 4 //Salt +#define PKG_MNFST_ITERATION 5 //IterationCount +#define PKG_MNFST_UCOMPSIZE 6 //Size +#define PKG_MNFST_DIGEST 7 //Digest +#define PKG_MNFST_ENCALG 8 //EncryptionAlgorithm +#define PKG_MNFST_STARTALG 9 //StartKeyAlgorithm +#define PKG_MNFST_DIGESTALG 10 //DigestAlgorithm +#define PKG_MNFST_DERKEYSIZE 11 //DerivedKeySize +#define PKG_MNFST_KDF 12 // KeyDerivationFunction +#define PKG_MNFST_ARGON2ARGS 13 // Argon2 arguments +#define PKG_MNFST_KEYINFO 14 // PGP KeyInfo + +#define PKG_SIZE_NOENCR_MNFST 3 +#define PKG_SIZE_ENCR_MNFST 15 // max size + +// the properties related constants +inline constexpr OUString ENCRYPTION_KEY_PROPERTY = u"EncryptionKey"_ustr; +inline constexpr OUString STORAGE_ENCRYPTION_KEYS_PROPERTY = u"StorageEncryptionKeys"_ustr; +inline constexpr OUString ENCRYPTION_ALGORITHMS_PROPERTY = u"EncryptionAlgorithms"_ustr; +inline constexpr OUString ENCRYPTION_GPG_PROPERTIES = u"EncryptionGpGProperties"_ustr; +#define HAS_ENCRYPTED_ENTRIES_PROPERTY "HasEncryptedEntries" +#define HAS_NONENCRYPTED_ENTRIES_PROPERTY "HasNonEncryptedEntries" +#define IS_INCONSISTENT_PROPERTY "IsInconsistent" +inline constexpr OUString MEDIATYPE_FALLBACK_USED_PROPERTY = u"MediaTypeFallbackUsed"_ustr; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/inc/ThreadedDeflater.hxx b/package/inc/ThreadedDeflater.hxx new file mode 100644 index 0000000000..361129d0f9 --- /dev/null +++ b/package/inc/ThreadedDeflater.hxx @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_PACKAGE_THREADEDDEFLATER_HXX +#define INCLUDED_PACKAGE_THREADEDDEFLATER_HXX + +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <comphelper/threadpool.hxx> +#include <memory> +#include <vector> +#include <functional> + +namespace ZipUtils +{ +/// Parallel compression a stream using the libz deflate algorithm. +/// +/// Call deflateWrite() with the input stream and input/output processing functions. +/// This will use multiple threads for compression on each batch of data from the stream. +class ThreadedDeflater final +{ + class Task; + // Note: All this should be lock-less. Each task writes only to its part + // of the data. + std::vector<std::vector<sal_Int8>> outBuffers; + std::shared_ptr<comphelper::ThreadTaskTag> threadTaskTag; + css::uno::Sequence<sal_Int8> inBuffer; + css::uno::Sequence<sal_Int8> prevDataBlock; + std::function<void(const css::uno::Sequence<sal_Int8>&, sal_Int32)> maProcessOutputFunc; + sal_Int64 totalIn; + sal_Int64 totalOut; + int zlibLevel; + +public: + // Unlike with Deflater class, bNoWrap is always true. + ThreadedDeflater(sal_Int32 nSetLevel); + ~ThreadedDeflater() COVERITY_NOEXCEPT_FALSE; + void deflateWrite( + const css::uno::Reference<css::io::XInputStream>& xInStream, + std::function<void(const css::uno::Sequence<sal_Int8>&, sal_Int32)> aProcessInputFunc, + std::function<void(const css::uno::Sequence<sal_Int8>&, sal_Int32)> aProcessOutputFunc); + sal_Int64 getTotalIn() const { return totalIn; } + sal_Int64 getTotalOut() const { return totalOut; } + +private: + void processDeflatedBuffers(); + void clear(); +}; + +} // namespace + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/inc/ZipEntry.hxx b/package/inc/ZipEntry.hxx new file mode 100644 index 0000000000..db87f660d5 --- /dev/null +++ b/package/inc/ZipEntry.hxx @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_PACKAGE_INC_ZIPENTRY_HXX +#define INCLUDED_PACKAGE_INC_ZIPENTRY_HXX + +#include <rtl/ustring.hxx> + +struct ZipEntry +{ + sal_Int16 nVersion; + sal_Int16 nFlag; + sal_Int16 nMethod; + sal_Int32 nTime; + sal_Int32 nCrc; + sal_Int64 nCompressedSize; + sal_Int64 nSize; + sal_Int64 nOffset; + sal_Int16 nPathLen; + sal_Int16 nExtraLen; + OUString sPath; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/inc/ZipEnumeration.hxx b/package/inc/ZipEnumeration.hxx new file mode 100644 index 0000000000..50533cc289 --- /dev/null +++ b/package/inc/ZipEnumeration.hxx @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include "HashMaps.hxx" + +class ZipEnumeration final +{ + EntryHash& rEntryHash; + EntryHash::const_iterator aIterator; + +public: + bool hasMoreElements(); + const ZipEntry* nextElement(); + ZipEnumeration(EntryHash& rNewEntryHash); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/inc/ZipFile.hxx b/package/inc/ZipFile.hxx new file mode 100644 index 0000000000..2d42ed4031 --- /dev/null +++ b/package/inc/ZipFile.hxx @@ -0,0 +1,173 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_PACKAGE_INC_ZIPFILE_HXX +#define INCLUDED_PACKAGE_INC_ZIPFILE_HXX + +#include <com/sun/star/xml/crypto/XCipherContext.hpp> +#include <com/sun/star/xml/crypto/XDigestContext.hpp> + +#include <comphelper/refcountedmutex.hxx> +#include <package/Inflater.hxx> +#include <rtl/ref.hxx> +#include "ByteGrabber.hxx" +#include "HashMaps.hxx" +#include "EncryptionData.hxx" + +class MemoryByteGrabber; +namespace com::sun::star { + namespace uno { class XComponentContext; } + namespace ucb { class XProgressHandler; } +} +namespace rtl +{ + template < class T > class Reference; +} + +/* + * We impose arbitrary but reasonable limit on ZIP files. + */ + +#define ZIP_MAXNAMELEN 512 +#define ZIP_MAXENTRIES (0x10000 - 2) + +class ZipEnumeration; + +class ZipFile +{ + rtl::Reference<comphelper::RefCountedMutex> m_aMutexHolder; + + EntryHash aEntries; + ByteGrabber aGrabber; + ZipUtils::Inflater aInflater; + css::uno::Reference < css::io::XInputStream > xStream; + const css::uno::Reference < css::uno::XComponentContext > m_xContext; + + bool bRecoveryMode; + + // aMediaType parameter is used only for raw stream header creation + css::uno::Reference < css::io::XInputStream > createStreamForZipEntry( + const rtl::Reference<comphelper::RefCountedMutex>& aMutexHolder, + ZipEntry const & rEntry, + const ::rtl::Reference < EncryptionData > &rData, + sal_Int8 nStreamMode, + bool bDecrypt, + const bool bUseBufferedStream = true, + const OUString& aMediaType = OUString() ); + + css::uno::Reference<css::io::XInputStream> checkValidPassword( + ZipEntry const& rEntry, rtl::Reference<EncryptionData> const& rData, + rtl::Reference<comphelper::RefCountedMutex> const& rMutexHolder); + + bool checkSizeAndCRC( const ZipEntry& aEntry ); + + sal_Int32 getCRC( sal_Int64 nOffset, sal_Int64 nSize ); + + void getSizeAndCRC( sal_Int64 nOffset, sal_Int64 nCompressedSize, sal_Int64 *nSize, sal_Int32 *nCRC ); + + void readLOC( ZipEntry &rEntry ); + sal_Int32 readCEN(); + sal_Int32 findEND(); + void recover(); + static void readExtraFields(MemoryByteGrabber& aMemGrabber, sal_Int16 nExtraLen, + sal_uInt64& nSize, sal_uInt64& nCompressedSize, + sal_uInt64* nOffset); + +public: + + ZipFile( rtl::Reference<comphelper::RefCountedMutex> aMutexHolder, + css::uno::Reference < css::io::XInputStream > const &xInput, + css::uno::Reference < css::uno::XComponentContext > xContext, + bool bInitialise ); + + ZipFile( rtl::Reference<comphelper::RefCountedMutex> aMutexHolder, + css::uno::Reference < css::io::XInputStream > const &xInput, + css::uno::Reference < css::uno::XComponentContext > xContext, + bool bInitialise, + bool bForceRecover ); + + ~ZipFile(); + + EntryHash& GetEntryHash() { return aEntries; } + + void setInputStream ( const css::uno::Reference < css::io::XInputStream >& xNewStream ); + css::uno::Reference< css::io::XInputStream > getRawData( + ZipEntry& rEntry, + const ::rtl::Reference < EncryptionData > &rData, + bool bDecrypt, + const rtl::Reference<comphelper::RefCountedMutex>& aMutexHolder, + const bool bUseBufferedStream = true ); + + static css::uno::Reference< css::xml::crypto::XDigestContext > StaticGetDigestContextForChecksum( + const css::uno::Reference< css::uno::XComponentContext >& xArgContext, + const ::rtl::Reference< EncryptionData >& xEncryptionData ); + + static css::uno::Reference< css::xml::crypto::XCipherContext > StaticGetCipher( + const css::uno::Reference< css::uno::XComponentContext >& xArgContext, + const ::rtl::Reference< EncryptionData >& xEncryptionData, + bool bEncrypt ); + + static void StaticFillHeader ( const ::rtl::Reference < EncryptionData > & rData, + sal_Int64 nSize, + const OUString& aMediaType, + sal_Int8 * & pHeader ); + + static bool StaticFillData ( ::rtl::Reference < BaseEncryptionData > const & rData, + sal_Int32 &rEncAlgorithm, + sal_Int32 &rChecksumAlgorithm, + sal_Int32 &rDerivedKeySize, + sal_Int32 &rStartKeyGenID, + sal_Int32 &rSize, + OUString& aMediaType, + const css::uno::Reference < css::io::XInputStream >& rStream ); + + static css::uno::Reference< css::io::XInputStream > StaticGetDataFromRawStream( + const rtl::Reference<comphelper::RefCountedMutex>& aMutexHolder, + const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const css::uno::Reference< css::io::XInputStream >& xStream, + const ::rtl::Reference < EncryptionData > &rData ); + + static bool StaticHasValidPassword ( + const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const css::uno::Sequence< sal_Int8 > &aReadBuffer, + const ::rtl::Reference < EncryptionData > &rData ); + + css::uno::Reference< css::io::XInputStream > getInputStream( + ZipEntry& rEntry, + const ::rtl::Reference < EncryptionData > &rData, + bool bDecrypt, + const rtl::Reference<comphelper::RefCountedMutex>& aMutexHolder ); + + css::uno::Reference< css::io::XInputStream > getDataStream( + ZipEntry& rEntry, + const ::rtl::Reference < EncryptionData > &rData, + bool bDecrypt, + const rtl::Reference<comphelper::RefCountedMutex>& aMutexHolder ); + + css::uno::Reference< css::io::XInputStream > getWrappedRawStream( + ZipEntry& rEntry, + const ::rtl::Reference < EncryptionData > &rData, + const OUString& aMediaType, + const rtl::Reference<comphelper::RefCountedMutex>& aMutexHolder ); + + ZipEnumeration entries(); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/inc/ZipOutputEntry.hxx b/package/inc/ZipOutputEntry.hxx new file mode 100644 index 0000000000..94eb988d8b --- /dev/null +++ b/package/inc/ZipOutputEntry.hxx @@ -0,0 +1,161 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_PACKAGE_INC_ZIPOUTPUTENTRY_HXX +#define INCLUDED_PACKAGE_INC_ZIPOUTPUTENTRY_HXX + +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/io/XTempFile.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/xml/crypto/XCipherContext.hpp> +#include <com/sun/star/xml/crypto/XDigestContext.hpp> + +#include <package/Deflater.hxx> +#include <comphelper/threadpool.hxx> +#include <unotools/tempfile.hxx> +#include "CRC32.hxx" +#include <atomic> +#include <exception> + +struct ZipEntry; +class ZipPackageBuffer; +class ZipPackageStream; + +class ZipOutputEntryBase +{ +protected: + css::uno::Reference< css::uno::XComponentContext > m_xContext; + css::uno::Reference< css::io::XOutputStream > m_xOutStream; + + css::uno::Reference< css::xml::crypto::XCipherContext > m_xCipherContext; + css::uno::Reference< css::xml::crypto::XDigestContext > m_xDigestContext; + + CRC32 m_aCRC; + ZipEntry *m_pCurrentEntry; + sal_Int16 m_nDigested; + ZipPackageStream* m_pCurrentStream; + bool m_bEncryptCurrentEntry; + +public: + virtual ~ZipOutputEntryBase() = default; + + virtual void writeStream(const css::uno::Reference< css::io::XInputStream >& xInStream) = 0; + + ZipEntry* getZipEntry() { return m_pCurrentEntry; } + ZipPackageStream* getZipPackageStream() { return m_pCurrentStream; } + bool isEncrypt() const { return m_bEncryptCurrentEntry; } + + void closeEntry(); + +protected: + ZipOutputEntryBase( + css::uno::Reference< css::io::XOutputStream > xOutStream, + css::uno::Reference< css::uno::XComponentContext > xContext, + ZipEntry& rEntry, ZipPackageStream* pStream, bool bEncrypt, bool checkStream); + + // Inherited classes call this with deflated data buffer. + void processDeflated( const css::uno::Sequence< sal_Int8 >& deflateBuffer, sal_Int32 nLength ); + // Inherited classes call this with the input buffer. + void processInput( const css::uno::Sequence< sal_Int8 >& rBuffer ); + + virtual void finishDeflater() = 0; + virtual sal_Int64 getDeflaterTotalIn() const = 0; + virtual sal_Int64 getDeflaterTotalOut() const = 0; + virtual void deflaterReset() = 0; + virtual bool isDeflaterFinished() const = 0; +}; + +// Normal non-threaded case. +class ZipOutputEntry : public ZipOutputEntryBase +{ + css::uno::Sequence< sal_Int8 > m_aDeflateBuffer; + ZipUtils::Deflater m_aDeflater; + +public: + ZipOutputEntry( + const css::uno::Reference< css::io::XOutputStream >& rxOutStream, + const css::uno::Reference< css::uno::XComponentContext >& rxContext, + ZipEntry& rEntry, ZipPackageStream* pStream, bool bEncrypt); + void writeStream(const css::uno::Reference< css::io::XInputStream >& xInStream) override; + void write(const css::uno::Sequence< sal_Int8 >& rBuffer); + +protected: + ZipOutputEntry( + const css::uno::Reference< css::io::XOutputStream >& rxOutStream, + const css::uno::Reference< css::uno::XComponentContext >& rxContext, + ZipEntry& rEntry, ZipPackageStream* pStream, bool bEncrypt, bool checkStream); + virtual void finishDeflater() override; + virtual sal_Int64 getDeflaterTotalIn() const override; + virtual sal_Int64 getDeflaterTotalOut() const override; + virtual void deflaterReset() override; + virtual bool isDeflaterFinished() const override; + void doDeflate(); +}; + +// Class that runs the compression in a background thread. +class ZipOutputEntryInThread final : public ZipOutputEntry +{ + class Task; + rtl::Reference<utl::TempFileFastService> m_xTempFile; + std::exception_ptr m_aParallelDeflateException; + std::atomic<bool> m_bFinished; + +public: + ZipOutputEntryInThread( + const css::uno::Reference< css::uno::XComponentContext >& rxContext, + ZipEntry& rEntry, ZipPackageStream* pStream, bool bEncrypt); + std::unique_ptr<comphelper::ThreadTask> createTask( + const std::shared_ptr<comphelper::ThreadTaskTag>& pTag, + const css::uno::Reference< css::io::XInputStream >& xInStream ); + /* This block of methods is for threaded zipping, where we compress to a temp stream, whose + data is retrieved via getData */ + void createBufferFile(); + void setParallelDeflateException(const std::exception_ptr& exception) { m_aParallelDeflateException = exception; } + css::uno::Reference< css::io::XInputStream > getData() const; + const std::exception_ptr& getParallelDeflateException() const { return m_aParallelDeflateException; } + void closeBufferFile(); + void deleteBufferFile(); + bool isFinished() const { return m_bFinished; } +private: + void setFinished() { m_bFinished = true; } +}; + +// Class that synchronously runs the compression in multiple threads (using ThreadDeflater). +class ZipOutputEntryParallel final : public ZipOutputEntryBase +{ + sal_Int64 totalIn; + sal_Int64 totalOut; + bool finished; +public: + ZipOutputEntryParallel( + const css::uno::Reference< css::io::XOutputStream >& rxOutStream, + const css::uno::Reference< css::uno::XComponentContext >& rxContext, + ZipEntry& rEntry, ZipPackageStream* pStream, bool bEncrypt); + void writeStream(const css::uno::Reference< css::io::XInputStream >& xInStream) override; +private: + virtual void finishDeflater() override; + virtual sal_Int64 getDeflaterTotalIn() const override; + virtual sal_Int64 getDeflaterTotalOut() const override; + virtual void deflaterReset() override; + virtual bool isDeflaterFinished() const override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/inc/ZipOutputStream.hxx b/package/inc/ZipOutputStream.hxx new file mode 100644 index 0000000000..d92f140f70 --- /dev/null +++ b/package/inc/ZipOutputStream.hxx @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_PACKAGE_INC_ZIPOUTPUTSTREAM_HXX +#define INCLUDED_PACKAGE_INC_ZIPOUTPUTSTREAM_HXX + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/io/XOutputStream.hpp> + +#include "ByteChucker.hxx" +#include <comphelper/threadpool.hxx> + +#include <cstddef> +#include <exception> +#include <vector> + +struct ZipEntry; +class ZipOutputEntry; +class ZipOutputEntryInThread; +class ZipPackageStream; + +class ZipOutputStream +{ + css::uno::Reference< css::io::XOutputStream > m_xStream; + ::std::vector < ZipEntry * > m_aZipList; + std::shared_ptr<comphelper::ThreadTaskTag> mpThreadTaskTag; + + ByteChucker m_aChucker; + ZipEntry *m_pCurrentEntry; + std::vector< ZipOutputEntryInThread* > m_aEntries; + std::exception_ptr m_aDeflateException; + +public: + ZipOutputStream( + const css::uno::Reference< css::io::XOutputStream > &xOStream ); + ~ZipOutputStream(); + + void addDeflatingThreadTask( ZipOutputEntryInThread *pEntry, std::unique_ptr<comphelper::ThreadTask> pThreadTask ); + + /// @throws css::io::IOException + /// @throws css::uno::RuntimeException + void writeLOC( ZipEntry *pEntry, bool bEncrypt = false ); + /// @throws css::io::IOException + /// @throws css::uno::RuntimeException + void rawWrite( const css::uno::Sequence< sal_Int8 >& rBuffer ); + /// @throws css::io::IOException + /// @throws css::uno::RuntimeException + void rawCloseEntry( bool bEncrypt = false ); + + /// @throws css::io::IOException + /// @throws css::uno::RuntimeException + void finish(); + const css::uno::Reference< css::io::XOutputStream >& getStream() const; + + static sal_uInt32 getCurrentDosTime(); + static void setEntry( ZipEntry *pEntry ); + +private: + /// @throws css::io::IOException + /// @throws css::uno::RuntimeException + void writeEND(sal_uInt32 nOffset, sal_uInt32 nLength); + /// @throws css::io::IOException + /// @throws css::uno::RuntimeException + void writeCEN( const ZipEntry &rEntry ); + /// @throws css::io::IOException + /// @throws css::uno::RuntimeException + void writeDataDescriptor( const ZipEntry &rEntry ); + void writeExtraFields( const ZipEntry& rEntry ); + + // ScheduledThread handling helpers + void consumeScheduledThreadTaskEntry(std::unique_ptr<ZipOutputEntryInThread> pCandidate); + void consumeFinishedScheduledThreadTaskEntries(); + +public: + void reduceScheduledThreadTasksToGivenNumberOrLess( + std::size_t nThreadTasks); + + const std::shared_ptr<comphelper::ThreadTaskTag>& getThreadTaskTag() const { return mpThreadTaskTag; } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/inc/ZipPackage.hxx b/package/inc/ZipPackage.hxx new file mode 100644 index 0000000000..dbfbfe1bc1 --- /dev/null +++ b/package/inc/ZipPackage.hxx @@ -0,0 +1,177 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_PACKAGE_INC_ZIPPACKAGE_HXX +#define INCLUDED_PACKAGE_INC_ZIPPACKAGE_HXX + +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/util/XChangesBatch.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/xml/crypto/CipherID.hpp> +#include <comphelper/refcountedmutex.hxx> +#include <rtl/ref.hxx> +#include <o3tl/unreachable.hxx> + +#include "HashMaps.hxx" +#include "ZipFile.hxx" +#include <vector> +#include <optional> + +class SvStream; +class ZipOutputStream; +class ZipPackageFolder; +class ZipFile; +namespace com::sun::star { + namespace container { class XNameContainer; } + namespace io { class XStream; class XOutputStream; class XInputStream; class XSeekable; class XActiveDataStreamer; } + namespace lang { class XMultiServiceFactory; } + namespace uno { class XComponentContext; } + namespace task { class XInteractionHandler; } +} + +enum InitialisationMode +{ + e_IMode_None, + e_IMode_URL, + e_IMode_XInputStream, + e_IMode_XStream +}; + +class ZipPackage final : public cppu::WeakImplHelper + < + css::lang::XInitialization, + css::lang::XSingleServiceFactory, + css::lang::XServiceInfo, + css::container::XHierarchicalNameAccess, + css::util::XChangesBatch, + css::beans::XPropertySet + > +{ + rtl::Reference<comphelper::RefCountedMutex> m_aMutexHolder; + + css::uno::Sequence< css::beans::NamedValue > m_aStorageEncryptionKeys; + css::uno::Sequence< sal_Int8 > m_aEncryptionKey; + css::uno::Sequence< css::uno::Sequence< css::beans::NamedValue > > m_aGpgProps; + + FolderHash m_aRecent; + OUString m_aURL; + + sal_Int32 m_nStartKeyGenerationID; + ::std::optional<sal_Int32> m_oChecksumDigestID; + sal_Int32 m_nKeyDerivationFunctionID; + sal_Int32 m_nCommonEncryptionID; + bool m_bHasEncryptedEntries; + bool m_bHasNonEncryptedEntries; + + bool m_bInconsistent; + bool m_bForceRecovery; + + bool m_bMediaTypeFallbackUsed; + sal_Int32 m_nFormat; + bool m_bAllowRemoveOnInsert; + + InitialisationMode m_eMode; + + rtl::Reference < ZipPackageFolder > m_xRootFolder; + css::uno::Reference < css::io::XStream > m_xStream; + css::uno::Reference < css::io::XInputStream > m_xContentStream; + css::uno::Reference < css::io::XSeekable > m_xContentSeek; + const css::uno::Reference < css::uno::XComponentContext > m_xContext; + + std::optional<ZipFile> m_pZipFile; + bool m_bDisableFileSync = false; + + bool isLocalFile() const; + + void parseManifest(); + void parseContentType(); + void getZipFileContents(); + + void WriteMimetypeMagicFile( ZipOutputStream& aZipOut ); + void WriteManifest( ZipOutputStream& aZipOut, const ::std::vector< css::uno::Sequence< css::beans::PropertyValue > >& aManList ); + void WriteContentTypes( ZipOutputStream& aZipOut, const ::std::vector< css::uno::Sequence< css::beans::PropertyValue > >& aManList ); + + css::uno::Reference< css::io::XInputStream > writeTempFile(); + css::uno::Reference < css::io::XActiveDataStreamer > openOriginalForOutput(); + void DisconnectFromTargetAndThrowException_Impl( + const css::uno::Reference< css::io::XInputStream >& xTempStream ); + +public: + ZipPackage( css::uno::Reference < css::uno::XComponentContext > xContext ); + virtual ~ZipPackage() override; + ZipFile& getZipFile() { return *m_pZipFile;} + sal_Int32 getFormat() const { return m_nFormat; } + + sal_Int32 GetStartKeyGenID() const { return m_nStartKeyGenerationID; } + sal_Int32 GetEncAlgID() const { return m_nCommonEncryptionID; } + ::std::optional<sal_Int32> GetChecksumAlgID() const { return m_oChecksumDigestID; } + sal_Int32 GetDefaultDerivedKeySize() const { + switch (m_nCommonEncryptionID) + { + case css::xml::crypto::CipherID::BLOWFISH_CFB_8: + return 16; + case css::xml::crypto::CipherID::AES_CBC_W3C_PADDING: + case css::xml::crypto::CipherID::AES_GCM_W3C: + return 32; + default: + O3TL_UNREACHABLE; + } + } + + rtl::Reference<comphelper::RefCountedMutex>& GetSharedMutexRef() { return m_aMutexHolder; } + + void ConnectTo( const css::uno::Reference< css::io::XInputStream >& xInStream ); + css::uno::Sequence< sal_Int8 > GetEncryptionKey(); + + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + // XHierarchicalNameAccess + virtual css::uno::Any SAL_CALL getByHierarchicalName( const OUString& aName ) override; + virtual sal_Bool SAL_CALL hasByHierarchicalName( const OUString& aName ) override; + // XSingleServiceFactory + virtual css::uno::Reference< css::uno::XInterface > SAL_CALL createInstance( ) override; + virtual css::uno::Reference< css::uno::XInterface > SAL_CALL createInstanceWithArguments( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + // XChangesBatch + virtual void SAL_CALL commitChanges( ) override; + virtual sal_Bool SAL_CALL hasPendingChanges( ) override; + virtual css::uno::Sequence< css::util::ElementChange > SAL_CALL getPendingChanges( ) override; + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo( ) override; + virtual void SAL_CALL setPropertyValue( const OUString& aPropertyName, const css::uno::Any& aValue ) override; + virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& PropertyName ) override; + virtual void SAL_CALL addPropertyChangeListener( const OUString& aPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener ) override; + virtual void SAL_CALL removePropertyChangeListener( const OUString& aPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener >& aListener ) override; + virtual void SAL_CALL addVetoableChangeListener( const OUString& PropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override; + virtual void SAL_CALL removeVetoableChangeListener( const OUString& PropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; +}; +#endif + +extern "C" SAL_DLLPUBLIC_EXPORT bool TestImportZip(SvStream& rStream); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/inc/ZipPackageBuffer.hxx b/package/inc/ZipPackageBuffer.hxx new file mode 100644 index 0000000000..64a24563cb --- /dev/null +++ b/package/inc/ZipPackageBuffer.hxx @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_PACKAGE_INC_ZIPPACKAGEBUFFER_HXX +#define INCLUDED_PACKAGE_INC_ZIPPACKAGEBUFFER_HXX + +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/io/XSeekable.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <cppuhelper/implbase.hxx> + +class ZipPackageBuffer final : public ::cppu::WeakImplHelper +< + css::io::XInputStream, + css::io::XOutputStream, + css::io::XSeekable +> +{ + css::uno::Sequence < sal_Int8 > m_aBuffer; + sal_Int64 m_nBufferSize, m_nEnd, m_nCurrent; + bool m_bMustInitBuffer; +public: + ZipPackageBuffer(); + virtual ~ZipPackageBuffer() override; + + void realloc ( sal_Int32 nSize ) { m_aBuffer.realloc ( nSize ); } + const css::uno::Sequence < sal_Int8>& getSequence () const { return m_aBuffer; } + + // XInputStream + virtual sal_Int32 SAL_CALL readBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) override; + virtual sal_Int32 SAL_CALL readSomeBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) override; + virtual void SAL_CALL skipBytes( sal_Int32 nBytesToSkip ) override; + virtual sal_Int32 SAL_CALL available( ) override; + virtual void SAL_CALL closeInput( ) override; + // XOutputStream + virtual void SAL_CALL writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) override; + virtual void SAL_CALL flush( ) override; + virtual void SAL_CALL closeOutput( ) override; + // XSeekable + virtual void SAL_CALL seek( sal_Int64 location ) override; + virtual sal_Int64 SAL_CALL getPosition( ) override; + virtual sal_Int64 SAL_CALL getLength( ) override; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/inc/ZipPackageEntry.hxx b/package/inc/ZipPackageEntry.hxx new file mode 100644 index 0000000000..f25cdc19bd --- /dev/null +++ b/package/inc/ZipPackageEntry.hxx @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_PACKAGE_INC_ZIPPACKAGEENTRY_HXX +#define INCLUDED_PACKAGE_INC_ZIPPACKAGEENTRY_HXX + +#include <com/sun/star/container/XChild.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include "ZipEntry.hxx" +#include <cppuhelper/implbase.hxx> + +#include <vector> +#include <optional> +#include <tuple> + +typedef void* rtlRandomPool; +class ZipOutputStream; +class ZipPackageFolder; + +class ZipPackageEntry : public cppu::WeakImplHelper +< + css::container::XNamed, + css::container::XChild, + css::beans::XPropertySet, + css::lang::XServiceInfo +> +{ +protected: + css::uno::Reference< css::uno::XComponentContext > m_xContext; + OUString msName; + bool mbIsFolder:1; + bool mbAllowRemoveOnInsert:1; + OUString msMediaType; + ZipPackageFolder* mpParent; + sal_Int32 m_nFormat; + +public: + ZipEntry aEntry; + ZipPackageEntry(); + virtual ~ZipPackageEntry() override; + + const OUString& GetMediaType() const { return msMediaType; } + void SetMediaType(const OUString & sNewType) { msMediaType = sNewType; } + void doSetParent(ZipPackageFolder * pNewParent); + bool IsFolder() const { return mbIsFolder; } + void SetFolder(const bool bSetFolder) { mbIsFolder = bSetFolder; } + + virtual bool saveChild( const OUString &rPath, + std::vector < css::uno::Sequence < css::beans::PropertyValue > > &rManList, + ZipOutputStream & rZipOut, + const css::uno::Sequence < sal_Int8 >& rEncryptionKey, + ::std::optional<sal_Int32> oPBKDF2IterationCount, + ::std::optional<::std::tuple<sal_Int32, sal_Int32, sal_Int32>> oArgon2Args, + const rtlRandomPool &rRandomPool ) = 0; + + void clearParent() + { + // xParent.clear(); + mpParent = nullptr; + } + // XNamed + virtual OUString SAL_CALL getName( ) override; + virtual void SAL_CALL setName( const OUString& aName ) override; + // XChild + virtual css::uno::Reference< css::uno::XInterface > SAL_CALL getParent( ) override; + virtual void SAL_CALL setParent( const css::uno::Reference< css::uno::XInterface >& Parent ) override; + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo( ) override; + virtual void SAL_CALL setPropertyValue( const OUString& aPropertyName, const css::uno::Any& aValue ) override = 0; + virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& PropertyName ) override = 0; + virtual void SAL_CALL addPropertyChangeListener( const OUString& aPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener ) override; + virtual void SAL_CALL removePropertyChangeListener( const OUString& aPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener >& aListener ) override; + virtual void SAL_CALL addVetoableChangeListener( const OUString& PropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override; + virtual void SAL_CALL removeVetoableChangeListener( const OUString& PropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/inc/ZipPackageFolder.hxx b/package/inc/ZipPackageFolder.hxx new file mode 100644 index 0000000000..2b1b981913 --- /dev/null +++ b/package/inc/ZipPackageFolder.hxx @@ -0,0 +1,146 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_PACKAGE_INC_ZIPPACKAGEFOLDER_HXX +#define INCLUDED_PACKAGE_INC_ZIPPACKAGEFOLDER_HXX + +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/container/XEnumerationAccess.hpp> +#include <com/sun/star/beans/StringPair.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include "ZipPackageEntry.hxx" +#include <cppuhelper/implbase.hxx> +#include <rtl/ref.hxx> + +#include <string_view> +#include <unordered_map> +#include <vector> + +class ZipOutputStream; +struct ZipEntry; +class ZipPackageFolder; +class ZipPackageStream; + +struct ZipContentInfo +{ + rtl::Reference < ZipPackageEntry > xPackageEntry; + bool bFolder; + union + { + ZipPackageFolder *pFolder; + ZipPackageStream *pStream; + }; + ZipContentInfo( ZipPackageStream * pNewStream ); + ZipContentInfo( ZipPackageFolder * pNewFolder ); + ZipContentInfo( const ZipContentInfo& ); + ZipContentInfo( ZipContentInfo&& ); + ZipContentInfo& operator=( const ZipContentInfo& ); + ZipContentInfo& operator=( ZipContentInfo&& ); + + ~ZipContentInfo(); +}; + +typedef std::unordered_map < OUString, + ZipContentInfo > ContentHash; + +class ZipPackageFolder final : public cppu::ImplInheritanceHelper +< + ZipPackageEntry, + css::container::XNameContainer, + css::container::XEnumerationAccess +> +{ +private: + ContentHash maContents; + OUString m_sVersion; + +public: + + ZipPackageFolder( const css::uno::Reference < css::uno::XComponentContext >& xContext, + sal_Int32 nFormat, + bool bAllowRemoveOnInsert ); + virtual ~ZipPackageFolder() override; + + const OUString& GetVersion() const { return m_sVersion; } + void SetVersion( const OUString& aVersion ) { m_sVersion = aVersion; } + + bool LookForUnexpectedODF12Streams(std::u16string_view aPath, bool isWholesomeEncryption); + + void setChildStreamsTypeByExtension( const css::beans::StringPair& aPair ); + + /// @throws css::lang::IllegalArgumentException + /// @throws css::container::ElementExistException + /// @throws css::lang::WrappedTargetException + /// @throws css::uno::RuntimeException + void doInsertByName ( ZipPackageEntry *pEntry, bool bSetParent ); + + ZipContentInfo& doGetByName( const OUString& aName ); + + void setPackageFormat_Impl( sal_Int32 nFormat ) { m_nFormat = nFormat; } + void setRemoveOnInsertMode_Impl( bool bRemove ) { mbAllowRemoveOnInsert = bRemove; } + + virtual bool saveChild( const OUString &rPath, + std::vector < css::uno::Sequence < css::beans::PropertyValue > > &rManList, + ZipOutputStream & rZipOut, + const css::uno::Sequence < sal_Int8 >& rEncryptionKey, + ::std::optional<sal_Int32> oPBKDF2IterationCount, + ::std::optional<::std::tuple<sal_Int32, sal_Int32, sal_Int32>> oArgon2Args, + const rtlRandomPool &rRandomPool ) override; + + // Recursive functions + /// @throws css::uno::RuntimeException + void saveContents( + const OUString &rPath, + std::vector < css::uno::Sequence < css::beans::PropertyValue > > &rManList, + ZipOutputStream & rZipOut, + const css::uno::Sequence< sal_Int8 > &rEncryptionKey, + ::std::optional<sal_Int32> oPBKDF2IterationCount, + ::std::optional<::std::tuple<sal_Int32, sal_Int32, sal_Int32>> oArgon2Args, + const rtlRandomPool & rRandomPool) const; + + // XNameContainer + virtual void SAL_CALL insertByName( const OUString& aName, const css::uno::Any& aElement ) override; + virtual void SAL_CALL removeByName( const OUString& Name ) override; + + // XEnumerationAccess + virtual css::uno::Reference< css::container::XEnumeration > SAL_CALL createEnumeration( ) override; + + // XElementAccess + virtual css::uno::Type SAL_CALL getElementType( ) override; + virtual sal_Bool SAL_CALL hasElements( ) override; + + // XNameAccess + virtual css::uno::Any SAL_CALL getByName( const OUString& aName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getElementNames( ) override; + virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override; + + // XNameReplace + virtual void SAL_CALL replaceByName( const OUString& aName, const css::uno::Any& aElement ) override; + + // XPropertySet + virtual void SAL_CALL setPropertyValue( const OUString& aPropertyName, const css::uno::Any& aValue ) override; + virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& PropertyName ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/inc/ZipPackageStream.hxx b/package/inc/ZipPackageStream.hxx new file mode 100644 index 0000000000..0cb52e88c8 --- /dev/null +++ b/package/inc/ZipPackageStream.hxx @@ -0,0 +1,172 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_PACKAGE_INC_ZIPPACKAGESTREAM_HXX +#define INCLUDED_PACKAGE_INC_ZIPPACKAGESTREAM_HXX + +#include <com/sun/star/io/XActiveDataSink.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/packages/XDataSinkEncrSupport.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include "ZipPackageEntry.hxx" +#include <rtl/ref.hxx> +#include <cppuhelper/implbase.hxx> + +#include "EncryptionData.hxx" + + +#define PACKAGE_STREAM_NOTSET 0 +#define PACKAGE_STREAM_PACKAGEMEMBER 1 +#define PACKAGE_STREAM_DETECT 2 +#define PACKAGE_STREAM_DATA 3 +#define PACKAGE_STREAM_RAW 4 + +class ZipPackage; +struct ZipEntry; +class ZipPackageStream final : public cppu::ImplInheritanceHelper +< + ZipPackageEntry, + css::io::XActiveDataSink, + css::packages::XDataSinkEncrSupport +> +{ +private: + css::uno::Reference < css::io::XInputStream > m_xStream; + ZipPackage &m_rZipPackage; + bool m_bToBeCompressed, m_bToBeEncrypted, m_bHaveOwnKey, m_bIsEncrypted; + + ::rtl::Reference< BaseEncryptionData > m_xBaseEncryptionData; + css::uno::Sequence< css::beans::NamedValue > m_aStorageEncryptionKeys; + css::uno::Sequence< sal_Int8 > m_aEncryptionKey; + + sal_Int32 m_nImportedStartKeyAlgorithm; + sal_Int32 m_nImportedEncryptionAlgorithm; + ::std::optional<sal_Int32> m_oImportedChecksumAlgorithm; + sal_Int32 m_nImportedDerivedKeySize; + + sal_uInt8 m_nStreamMode; + sal_uInt32 m_nMagicalHackPos; + sal_uInt32 m_nMagicalHackSize; + sal_Int64 m_nOwnStreamOrigSize; + + bool m_bHasSeekable; + bool m_bCompressedIsSetFromOutside; + bool m_bFromManifest; + bool m_bUseWinEncoding; + bool m_bRawStream; + + /// Check that m_xStream implements io::XSeekable and return it + css::uno::Reference< css::io::XInputStream > const & GetOwnSeekStream(); + /// get raw data using unbuffered stream + /// @throws css::uno::RuntimeException + css::uno::Reference< css::io::XInputStream > getRawData(); + +public: + bool IsPackageMember () const { return m_nStreamMode == PACKAGE_STREAM_PACKAGEMEMBER;} + + bool IsFromManifest() const { return m_bFromManifest; } + void SetFromManifest( bool bValue ) { m_bFromManifest = bValue; } + + enum class Bugs { None, WinEncodingWrongSHA1, WrongSHA1 }; + ::rtl::Reference<EncryptionData> GetEncryptionData(Bugs bugs = Bugs::None); + + css::uno::Sequence<sal_Int8> GetEncryptionKey(Bugs bugs = Bugs::None); + + sal_Int32 GetStartKeyGenID() const; + + sal_Int32 GetEncryptionAlgorithm() const; + sal_Int32 GetIVSize() const; + + void SetToBeCompressed (bool bNewValue) { m_bToBeCompressed = bNewValue;} + void SetIsEncrypted (bool bNewValue) { m_bIsEncrypted = bNewValue;} + void SetImportedStartKeyAlgorithm( sal_Int32 nAlgorithm ) { m_nImportedStartKeyAlgorithm = nAlgorithm; } + void SetImportedEncryptionAlgorithm( sal_Int32 nAlgorithm ) { m_nImportedEncryptionAlgorithm = nAlgorithm; } + void SetImportedChecksumAlgorithm(::std::optional<sal_Int32> const& roAlgorithm) { m_oImportedChecksumAlgorithm = roAlgorithm; } + void SetImportedDerivedKeySize( sal_Int32 nSize ) { m_nImportedDerivedKeySize = nSize; } + void SetToBeEncrypted (bool bNewValue) + { + m_bToBeEncrypted = bNewValue; + if ( m_bToBeEncrypted && !m_xBaseEncryptionData.is()) + m_xBaseEncryptionData = new BaseEncryptionData; + else if ( !m_bToBeEncrypted && m_xBaseEncryptionData.is() ) + m_xBaseEncryptionData.clear(); + } + void SetPackageMember (bool bNewValue); + + void setInitialisationVector (const css::uno::Sequence < sal_Int8 >& rNewVector ) + { m_xBaseEncryptionData->m_aInitVector = rNewVector;} + void setSalt (const css::uno::Sequence < sal_Int8 >& rNewSalt ) + { m_xBaseEncryptionData->m_aSalt = rNewSalt;} + void setDigest (const css::uno::Sequence < sal_Int8 >& rNewDigest ) + { m_xBaseEncryptionData->m_aDigest = rNewDigest;} + void setIterationCount(::std::optional<sal_Int32> const oNewCount) + { + m_xBaseEncryptionData->m_oPBKDFIterationCount = oNewCount; + } + void setArgon2Args(::std::optional<::std::tuple<sal_Int32, sal_Int32, sal_Int32>> const oArgon2Args) + { + m_xBaseEncryptionData->m_oArgon2Args = oArgon2Args; + } + void setSize (const sal_Int64 nNewSize); + + ZipPackageStream( ZipPackage & rNewPackage, + const css::uno::Reference < css::uno::XComponentContext >& xContext, + sal_Int32 nFormat, + bool bAllowRemoveOnInsert ); + virtual ~ZipPackageStream() override; + + css::uno::Reference< css::io::XInputStream > GetRawEncrStreamNoHeaderCopy(); + css::uno::Reference< css::io::XInputStream > TryToGetRawFromDataStream(bool bAddHeaderForEncr ); + + bool ParsePackageRawStream(); + virtual bool saveChild( const OUString &rPath, + std::vector < css::uno::Sequence < css::beans::PropertyValue > > &rManList, + ZipOutputStream & rZipOut, + const css::uno::Sequence < sal_Int8 >& rEncryptionKey, + ::std::optional<sal_Int32> oPBKDF2IterationCount, + ::std::optional<::std::tuple<sal_Int32, sal_Int32, sal_Int32>> oArgon2Args, + const rtlRandomPool &rRandomPool ) override; + + void setZipEntryOnLoading( const ZipEntry &rInEntry); + void successfullyWritten( ZipEntry const *pEntry ); + + // XActiveDataSink + virtual void SAL_CALL setInputStream( const css::uno::Reference< css::io::XInputStream >& aStream ) override; + virtual css::uno::Reference< css::io::XInputStream > SAL_CALL getInputStream( ) override; + + // XDataSinkEncrSupport + virtual css::uno::Reference< css::io::XInputStream > SAL_CALL getDataStream() override; + virtual css::uno::Reference< css::io::XInputStream > SAL_CALL getRawStream() override; + virtual void SAL_CALL setDataStream( + const css::uno::Reference< css::io::XInputStream >& aStream ) override; + virtual void SAL_CALL setRawStream( + const css::uno::Reference< css::io::XInputStream >& aStream ) override; + virtual css::uno::Reference< css::io::XInputStream > SAL_CALL getPlainRawStream() override; + + // XPropertySet + virtual void SAL_CALL setPropertyValue( const OUString& aPropertyName, const css::uno::Any& aValue ) override; + virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& PropertyName ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/inc/pch/precompiled_package2.cxx b/package/inc/pch/precompiled_package2.cxx new file mode 100644 index 0000000000..63e41961d6 --- /dev/null +++ b/package/inc/pch/precompiled_package2.cxx @@ -0,0 +1,12 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "precompiled_package2.hxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/inc/pch/precompiled_package2.hxx b/package/inc/pch/precompiled_package2.hxx new file mode 100644 index 0000000000..46d0b427c9 --- /dev/null +++ b/package/inc/pch/precompiled_package2.hxx @@ -0,0 +1,178 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +/* + This file has been autogenerated by update_pch.sh. It is possible to edit it + manually (such as when an include file has been moved/renamed/removed). All such + manual changes will be rewritten by the next run of update_pch.sh (which presumably + also fixes all possible problems, so it's usually better to use it). + + Generated on 2021-04-08 13:55:58 using: + ./bin/update_pch package package2 --cutoff=3 --exclude:system --include:module --include:local + + If after updating build fails, use the following command to locate conflicting headers: + ./bin/update_pch_bisect ./package/inc/pch/precompiled_package2.hxx "make package.build" --find-conflicts +*/ + +#include <sal/config.h> +#if PCH_LEVEL >= 1 +#include <algorithm> +#include <cassert> +#include <chrono> +#include <cmath> +#include <cstddef> +#include <cstdlib> +#include <cstring> +#include <initializer_list> +#include <iomanip> +#include <limits> +#include <math.h> +#include <memory> +#include <new> +#include <ostream> +#include <stddef.h> +#include <string.h> +#include <string> +#include <string_view> +#include <type_traits> +#include <unordered_map> +#include <utility> +#include <vector> +#include <zlib.h> +#endif // PCH_LEVEL >= 1 +#if PCH_LEVEL >= 2 +#include <osl/diagnose.h> +#include <osl/diagnose.hxx> +#include <osl/doublecheckedlocking.h> +#include <osl/endian.h> +#include <osl/file.h> +#include <osl/getglobalmutex.hxx> +#include <osl/interlck.h> +#include <osl/mutex.h> +#include <osl/mutex.hxx> +#include <osl/thread.hxx> +#include <osl/time.h> +#include <rtl/alloc.h> +#include <rtl/cipher.h> +#include <rtl/crc.h> +#include <rtl/digest.h> +#include <rtl/instance.hxx> +#include <rtl/math.h> +#include <rtl/random.h> +#include <rtl/ref.hxx> +#include <rtl/strbuf.h> +#include <rtl/strbuf.hxx> +#include <rtl/string.h> +#include <rtl/string.hxx> +#include <rtl/stringconcat.hxx> +#include <rtl/stringutils.hxx> +#include <rtl/textcvt.h> +#include <rtl/textenc.h> +#include <rtl/uri.hxx> +#include <rtl/ustrbuf.h> +#include <rtl/ustrbuf.hxx> +#include <rtl/ustring.h> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <sal/macros.h> +#include <sal/saldllapi.h> +#include <sal/types.h> +#include <sal/typesizes.h> +#endif // PCH_LEVEL >= 2 +#if PCH_LEVEL >= 3 +#include <basegfx/basegfxdllapi.h> +#include <basegfx/color/bcolor.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/tuple/b3dtuple.hxx> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/embed/StorageFormats.hpp> +#include <com/sun/star/io/TempFile.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/io/XSeekable.hpp> +#include <com/sun/star/io/XStream.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/EventObject.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XTypeProvider.hpp> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/packages/zip/ZipConstants.hpp> +#include <com/sun/star/packages/zip/ZipIOException.hpp> +#include <com/sun/star/uno/Any.h> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/Sequence.h> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/Type.h> +#include <com/sun/star/uno/Type.hxx> +#include <com/sun/star/uno/TypeClass.hdl> +#include <com/sun/star/uno/XInterface.hpp> +#include <com/sun/star/uno/XWeak.hpp> +#include <com/sun/star/uno/genfunc.h> +#include <com/sun/star/uno/genfunc.hxx> +#include <com/sun/star/xml/crypto/CipherID.hpp> +#include <com/sun/star/xml/crypto/DigestID.hpp> +#include <comphelper/comphelperdllapi.h> +#include <comphelper/processfactory.hxx> +#include <comphelper/refcountedmutex.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/servicehelper.hxx> +#include <comphelper/storagehelper.hxx> +#include <cppu/cppudllapi.h> +#include <cppu/unotype.hxx> +#include <cppuhelper/cppuhelperdllapi.h> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/factory.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/implbase_ex.hxx> +#include <cppuhelper/implbase_ex_post.hxx> +#include <cppuhelper/implbase_ex_pre.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/typeprovider.hxx> +#include <cppuhelper/weak.hxx> +#include <o3tl/typed_flags_set.hxx> +#include <o3tl/underlyingenumvalue.hxx> +#include <salhelper/salhelperdllapi.h> +#include <salhelper/simplereferenceobject.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <tools/toolsdllapi.h> +#include <typelib/typeclass.h> +#include <typelib/typedescription.h> +#include <typelib/uik.h> +#include <uno/any2.h> +#include <uno/data.h> +#include <uno/sequence2.h> +#include <unotools/options.hxx> +#include <unotools/unotoolsdllapi.h> +#endif // PCH_LEVEL >= 3 +#if PCH_LEVEL >= 4 +#include <CRC32.hxx> +#include <EncryptedDataHeader.hxx> +#include <EncryptionData.hxx> +#include <HashMaps.hxx> +#include <PackageConstants.hxx> +#include <ThreadedDeflater.hxx> +#include <ZipEntry.hxx> +#include <ZipEnumeration.hxx> +#include <ZipFile.hxx> +#include <ZipOutputEntry.hxx> +#include <ZipOutputStream.hxx> +#include <ZipPackageBuffer.hxx> +#include <ZipPackageFolder.hxx> +#include <ZipPackageStream.hxx> +#include <package/packagedllapi.hxx> +#endif // PCH_LEVEL >= 4 + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/inc/pch/precompiled_xstor.cxx b/package/inc/pch/precompiled_xstor.cxx new file mode 100644 index 0000000000..10f1d920d2 --- /dev/null +++ b/package/inc/pch/precompiled_xstor.cxx @@ -0,0 +1,12 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "precompiled_xstor.hxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/inc/pch/precompiled_xstor.hxx b/package/inc/pch/precompiled_xstor.hxx new file mode 100644 index 0000000000..9b5e894a1e --- /dev/null +++ b/package/inc/pch/precompiled_xstor.hxx @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +/* + This file has been autogenerated by update_pch.sh. It is possible to edit it + manually (such as when an include file has been moved/renamed/removed). All such + manual changes will be rewritten by the next run of update_pch.sh (which presumably + also fixes all possible problems, so it's usually better to use it). + + Generated on 2021-03-08 13:14:06 using: + ./bin/update_pch package xstor --cutoff=2 --exclude:system --include:module --exclude:local + + If after updating build fails, use the following command to locate conflicting headers: + ./bin/update_pch_bisect ./package/inc/pch/precompiled_xstor.hxx "make package.build" --find-conflicts +*/ + +#include <sal/config.h> +#if PCH_LEVEL >= 1 +#include <algorithm> +#include <cassert> +#include <cstddef> +#include <cstdlib> +#include <iomanip> +#include <limits> +#include <memory> +#include <new> +#include <ostream> +#include <stddef.h> +#include <string.h> +#include <string> +#include <string_view> +#include <type_traits> +#include <utility> +#endif // PCH_LEVEL >= 1 +#if PCH_LEVEL >= 2 +#include <osl/diagnose.h> +#include <osl/interlck.h> +#include <osl/mutex.h> +#include <osl/mutex.hxx> +#include <rtl/alloc.h> +#include <rtl/digest.h> +#include <rtl/instance.hxx> +#include <rtl/string.h> +#include <rtl/string.hxx> +#include <rtl/stringconcat.hxx> +#include <rtl/stringutils.hxx> +#include <rtl/textcvt.h> +#include <rtl/textenc.h> +#include <rtl/ustring.h> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <sal/macros.h> +#include <sal/saldllapi.h> +#include <sal/types.h> +#include <sal/typesizes.h> +#endif // PCH_LEVEL >= 2 +#if PCH_LEVEL >= 3 +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/embed/StorageFormats.hpp> +#include <com/sun/star/embed/StorageWrappedTargetException.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/io/NotConnectedException.hpp> +#include <com/sun/star/io/TempFile.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/packages/WrongPasswordException.hpp> +#include <com/sun/star/ucb/SimpleFileAccess.hpp> +#include <com/sun/star/uno/Any.h> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/Type.h> +#include <com/sun/star/uno/Type.hxx> +#include <com/sun/star/uno/TypeClass.hdl> +#include <com/sun/star/uno/XInterface.hpp> +#include <com/sun/star/uno/XWeak.hpp> +#include <com/sun/star/uno/genfunc.h> +#include <com/sun/star/uno/genfunc.hxx> +#include <comphelper/comphelperdllapi.h> +#include <comphelper/ofopxmlhelper.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/storagehelper.hxx> +#include <cppu/cppudllapi.h> +#include <cppu/unotype.hxx> +#include <cppuhelper/cppuhelperdllapi.h> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <cppuhelper/typeprovider.hxx> +#include <salhelper/salhelperdllapi.h> +#include <salhelper/simplereferenceobject.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <typelib/typeclass.h> +#include <typelib/typedescription.h> +#include <typelib/uik.h> +#include <uno/any2.h> +#include <uno/data.h> +#include <uno/sequence2.h> +#endif // PCH_LEVEL >= 3 +#if PCH_LEVEL >= 4 +#endif // PCH_LEVEL >= 4 + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/inc/zipfileaccess.hxx b/package/inc/zipfileaccess.hxx new file mode 100644 index 0000000000..b67a44b5c1 --- /dev/null +++ b/package/inc/zipfileaccess.hxx @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_PACKAGE_INC_ZIPFILEACCESS_HXX +#define INCLUDED_PACKAGE_INC_ZIPFILEACCESS_HXX + +#include <com/sun/star/packages/zip/XZipFileAccess2.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> + +#include <comphelper/interfacecontainer3.hxx> +#include <comphelper/refcountedmutex.hxx> +#include <cppuhelper/implbase.hxx> + +#include "ZipFile.hxx" + +#include <memory> +#include <optional> + +class OZipFileAccess final : public ::cppu::WeakImplHelper< + css::packages::zip::XZipFileAccess2, + css::lang::XInitialization, + css::lang::XComponent, + css::lang::XServiceInfo > +{ + rtl::Reference<comphelper::RefCountedMutex> m_aMutexHolder; + css::uno::Reference< css::uno::XComponentContext > m_xContext; + css::uno::Reference< css::io::XInputStream > m_xContentStream; + std::optional<ZipFile> m_pZipFile; + std::unique_ptr<::comphelper::OInterfaceContainerHelper3<css::lang::XEventListener>> m_pListenersContainer; + bool m_bDisposed; + bool m_bOwnContent; + +public: + OZipFileAccess( const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + + virtual ~OZipFileAccess() override; + + static css::uno::Sequence< OUString > GetPatternsFromString_Impl( const OUString& aString ); + + static bool StringGoodForPattern_Impl( std::u16string_view, + const css::uno::Sequence< OUString >& aPattern ); + + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + // XNameAccess + virtual css::uno::Any SAL_CALL getByName( const OUString& aName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getElementNames( ) override; + virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override; + virtual css::uno::Type SAL_CALL getElementType( ) override; + virtual sal_Bool SAL_CALL hasElements( ) override; + + // XZipFileAccess + virtual css::uno::Reference< css::io::XInputStream > SAL_CALL getStreamByPattern( const OUString& aPattern ) override; + + // XComponent + virtual void SAL_CALL dispose( ) override; + virtual void SAL_CALL addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) override; + virtual void SAL_CALL removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/qa/cppunit/data/a2z.zip b/package/qa/cppunit/data/a2z.zip Binary files differnew file mode 100644 index 0000000000..4a04508b8f --- /dev/null +++ b/package/qa/cppunit/data/a2z.zip diff --git a/package/qa/cppunit/data/export64.zip b/package/qa/cppunit/data/export64.zip Binary files differnew file mode 100644 index 0000000000..b303266967 --- /dev/null +++ b/package/qa/cppunit/data/export64.zip diff --git a/package/qa/cppunit/data/fail/.gitignore b/package/qa/cppunit/data/fail/.gitignore new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/package/qa/cppunit/data/fail/.gitignore diff --git a/package/qa/cppunit/data/fail/EDB-29934-1.zip b/package/qa/cppunit/data/fail/EDB-29934-1.zip Binary files differnew file mode 100644 index 0000000000..510e75eaa0 --- /dev/null +++ b/package/qa/cppunit/data/fail/EDB-29934-1.zip diff --git a/package/qa/cppunit/data/indeterminate/.gitignore b/package/qa/cppunit/data/indeterminate/.gitignore new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/package/qa/cppunit/data/indeterminate/.gitignore diff --git a/package/qa/cppunit/data/pass/.gitignore b/package/qa/cppunit/data/pass/.gitignore new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/package/qa/cppunit/data/pass/.gitignore diff --git a/package/qa/cppunit/data/pass/ofz56826-1.zip b/package/qa/cppunit/data/pass/ofz56826-1.zip Binary files differnew file mode 100644 index 0000000000..b9acfe34da --- /dev/null +++ b/package/qa/cppunit/data/pass/ofz56826-1.zip diff --git a/package/qa/cppunit/test_package.cxx b/package/qa/cppunit/test_package.cxx new file mode 100644 index 0000000000..911e0ea603 --- /dev/null +++ b/package/qa/cppunit/test_package.cxx @@ -0,0 +1,255 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <comphelper/processfactory.hxx> +#include <unotest/filters-test.hxx> +#include <unotest/bootstrapfixturebase.hxx> +#include <comphelper/threadpool.hxx> +#include <com/sun/star/packages/zip/ZipFileAccess.hpp> +#include <com/sun/star/beans/NamedValue.hpp> + +#include <iterator> + +using namespace ::com::sun::star; + +namespace +{ + class PackageTest + : public test::FiltersTest + , public test::BootstrapFixtureBase + { + public: + PackageTest() {} + + virtual void setUp() override; + + virtual bool load(const OUString &, + const OUString &rURL, const OUString &, + SfxFilterFlags, SotClipboardFormatId, unsigned int) override; + + void test(); + void testThreadedStreams(); + void testBufferedThreadedStreams(); + void testZip64(); + + CPPUNIT_TEST_SUITE(PackageTest); + CPPUNIT_TEST(test); + CPPUNIT_TEST(testThreadedStreams); + CPPUNIT_TEST(testBufferedThreadedStreams); + CPPUNIT_TEST(testZip64); + CPPUNIT_TEST_SUITE_END(); + + private: + uno::Reference<container::XNameAccess> mxNA; + void verifyStreams( std::vector<std::vector<char>> &aBuffers ); + }; + + void PackageTest::setUp() + { + BootstrapFixtureBase::setUp(); + OUString aURL = m_directories.getURLFromSrc(u"/package/qa/cppunit/data/a2z.zip"); + + uno::Sequence<beans::NamedValue> aNVs{ { "URL", uno::Any(aURL) } }; + uno::Sequence<uno::Any> aArgs{ uno::Any(aNVs) }; + + uno::Reference<uno::XComponentContext> xCxt = comphelper::getProcessComponentContext(); + uno::Reference<lang::XMultiComponentFactory> xSvcMgr = xCxt->getServiceManager(); + + uno::Reference<packages::zip::XZipFileAccess2> xZip( + xSvcMgr->createInstanceWithArgumentsAndContext( + "com.sun.star.packages.zip.ZipFileAccess", aArgs, xCxt), + uno::UNO_QUERY); + + CPPUNIT_ASSERT(xZip.is()); + + mxNA = xZip; + CPPUNIT_ASSERT(mxNA.is()); + } + + bool PackageTest::load(const OUString &, + const OUString &rURL, const OUString &, + SfxFilterFlags, SotClipboardFormatId, unsigned int) + { + try + { + uno::Reference<css::packages::zip::XZipFileAccess2> xZip( + css::packages::zip::ZipFileAccess::createWithURL(comphelper::getProcessComponentContext(), rURL)); + return xZip.is(); + } + catch(...) + { + return false; + } + } + + void PackageTest::test() + { + testDir(OUString(), + m_directories.getURLFromSrc(u"/package/qa/cppunit/data/")); + } + + void PackageTest::verifyStreams( std::vector<std::vector<char>> &aBuffers ) + { + CPPUNIT_ASSERT_EQUAL(size_t(26), aBuffers.size()); + auto itBuf = aBuffers.begin(); + + for (char c = 'a'; c <= 'z'; ++c, ++itBuf) + { + const std::vector<char>& rBuf = *itBuf; + CPPUNIT_ASSERT_EQUAL(size_t(1048576), rBuf.size()); // 1 MB each. + for (char check : rBuf) + if (c != check) + CPPUNIT_ASSERT_MESSAGE("stream does not contain expected data", false); + } + } + + // TODO : This test currently doesn't fail even when you set + // UseBufferedStream to false. Look into this and replace it with a better + // test that actually fails when the aforementioned flag is set to false. + void PackageTest::testThreadedStreams() + { + class Worker : public comphelper::ThreadTask + { + uno::Reference<io::XInputStream> mxStrm; + std::vector<char>& mrBuf; + + public: + Worker( + const std::shared_ptr<comphelper::ThreadTaskTag>& pTag, + const uno::Reference<io::XInputStream>& xStrm, + std::vector<char>& rBuf ) : + comphelper::ThreadTask(pTag), mxStrm(xStrm), mrBuf(rBuf) {} + + virtual void doWork() override + { + sal_Int32 nSize = mxStrm->available(); + + uno::Sequence<sal_Int8> aBytes; + while (nSize > 0) + { + sal_Int32 nBytesRead = mxStrm->readBytes(aBytes, 4096); + const sal_Int8* p = aBytes.getArray(); + const sal_Int8* pEnd = p + nBytesRead; + std::copy(p, pEnd, std::back_inserter(mrBuf)); + nSize -= nBytesRead; + } + } + }; + + { + comphelper::ThreadPool aPool(4); + std::shared_ptr<comphelper::ThreadTaskTag> pTag = comphelper::ThreadPool::createThreadTaskTag(); + + std::vector<std::vector<char>> aTestBuffers(26); + auto itBuf = aTestBuffers.begin(); + + for (char c = 'a'; c <= 'z'; ++c, ++itBuf) + { + OUString aName = OUStringChar(c) + ".txt"; + + uno::Reference<io::XInputStream> xStrm; + mxNA->getByName(aName) >>= xStrm; + + CPPUNIT_ASSERT(xStrm.is()); + aPool.pushTask(std::make_unique<Worker>(pTag, xStrm, *itBuf)); + } + + aPool.waitUntilDone(pTag); + verifyStreams( aTestBuffers ); + } + } + + void PackageTest::testBufferedThreadedStreams() + { + std::vector<std::vector<char>> aTestBuffers(26); + auto itBuf = aTestBuffers.begin(); + sal_Int32 nReadSize = 0; + + for (char c = 'a'; c <= 'z'; ++c, ++itBuf) + { + itBuf->reserve(1024*1024); + OUString aName = OUStringChar(c) + ".txt"; + + uno::Reference<io::XInputStream> xStrm; + //Size of each stream is 1mb (>10000) => XBufferedThreadedStream + mxNA->getByName(aName) >>= xStrm; + + CPPUNIT_ASSERT(xStrm.is()); + sal_Int32 nSize = xStrm->available(); + + uno::Sequence<sal_Int8> aBytes; + //Read chunks of increasing size + nReadSize += 1024; + + while (nSize > 0) + { + sal_Int32 nBytesRead = xStrm->readBytes(aBytes, nReadSize); + const sal_Int8* p = aBytes.getArray(); + const sal_Int8* pEnd = p + nBytesRead; + std::copy(p, pEnd, std::back_inserter(*itBuf)); + nSize -= nBytesRead; + } + } + + verifyStreams( aTestBuffers ); + } + + void PackageTest::testZip64() + { + // This small zip file have 2 files (content.xml, styles.xml) that have + // Zip64 Extended Information Extra Field in both + // "Local file header" and "Central directory file header", + // and have ZIP64 format "Data descriptor". + OUString aURL2 = m_directories.getURLFromSrc(u"/package/qa/cppunit/data/export64.zip"); + + uno::Sequence<beans::NamedValue> aNVs2{ { "URL", uno::Any(aURL2) } }; + uno::Sequence<uno::Any> aArgs2{ uno::Any(aNVs2) }; + + uno::Reference<uno::XComponentContext> xCxt = comphelper::getProcessComponentContext(); + uno::Reference<lang::XMultiComponentFactory> xSvcMgr = xCxt->getServiceManager(); + + // Without Zip64 support, it would crash here + uno::Reference<packages::zip::XZipFileAccess2> xZip2( + xSvcMgr->createInstanceWithArgumentsAndContext( + "com.sun.star.packages.zip.ZipFileAccess", aArgs2, xCxt), + uno::UNO_QUERY); + + CPPUNIT_ASSERT(xZip2.is()); + + uno::Reference<container::XNameAccess> xNA; + xNA = xZip2; + CPPUNIT_ASSERT(xNA.is()); + + // Check if the styles.xml seems to be right + uno::Reference<io::XInputStream> xStrm; + xNA->getByName("styles.xml") >>= xStrm; + CPPUNIT_ASSERT(xStrm.is()); + // Filesize check + sal_Int32 nSize = xStrm->available(); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1112), nSize); + + uno::Sequence<sal_Int8> aBytes; + sal_Int32 nBytesRead = xStrm->readBytes(aBytes, nSize); + const sal_Int8* p = aBytes.getArray(); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1112), nBytesRead); + + // Check the uncompressed styles.xml file content. + OString aFile(static_cast<const char*>(static_cast<const void*>(p)), nSize); + CPPUNIT_ASSERT(aFile.startsWith( + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<office:document-styles")); + CPPUNIT_ASSERT(aFile.endsWith( + "</number:time-style>\r\n </office:styles>\r\n</office:document-styles>\r\n")); + } + + CPPUNIT_TEST_SUITE_REGISTRATION(PackageTest); +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/qa/ofopxmlstorages/StorageTest.java b/package/qa/ofopxmlstorages/StorageTest.java new file mode 100644 index 0000000000..5f3b2ff2a7 --- /dev/null +++ b/package/qa/ofopxmlstorages/StorageTest.java @@ -0,0 +1,7 @@ +package complex.ofopxmlstorages; + +public interface StorageTest +{ + boolean test(); +} + diff --git a/package/qa/ofopxmlstorages/StorageUnitTest.java b/package/qa/ofopxmlstorages/StorageUnitTest.java new file mode 100644 index 0000000000..e8ee7696a6 --- /dev/null +++ b/package/qa/ofopxmlstorages/StorageUnitTest.java @@ -0,0 +1,149 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +package complex.ofopxmlstorages; + +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XMultiComponentFactory; +import com.sun.star.connection.XConnector; +import com.sun.star.connection.XConnection; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.uno.XNamingService; +import com.sun.star.uno.XComponentContext; + +import com.sun.star.container.*; +import com.sun.star.beans.*; +import com.sun.star.lang.*; + +import complexlib.ComplexTestCase; + +import complex.ofopxmlstorages.*; + +import util.utils; +import java.util.*; +import java.io.*; + +/* This unit test for storage objects is designed to + * test most important statements from storage service + * specification. + * + * Regression tests are added to extend the tested + * functionalities. + */ +public class StorageUnitTest extends ComplexTestCase +{ + private XMultiServiceFactory m_xMSF = null; + private XSingleServiceFactory m_xStorageFactory = null; + + public String[] getTestMethodNames() + { + return new String[] { + "ExecuteTest01", + "ExecuteTest02", + "ExecuteTest03", + "ExecuteTest04", + "ExecuteTest05", + "ExecuteTest06", + "ExecuteTest07", + "ExecuteTest08" + }; + } + + public String getTestObjectName() + { + return "StorageUnitTest"; + } + + public void before() + { + m_xMSF = (XMultiServiceFactory)param.getMSF(); + if ( m_xMSF == null ) + { + failed( "Can't create service factory!" ); + return; + } + + try { + Object oStorageFactory = m_xMSF.createInstance( "com.sun.star.embed.StorageFactory" ); + m_xStorageFactory = (XSingleServiceFactory)UnoRuntime.queryInterface( XSingleServiceFactory.class, + oStorageFactory ); + } + catch( Exception e ) + { + failed( "Can't create storage factory!" ); + return; + } + + if ( m_xStorageFactory == null ) + { + failed( "Can't create service factory!" ); + return; + } + } + + public void ExecuteTest01() + { + StorageTest aTest = new Test01( m_xMSF, m_xStorageFactory, log ); + assure( "Test01 failed!", aTest.test() ); + } + + public void ExecuteTest02() + { + StorageTest aTest = new Test02( m_xMSF, m_xStorageFactory, log ); + assure( "Test02 failed!", aTest.test() ); + } + + public void ExecuteTest03() + { + StorageTest aTest = new Test03( m_xMSF, m_xStorageFactory, log ); + assure( "Test03 failed!", aTest.test() ); + } + + public void ExecuteTest04() + { + StorageTest aTest = new Test04( m_xMSF, m_xStorageFactory, log ); + assure( "Test04 failed!", aTest.test() ); + } + + public void ExecuteTest05() + { + StorageTest aTest = new Test05( m_xMSF, m_xStorageFactory, log ); + assure( "Test05 failed!", aTest.test() ); + } + + public void ExecuteTest06() + { + StorageTest aTest = new Test06( m_xMSF, m_xStorageFactory, log ); + assure( "Test06 failed!", aTest.test() ); + } + + public void ExecuteTest07() + { + StorageTest aTest = new Test07( m_xMSF, m_xStorageFactory, log ); + assure( "Test07 failed!", aTest.test() ); + } + + public void ExecuteTest08() + { + StorageTest aTest = new Test08( m_xMSF, m_xStorageFactory, log ); + assure( "Test08 failed!", aTest.test() ); + } +} + diff --git a/package/qa/ofopxmlstorages/Test01.java b/package/qa/ofopxmlstorages/Test01.java new file mode 100644 index 0000000000..dd8665bc54 --- /dev/null +++ b/package/qa/ofopxmlstorages/Test01.java @@ -0,0 +1,218 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.ofopxmlstorages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; + +import com.sun.star.embed.*; +import com.sun.star.beans.StringPair; + +import share.LogWriter; +import complex.ofopxmlstorages.TestHelper; +import complex.ofopxmlstorages.StorageTest; + +public class Test01 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public Test01( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "Test01: " ); + } + + public boolean test() + { + StringPair[][] aRelations1 = + { { new StringPair( "Id", "Num1" ) }, + { new StringPair( "Target", "TargetURLValue1" ), new StringPair( "Id", "Num6" ) }, + { new StringPair( "Target", "" ), new StringPair( "Id", "Num7" ) }, + { new StringPair( "Id", "Num2" ), new StringPair( "TargetMode", "Internal1" ), new StringPair( "Type", "unknown1" ), new StringPair( "Target", "URL value 1" ) }, + { new StringPair( "Id", "Num3" ), new StringPair( "TargetMode", "Internal1" ), new StringPair( "Type", "unknown1" ), new StringPair( "Target", "URL value 1" ) }, + { new StringPair( "Id", "Num4" ), new StringPair( "TargetMode", "Internal1" ), new StringPair( "Type", "unknown1" ), new StringPair( "Target", "URL value 1" ) }, + { new StringPair( "Id", "Num5" ), new StringPair( "TargetMode", "" ), new StringPair( "Type", "unknown1" ), new StringPair( "Target", "URL value1" ) } + }; + + StringPair[][] aRelations2 = + { { new StringPair( "Id", "Num1" ) }, + { new StringPair( "Target", "TargetURLValue2" ), new StringPair( "Id", "Num6" ) }, + { new StringPair( "Target", "" ), new StringPair( "Id", "Num7" ) }, + { new StringPair( "Id", "Num2" ), new StringPair( "TargetMode", "Internal2" ), new StringPair( "Type", "unknown2" ), new StringPair( "Target", "URL value 2" ) }, + { new StringPair( "Id", "Num3" ), new StringPair( "TargetMode", "Internal2" ), new StringPair( "Type", "unknown2" ), new StringPair( "Target", "URL value 2" ) }, + { new StringPair( "Id", "Num4" ), new StringPair( "TargetMode", "Internal2" ), new StringPair( "Type", "unknown" ), new StringPair( "Target", "URL value" ) }, + { new StringPair( "Id", "Num5" ), new StringPair( "TargetMode", "" ), new StringPair( "Type", "unknown" ), new StringPair( "Target", "URL value" ) } + }; + + try + { + String sTempFileURL = m_aTestHelper.CreateTempFile( m_xMSF ); + if ( sTempFileURL == null || sTempFileURL == "" ) + { + m_aTestHelper.Error( "No valid temporary file was created!" ); + return false; + } + + // create temporary storage based on arbitrary medium + // after such a storage is closed it is lost + XStorage xTempStorage = m_aTestHelper.createTempStorage( m_xMSF, m_xStorageFactory ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, + "SubStream1", + "MediaType1", + true, + pBytes1, + aRelations1 ) ) + return false; + + byte pBytes2[] = { 2, 2, 2, 2, 2 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, + "SubStream2", + "MediaType2", + false, + pBytes2, + aRelations2 ) ) + return false; + + // set Relations for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempStorage, + true, + ElementModes.WRITE, + aRelations1 ) ) + return false; + + // set Relations for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + false, + ElementModes.WRITE, + aRelations1 ) ) + return false; + + // create temporary storage based on a previously created temporary file + XStorage xTempFileStorage = m_aTestHelper.createStorageFromURL( m_xStorageFactory, + sTempFileURL, + ElementModes.WRITE ); + if ( xTempFileStorage == null ) + { + m_aTestHelper.Error( "Can't create storage based on temporary file!" ); + return false; + } + + // copy xTempStorage to xTempFileStorage + // xTempFileStorage will be automatically committed + if ( !m_aTestHelper.copyStorage( xTempStorage, xTempFileStorage ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xTempStorage ) || !m_aTestHelper.disposeStorage( xTempFileStorage ) ) + return false; + + + // now check all the written and copied information + + + // the temporary file must not be locked any more after storage disposing + XStorage xResultStorage = m_aTestHelper.createStorageFromURL( m_xStorageFactory, + sTempFileURL, + ElementModes.WRITE ); + if ( xResultStorage == null ) + { + m_aTestHelper.Error( "Can't reopen storage based on temporary file!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultStorage, + true, + ElementModes.WRITE, + aRelations1 ) ) + return false; + + // open existing substorage + XStorage xResultSubStorage = m_aTestHelper.openSubStorage( xResultStorage, + "SubStorage1", + ElementModes.READ ); + if ( xResultSubStorage == null ) + { + m_aTestHelper.Error( "Can't open existing substorage!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultSubStorage, + false, + ElementModes.READ, + aRelations1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResultSubStorage, + "SubStream1", + "MediaType1", + pBytes1, + aRelations1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResultSubStorage, + "SubStream2", + "MediaType2", + pBytes2, + aRelations2 ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xResultStorage ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } + +} + diff --git a/package/qa/ofopxmlstorages/Test02.java b/package/qa/ofopxmlstorages/Test02.java new file mode 100644 index 0000000000..a39e78ca47 --- /dev/null +++ b/package/qa/ofopxmlstorages/Test02.java @@ -0,0 +1,182 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.ofopxmlstorages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.io.XStream; +import com.sun.star.io.XInputStream; + +import com.sun.star.embed.*; +import com.sun.star.beans.StringPair; + +import share.LogWriter; +import complex.ofopxmlstorages.TestHelper; +import complex.ofopxmlstorages.StorageTest; + +public class Test02 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public Test02( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "Test02: " ); + } + + public boolean test() + { + try + { + StringPair[][] aRelations = + { { new StringPair( "Id", "Num1" ) }, + { new StringPair( "Target", "TargetURLValue" ), new StringPair( "Id", "Num6" ) }, + { new StringPair( "Target", "" ), new StringPair( "Id", "Num7" ) }, + { new StringPair( "Id", "Num2" ), new StringPair( "TargetMode", "Internal" ), new StringPair( "Type", "unknown" ), new StringPair( "Target", "URL value" ) }, + { new StringPair( "Id", "Num3" ), new StringPair( "TargetMode", "Internal" ), new StringPair( "Type", "unknown" ), new StringPair( "Target", "URL value" ) }, + { new StringPair( "Id", "Num4" ), new StringPair( "TargetMode", "Internal" ), new StringPair( "Type", "unknown" ), new StringPair( "Target", "URL value" ) }, + { new StringPair( "Id", "Num5" ), new StringPair( "TargetMode", "" ), new StringPair( "Type", "unknown" ), new StringPair( "Target", "URL value" ) } + }; + + + XStream xTempFileStream = m_aTestHelper.CreateTempFileStream( m_xMSF ); + if ( xTempFileStream == null ) + return false; + + // create storage based on the temporary stream + XStorage xTempStorage = m_aTestHelper.createStorageFromStream( m_xStorageFactory, + xTempFileStream, + ElementModes.WRITE ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, + "SubStream1", + "MediaType1", + true, + pBytes1, + aRelations ) ) + return false; + + // set Relations for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempStorage, + true, + ElementModes.WRITE, + aRelations ) ) + return false; + + // set Relations for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + false, + ElementModes.WRITE, + aRelations ) ) + return false; + + // commit substorage first + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + // dispose used storage to free resources + if ( !m_aTestHelper.disposeStorage( xTempStorage ) ) + return false; + + + + // now check all the written information + + + // close the output part of the temporary stream + // the output part must present since we already wrote to the stream + if ( !m_aTestHelper.closeOutput( xTempFileStream ) ) + return false; + + XInputStream xTempInStream = m_aTestHelper.getInputStream( xTempFileStream ); + if ( xTempInStream == null ) + return false; + + + // open input stream + XStorage xResultStorage = m_aTestHelper.createStorageFromInputStream( m_xStorageFactory, xTempInStream ); + if ( xResultStorage == null ) + { + m_aTestHelper.Error( "Can't open storage based on input stream!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultStorage, true, ElementModes.READ, aRelations ) ) + return false; + + // open existing substorage + XStorage xResultSubStorage = m_aTestHelper.openSubStorage( xResultStorage, + "SubStorage1", + ElementModes.READ ); + if ( xResultSubStorage == null ) + { + m_aTestHelper.Error( "Can't open existing substorage!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultSubStorage, + false, + ElementModes.READ, + aRelations ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResultSubStorage, "SubStream1", "MediaType1", pBytes1, aRelations ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } + +} + diff --git a/package/qa/ofopxmlstorages/Test03.java b/package/qa/ofopxmlstorages/Test03.java new file mode 100644 index 0000000000..d2f7bfa5b3 --- /dev/null +++ b/package/qa/ofopxmlstorages/Test03.java @@ -0,0 +1,251 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.ofopxmlstorages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; + +import com.sun.star.embed.*; +import com.sun.star.container.XNameAccess; +import com.sun.star.beans.StringPair; + +import share.LogWriter; +import complex.ofopxmlstorages.TestHelper; +import complex.ofopxmlstorages.StorageTest; + +public class Test03 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public Test03( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "Test03: " ); + } + + public boolean test() + { + try + { + StringPair[][] aRelations = + { { new StringPair( "Id", "Num1" ) }, + { new StringPair( "Target", "TargetURLValue" ), new StringPair( "Id", "Num6" ) }, + { new StringPair( "Target", "" ), new StringPair( "Id", "Num7" ) }, + { new StringPair( "Id", "Num2" ), new StringPair( "TargetMode", "Internal" ), new StringPair( "Type", "unknown" ), new StringPair( "Target", "URL value" ) }, + { new StringPair( "Id", "Num3" ), new StringPair( "TargetMode", "Internal" ), new StringPair( "Type", "unknown" ), new StringPair( "Target", "URL value" ) }, + { new StringPair( "Id", "Num4" ), new StringPair( "TargetMode", "Internal" ), new StringPair( "Type", "unknown" ), new StringPair( "Target", "URL value" ) }, + { new StringPair( "Id", "Num5" ), new StringPair( "TargetMode", "" ), new StringPair( "Type", "unknown" ), new StringPair( "Target", "URL value" ) } + }; + + // create temporary storage based on arbitrary medium + // after such a storage is closed it is lost + XStorage xTempStorage = m_aTestHelper.createTempStorage( m_xMSF, m_xStorageFactory ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempStorage, + "SubStream1", + "MediaType1", + true, + pBytes1, + aRelations ) ) + return false; + + byte pBytes2[] = { 2, 2, 2, 2, 2 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, + "SubStream2", + "MediaType2", + false, + pBytes2, + aRelations ) ) + return false; + + // set Relations for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + false, + ElementModes.WRITE, + aRelations ) ) + return false; + + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + if ( !m_aTestHelper.disposeStorage( xTempSubStorage ) ) + return false; + + + // check storage hierarchy tree + + + // check that isStorageElement() and isStreamElement reacts to nonexistent object correctly + try { + xTempStorage.isStorageElement( "does not exist" ); + m_aTestHelper.Error( "Nonexistent element doesn't detected by isStorageElement() call!" ); + return false; + } + catch( com.sun.star.container.NoSuchElementException ne ) + { + } + catch( Exception e ) + { + m_aTestHelper.Error( "Wrong exception is thrown by isStorageElement() call: " + e ); + return false; + } + + try { + xTempStorage.isStreamElement( "does not exist" ); + m_aTestHelper.Error( "Nonexistent element doesn't detected by isStreamElement() call!" ); + return false; + } + catch( com.sun.star.container.NoSuchElementException ne ) + { + } + catch( Exception e ) + { + m_aTestHelper.Error( "Wrong exception is thrown by isStreamElement() call: " + e ); + return false; + } + + XNameAccess xRootNameAccess = (XNameAccess) UnoRuntime.queryInterface( XNameAccess.class, xTempStorage ); + if ( xRootNameAccess == null ) + { + m_aTestHelper.Error( "Root storage doesn't support XNameAccess!" ); + return false; + } + + try { + if ( !xTempStorage.isStorageElement( "SubStorage1" ) || xTempStorage.isStreamElement( "SubStorage1" ) ) + { + m_aTestHelper.Error( "Child 'SubStorage1' can not be detected as storage!" ); + return false; + } + + if ( xTempStorage.isStorageElement( "SubStream1" ) || !xTempStorage.isStreamElement( "SubStream1" ) ) + { + m_aTestHelper.Error( "Child 'SubStream1' can not be detected as stream!" ); + return false; + } + } + catch( Exception e ) + { + m_aTestHelper.Error( "Child's type can not be detected, exception: " + e ); + return false; + } + + + // check that root storage contents are represented correctly + String sRootCont[] = xRootNameAccess.getElementNames(); + + if ( sRootCont.length != 2 ) + { + m_aTestHelper.Error( "Root storage contains wrong amount of children!" ); + return false; + } + + if ( !( sRootCont[0].equals( "SubStorage1" ) && sRootCont[1].equals( "SubStream1" ) + || sRootCont[0].equals( "SubStream1" ) && sRootCont[1].equals( "SubStorage1" ) ) + || !( xRootNameAccess.hasByName( "SubStream1" ) && xRootNameAccess.hasByName( "SubStorage1" ) ) ) + { + m_aTestHelper.Error( "Root storage contains wrong list of children!" ); + return false; + } + + // get storage through XNameAccess + XStorage xResultSubStorage = getStorageFromNameAccess( xRootNameAccess, "SubStorage1" ); + if ( xResultSubStorage == null ) + return false; + + if ( !m_aTestHelper.checkStorageProperties( xResultSubStorage, + false, + ElementModes.READ, + aRelations ) ) + return false; + + XNameAccess xChildAccess = (XNameAccess) UnoRuntime.queryInterface( XNameAccess.class, xResultSubStorage ); + if ( xChildAccess == null ) + { + m_aTestHelper.Error( "Child storage doesn't support XNameAccess!" ); + return false; + } + + if ( !xChildAccess.hasByName( "SubStream2" ) + || !xResultSubStorage.isStreamElement( "SubStream2" ) + || xResultSubStorage.isStorageElement( "SubStream2" ) ) + { + m_aTestHelper.Error( "'SubStream2' can not be detected as child stream element of 'SubStorage1'!" ); + return false; + } + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } + + public XStorage getStorageFromNameAccess( XNameAccess xAccess, String sName ) + { + try + { + Object oStorage = xAccess.getByName( sName ); + XStorage xResult = (XStorage) UnoRuntime.queryInterface( XStorage.class, oStorage ); + + if ( xResult != null ) + return xResult; + else + m_aTestHelper.Error( "Can't retrieve substorage '" + sName + "' through XNameAccess!" ); + } + catch( Exception e ) + { + m_aTestHelper.Error( "Can't retrieve substorage '" + sName + "' through XNameAccess, exception: " + e ); + } + + return null; + } + +} + diff --git a/package/qa/ofopxmlstorages/Test04.java b/package/qa/ofopxmlstorages/Test04.java new file mode 100644 index 0000000000..7bc793a603 --- /dev/null +++ b/package/qa/ofopxmlstorages/Test04.java @@ -0,0 +1,326 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.ofopxmlstorages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; +import com.sun.star.lang.DisposedException; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; + +import com.sun.star.container.XNameAccess; + +import com.sun.star.embed.*; +import com.sun.star.beans.StringPair; + +import share.LogWriter; +import complex.ofopxmlstorages.TestHelper; +import complex.ofopxmlstorages.StorageTest; + +public class Test04 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public Test04( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "Test04: " ); + } + + public boolean test() + { + StringPair[][] aRelations1 = + { { new StringPair( "Id", "Num1" ) }, + { new StringPair( "Target", "TargetURLValue1" ), new StringPair( "Id", "Num6" ) }, + { new StringPair( "Target", "" ), new StringPair( "Id", "Num7" ) }, + { new StringPair( "Id", "Num2" ), new StringPair( "TargetMode", "Internal1" ), new StringPair( "Type", "unknown1" ), new StringPair( "Target", "URL value 1" ) }, + { new StringPair( "Id", "Num3" ), new StringPair( "TargetMode", "Internal1" ), new StringPair( "Type", "unknown1" ), new StringPair( "Target", "URL value 1" ) }, + { new StringPair( "Id", "Num4" ), new StringPair( "TargetMode", "Internal1" ), new StringPair( "Type", "unknown1" ), new StringPair( "Target", "URL value 1" ) }, + { new StringPair( "Id", "Num5" ), new StringPair( "TargetMode", "" ), new StringPair( "Type", "unknown1" ), new StringPair( "Target", "URL value1" ) } + }; + + StringPair[][] aRelations2 = + { { new StringPair( "Id", "Num1" ) }, + { new StringPair( "Target", "TargetURLValue2" ), new StringPair( "Id", "Num6" ) }, + { new StringPair( "Target", "" ), new StringPair( "Id", "Num7" ) }, + { new StringPair( "Id", "Num2" ), new StringPair( "TargetMode", "Internal2" ), new StringPair( "Type", "unknown2" ), new StringPair( "Target", "URL value 2" ) }, + { new StringPair( "Id", "Num3" ), new StringPair( "TargetMode", "Internal2" ), new StringPair( "Type", "unknown2" ), new StringPair( "Target", "URL value 2" ) }, + { new StringPair( "Id", "Num4" ), new StringPair( "TargetMode", "Internal2" ), new StringPair( "Type", "unknown" ), new StringPair( "Target", "URL value" ) }, + { new StringPair( "Id", "Num5" ), new StringPair( "TargetMode", "" ), new StringPair( "Type", "unknown" ), new StringPair( "Target", "URL value" ) } + }; + + try + { + String sTempFileURL = m_aTestHelper.CreateTempFile( m_xMSF ); + if ( sTempFileURL == null || sTempFileURL == "" ) + { + m_aTestHelper.Error( "No valid temporary file was created!" ); + return false; + } + + // create temporary storage based on arbitrary medium + // after such a storage is closed it is lost + XStorage xTempStorage = m_aTestHelper.createTempStorage( m_xMSF, m_xStorageFactory ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open substorages and create streams there + + // first substorage of the root storage + XStorage xTempSubStorage1 = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage1 == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage1, + "SubStream1", + "MediaType1", + true, + pBytes1, + aRelations1 ) ) + return false; + + // second substorage of the root storage + XStorage xTempSubStorage2 = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage2", + ElementModes.WRITE ); + if ( xTempSubStorage2 == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + byte pBytes2[] = { 2, 2, 2, 2, 2 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage2, + "SubStream2", + "MediaType2", + false, + pBytes2, + aRelations2 ) ) + return false; + + // set Relations for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempStorage, + true, + ElementModes.WRITE, + aRelations2 ) ) + return false; + + // set Relations for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage1, + false, + ElementModes.WRITE, + aRelations2 ) ) + return false; + + // set Relations for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage2, + false, + ElementModes.WRITE, + aRelations2 ) ) + return false; + + // create temporary storage based on a previously created temporary file + XStorage xTempFileStorage = m_aTestHelper.createStorageFromURL( m_xStorageFactory, + sTempFileURL, + ElementModes.WRITE ); + if ( xTempFileStorage == null ) + { + m_aTestHelper.Error( "Can't create storage based on temporary file!" ); + return false; + } + + if ( !m_aTestHelper.copyElementTo( xTempStorage, "SubStorage1", xTempFileStorage ) ) + return false; + + // if storage is not committed before disposing all the changes will be lost + if ( !m_aTestHelper.commitStorage( xTempSubStorage2 ) ) + return false; + + // a storage must be disposed before moving/removing otherwise the access will be denied + if ( !m_aTestHelper.disposeStorage( xTempSubStorage2 ) ) + return false; + + if ( !m_aTestHelper.moveElementTo( xTempStorage, "SubStorage2", xTempFileStorage ) ) + return false; + + // SubStorage2 must be removed and disposed now + try + { + xTempSubStorage2.isStreamElement( "SubStream2" ); + m_aTestHelper.Error( "SubStorage2 must be disposed already!" ); + return false; + } + catch( com.sun.star.lang.DisposedException de ) + { + } + catch( Exception e ) + { + m_aTestHelper.Error( "Wrong exception in case of disposed storage, exception: " + e ); + return false; + } + + if ( !m_aTestHelper.copyElementTo( xTempSubStorage1, "SubStream1", xTempFileStorage ) ) + return false; + + if ( !m_aTestHelper.renameElement( xTempFileStorage, "SubStream1", "SubStream1_copy" ) ) + return false; + + if ( !m_aTestHelper.moveElementTo( xTempSubStorage1, "SubStream1", xTempFileStorage ) ) + return false; + + if ( !m_aTestHelper.commitStorage( xTempFileStorage ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xTempStorage ) || !m_aTestHelper.disposeStorage( xTempFileStorage ) ) + return false; + + + // now check all the written and copied information + + + // the temporary file must not be locked any more after storage disposing + XStorage xResStorage = m_aTestHelper.createStorageFromURL( m_xStorageFactory, + sTempFileURL, + ElementModes.WRITE ); + + if ( xResStorage == null ) + { + m_aTestHelper.Error( "Can't reopen storage based on temporary file!" ); + return false; + } + + // open and check SubStorage1 + XStorage xResSubStorage1 = m_aTestHelper.openSubStorage( xResStorage, + "SubStorage1", + ElementModes.READ ); + if ( xResSubStorage1 == null ) + { + m_aTestHelper.Error( "Can't open existing substorage!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResSubStorage1, + false, + ElementModes.READ, + aRelations2 ) ) + return false; + + + // open and check SubStorage2 + XStorage xResSubStorage2 = m_aTestHelper.openSubStorage( xResStorage, + "SubStorage2", + ElementModes.READ ); + if ( xResSubStorage2 == null ) + { + m_aTestHelper.Error( "Can't open existing substorage!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResSubStorage2, + false, + ElementModes.READ, + aRelations2 ) ) + return false; + + + // check all the result streams + + if ( !m_aTestHelper.checkStream( xResStorage, "SubStream1", "MediaType1", pBytes1, aRelations1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResStorage, "SubStream1_copy", "MediaType1", pBytes1, aRelations1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResSubStorage1, "SubStream1", "MediaType1", pBytes1, aRelations1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResSubStorage2, "SubStream2", "MediaType2", pBytes2, aRelations2 ) ) + return false; + + // the storage must be disposed before removing + if ( !m_aTestHelper.disposeStorage( xResSubStorage2 ) ) + return false; + + // remove element and check that it was removed completely + if ( !m_aTestHelper.removeElement( xResStorage, "SubStorage2" ) ) + return false; + + try + { + XNameAccess xResAccess = (XNameAccess) UnoRuntime.queryInterface( XNameAccess.class, xResStorage ); + if ( xResAccess.hasByName( "SubStorage2" ) ) + m_aTestHelper.Error( "SubStorage2 must be removed already!" ); + } + catch( Exception e ) + { + m_aTestHelper.Error( "Can't get access to root storage, exception: " + e ); + return false; + } + + try + { + xResSubStorage2.isStreamElement( "SubStream2" ); + + m_aTestHelper.Error( "SubStorage2 must be disposed already!" ); + return false; + } + catch( com.sun.star.lang.DisposedException de ) + { + } + catch( Exception e ) + { + m_aTestHelper.Error( "Wrong exception in case of disposed storage, exception: " + e ); + return false; + } + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xResStorage ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } + +} + diff --git a/package/qa/ofopxmlstorages/Test05.java b/package/qa/ofopxmlstorages/Test05.java new file mode 100644 index 0000000000..1ed0ba4029 --- /dev/null +++ b/package/qa/ofopxmlstorages/Test05.java @@ -0,0 +1,332 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.ofopxmlstorages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.io.XStream; + +import com.sun.star.embed.*; +import com.sun.star.beans.StringPair; + +import share.LogWriter; +import complex.ofopxmlstorages.TestHelper; +import complex.ofopxmlstorages.StorageTest; + +public class Test05 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public Test05( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "Test05: " ); + } + + public boolean test() + { + StringPair[][] aRelations1 = + { { new StringPair( "Id", "Num1" ) }, + { new StringPair( "Target", "TargetURLValue1" ), new StringPair( "Id", "Num6" ) }, + { new StringPair( "Target", "" ), new StringPair( "Id", "Num7" ) }, + { new StringPair( "Id", "Num2" ), new StringPair( "TargetMode", "Internal1" ), new StringPair( "Type", "unknown1" ), new StringPair( "Target", "URL value 1" ) }, + { new StringPair( "Id", "Num3" ), new StringPair( "TargetMode", "Internal1" ), new StringPair( "Type", "unknown1" ), new StringPair( "Target", "URL value 1" ) }, + { new StringPair( "Id", "Num4" ), new StringPair( "TargetMode", "Internal1" ), new StringPair( "Type", "unknown1" ), new StringPair( "Target", "URL value 1" ) }, + { new StringPair( "Id", "Num5" ), new StringPair( "TargetMode", "" ), new StringPair( "Type", "unknown1" ), new StringPair( "Target", "URL value1" ) } + }; + + StringPair[][] aRelations2 = + { { new StringPair( "Id", "Num1" ) }, + { new StringPair( "Target", "TargetURLValue2" ), new StringPair( "Id", "Num6" ) }, + { new StringPair( "Target", "" ), new StringPair( "Id", "Num7" ) }, + { new StringPair( "Id", "Num2" ), new StringPair( "TargetMode", "Internal2" ), new StringPair( "Type", "unknown2" ), new StringPair( "Target", "URL value 2" ) }, + { new StringPair( "Id", "Num3" ), new StringPair( "TargetMode", "Internal2" ), new StringPair( "Type", "unknown2" ), new StringPair( "Target", "URL value 2" ) }, + { new StringPair( "Id", "Num4" ), new StringPair( "TargetMode", "Internal2" ), new StringPair( "Type", "unknown" ), new StringPair( "Target", "URL value" ) }, + { new StringPair( "Id", "Num5" ), new StringPair( "TargetMode", "" ), new StringPair( "Type", "unknown" ), new StringPair( "Target", "URL value" ) } + }; + + try + { + String sTempFileURL = m_aTestHelper.CreateTempFile( m_xMSF ); + if ( sTempFileURL == null || sTempFileURL == "" ) + { + m_aTestHelper.Error( "No valid temporary file was created!" ); + return false; + } + + // create temporary storage based on a previously created temporary file + XStorage xTempFileStorage = m_aTestHelper.createStorageFromURL( m_xStorageFactory, + sTempFileURL, + ElementModes.WRITE ); + if ( xTempFileStorage == null ) + { + m_aTestHelper.Error( "Can't create storage based on temporary file!" ); + return false; + } + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempFileStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open a new substorage + XStorage xSubSubStorage = m_aTestHelper.openSubStorage( xTempSubStorage, + "SubSubStorage1", + ElementModes.WRITE ); + if ( xSubSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xSubSubStorage, + "SubStream1", + "MediaType1", + true, + pBytes1, + aRelations1 ) ) + return false; + + byte pBytes2[] = { 2, 2, 2, 2, 2 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xSubSubStorage, + "SubStream2", + "MediaType2", + false, + pBytes2, + aRelations2 ) ) + return false; + + // set Relations for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempFileStorage, + true, + ElementModes.WRITE, + aRelations2 ) ) + return false; + + // set Relations for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + false, + ElementModes.WRITE, + aRelations2 ) ) + return false; + + // set Relations for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xSubSubStorage, + false, + ElementModes.WRITE, + aRelations2 ) ) + return false; + + + // commit all the storages + if ( !m_aTestHelper.commitStorage( xSubSubStorage ) ) + return false; + + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + if ( !m_aTestHelper.commitStorage( xTempFileStorage ) ) + return false; + + // try to open an opened substorage, open call must fail + if ( !m_aTestHelper.cantOpenStorage( xTempFileStorage, "SubStorage1" ) ) + return false; + + + // reopen created streams + XStream xSubStream1 = m_aTestHelper.OpenStream( xSubSubStorage, + "SubStream1", + ElementModes.WRITE | ElementModes.NOCREATE ); + XStream xSubStream2 = m_aTestHelper.OpenStream( xSubSubStorage, + "SubStream2", + ElementModes.READ | ElementModes.NOCREATE ); + if ( xSubStream1 == null || xSubStream2 == null ) + return false; + + // it should be possible to have more than one copy of stream for reading + XStream xSubStream2clone = m_aTestHelper.OpenStream( xSubSubStorage, + "SubStream2", + ElementModes.READ | ElementModes.NOCREATE ); + if ( xSubStream2 == null ) + return false; + + + // so now the first stream can not be open neither for reading nor for writing + if ( !m_aTestHelper.cantOpenStream( xSubSubStorage, "SubStream1", ElementModes.WRITE ) + || !m_aTestHelper.cantOpenStream( xSubSubStorage, "SubStream1", ElementModes.READ ) ) + return false; + + // the second stream can not be open for writing + if ( !m_aTestHelper.cantOpenStream( xSubSubStorage, "SubStream2", ElementModes.WRITE ) ) + return false; + + + // dispose xTestSubStorage, all the subtree must be disposed + if ( !m_aTestHelper.disposeStorage( xTempSubStorage ) ) + return false; + + // check that subtree was disposed correctly + try + { + xSubSubStorage.isStreamElement( "SubStream1" ); + m_aTestHelper.Error( "Substorage was not disposed!" ); + return false; + } + catch ( com.sun.star.lang.DisposedException de ) + {} + catch ( Exception e ) + { + m_aTestHelper.Error( "Wrong exception is thrown by disposed storage: " + e ); + return false; + } + + try + { + xSubStream1.getInputStream(); + m_aTestHelper.Error( "Writeable substream was not disposed!" ); + return false; + } + catch ( com.sun.star.lang.DisposedException de ) + {} + catch ( Exception e ) + { + m_aTestHelper.Error( "Wrong exception is thrown by disposed stream: " + e ); + return false; + } + + try + { + xSubStream2.getInputStream(); + m_aTestHelper.Error( "Readonly substream was not disposed!" ); + return false; + } + catch ( com.sun.star.lang.DisposedException de ) + {} + catch ( Exception e ) + { + m_aTestHelper.Error( "Wrong exception is thrown by disposed stream: " + e ); + return false; + } + + + // dispose root storage + if ( !m_aTestHelper.disposeStorage( xTempFileStorage ) ) + return false; + + + + // now check all the written and copied information + + + XStorage xResultStorage = m_aTestHelper.createStorageFromURL( m_xStorageFactory, + sTempFileURL, + ElementModes.READ ); + if ( xResultStorage == null ) + { + m_aTestHelper.Error( "Can't reopen storage based on temporary file!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultStorage, + true, + ElementModes.READ, + aRelations2 ) ) + return false; + + // open existing substorage + XStorage xResSubStorage = m_aTestHelper.openSubStorage( xResultStorage, + "SubStorage1", + ElementModes.READ ); + if ( xResSubStorage == null ) + { + m_aTestHelper.Error( "Can't open existing substorage 'SubSubStorage'!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResSubStorage, + false, + ElementModes.READ, + aRelations2 ) ) + return false; + + // open existing substorage + XStorage xResSubSubStorage = m_aTestHelper.openSubStorage( xResSubStorage, + "SubSubStorage1", + ElementModes.READ ); + if ( xResSubSubStorage == null ) + { + m_aTestHelper.Error( "Can't open existing substorage 'SubSubStorage'!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResSubSubStorage, + false, + ElementModes.READ, + aRelations2 ) ) + return false; + + // check substreams + if ( !m_aTestHelper.checkStream( xResSubSubStorage, + "SubStream1", + "MediaType1", + pBytes1, + aRelations1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResSubSubStorage, + "SubStream2", + "MediaType2", + pBytes2, + aRelations2 ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xResultStorage ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } + +} + diff --git a/package/qa/ofopxmlstorages/Test06.java b/package/qa/ofopxmlstorages/Test06.java new file mode 100644 index 0000000000..b0b6b7bcc7 --- /dev/null +++ b/package/qa/ofopxmlstorages/Test06.java @@ -0,0 +1,295 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.ofopxmlstorages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; + +import com.sun.star.lang.IllegalArgumentException; +import com.sun.star.container.NoSuchElementException; +import com.sun.star.container.ElementExistException; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.ofopxmlstorages.TestHelper; +import complex.ofopxmlstorages.StorageTest; + +public class Test06 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public Test06( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "Test06: " ); + } + + public boolean test() + { + try + { + // create temporary storage based on arbitrary medium + // after such a storage is closed it is lost + XStorage xTempStorage = m_aTestHelper.createTempStorage( m_xMSF, m_xStorageFactory ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + try + { + xTempStorage.copyToStorage( null ); + m_aTestHelper.Error( "The method must throw an exception because of illegal parameter!" ); + return false; + } + catch( com.sun.star.lang.IllegalArgumentException iae ) + {} + catch( com.sun.star.uno.Exception ue ) + {} + catch( Exception e ) + { + m_aTestHelper.Error( "Unexpected exception because of illegal parameter : " + e ); + return false; + } + + // open new substorages + XStorage xTempSubStorage1 = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + XStorage xTempSubStorage2 = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage2", + ElementModes.WRITE ); + if ( xTempSubStorage1 == null || xTempSubStorage2 == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // in case stream is open for reading it must exist + try + { + xTempSubStorage1.openStreamElement( "NonExistingStream", ElementModes.READ ); + m_aTestHelper.Error( "The method must throw an exception in case of try to open nonexistent stream for reading!" ); + return false; + } + catch( com.sun.star.uno.Exception ue ) + {} + catch( Exception e ) + { + m_aTestHelper.Error( "Unexpected exception in case of try to open nonexistent stream for reading : " + e ); + return false; + } + + // in case a storage is open for reading it must exist + try + { + xTempSubStorage1.openStreamElement( "NonExistingStorage", ElementModes.READ ); + m_aTestHelper.Error( "The method must throw an exception in case of try to open nonexistent storage for reading!" ); + return false; + } + catch( com.sun.star.uno.Exception ue ) + {} + catch( Exception e ) + { + m_aTestHelper.Error( "Unexpected exception in case of try to open nonexistent storage for reading : " + e ); + return false; + } + + // in case of removing nonexistent element an exception must be thrown + try + { + xTempSubStorage1.removeElement( "NonExistingElement" ); + m_aTestHelper.Error( "An exception must be thrown in case of removing nonexistent element!" ); + return false; + } + catch( com.sun.star.container.NoSuchElementException ne ) + {} + catch( Exception e ) + { + m_aTestHelper.Error( "Unexpected exception in case of try to remove nonexistent element : " + e ); + return false; + } + + // in case of renaming of nonexistent element an exception must be thrown + try + { + xTempSubStorage1.renameElement( "NonExistingElement", "NewName" ); + m_aTestHelper.Error( "An exception must be thrown in case of renaming nonexistent element!" ); + return false; + } + catch( com.sun.star.container.NoSuchElementException ne ) + {} + catch( Exception e ) + { + m_aTestHelper.Error( "Unexpected exception in case of try to rename nonexistent element : " + e ); + return false; + } + + // in case of renaming to a name of existent element an exception must be thrown + try + { + xTempStorage.renameElement( "SubStorage1", "SubStorage2" ); + m_aTestHelper.Error( "An exception must be thrown in case of renaming to the name of existent element!" ); + return false; + } + catch( com.sun.star.container.ElementExistException ee ) + {} + catch( Exception e ) + { + m_aTestHelper.Error( "Unexpected exception in case of try to rename to the name of existent element : " + e ); + return false; + } + + // in case of copying target storage must be provided + try + { + xTempStorage.copyElementTo( "SubStorage1", null, "SubStorage1" ); + m_aTestHelper.Error( "An exception must be thrown in case empty reference is provided as target for copying!" ); + return false; + } + catch( com.sun.star.lang.IllegalArgumentException iae ) + {} + catch( com.sun.star.uno.Exception ue ) + {} + catch( Exception e ) + { + m_aTestHelper.Error( "Unexpected exception in case empty reference is provided as target for copying : " + e ); + return false; + } + + // in case of moving target storage must be provided + try + { + xTempStorage.moveElementTo( "SubStorage1", null, "SubStorage1" ); + m_aTestHelper.Error( "An exception must be thrown in case empty reference is provided as target for moving!" ); + return false; + } + catch( com.sun.star.lang.IllegalArgumentException iae ) + {} + catch( com.sun.star.uno.Exception ue ) + {} + catch( Exception e ) + { + m_aTestHelper.Error( "Unexpected exception in case empty reference is provided as target for moving : " + e ); + return false; + } + + + // prepare target for further testings + + // create new temporary storage based on arbitrary medium + XStorage xTargetStorage = m_aTestHelper.createTempStorage( m_xMSF, m_xStorageFactory ); + if ( xTargetStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open a new substorage + XStorage xTargetSubStorage = m_aTestHelper.openSubStorage( xTargetStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTargetSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // in case of copying of nonexistent element an exception must be thrown + try + { + xTempStorage.copyElementTo( "Nonexistent element", xTargetStorage, "Target" ); + m_aTestHelper.Error( "An exception must be thrown in case of copying of nonexistent element!" ); + return false; + } + catch( com.sun.star.container.NoSuchElementException ne ) + {} + catch( Exception e ) + { + m_aTestHelper.Error( "Unexpected exception in case of copying of nonexistent element: " + e ); + return false; + } + + // in case of moving of nonexistent element an exception must be thrown + try + { + xTempStorage.moveElementTo( "Nonexistent element", xTargetStorage, "Target" ); + m_aTestHelper.Error( "An exception must be thrown in case of moving of nonexistent element!" ); + return false; + } + catch( com.sun.star.container.NoSuchElementException ne ) + {} + catch( Exception e ) + { + m_aTestHelper.Error( "Unexpected exception in case of moving of nonexistent element: " + e ); + return false; + } + + // in case target for copying already exists an exception must be thrown + try + { + xTempStorage.copyElementTo( "SubStorage1", xTargetStorage, "SubStorage1" ); + m_aTestHelper.Error( "An exception must be thrown in case target for copying already exists!" ); + return false; + } + catch( com.sun.star.container.ElementExistException ee ) + {} + catch( Exception e ) + { + m_aTestHelper.Error( "Unexpected exception in case target for copying already exists: " + e ); + return false; + } + + // in case target for moving already exists an exception must be thrown + try + { + xTempStorage.moveElementTo( "SubStorage1", xTargetStorage, "SubStorage1" ); + m_aTestHelper.Error( "An exception must be thrown in case target for moving already exists!" ); + return false; + } + catch( com.sun.star.container.ElementExistException ee ) + {} + catch( Exception e ) + { + m_aTestHelper.Error( "Unexpected exception in case target for moving already exists: " + e ); + return false; + } + + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } + +} + diff --git a/package/qa/ofopxmlstorages/Test07.java b/package/qa/ofopxmlstorages/Test07.java new file mode 100644 index 0000000000..54f9a80b2c --- /dev/null +++ b/package/qa/ofopxmlstorages/Test07.java @@ -0,0 +1,276 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.ofopxmlstorages; + +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; + +import com.sun.star.container.XNameAccess; +import com.sun.star.io.XStream; + +import com.sun.star.embed.*; +import com.sun.star.beans.StringPair; + +import share.LogWriter; +import complex.ofopxmlstorages.TestHelper; +import complex.ofopxmlstorages.StorageTest; + +public class Test07 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public Test07( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "Test07: " ); + } + + public boolean test() + { + StringPair[][] aRelations1 = + { { new StringPair( "Id", "Num1" ) }, + { new StringPair( "Target", "TargetURLValue1" ), new StringPair( "Id", "Num6" ) }, + { new StringPair( "Target", "" ), new StringPair( "Id", "Num7" ) }, + { new StringPair( "Id", "Num2" ), new StringPair( "TargetMode", "Internal1" ), new StringPair( "Type", "unknown1" ), new StringPair( "Target", "URL value 1" ) }, + { new StringPair( "Id", "Num3" ), new StringPair( "TargetMode", "Internal1" ), new StringPair( "Type", "unknown1" ), new StringPair( "Target", "URL value 1" ) }, + { new StringPair( "Id", "Num4" ), new StringPair( "TargetMode", "Internal1" ), new StringPair( "Type", "unknown1" ), new StringPair( "Target", "URL value 1" ) }, + { new StringPair( "Id", "Num5" ), new StringPair( "TargetMode", "" ), new StringPair( "Type", "unknown1" ), new StringPair( "Target", "URL value1" ) } + }; + + StringPair[][] aRelations2 = + { { new StringPair( "Id", "Num1" ) }, + { new StringPair( "Target", "TargetURLValue2" ), new StringPair( "Id", "Num6" ) }, + { new StringPair( "Target", "" ), new StringPair( "Id", "Num7" ) }, + { new StringPair( "Id", "Num2" ), new StringPair( "TargetMode", "Internal2" ), new StringPair( "Type", "unknown2" ), new StringPair( "Target", "URL value 2" ) }, + { new StringPair( "Id", "Num3" ), new StringPair( "TargetMode", "Internal2" ), new StringPair( "Type", "unknown2" ), new StringPair( "Target", "URL value 2" ) }, + { new StringPair( "Id", "Num4" ), new StringPair( "TargetMode", "Internal2" ), new StringPair( "Type", "unknown" ), new StringPair( "Target", "URL value" ) }, + { new StringPair( "Id", "Num5" ), new StringPair( "TargetMode", "" ), new StringPair( "Type", "unknown" ), new StringPair( "Target", "URL value" ) } + }; + + try + { + // create temporary storage based on arbitrary medium + // after such a storage is closed it is lost + XStorage xTempStorage = m_aTestHelper.createTempStorage( m_xMSF, m_xStorageFactory ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempStorage, + "SubStream1", + "MediaType1", + true, + pBytes1, + aRelations1 ) ) + return false; + + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + byte pBytes2[] = { 2, 2, 2, 2, 2 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, + "SubStream2", + "MediaType2", + true, + pBytes2, + aRelations2 ) ) + return false; + + // set Relations for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempStorage, + true, + ElementModes.WRITE, + aRelations2 ) ) + return false; + + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + false, + ElementModes.WRITE, + aRelations2 ) ) + return false; + + + // check cloning at current state + + + // the new storage still was not committed so the clone must be empty + XStorage xClonedSubStorage = m_aTestHelper.cloneSubStorage( m_xMSF, m_xStorageFactory, xTempStorage, "SubStorage1" ); + + if ( xClonedSubStorage == null ) + { + m_aTestHelper.Error( "The result of clone is empty!" ); + return false; + } + + XNameAccess xClonedNameAccess = (XNameAccess) UnoRuntime.queryInterface( XNameAccess.class, xClonedSubStorage ); + if ( xClonedNameAccess == null ) + { + m_aTestHelper.Error( "XNameAccess is not implemented by the clone!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xClonedSubStorage, + true, + ElementModes.WRITE, + new StringPair[0][0] ) ) + return false; + + if ( xClonedNameAccess.hasElements() ) + { + m_aTestHelper.Error( "The new substorage still was not committed so it must be empty!" ); + return false; + } + + if ( !m_aTestHelper.disposeStorage( xClonedSubStorage ) ) + return false; + + xClonedSubStorage = null; + xClonedNameAccess = null; + + // the new stream was opened, written and closed, that means flashed + // so the clone must contain all the information + XStream xClonedSubStream = m_aTestHelper.cloneSubStream( xTempStorage, "SubStream1" ); + if ( !m_aTestHelper.InternalCheckStream( xClonedSubStream, + "SubStream1", + "MediaType1", + pBytes1, + aRelations1 ) ) + return false; + + if ( !m_aTestHelper.disposeStream( xClonedSubStream, "SubStream1" ) ) + return false; + + + // commit substorage and check cloning + + + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + xClonedSubStorage = m_aTestHelper.cloneSubStorage( m_xMSF, m_xStorageFactory, xTempStorage, "SubStorage1" ); + if ( xClonedSubStorage == null ) + { + m_aTestHelper.Error( "The result of clone is empty!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xClonedSubStorage, + true, + ElementModes.WRITE, + aRelations2 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xClonedSubStorage, + "SubStream2", + "MediaType2", + pBytes2, + aRelations2 ) ) + return false; + + XStorage xCloneOfRoot = m_aTestHelper.cloneStorage( m_xMSF, m_xStorageFactory, xTempStorage ); + if ( xCloneOfRoot == null ) + { + m_aTestHelper.Error( "The result of root clone is empty!" ); + return false; + } + + XNameAccess xCloneOfRootNA = (XNameAccess) UnoRuntime.queryInterface( XNameAccess.class, xCloneOfRoot ); + if ( xCloneOfRootNA == null ) + { + m_aTestHelper.Error( "XNameAccess is not implemented by the root clone!" ); + return false; + } + + if ( xCloneOfRootNA.hasElements() ) + { + m_aTestHelper.Error( "The root storage still was not committed so it's clone must be empty!" ); + return false; + } + + if ( !m_aTestHelper.disposeStorage( xCloneOfRoot ) ) + return false; + + xCloneOfRoot = null; + + + // commit root storage and check cloning + + + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + xCloneOfRoot = m_aTestHelper.cloneStorage( m_xMSF, m_xStorageFactory, xTempStorage ); + if ( xCloneOfRoot == null ) + { + m_aTestHelper.Error( "The result of root clone is empty!" ); + return false; + } + + XStorage xSubStorageOfClone = xCloneOfRoot.openStorageElement( "SubStorage1", ElementModes.READ ); + if ( xSubStorageOfClone == null ) + { + m_aTestHelper.Error( "The result of root clone is wrong!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xSubStorageOfClone, + false, + ElementModes.READ, + aRelations2 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xSubStorageOfClone, + "SubStream2", + "MediaType2", + pBytes2, + aRelations2 ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } +} + diff --git a/package/qa/ofopxmlstorages/Test08.java b/package/qa/ofopxmlstorages/Test08.java new file mode 100644 index 0000000000..a2607141f9 --- /dev/null +++ b/package/qa/ofopxmlstorages/Test08.java @@ -0,0 +1,279 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.ofopxmlstorages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.io.XStream; +import com.sun.star.io.XInputStream; + +import com.sun.star.embed.*; +import com.sun.star.beans.StringPair; + +import share.LogWriter; +import complex.ofopxmlstorages.TestHelper; +import complex.ofopxmlstorages.StorageTest; + +public class Test08 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public Test08( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "Test08: " ); + } + + public boolean test() + { + StringPair[][] aRelations1 = + { { new StringPair( "Id", "Num1" ) }, + { new StringPair( "Target", "TargetURLValue1" ), new StringPair( "Id", "Num6" ) }, + { new StringPair( "Target", "" ), new StringPair( "Id", "Num7" ) }, + { new StringPair( "Id", "Num2" ), new StringPair( "TargetMode", "Internal1" ), new StringPair( "Type", "unknown1" ), new StringPair( "Target", "URL value 1" ) }, + { new StringPair( "Id", "Num3" ), new StringPair( "TargetMode", "Internal1" ), new StringPair( "Type", "unknown1" ), new StringPair( "Target", "URL value 1" ) }, + { new StringPair( "Id", "Num4" ), new StringPair( "TargetMode", "Internal1" ), new StringPair( "Type", "unknown1" ), new StringPair( "Target", "URL value 1" ) }, + { new StringPair( "Id", "Num5" ), new StringPair( "TargetMode", "" ), new StringPair( "Type", "unknown1" ), new StringPair( "Target", "URL value1" ) } + }; + + try + { + XStream xTempFileStream = m_aTestHelper.CreateTempFileStream( m_xMSF ); + if ( xTempFileStream == null ) + return false; + + // create storage based on the temporary stream + XStorage xTempStorage = m_aTestHelper.createStorageFromStream( m_xStorageFactory, + xTempFileStream, + ElementModes.WRITE ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, + "SubStream1", + "MediaType1", + true, + pBytes1, + aRelations1 ) ) + return false; + + // set Relations for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempStorage, + true, + ElementModes.WRITE, + aRelations1 ) ) + return false; + + // set Relations for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + false, + ElementModes.WRITE, + aRelations1 ) ) + return false; + + // commit substorage first + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + // dispose substorage + if ( !m_aTestHelper.disposeStorage( xTempSubStorage ) ) + return false; + + + // check substorage + + + if ( !checkSubStorages( xTempStorage, pBytes1, aRelations1 ) ) + return false; + + // dispose used storage to free resources + if ( !m_aTestHelper.disposeStorage( xTempStorage ) ) + return false; + + + // now check all the written information with readwrite access + + + XStorage xResWriteStorage = m_aTestHelper.createStorageFromStream( m_xStorageFactory, + xTempFileStream, + ElementModes.WRITE ); + if ( xResWriteStorage == null ) + { + m_aTestHelper.Error( "Can't open storage based on input stream!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResWriteStorage, + true, + ElementModes.WRITE, + aRelations1 ) ) + return false; + + if( !checkSubStorages( xResWriteStorage, pBytes1, aRelations1 ) ) + return false; + + // try to open for writing after opening for reading + XStorage xResWSubStorage = m_aTestHelper.openSubStorage( xResWriteStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xResWSubStorage == null ) + { + m_aTestHelper.Error( "Can't open substorage for writing after it was opened for reading!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResWSubStorage, + false, + ElementModes.WRITE, + aRelations1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResWSubStorage, + "SubStream1", + "MediaType1", + pBytes1, + aRelations1 ) ) + return false; + + // dispose used storage to free resources + if ( !m_aTestHelper.disposeStorage( xResWriteStorage ) ) + return false; + + + + // now check all the written information with readonly access + + + // close the output part of the temporary stream + // the output part must present since we already wrote to the stream + if ( !m_aTestHelper.closeOutput( xTempFileStream ) ) + return false; + + XInputStream xTempInStream = m_aTestHelper.getInputStream( xTempFileStream ); + if ( xTempInStream == null ) + return false; + + // open input stream + // since no mode is provided the result storage must be opened readonly + XStorage xResultStorage = m_aTestHelper.createStorageFromInputStream( m_xStorageFactory, + xTempInStream ); + if ( xResultStorage == null ) + { + m_aTestHelper.Error( "Can't open storage based on input stream!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultStorage, + true, + ElementModes.READ, + aRelations1 ) ) + return false; + + if( !checkSubStorages( xResultStorage, pBytes1, aRelations1 ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } + + private boolean checkSubStorages( XStorage xStorage, byte[] pBytes1, StringPair[][] aRelations ) + { + XStorage xReadSubStorage1 = m_aTestHelper.openSubStorage( xStorage, + "SubStorage1", + ElementModes.READ ); + + XStorage xReadSubStorage2 = m_aTestHelper.openSubStorage( xStorage, + "SubStorage1", + ElementModes.READ ); + + if ( xReadSubStorage1 == null || xReadSubStorage2 == null ) + { + m_aTestHelper.Error( "Can't open substorage for reading!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xReadSubStorage1, + false, + ElementModes.READ, + aRelations ) ) + return false; + + if ( !m_aTestHelper.checkStorageProperties( xReadSubStorage2, + false, + ElementModes.READ, + aRelations ) ) + return false; + + if ( !m_aTestHelper.checkStream( xReadSubStorage1, + "SubStream1", + "MediaType1", + pBytes1, + aRelations ) ) + return false; + + if ( !m_aTestHelper.checkStream( xReadSubStorage2, + "SubStream1", + "MediaType1", + pBytes1, + aRelations ) ) + return false; + + if ( !m_aTestHelper.disposeStorage( xReadSubStorage1 ) ) + return false; + + if ( !m_aTestHelper.disposeStorage( xReadSubStorage2 ) ) + return false; + + return true; + } +} + diff --git a/package/qa/ofopxmlstorages/TestHelper.java b/package/qa/ofopxmlstorages/TestHelper.java new file mode 100644 index 0000000000..b7f42ea7fd --- /dev/null +++ b/package/qa/ofopxmlstorages/TestHelper.java @@ -0,0 +1,1111 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.ofopxmlstorages; + +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.uno.AnyConverter; + +import com.sun.star.lang.*; +import com.sun.star.embed.*; +import com.sun.star.packages.*; +import com.sun.star.io.*; +import com.sun.star.beans.*; + +import share.LogWriter; + +public class TestHelper { + + LogWriter m_aLogWriter; + String m_sTestPrefix; + + public TestHelper( LogWriter aLogWriter, String sTestPrefix ) + { + m_aLogWriter = aLogWriter; + m_sTestPrefix = sTestPrefix; + } + + public boolean WriteBytesToStream( XStream xStream, + String sStreamName, + String sMediaType, + boolean bCompressed, + byte[] pBytes, + StringPair[][] aRelations ) + { + // get output stream of substream + XOutputStream xOutput = xStream.getOutputStream(); + if ( xOutput == null ) + { + Error( "Can't get XOutputStream implementation from substream '" + sStreamName + "'!" ); + return false; + } + + // get XTruncate implementation from output stream + XTruncate xTruncate = (XTruncate) UnoRuntime.queryInterface( XTruncate.class, xOutput ); + if ( xTruncate == null ) + { + Error( "Can't get XTruncate implementation from substream '" + sStreamName + "'!" ); + return false; + } + + // write requested byte sequence + try + { + xTruncate.truncate(); + xOutput.writeBytes( pBytes ); + } + catch( Exception e ) + { + Error( "Can't write to stream '" + sStreamName + "', exception: " + e ); + return false; + } + + // get access to the XPropertySet interface + XPropertySet xPropSet = (XPropertySet) UnoRuntime.queryInterface( XPropertySet.class, xStream ); + if ( xPropSet == null ) + { + Error( "Can't get XPropertySet implementation from substream '" + sStreamName + "'!" ); + return false; + } + + // set properties to the stream + try + { + xPropSet.setPropertyValue( "MediaType", sMediaType ); + xPropSet.setPropertyValue( "Compressed", Boolean.valueOf( bCompressed ) ); + } + catch( Exception e ) + { + Error( "Can't set properties to substream '" + sStreamName + "', exception: " + e ); + return false; + } + + // check size property of the stream + try + { + long nSize = AnyConverter.toLong( xPropSet.getPropertyValue( "Size" ) ); + if ( nSize != pBytes.length ) + { + Error( "The 'Size' property of substream '" + sStreamName + "' contains wrong value!" ); + return false; + } + } + catch( Exception e ) + { + Error( "Can't get 'Size' property from substream '" + sStreamName + "', exception: " + e ); + return false; + } + + // get access to the relationship information + XRelationshipAccess xRelAccess = (XRelationshipAccess) UnoRuntime.queryInterface( XRelationshipAccess.class, xStream ); + if ( xRelAccess == null ) + { + Error( "Can't get XRelationshipAccess implementation from substream '" + sStreamName + "'!" ); + return false; + } + + // set the relationship information + try + { + xRelAccess.insertRelationships( aRelations, false ); + } + catch( Exception e ) + { + Error( "Can't set relationships to substream '" + sStreamName + "', exception: " + e ); + return false; + } + + // free the stream resources, garbage collector may remove the object too late + if ( !disposeStream( xStream, sStreamName ) ) + return false; + + return true; + } + + public boolean WriteBytesToSubstream( XStorage xStorage, + String sStreamName, + String sMediaType, + boolean bCompressed, + byte[] pBytes, + StringPair[][] aRelations ) + { + // open substream element + XStream xSubStream = null; + try + { + Object oSubStream = xStorage.openStreamElement( sStreamName, ElementModes.WRITE ); + xSubStream = (XStream) UnoRuntime.queryInterface( XStream.class, oSubStream ); + if ( xSubStream == null ) + { + Error( "Can't create substream '" + sStreamName + "'!" ); + return false; + } + } + catch( Exception e ) + { + Error( "Can't create substream '" + sStreamName + "', exception : " + e + "!" ); + return false; + } + + return WriteBytesToStream( xSubStream, sStreamName, sMediaType, bCompressed, pBytes, aRelations ); + } + + public boolean setStorageTypeAndCheckProps( XStorage xStorage, + boolean bIsRoot, + int nMode, + StringPair[][] aRelations ) + { + boolean bOk = false; + + // get access to the XPropertySet interface + XPropertySet xPropSet = (XPropertySet) UnoRuntime.queryInterface( XPropertySet.class, xStorage ); + if ( xPropSet != null ) + { + try + { + // get "IsRoot" and "OpenMode" properties and control there values + boolean bPropIsRoot = AnyConverter.toBoolean( xPropSet.getPropertyValue( "IsRoot" ) ); + int nPropMode = AnyConverter.toInt( xPropSet.getPropertyValue( "OpenMode" ) ); + + bOk = true; + if ( bPropIsRoot != bIsRoot ) + { + Error( "'IsRoot' property contains wrong value!" ); + bOk = false; + } + + if ( ( bIsRoot + && ( nPropMode | ElementModes.READ ) != ( nMode | ElementModes.READ ) ) + || ( !bIsRoot && ( nPropMode & nMode ) != nMode ) ) + { + Error( "'OpenMode' property contains wrong value, expected " + nMode + ", in reality " + nPropMode + "!" ); + bOk = false; + } + } + catch( Exception e ) + { + Error( "Can't control properties of substorage, exception: " + e ); + } + } + else + { + Error( "Can't get XPropertySet implementation from storage!" ); + } + + // get access to the relationship information + XRelationshipAccess xRelAccess = (XRelationshipAccess) UnoRuntime.queryInterface( XRelationshipAccess.class, xStorage ); + + if ( xRelAccess == null ) + { + Error( "Can't get XRelationshipAccess implementation from the storage!" ); + return false; + } + + // set the relationship information + try + { + xRelAccess.insertRelationships( aRelations, false ); + } + catch( Exception e ) + { + Error( "Can't set relationships to the storage, exception: " + e ); + return false; + } + + + return bOk; + } + + public boolean checkRelations( StringPair[][] aStorRels, StringPair[][] aTestRels ) + { + if ( aStorRels.length != aTestRels.length ) + { + Error( "The provided relations sequence has different size than the storage's one!" ); + return false; + } + + for ( int nStorInd = 0; nStorInd < aStorRels.length; nStorInd++ ) + { + int nStorIDInd = -1; + for ( int nStorTagInd = 0; nStorTagInd < aStorRels[nStorInd].length; nStorTagInd++ ) + { + if ( aStorRels[nStorInd][nStorTagInd].First.equals( "Id" ) ) + { + nStorIDInd = nStorTagInd; + break; + } + } + + if ( nStorIDInd == -1 ) + { + Error( "One of the storage relations entries has no ID!" ); + return false; + } + + for ( int nInd = 0; nInd < aTestRels.length; nInd++ ) + { + int nIDInd = -1; + for ( int nTagInd = 0; nTagInd < aTestRels[nInd].length; nTagInd++ ) + { + if ( aTestRels[nInd][nTagInd].First.equals( "Id" ) ) + { + nIDInd = nTagInd; + break; + } + } + + if ( nIDInd == -1 ) + { + Error( "One of the test hardcoded entries has no ID, num = " + nInd + ", length = " + aTestRels[nInd].length + ", global length = " + aTestRels.length + "!" ); + return false; + } + + if ( aStorRels[nStorInd][nStorIDInd].Second.equals( aTestRels[nInd][nIDInd].Second ) ) + { + boolean[] pRelCheckMark = new boolean[ aTestRels[nInd].length ]; + for ( int nCheckInd = 0; nCheckInd < pRelCheckMark.length; nCheckInd++ ) + { + pRelCheckMark[nCheckInd] = false; + } + + for ( int nStorTagInd = 0; nStorTagInd < aStorRels[nStorInd].length; nStorTagInd++ ) + { + boolean bFound = false; + for ( int nTagInd = 0; nTagInd < aTestRels[nInd].length; nTagInd++ ) + { + if ( aTestRels[nInd][nTagInd].First.equals( aStorRels[nStorInd][nStorTagInd].First ) ) + { + if ( !aTestRels[nInd][nTagInd].Second.equals( aStorRels[nStorInd][nStorTagInd].Second ) ) + { + Error( "Test rel. num. " + nInd + " has different tag \"" + aTestRels[nInd][nTagInd].First + "\" value!" ); + return false; + } + + bFound = true; + pRelCheckMark[nTagInd] = true; + break; + } + } + + if ( !bFound ) + { + Error( "Stor rel. num. " + nStorInd + " has unexpected tag \"" + aStorRels[nStorInd][nStorTagInd].First + "\", ID = \"" + aStorRels[nStorInd][nStorIDInd].Second + "\"!" ); + return false; + } + } + + for ( int nCheckInd = 0; nCheckInd < pRelCheckMark.length; nCheckInd++ ) + { + if ( !pRelCheckMark[nCheckInd] && !aTestRels[nInd][nCheckInd].Second.equals( "" ) ) + { + Error( "Test rel. num. " + nInd + " has unexpected tag \"" + aTestRels[nInd][nCheckInd].First + "\" with nonempty value!" ); + return false; + } + } + + break; + } + } + } + + return true; + } + + public boolean checkStorageProperties( XStorage xStorage, + boolean bIsRoot, + int nMode, + StringPair[][] aRelations ) + { + boolean bOk = false; + + // get access to the XPropertySet interface + XPropertySet xPropSet = (XPropertySet) UnoRuntime.queryInterface( XPropertySet.class, xStorage ); + if ( xPropSet != null ) + { + try + { + // get "IsRoot" and "OpenMode" properties and control there values + boolean bPropIsRoot = AnyConverter.toBoolean( xPropSet.getPropertyValue( "IsRoot" ) ); + int nPropMode = AnyConverter.toInt( xPropSet.getPropertyValue( "OpenMode" ) ); + + bOk = true; + if ( bPropIsRoot != bIsRoot ) + { + Error( "'IsRoot' property contains wrong value!" ); + bOk = false; + } + + if ( ( bIsRoot + && ( nPropMode | ElementModes.READ ) != ( nMode | ElementModes.READ ) ) + || ( !bIsRoot && ( nPropMode & nMode ) != nMode ) ) + { + Error( "'OpenMode' property contains wrong value, expected " + nMode + ", in reality " + nPropMode + "!" ); + bOk = false; + } + } + catch( Exception e ) + { + Error( "Can't get properties of substorage, exception: " + e ); + } + } + else + { + Error( "Can't get XPropertySet implementation from storage!" ); + } + + // get access to the relationship information + XRelationshipAccess xRelAccess = (XRelationshipAccess) UnoRuntime.queryInterface( XRelationshipAccess.class, xStorage ); + + if ( xRelAccess == null ) + { + Error( "Can't get XRelationshipAccess implementation from the checked storage!" ); + return false; + } + + // get the relationship information + StringPair[][] aStorRels; + try + { + aStorRels = xRelAccess.getAllRelationships(); + } + catch( Exception e ) + { + Error( "Can't get relationships of the checked storage, exception: " + e ); + return false; + } + + if ( !checkRelations( aStorRels, aRelations ) ) + { + Error( "StorageRelationsChecking has failed!" ); + return false; + } + + return bOk; + } + + public boolean InternalCheckStream( XStream xStream, + String sName, + String sMediaType, + byte[] pBytes, + StringPair[][] aRelations ) + { + // get input stream of substream + XInputStream xInput = xStream.getInputStream(); + if ( xInput == null ) + { + Error( "Can't get XInputStream implementation from substream '" + sName + "'!" ); + return false; + } + + byte pContents[][] = new byte[1][]; // ??? + + // read contents + try + { + xInput.readBytes( pContents, pBytes.length + 1 ); + } + catch( Exception e ) + { + Error( "Can't read from stream '" + sName + "', exception: " + e ); + return false; + } + + // check size of stream data + if ( pContents.length == 0 ) + { + Error( "SubStream '" + sName + "' reading produced disaster!" ); + return false; + } + + if ( pBytes.length != pContents[0].length ) + { + Error( "SubStream '" + sName + "' contains wrong amount of data! (" + pContents[0].length + "/" + pBytes.length + ")" ); + return false; + } + + // check stream data + for ( int ind = 0; ind < pBytes.length; ind++ ) + { + if ( pBytes[ind] != pContents[0][ind] ) + { + Error( "SubStream '" + sName + "' contains wrong data! ( byte num. " + + ind + " should be" + pBytes[ind] + " but it is " + pContents[0][ind] + ")" ); + return false; + } + } + + // check properties + boolean bOk = false; + + // get access to the XPropertySet interface + XPropertySet xPropSet = (XPropertySet) UnoRuntime.queryInterface( XPropertySet.class, xStream ); + if ( xPropSet != null ) + { + try + { + // get "MediaType" and "Size" properties and control there values + String sPropMediaType = AnyConverter.toString( xPropSet.getPropertyValue( "MediaType" ) ); + long nPropSize = AnyConverter.toLong( xPropSet.getPropertyValue( "Size" ) ); + + bOk = true; + if ( !sPropMediaType.equals( sMediaType ) ) + { + Error( "'MediaType' property contains wrong value for stream '" + sName + "',\nexpected: '" + + sMediaType + "', set: '" + sPropMediaType + "'!" ); + bOk = false; + } + + if ( nPropSize != pBytes.length ) + { + Error( "'Size' property contains wrong value for stream'" + sName + "'!" ); + bOk = false; + } + } + catch( Exception e ) + { + Error( "Can't get properties of substream '" + sName + "', exception: " + e ); + } + } + else + { + Error( "Can't get XPropertySet implementation from stream '" + sName + "'!" ); + } + + + // get access to the relationship information + XRelationshipAccess xRelAccess = (XRelationshipAccess) UnoRuntime.queryInterface( XRelationshipAccess.class, xStream ); + + if ( xRelAccess == null ) + { + Error( "Can't get XRelationshipAccess implementation from the stream\"" + sName + "\"!" ); + return false; + } + + // get the relationship information + StringPair[][] aStorRels; + try + { + aStorRels = xRelAccess.getAllRelationships(); + } + catch( Exception e ) + { + Error( "Can't get relationships of the substream '" + sName + "', exception: " + e ); + return false; + } + + if ( !checkRelations( aStorRels, aRelations ) ) + { + Error( "Stream '" + sName + "' RelationsChecking has failed!" ); + return false; + } + + return bOk; + } + + public boolean checkStream( XStorage xParentStorage, + String sName, + String sMediaType, + byte[] pBytes, + StringPair[][] aRelations ) + { + // open substream element first + XStream xSubStream = null; + try + { + Object oSubStream = xParentStorage.openStreamElement( sName, ElementModes.READ ); + xSubStream = (XStream) UnoRuntime.queryInterface( XStream.class, oSubStream ); + if ( xSubStream == null ) + { + Error( "Can't open substream '" + sName + "'!" ); + return false; + } + } + catch( Exception e ) + { + Error( "Can't open substream '" + sName + "', exception : " + e + "!" ); + return false; + } + + boolean bResult = InternalCheckStream( xSubStream, sName, sMediaType, pBytes, aRelations ); + + // free the stream resources, garbage collector may remove the object too late + if ( !disposeStream( xSubStream, sName ) ) + return false; + + return bResult; + } + + public boolean copyStorage( XStorage xSourceStorage, XStorage xDestStorage ) + { + // copy xSourceStorage to xDestStorage + try + { + xSourceStorage.copyToStorage( xDestStorage ); + } + catch( Exception e ) + { + Error( "Storage copying failed, exception: " + e ); + return false; + } + + return true; + } + + public boolean commitStorage( XStorage xStorage ) + { + // XTransactedObject must be supported by storages + XTransactedObject xTransact = (XTransactedObject) UnoRuntime.queryInterface( XTransactedObject.class, xStorage ); + if ( xTransact == null ) + { + Error( "Storage doesn't implement transacted access!" ); + return false; + } + + try + { + xTransact.commit(); + } + catch( Exception e ) + { + Error( "Storage commit failed, exception:" + e ); + return false; + } + + return true; + } + + public boolean disposeStream( XStream xStream, String sStreamName ) + { + XComponent xComponent = (XComponent) UnoRuntime.queryInterface( XComponent.class, xStream ); + if ( xComponent == null ) + { + Error( "Can't get XComponent implementation from substream '" + sStreamName + "'!" ); + return false; + } + + try + { + xComponent.dispose(); + } + catch( Exception e ) + { + Error( "Substream '" + sStreamName + "' disposing throws exception: " + e ); + return false; + } + + return true; + } + + public boolean disposeStorage( XStorage xStorage ) + { + // dispose the storage + XComponent xComponent = (XComponent) UnoRuntime.queryInterface( XComponent.class, xStorage ); + if ( xComponent == null ) + { + Error( "Can't retrieve XComponent implementation from storage!" ); + return false; + } + + try + { + xComponent.dispose(); + } + catch( Exception e ) + { + Error( "Storage disposing failed!" ); + return false; + } + + return true; + } + + public XInputStream getInputStream( XStream xStream ) + { + XInputStream xInTemp = null; + try + { + xInTemp = xStream.getInputStream(); + if ( xInTemp == null ) + Error( "Can't get the input part of a stream!" ); + } + catch ( Exception e ) + { + Error( "Can't get the input part of a stream, exception :" + e ); + } + + return xInTemp; + } + + public boolean closeOutput( XStream xStream ) + { + XOutputStream xOutTemp = null; + try + { + xOutTemp = xStream.getOutputStream(); + if ( xOutTemp == null ) + { + Error( "Can't get the output part of a stream!" ); + return false; + } + } + catch ( Exception e ) + { + Error( "Can't get the output part of a stream, exception :" + e ); + return false; + } + + try + { + xOutTemp.closeOutput(); + } + catch ( Exception e ) + { + Error( "Can't close output part of a stream, exception :" + e ); + return false; + } + + return true; + } + + public XStorage openSubStorage( XStorage xStorage, String sName, int nMode ) + { + // open existing substorage + try + { + Object oSubStorage = xStorage.openStorageElement( sName, nMode ); + XStorage xSubStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oSubStorage ); + return xSubStorage; + } + catch( Exception e ) + { + Error( "Can't open substorage '" + sName + "', exception: " + e ); + } + + return null; + } + + public XStream CreateTempFileStream( XMultiServiceFactory xMSF ) + { + // try to get temporary file representation + XStream xTempFileStream = null; + try + { + Object oTempFile = xMSF.createInstance( "com.sun.star.io.TempFile" ); + xTempFileStream = (XStream)UnoRuntime.queryInterface( XStream.class, oTempFile ); + } + catch( Exception e ) + {} + + if ( xTempFileStream == null ) + Error( "Can't create temporary file!" ); + + return xTempFileStream; + } + + public String CreateTempFile( XMultiServiceFactory xMSF ) + { + String sResult = null; + + // try to get temporary file representation + XPropertySet xTempFileProps = null; + try + { + Object oTempFile = xMSF.createInstance( "com.sun.star.io.TempFile" ); + xTempFileProps = (XPropertySet)UnoRuntime.queryInterface( XPropertySet.class, oTempFile ); + } + catch( Exception e ) + {} + + if ( xTempFileProps != null ) + { + try + { + xTempFileProps.setPropertyValue( "RemoveFile", Boolean.FALSE ); + sResult = AnyConverter.toString( xTempFileProps.getPropertyValue( "Uri" ) ); + } + catch( Exception e ) + { + Error( "Can't control TempFile properties, exception: " + e ); + } + } + else + { + Error( "Can't create temporary file representation!" ); + } + + // close temporary file explicitly + try + { + XStream xStream = (XStream)UnoRuntime.queryInterface( XStream.class, xTempFileProps ); + if ( xStream != null ) + { + XOutputStream xOut = xStream.getOutputStream(); + if ( xOut != null ) + xOut.closeOutput(); + + XInputStream xIn = xStream.getInputStream(); + if ( xIn != null ) + xIn.closeInput(); + } + else + Error( "Can't close TempFile!" ); + } + catch( Exception e ) + { + Error( "Can't close TempFile, exception: " + e ); + } + + return sResult; + } + + public boolean copyElementTo( XStorage xSource, String sName, XStorage xDest ) + { + // copy element with name sName from xSource to xDest + try + { + xSource.copyElementTo( sName, xDest, sName ); + } + catch( Exception e ) + { + Error( "Element copying failed, exception: " + e ); + return false; + } + + return true; + } + + public boolean copyElementTo( XStorage xSource, String sName, XStorage xDest, String sTargetName ) + { + // copy element with name sName from xSource to xDest + try + { + xSource.copyElementTo( sName, xDest, sTargetName ); + } + catch( Exception e ) + { + Error( "Element copying failed, exception: " + e ); + return false; + } + + return true; + } + + public boolean moveElementTo( XStorage xSource, String sName, XStorage xDest ) + { + // move element with name sName from xSource to xDest + try + { + xSource.moveElementTo( sName, xDest, sName ); + } + catch( Exception e ) + { + Error( "Element moving failed, exception: " + e ); + return false; + } + + return true; + } + + public boolean renameElement( XStorage xStorage, String sOldName, String sNewName ) + { + // rename element with name sOldName to sNewName + try + { + xStorage.renameElement( sOldName, sNewName ); + } + catch( Exception e ) + { + Error( "Element renaming failed, exception: " + e ); + return false; + } + + return true; + } + + public boolean removeElement( XStorage xStorage, String sName ) + { + // remove element with name sName + try + { + xStorage.removeElement( sName ); + } + catch( Exception e ) + { + Error( "Element removing failed, exception: " + e ); + return false; + } + + return true; + } + + public XStream OpenStream( XStorage xStorage, + String sStreamName, + int nMode ) + { + // open substream element + XStream xSubStream = null; + try + { + Object oSubStream = xStorage.openStreamElement( sStreamName, nMode ); + xSubStream = (XStream) UnoRuntime.queryInterface( XStream.class, oSubStream ); + if ( xSubStream == null ) + Error( "Can't create substream '" + sStreamName + "'!" ); + } + catch( Exception e ) + { + Error( "Can't create substream '" + sStreamName + "', exception : " + e + "!" ); + } + + return xSubStream; + } + + public boolean cantOpenStorage( XStorage xStorage, String sName ) + { + // try to open an opened substorage, open call must fail + try + { + Object oDummyStorage = xStorage.openStorageElement( sName, ElementModes.READ ); + Error( "The trying to reopen opened substorage '" + sName + "' must fail!" ); + } + catch( Exception e ) + { + return true; + } + + return false; + } + + public boolean cantOpenStream( XStorage xStorage, String sName, int nMode ) + { + // try to open the substream with specified mode must fail + try + { + Object oDummyStream = xStorage.openStreamElement( sName, nMode ); + Error( "The trying to open substream '" + sName + "' must fail!" ); + } + catch( Exception e ) + { + return true; + } + + return false; + } + + public XStorage createStorageFromURL( + XSingleServiceFactory xFactory, + String aURL, + int nMode ) + { + XStorage xResult = null; + + try + { + PropertyValue[] aAddArgs = new PropertyValue[1]; + aAddArgs[0] = new PropertyValue(); + aAddArgs[0].Name = "StorageFormat"; + aAddArgs[0].Value = "OFOPXMLFormat"; + + Object pArgs[] = new Object[3]; + pArgs[0] = (Object) aURL; + pArgs[1] = Integer.valueOf( nMode ); + pArgs[2] = (Object) aAddArgs; + + Object oTempStorage = xFactory.createInstanceWithArguments( pArgs ); + xResult = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + } + catch( Exception e ) + { + Error( "Can't create storage from URL, exception: " + e ); + return null; + } + + if ( xResult == null ) + Error( "Can't create storage from URL!" ); + + return xResult; + } + + public XStorage createStorageFromStream( + XSingleServiceFactory xFactory, + XStream xStream, + int nMode ) + { + XStorage xResult = null; + + try + { + PropertyValue[] aAddArgs = new PropertyValue[1]; + aAddArgs[0] = new PropertyValue(); + aAddArgs[0].Name = "StorageFormat"; + aAddArgs[0].Value = "OFOPXMLFormat"; + + Object pArgs[] = new Object[3]; + pArgs[0] = (Object) xStream; + pArgs[1] = Integer.valueOf( nMode ); + pArgs[2] = (Object) aAddArgs; + + Object oTempStorage = xFactory.createInstanceWithArguments( pArgs ); + xResult = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + } + catch( Exception e ) + { + Error( "Can't create storage from stream, exception: " + e ); + return null; + } + + if ( xResult == null ) + Error( "Can't create storage from stream!" ); + + return xResult; + } + + public XStorage createStorageFromInputStream( + XSingleServiceFactory xFactory, + XInputStream xInStream ) + { + XStorage xResult = null; + + try + { + PropertyValue[] aAddArgs = new PropertyValue[1]; + aAddArgs[0] = new PropertyValue(); + aAddArgs[0].Name = "StorageFormat"; + aAddArgs[0].Value = "OFOPXMLFormat"; + + Object pArgs[] = new Object[3]; + pArgs[0] = (Object) xInStream; + pArgs[1] = Integer.valueOf( ElementModes.READ ); + pArgs[2] = (Object) aAddArgs; + + Object oTempStorage = xFactory.createInstanceWithArguments( pArgs ); + xResult = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + } + catch( Exception e ) + { + Error( "Can't create storage from input stream, exception: " + e ); + return null; + } + + if ( xResult == null ) + Error( "Can't create storage from input stream!" ); + + return xResult; + } + + public XStorage createTempStorage( XMultiServiceFactory xMSF, XSingleServiceFactory xFactory ) + { + // create a temporary storage + XStorage xResult = null; + XStream xStream = CreateTempFileStream( xMSF ); + if ( xStream == null ) + { + Error( "Can't create temp file stream!" ); + return null; + } + + try + { + xResult = createStorageFromStream( xFactory, xStream, ElementModes.WRITE ); + } + catch( Exception e ) + { + Error( "Can't create temp storage, exception: " + e ); + } + + return xResult; + } + + public XStorage cloneStorage( XMultiServiceFactory xMSF, XSingleServiceFactory xFactory, XStorage xStorage ) + { + // create a copy of a last committed version of specified storage + XStorage xResult = null; + try + { + xResult = createTempStorage( xMSF, xFactory ); + if ( xResult != null ) + xStorage.copyLastCommitTo( xResult ); + } + catch( Exception e ) + { + Error( "Can't clone storage, exception: " + e ); + return null; + } + + return xResult; + } + + public XStorage cloneSubStorage( XMultiServiceFactory xMSF, XSingleServiceFactory xFactory, XStorage xStorage, String sName ) + { + // create a copy of a last committed version of specified substorage + XStorage xResult = null; + try + { + xResult = createTempStorage( xMSF, xFactory ); + if ( xResult != null ) + xStorage.copyStorageElementLastCommitTo( sName, xResult ); + } + catch( Exception e ) + { + Error( "Can't clone substorage '" + sName + "', exception: " + e ); + return null; + } + + return xResult; + } + + public XStream cloneSubStream( XStorage xStorage, String sName ) + { + // clone existing substream + try + { + XStream xStream = xStorage.cloneStreamElement( sName ); + return xStream; + } + catch( Exception e ) + { + Error( "Can't clone substream '" + sName + "', exception: " + e ); + } + + return null; + } + + public void Error( String sError ) + { + m_aLogWriter.println( m_sTestPrefix + "Error: " + sError ); + } + + public void Message( String sMessage ) + { + m_aLogWriter.println( m_sTestPrefix + sMessage ); + } + + public void PrintRelations( StringPair[][] aRels ) + { + m_aLogWriter.println( "========" ); + for ( int nInd1 = 0; nInd1 < aRels.length; nInd1++ ) + { + for ( int nInd2 = 0; nInd2 < aRels[nInd1].length; nInd2++ ) + { + m_aLogWriter.println( "\"" + aRels[nInd1][nInd2].First + "\" = \"" + aRels[nInd1][nInd2].Second + "\", " ); + } + m_aLogWriter.println( "========" ); + } + } +} + diff --git a/package/qa/ofopxmlstorages/makefile.mk b/package/qa/ofopxmlstorages/makefile.mk new file mode 100644 index 0000000000..5cf93e98e0 --- /dev/null +++ b/package/qa/ofopxmlstorages/makefile.mk @@ -0,0 +1,82 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +PRJ = ..$/.. +TARGET = StorageUnitTest +PRJNAME = package +PACKAGE = complex$/ofopxmlstorages + +# --- Settings ----------------------------------------------------- +.INCLUDE: settings.mk + + +#----- compile .java files ----------------------------------------- + +JARFILES = ridl.jar unoil.jar jurt.jar juh.jar java_uno.jar OOoRunner.jar + +JAVAFILES =\ + StorageUnitTest.java\ + StorageTest.java\ + TestHelper.java\ + Test01.java\ + Test02.java\ + Test03.java\ + Test04.java\ + Test05.java\ + Test06.java\ + Test07.java\ + Test08.java + +JAVACLASSFILES = $(foreach,i,$(JAVAFILES) $(CLASSDIR)$/$(PACKAGE)$/$(i:b).class) + +#----- make a jar from compiled files ------------------------------ + +MAXLINELENGTH = 100000 + +JARCLASSDIRS = $(PACKAGE) +JARTARGET = $(TARGET).jar +JARCOMPRESS = TRUE + +# --- Parameters for the test -------------------------------------- + +# start an office if the parameter is set for the makefile +.IF "$(OFFICE)" == "" +CT_APPEXECCOMMAND = +.ELSE +CT_APPEXECCOMMAND = -AppExecutionCommand "$(OFFICE)$/soffice --accept=socket,host=localhost,port=8100;urp;" +.ENDIF + +# test base is java complex +CT_TESTBASE = -TestBase java_complex + +# test looks something like the.full.package.TestName +CT_TEST = -o $(PACKAGE:s\$/\.\).$(JAVAFILES:b) + +# start the runner application +CT_APP = org.openoffice.Runner + +# --- Targets ------------------------------------------------------ + +.INCLUDE: target.mk + +RUN: run + +run: + java -cp $(CLASSPATH) $(CT_APP) $(CT_TESTBASE) $(CT_APPEXECCOMMAND) $(CT_TEST) + + diff --git a/package/qa/storages/BorderedStream.java b/package/qa/storages/BorderedStream.java new file mode 100644 index 0000000000..ce7ebe55ff --- /dev/null +++ b/package/qa/storages/BorderedStream.java @@ -0,0 +1,213 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; + +import com.sun.star.io.XStream; +import com.sun.star.io.XInputStream; +import com.sun.star.io.XOutputStream; +import com.sun.star.io.XTruncate; +import com.sun.star.io.XSeekable; + + +public class BorderedStream + implements XStream, XInputStream, XOutputStream, XTruncate, XSeekable +{ + int m_nMaxSize; + int m_nCurSize; + int m_nCurPos; + byte m_pBytes[]; + + public BorderedStream( int nMaxSize ) + { + m_nMaxSize = nMaxSize; + m_nCurSize = 0; + m_nCurPos = 0; + m_pBytes = new byte[m_nMaxSize]; + } + + + // XStream + + + + public synchronized XInputStream getInputStream() + throws com.sun.star.uno.RuntimeException + { + return (XInputStream)UnoRuntime.queryInterface( XInputStream.class, this ); + } + + + public synchronized XOutputStream getOutputStream() + throws com.sun.star.uno.RuntimeException + { + return (XOutputStream)UnoRuntime.queryInterface( XOutputStream.class, this ); + } + + + // XInputStream + + + + public synchronized int readBytes( byte[][] aData, int nBytesToRead ) + throws com.sun.star.io.NotConnectedException, com.sun.star.io.BufferSizeExceededException, com.sun.star.io.IOException, com.sun.star.uno.RuntimeException + { + int nRead = 0; + if ( m_pBytes != null && nBytesToRead > 0 ) + { + int nAvailable = m_nCurSize - m_nCurPos; + if ( nBytesToRead > nAvailable ) + nBytesToRead = nAvailable; + + aData[0] = new byte[nBytesToRead]; + for ( int nInd = 0; nInd < nBytesToRead; nInd++ ) + aData[0][nInd] = m_pBytes[m_nCurPos+nInd]; + + nRead = nBytesToRead; + m_nCurPos += nRead; + } + else + { + aData[0] = new byte[0]; + } + + return nRead; + } + + + public synchronized int readSomeBytes( byte[][] aData, int nMaxBytesToRead ) + throws com.sun.star.io.NotConnectedException, com.sun.star.io.BufferSizeExceededException, com.sun.star.io.IOException, com.sun.star.uno.RuntimeException + { + return readBytes( aData, nMaxBytesToRead ); + } + + + public synchronized void skipBytes( int nBytesToSkip ) + throws com.sun.star.io.NotConnectedException, com.sun.star.io.BufferSizeExceededException, com.sun.star.io.IOException, com.sun.star.uno.RuntimeException + { + if ( nBytesToSkip < 0 ) + throw new com.sun.star.io.IOException(); // illegal argument + + if ( m_nCurSize - m_nCurPos > nBytesToSkip ) + m_nCurPos += nBytesToSkip; + else + m_nCurPos = m_nCurSize; + } + + + public synchronized int available() + throws com.sun.star.io.NotConnectedException, com.sun.star.io.IOException, com.sun.star.uno.RuntimeException + { + return 0; + } + + + public synchronized void closeInput() + throws com.sun.star.io.NotConnectedException, com.sun.star.io.IOException, com.sun.star.uno.RuntimeException + { + // no need to do anything + } + + + + // XOutputStream + + + + public synchronized void writeBytes( byte[] aData ) + throws com.sun.star.io.NotConnectedException, com.sun.star.io.BufferSizeExceededException, com.sun.star.io.IOException, com.sun.star.uno.RuntimeException + { + if ( m_pBytes != null && aData.length > 0 ) + { + if ( aData.length > m_nMaxSize - m_nCurPos ) + throw new com.sun.star.io.IOException(); + + for ( int nInd = 0; nInd < aData.length; nInd++ ) + m_pBytes[m_nCurPos+nInd] = aData[nInd]; + + m_nCurPos += aData.length; + if ( m_nCurPos > m_nCurSize ) + m_nCurSize = m_nCurPos; + } + } + + + public synchronized void flush() + throws com.sun.star.io.NotConnectedException, com.sun.star.io.BufferSizeExceededException, com.sun.star.io.IOException, com.sun.star.uno.RuntimeException + { + // nothing to do + } + + + public synchronized void closeOutput() + throws com.sun.star.io.NotConnectedException, com.sun.star.io.BufferSizeExceededException, com.sun.star.io.IOException, com.sun.star.uno.RuntimeException + { + // nothing to do + } + + + + // XTruncate + + + + public synchronized void truncate() + throws com.sun.star.io.IOException, com.sun.star.uno.RuntimeException + { + m_nCurSize = 0; + m_nCurPos = 0; + } + + + + // XSeekable + + + + public synchronized void seek( long location ) + throws com.sun.star.lang.IllegalArgumentException, com.sun.star.io.IOException, com.sun.star.uno.RuntimeException + { + if ( location > (long)m_nCurSize ) + throw new com.sun.star.lang.IllegalArgumentException(); + + m_nCurPos = (int)location; + } + + + public synchronized long getPosition() + throws com.sun.star.io.IOException, com.sun.star.uno.RuntimeException + { + return (long)m_nCurPos; + } + + + public synchronized long getLength() + throws com.sun.star.io.IOException, com.sun.star.uno.RuntimeException + { + return (long)m_nCurSize; + } +}; + diff --git a/package/qa/storages/RegressionTest_114358.java b/package/qa/storages/RegressionTest_114358.java new file mode 100644 index 0000000000..f76216cf22 --- /dev/null +++ b/package/qa/storages/RegressionTest_114358.java @@ -0,0 +1,208 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.io.XStream; +import com.sun.star.io.XInputStream; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class RegressionTest_114358 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public RegressionTest_114358( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "RegressionTest_114358: " ); + } + + public boolean test() + { + try + { + XStream xTempFileStream = m_aTestHelper.CreateTempFileStream( m_xMSF ); + if ( xTempFileStream == null ) + return false; + + // create storage based on the temporary stream + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) xTempFileStream; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, "SubStream1", "MediaType1", true, pBytes1 ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempStorage, + "MediaType2", + true, + ElementModes.WRITE ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + "MediaType3", + false, + ElementModes.WRITE ) ) + return false; + + // commit substorage first + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + // dispose substorage + if ( !m_aTestHelper.disposeStorage( xTempSubStorage ) ) + return false; + + // dispose the temporary storage + if ( !m_aTestHelper.disposeStorage( xTempStorage ) ) + return false; + + + // create a new storage based on the stream and change the substream + // as described in the bug description + + + byte pBytes2[] = { 2, 2 }; + + oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open the substorage + xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open the substream, set new "MediaType" and "Compressed" properties to it, truncate and write new contents + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, "SubStream1", "MediaType4", true, pBytes2 ) ) + return false; + + // commit substorage first + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + // dispose substorage + if ( !m_aTestHelper.disposeStorage( xTempSubStorage ) ) + return false; + + // dispose the temporary storage + if ( !m_aTestHelper.disposeStorage( xTempStorage ) ) + return false; + + + // create a new readonly storage based on the stream and check the contents + + + pArgs[1] = Integer.valueOf( ElementModes.READ ); + oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open the substorage + xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.READ ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xTempStorage, "MediaType2", true, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStorageProperties( xTempSubStorage, "MediaType3", false, ElementModes.READ ) ) + return false; + + // the MediaType and the contents must be up to date + if ( !m_aTestHelper.checkStream( xTempSubStorage, "SubStream1", "MediaType4", true, pBytes2 ) ) + return false; + + // dispose used storage to free resources + if ( !m_aTestHelper.disposeStorage( xTempStorage ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } +} + diff --git a/package/qa/storages/RegressionTest_125919.java b/package/qa/storages/RegressionTest_125919.java new file mode 100644 index 0000000000..3ad8b0e6ce --- /dev/null +++ b/package/qa/storages/RegressionTest_125919.java @@ -0,0 +1,152 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import java.lang.Integer; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.io.XStream; +import com.sun.star.io.XInputStream; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; +import complex.storages.BorderedStream; + +public class RegressionTest_125919 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + int nMinTestLen = 0; + int nMaxTestLen = 60000; + + public RegressionTest_125919( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "RegressionTest_125919: " ); + } + + public boolean test() + { + try + { + byte[] pBytes0 = new byte[0]; + byte[] pBytes18 = new byte[18000]; + byte[] pBytes36 = new byte[36000]; + + for ( int nInitInd = 0; nInitInd < 36000; nInitInd++ ) + { + pBytes36[nInitInd] = ( Integer.valueOf( nInitInd >> ( ( nInitInd % 2 ) * 8 ) ) ).byteValue(); + if ( nInitInd < 18000 ) + pBytes18[nInitInd] = ( Integer.valueOf( 256 - pBytes36[nInitInd] ) ).byteValue(); + } + + System.out.println( "This test can take up to some hours. The file size currently is about 50000." ); + System.out.println( "Progress: " ); + for ( int nAvailableBytes = nMinTestLen; nAvailableBytes < nMaxTestLen; nAvailableBytes++ ) + { + Object oBStream = new BorderedStream( nAvailableBytes ); + XStream xBorderedStream = (XStream)UnoRuntime.queryInterface( XStream.class, oBStream ); + if ( xBorderedStream == null ) + { + m_aTestHelper.Error( "Can't create bordered stream!" ); + return false; + } + + // create storage based on the temporary stream + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) xBorderedStream; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + XTransactedObject xTransact = (XTransactedObject) UnoRuntime.queryInterface( XTransactedObject.class, xTempStorage ); + if ( xTransact == null ) + { + m_aTestHelper.Error( "This test is designed for storages in transacted mode!" ); + return false; + } + + + if ( !m_aTestHelper.WriteBytesToSubstream( xTempStorage, "SubStream" + 0, "MediaType1", true, pBytes0 ) ) + return false; + if ( !m_aTestHelper.WriteBytesToSubstream( xTempStorage, "SubStream" + 18, "MediaType2", true, pBytes18 ) ) + return false; + if ( !m_aTestHelper.WriteBytesToSubstream( xTempStorage, "SubStream" + 36, "MediaType3", true, pBytes36 ) ) + return false; + + if ( nAvailableBytes > 0 && nAvailableBytes % 100 == 0 ) + System.out.println( " " + nAvailableBytes ); + + if ( nAvailableBytes > 0 && nAvailableBytes % 2 == 1 ) + System.out.print( "#" ); + + try + { + xTransact.commit(); + + System.out.println( "" ); + if ( !m_aTestHelper.disposeStorage( xTempStorage ) ) + return false; + + // SUCCESS + return true; + } + catch( UseBackupException aExc ) + { + // when there is not enough place in the target location and the target file is empty + // the direct writing will fail and must throw this exception with empty URL + if ( aExc.TemporaryFileURL.length() != 0 ) + return false; + } + catch( Exception e ) + { + System.out.println( "" ); + m_aTestHelper.Error( "Unexpected exception: " + e + "\nnAvailableBytes = " + nAvailableBytes ); + return false; + } + } + + return false; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } +} + diff --git a/package/qa/storages/RegressionTest_i26398.java b/package/qa/storages/RegressionTest_i26398.java new file mode 100644 index 0000000000..ec14400c8f --- /dev/null +++ b/package/qa/storages/RegressionTest_i26398.java @@ -0,0 +1,164 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.io.XStream; +import com.sun.star.io.XInputStream; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class RegressionTest_i26398 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public RegressionTest_i26398( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "RegressionTest_i26398: " ); + } + + public boolean test() + { + try + { + XStream xTempFileStream = m_aTestHelper.CreateTempFileStream( m_xMSF ); + if ( xTempFileStream == null ) + return false; + + // create storage based on the temporary stream + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) xTempFileStream; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, "SubStream1", "MediaType1", true, pBytes1 ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempStorage, + "MediaType2", + true, + ElementModes.WRITE ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + "MediaType3", + false, + ElementModes.WRITE ) ) + return false; + + + + // commit the substorage, dispose it, reopen readonly + // and dispose the reopened substorage + + + // commit substorage + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + // dispose substorage + if ( !m_aTestHelper.disposeStorage( xTempSubStorage ) ) + return false; + + // open a new substorage + xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.READ ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // dispose substorage + if ( !m_aTestHelper.disposeStorage( xTempSubStorage ) ) + return false; + + + // reopen the substorage in readwrite mode and check contents + + + // open a new substorage + xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xTempSubStorage, "MediaType3", false, ElementModes.WRITE ) ) + return false; + + if ( !m_aTestHelper.checkStorageProperties( xTempStorage, "MediaType2", true, ElementModes.WRITE ) ) + return false; + + if ( !m_aTestHelper.checkStream( xTempSubStorage, "SubStream1", "MediaType1", true, pBytes1 ) ) + return false; + + // the root storage is based on the temporary stream so it can be left undisposed, since it does not lock + // any resource, later the garbage collector will release the object and it must die by refcount + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } +} + diff --git a/package/qa/storages/RegressionTest_i27773.java b/package/qa/storages/RegressionTest_i27773.java new file mode 100644 index 0000000000..f5e455192a --- /dev/null +++ b/package/qa/storages/RegressionTest_i27773.java @@ -0,0 +1,317 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.io.XStream; +import com.sun.star.io.XInputStream; +import com.sun.star.beans.XPropertySet; +import com.sun.star.uno.AnyConverter; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + + +// Tests also fix for i51352 + + +public class RegressionTest_i27773 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public RegressionTest_i27773( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "RegressionTest_i27773: " ); + } + + public boolean test() + { + try + { + XStream xTempFileStream = m_aTestHelper.CreateTempFileStream( m_xMSF ); + if ( xTempFileStream == null ) + return false; + + if ( true ) + { + // for debugging proposes + + XPropertySet xPropSet = (XPropertySet) UnoRuntime.queryInterface( XPropertySet.class, xTempFileStream ); + if ( xPropSet != null ) + { + try + { + String sTempURL = AnyConverter.toString( xPropSet.getPropertyValue( "Uri" ) ); + // m_aTestHelper.Message( "URL: " + sTempURL ); + xPropSet.setPropertyValue( "RemoveFile", Boolean.FALSE ); + } + catch ( Exception e ) + { + } + } + } + + // create storage based on the temporary stream + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) xTempFileStream; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open an empty substorage + XStorage xEmptySubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "EmptySubStorage1", + ElementModes.WRITE ); + if ( xEmptySubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open an empty substorage + XStorage xEmptySubSubStorage = m_aTestHelper.openSubStorage( xTempSubStorage, + "EmptySubSubStorage1", + ElementModes.WRITE ); + if ( xEmptySubSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, "SubStream1", "MediaType1", true, pBytes1 ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempStorage, + "MediaType2", + true, + ElementModes.WRITE ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xEmptySubStorage, + "MediaType3", + false, + ElementModes.WRITE ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + "MediaType4", + false, + ElementModes.WRITE ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xEmptySubSubStorage, + "MediaType5", + false, + ElementModes.WRITE ) ) + return false; + + + // make a copy of substorage + + if ( !m_aTestHelper.copyElementTo( xTempStorage, "SubStorage1", xTempStorage, "SubStorage1_copy" ) ) + return false; + + if ( !m_aTestHelper.copyElementTo( xTempStorage, "EmptySubStorage1", xTempStorage, "EmptySubStorage1_copy" ) ) + return false; + + + // copy all the changed and noncommitted substorages + // and dispose them + + + if ( !m_aTestHelper.commitStorage( xEmptySubSubStorage ) ) + return false; + + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + if ( !m_aTestHelper.commitStorage( xEmptySubStorage ) ) + return false; + + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + // dispose substorages + + if ( !m_aTestHelper.disposeStorage( xEmptySubSubStorage ) ) + return false; + + if ( !m_aTestHelper.disposeStorage( xTempSubStorage ) ) + return false; + + if ( !m_aTestHelper.disposeStorage( xEmptySubStorage ) ) + return false; + + if ( !m_aTestHelper.disposeStorage( xTempStorage ) ) + return false; + + + // reopen the storage in readonly mode and check contents + + + pArgs[1] = Integer.valueOf( ElementModes.READ ); + + oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open original substorage + xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.READ ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open copy of the original substorage + XStorage xTempSubStorage_copy = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1_copy", + ElementModes.READ ); + if ( xTempSubStorage_copy == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open empty substorage + xEmptySubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "EmptySubStorage1", + ElementModes.READ ); + if ( xEmptySubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open copy of empty substorage + XStorage xEmptySubStorage_copy = m_aTestHelper.openSubStorage( xTempStorage, + "EmptySubStorage1_copy", + ElementModes.READ ); + if ( xEmptySubStorage_copy == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open an empty substorage of the substorage + xEmptySubSubStorage = m_aTestHelper.openSubStorage( xTempSubStorage, + "EmptySubSubStorage1", + ElementModes.READ ); + if ( xEmptySubSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open an empty substorage of the substorage copy + XStorage xEmptySubSubStorage_inCopy = m_aTestHelper.openSubStorage( xTempSubStorage_copy, + "EmptySubSubStorage1", + ElementModes.READ ); + if ( xEmptySubSubStorage_inCopy == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + + // check contents + + if ( !m_aTestHelper.checkStorageProperties( xEmptySubSubStorage, "MediaType5", false, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStorageProperties( xEmptySubSubStorage_inCopy, "MediaType5", false, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStorageProperties( xTempSubStorage, "MediaType4", false, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStorageProperties( xTempSubStorage_copy, "MediaType4", false, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStorageProperties( xEmptySubStorage, "MediaType3", false, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStorageProperties( xEmptySubStorage_copy, "MediaType3", false, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStorageProperties( xTempStorage, "MediaType2", true, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStream( xTempSubStorage, "SubStream1", "MediaType1", true, pBytes1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xTempSubStorage_copy, "SubStream1", "MediaType1", true, pBytes1 ) ) + return false; + + // the root storage is based on the temporary stream so it can be left undisposed, since it does not lock + // any resource, later the garbage collector will release the object and it must die by refcount + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } +} + diff --git a/package/qa/storages/RegressionTest_i29169.java b/package/qa/storages/RegressionTest_i29169.java new file mode 100644 index 0000000000..12286e7589 --- /dev/null +++ b/package/qa/storages/RegressionTest_i29169.java @@ -0,0 +1,387 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.io.XStream; +import com.sun.star.io.XInputStream; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class RegressionTest_i29169 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public RegressionTest_i29169( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "RegressionTest_i29169: " ); + } + + public boolean test() + { + try + { + XStream xTempFileStream = m_aTestHelper.CreateTempFileStream( m_xMSF ); + if ( xTempFileStream == null ) + return false; + + // create storage based on the temporary stream + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) xTempFileStream; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, "SubStream1", "MediaType1", true, pBytes1 ) ) + return false; + + // open a new substorage in the existing substorage + XStorage xTempSubSubStorage = m_aTestHelper.openSubStorage( xTempSubStorage, + "SubSubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubSubStorage, "SubSubStream1", "MediaType2", true, pBytes1 ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubSubStorage, + "MediaType3", + false, + ElementModes.WRITE ) ) + return false; + + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + "MediaType4", + false, + ElementModes.WRITE ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempStorage, + "MediaType5", + true, + ElementModes.WRITE ) ) + return false; + + + // commit the storages, and check the renaming in all stages + + + // rename the storage before it is committed + if ( !m_aTestHelper.renameElement( xTempSubStorage, "SubSubStorage1", "SubSubStorage2" ) ) + return false; + + // rename the stream + if ( !m_aTestHelper.renameElement( xTempSubStorage, "SubStream1", "SubStream2" ) ) + return false; + + // commit lowlevel substorage first + if ( !m_aTestHelper.commitStorage( xTempSubSubStorage ) ) + return false; + + // rename the storage after it is committed + if ( !m_aTestHelper.renameElement( xTempSubStorage, "SubSubStorage2", "SubSubStorage3" ) ) + return false; + + // rename the stream one more time + if ( !m_aTestHelper.renameElement( xTempSubStorage, "SubStream2", "SubStream3" ) ) + return false; + + // commit substorage + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + // rename the storage after it's parent is committed + if ( !m_aTestHelper.renameElement( xTempSubStorage, "SubSubStorage3", "SubSubStorage4" ) ) + return false; + + // rename the stream after it's parent is committed + if ( !m_aTestHelper.renameElement( xTempSubStorage, "SubStream3", "SubStream4" ) ) + return false; + + // commit substorage to let the renaming take place + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + // rename the storage after the package is committed + if ( !m_aTestHelper.renameElement( xTempSubStorage, "SubSubStorage4", "SubSubStorage5" ) ) + return false; + + // rename the stream after it's parent is committed + if ( !m_aTestHelper.renameElement( xTempSubStorage, "SubStream4", "SubStream5" ) ) + return false; + + // commit substorage to let the renaming take place + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + + // dispose the storages + + + // dispose lowerest substorage + if ( !m_aTestHelper.disposeStorage( xTempSubSubStorage ) ) + return false; + + // dispose substorage + if ( !m_aTestHelper.disposeStorage( xTempSubStorage ) ) + return false; + + // dispose the temporary storage + if ( !m_aTestHelper.disposeStorage( xTempStorage ) ) + return false; + + + // create a new storage based on the stream and check the substreams and substorages + + + oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open the substorage + xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open the lowlevel substorage + xTempSubSubStorage = m_aTestHelper.openSubStorage( xTempSubStorage, + "SubSubStorage5", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // check the storages and streams + + if ( !m_aTestHelper.checkStorageProperties( xTempSubSubStorage, "MediaType3", false, ElementModes.WRITE ) ) + return false; + + if ( !m_aTestHelper.checkStorageProperties( xTempSubStorage, "MediaType4", false, ElementModes.WRITE ) ) + return false; + + if ( !m_aTestHelper.checkStorageProperties( xTempStorage, "MediaType5", true, ElementModes.WRITE ) ) + return false; + + if ( !m_aTestHelper.checkStream( xTempSubStorage, "SubStream5", "MediaType1", true, pBytes1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xTempSubSubStorage, "SubSubStream1", "MediaType2", true, pBytes1 ) ) + return false; + + + // rename the reopened storages and streams + + + // rename the storage before it is committed + if ( !m_aTestHelper.renameElement( xTempSubStorage, "SubSubStorage5", "SubSubStorage6" ) ) + return false; + + // rename the stream + if ( !m_aTestHelper.renameElement( xTempSubStorage, "SubStream5", "SubStream6" ) ) + return false; + + // commit lowlevel substorage first + if ( !m_aTestHelper.commitStorage( xTempSubSubStorage ) ) + return false; + + // rename the storage after it is committed + if ( !m_aTestHelper.renameElement( xTempSubStorage, "SubSubStorage6", "SubSubStorage7" ) ) + return false; + + // rename the stream one more time + if ( !m_aTestHelper.renameElement( xTempSubStorage, "SubStream6", "SubStream7" ) ) + return false; + + // commit substorage + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + // rename the storage after it's parent is committed + if ( !m_aTestHelper.renameElement( xTempSubStorage, "SubSubStorage7", "SubSubStorage8" ) ) + return false; + + // rename the stream after it's parent is committed + if ( !m_aTestHelper.renameElement( xTempSubStorage, "SubStream7", "SubStream8" ) ) + return false; + + // commit substorage to let the renaming take place + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + // rename the storage after the package is committed + if ( !m_aTestHelper.renameElement( xTempSubStorage, "SubSubStorage8", "SubSubStorage9" ) ) + return false; + + // rename the stream after it`s parent is committed + if ( !m_aTestHelper.renameElement( xTempSubStorage, "SubStream8", "SubStream9" ) ) + return false; + + // commit substorage to let the renaming take place + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + + // dispose the storages + + + // dispose lowerest substorage + if ( !m_aTestHelper.disposeStorage( xTempSubSubStorage ) ) + return false; + + // dispose substorage + if ( !m_aTestHelper.disposeStorage( xTempSubStorage ) ) + return false; + + // dispose the temporary storage + if ( !m_aTestHelper.disposeStorage( xTempStorage ) ) + return false; + + + + // create a new readonly storage based on the stream and check the contents + + + pArgs[1] = Integer.valueOf( ElementModes.READ ); + oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open the substorage + xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.READ ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open the lowlevel substorage + xTempSubSubStorage = m_aTestHelper.openSubStorage( xTempSubStorage, + "SubSubStorage9", + ElementModes.READ ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // check the storages and streams + + if ( !m_aTestHelper.checkStorageProperties( xTempSubSubStorage, "MediaType3", false, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStorageProperties( xTempSubStorage, "MediaType4", false, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStorageProperties( xTempStorage, "MediaType5", true, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStream( xTempSubStorage, "SubStream9", "MediaType1", true, pBytes1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xTempSubSubStorage, "SubSubStream1", "MediaType2", true, pBytes1 ) ) + return false; + + // the storage is based on the temporary stream so it can be left undisposed, since it does not lock + // any resource, later the garbage collector will release the object and it must die by refcount + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } +} + diff --git a/package/qa/storages/RegressionTest_i29321.java b/package/qa/storages/RegressionTest_i29321.java new file mode 100644 index 0000000000..8fe980ba9d --- /dev/null +++ b/package/qa/storages/RegressionTest_i29321.java @@ -0,0 +1,188 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.io.XStream; +import com.sun.star.io.XInputStream; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class RegressionTest_i29321 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public RegressionTest_i29321( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "RegressionTest_i29321: " ); + } + + public boolean test() + { + try + { + XStream xTempFileStream = m_aTestHelper.CreateTempFileStream( m_xMSF ); + if ( xTempFileStream == null ) + return false; + + // create storage based on the temporary stream + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) xTempFileStream; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open a new substorage + XStorage xTempSubSubStorage = m_aTestHelper.openSubStorage( xTempSubStorage, + "SubSubStorage1", + ElementModes.WRITE ); + if ( xTempSubSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempStorage, "Stream1", "MediaType1", true, pBytes1 ) ) + return false; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, "SubStream1", "MediaType2", true, pBytes1 ) ) + return false; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubSubStorage, "SubSubStream1", "MediaType3", true, pBytes1 ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempStorage, + "MediaType4", + true, + ElementModes.WRITE ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + "MediaType5", + false, + ElementModes.WRITE ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubSubStorage, + "MediaType6", + false, + ElementModes.WRITE ) ) + return false; + + + // commit the storages twice to test the bug scenario + + + // commit lowlevel substorage first + if ( !m_aTestHelper.commitStorage( xTempSubSubStorage ) ) + return false; + + // commit substorage + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + // commit substorage to let the renaming take place + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + // commit lowlevel substorage first + if ( !m_aTestHelper.commitStorage( xTempSubSubStorage ) ) + return false; + + // commit substorage + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + // commit substorage to let the renaming take place + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + + // check the storages and streams without closing + + + if ( !m_aTestHelper.checkStorageProperties( xTempSubSubStorage, "MediaType6", false, ElementModes.WRITE ) ) + return false; + + if ( !m_aTestHelper.checkStorageProperties( xTempSubStorage, "MediaType5", false, ElementModes.WRITE ) ) + return false; + + if ( !m_aTestHelper.checkStorageProperties( xTempStorage, "MediaType4", true, ElementModes.WRITE ) ) + return false; + + if ( !m_aTestHelper.checkStream( xTempSubSubStorage, "SubSubStream1", "MediaType3", true, pBytes1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xTempSubStorage, "SubStream1", "MediaType2", true, pBytes1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xTempStorage, "Stream1", "MediaType1", true, pBytes1 ) ) + return false; + + // the root storage is based on the temporary stream so it can be left undisposed, since it does not lock + // any resource, later the garbage collector will release the object and it must die by refcount + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } +} + diff --git a/package/qa/storages/RegressionTest_i30400.java b/package/qa/storages/RegressionTest_i30400.java new file mode 100644 index 0000000000..39866503b6 --- /dev/null +++ b/package/qa/storages/RegressionTest_i30400.java @@ -0,0 +1,453 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.io.XStream; +import com.sun.star.io.XInputStream; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class RegressionTest_i30400 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public RegressionTest_i30400( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "RegressionTest_i30400: " ); + } + + public boolean test() + { + try + { + + // create a temporary stream and a storage based on it + // fill the storage with the data that will be used for testing + + + XStream xTempFileStream = m_aTestHelper.CreateTempFileStream( m_xMSF ); + if ( xTempFileStream == null ) + return false; + + // create storage based on the temporary stream + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) xTempFileStream; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + String pPass1 = "1, 2, 3, 4, 5"; + + Object oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempStorage, "Stream1", "MediaType1", true, pBytes1 ) ) + return false; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToEncrSubstream( xTempStorage, "EncrStream1", "MediaType2", true, pBytes1, pPass1 ) ) + return false; + + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, "SubStream1", "MediaType3", true, pBytes1 ) ) + return false; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToEncrSubstream( xTempSubStorage, "SubEncrStream1", "MediaType4", true, pBytes1, pPass1 ) ) + return false; + + // open a new substorage in the existing substorage + XStorage xTempSubSubStorage = m_aTestHelper.openSubStorage( xTempSubStorage, + "SubSubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubSubStorage, "SubSubStream1", "MediaType5", true, pBytes1 ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubSubStorage, + "MediaType6", + false, + ElementModes.WRITE ) ) + return false; + + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + "MediaType7", + false, + ElementModes.WRITE ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempStorage, + "MediaType8", + true, + ElementModes.WRITE ) ) + return false; + + + // check the copying with renaming + + + if ( !TestCopyWithRenaming( xTempStorage, xTempSubStorage, xTempSubSubStorage ) ) + return false; + + + // commit the storages + + + // commit lowlevel substorage + if ( !m_aTestHelper.commitStorage( xTempSubSubStorage ) ) + return false; + + // commit substorage + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + + // dispose the storages + + + // dispose lowerest substorage + if ( !m_aTestHelper.disposeStorage( xTempSubSubStorage ) ) + return false; + + // dispose substorage + if ( !m_aTestHelper.disposeStorage( xTempSubStorage ) ) + return false; + + // dispose the temporary storage + if ( !m_aTestHelper.disposeStorage( xTempStorage ) ) + return false; + + + // reopen the target storage readonly, and check the copying with renaming + + + pArgs[1] = Integer.valueOf( ElementModes.READ ); + oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open the substorages + + xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.READ ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open the lowlevel substorages + + xTempSubSubStorage = m_aTestHelper.openSubStorage( xTempSubStorage, + "SubSubStorage1", + ElementModes.READ ); + if ( xTempSubSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // test the copying with renaming + if ( !TestCopyWithRenaming( xTempStorage, xTempSubStorage, xTempSubSubStorage ) ) + return false; + + + // the storage is based on the temporary stream so it can be left undisposed, since it does not lock + // any resource, later the garbage collector will release the object and it must die by refcount + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } + + + public boolean TestCopyWithRenaming( XStorage xTempStorage, XStorage xTempSubStorage, XStorage xTempSubSubStorage ) + throws com.sun.star.uno.Exception + { + + // create a second temporary stream and copy all the staff there + // with renaming, check the success + + + XStream xTempFileStream2 = m_aTestHelper.CreateTempFileStream( m_xMSF ); + if ( xTempFileStream2 == null ) + return false; + + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) xTempFileStream2; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + String pPass1 = "1, 2, 3, 4, 5"; + + Object oTempStorage2 = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempStorage2 = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage2 ); + if ( xTempStorage2 == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open a new substorage + XStorage xTempSubStorage2 = m_aTestHelper.openSubStorage( xTempStorage2, + "SubStorage1_target", + ElementModes.WRITE ); + if ( xTempSubStorage2 == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open a new substorage in the existing substorage + XStorage xTempSubSubStorage2 = m_aTestHelper.openSubStorage( xTempSubStorage2, + "SubSubStorage1_target", + ElementModes.WRITE ); + if ( xTempSubSubStorage2 == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // make a copy with renaming on lowerest level + if ( !m_aTestHelper.copyElementTo( xTempSubSubStorage, "SubSubStream1", xTempSubSubStorage2, "SubSubStream1_renamed" ) ) + return false; + + // make a copy with renaming on the next level + + if ( !m_aTestHelper.copyElementTo( xTempSubStorage, "SubStream1", xTempSubStorage2, "SubStream1_renamed" ) ) + return false; + + if ( !m_aTestHelper.copyElementTo( xTempSubStorage, "SubEncrStream1", xTempSubStorage2, "SubEncrStream1_renamed" ) ) + return false; + + if ( !m_aTestHelper.copyElementTo( xTempSubStorage, "SubSubStorage1", xTempSubStorage2, "SubSubStorage1_renamed" ) ) + return false; + + // make a copy with renaming of subelements of the root storage + + if ( !m_aTestHelper.copyElementTo( xTempStorage, "Stream1", xTempStorage2, "Stream1_renamed" ) ) + return false; + + if ( !m_aTestHelper.copyElementTo( xTempStorage, "EncrStream1", xTempStorage2, "EncrStream1_renamed" ) ) + return false; + + if ( !m_aTestHelper.copyElementTo( xTempStorage, "SubStorage1", xTempStorage2, "SubStorage1_renamed" ) ) + return false; + + + // commit the storages, and check the renaming in all stages + + + // commit substorage to let the renaming take place + if ( !m_aTestHelper.commitStorage( xTempSubSubStorage2 ) ) + return false; + + // commit substorage to let the renaming take place + if ( !m_aTestHelper.commitStorage( xTempSubStorage2 ) ) + return false; + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xTempStorage2 ) ) + return false; + + + // dispose the storages + + + // dispose lowerest substorage + if ( !m_aTestHelper.disposeStorage( xTempSubSubStorage2 ) ) + return false; + + // dispose substorage + if ( !m_aTestHelper.disposeStorage( xTempSubStorage2 ) ) + return false; + + // dispose the temporary storage + if ( !m_aTestHelper.disposeStorage( xTempStorage2 ) ) + return false; + + + // reopen the target storage readonly, and check the contents + + + pArgs[1] = Integer.valueOf( ElementModes.READ ); + oTempStorage2 = m_xStorageFactory.createInstanceWithArguments( pArgs ); + xTempStorage2 = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage2 ); + if ( xTempStorage2 == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open the substorages + + XStorage xTempSubStorage2_target = m_aTestHelper.openSubStorage( xTempStorage2, + "SubStorage1_target", + ElementModes.READ ); + if ( xTempSubStorage2_target == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + XStorage xTempSubStorage2_renamed = m_aTestHelper.openSubStorage( xTempStorage2, + "SubStorage1_renamed", + ElementModes.READ ); + if ( xTempSubStorage2_renamed == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open the lowlevel substorages + + XStorage xTempSubSubStorage2_inRenamed = m_aTestHelper.openSubStorage( xTempSubStorage2_renamed, + "SubSubStorage1", + ElementModes.READ ); + if ( xTempSubSubStorage2_inRenamed == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + XStorage xTempSubSubStorage2_renamed = m_aTestHelper.openSubStorage( xTempSubStorage2_target, + "SubSubStorage1_renamed", + ElementModes.READ ); + if ( xTempSubSubStorage2_renamed == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + XStorage xTempSubSubStorage2_target = m_aTestHelper.openSubStorage( xTempSubStorage2_target, + "SubSubStorage1_target", + ElementModes.READ ); + if ( xTempSubSubStorage2_target == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // check the storages + + if ( !m_aTestHelper.checkStorageProperties( xTempSubSubStorage2_inRenamed, "MediaType6", false, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStorageProperties( xTempSubSubStorage2_renamed, "MediaType6", false, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStorageProperties( xTempSubStorage2_renamed, "MediaType7", false, ElementModes.READ ) ) + return false; + + + // check the streams + + + // sub sub level + + if ( !m_aTestHelper.checkStream( xTempSubSubStorage2_inRenamed, "SubSubStream1", "MediaType5", true, pBytes1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xTempSubSubStorage2_renamed, "SubSubStream1", "MediaType5", true, pBytes1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xTempSubSubStorage2_target, "SubSubStream1_renamed", "MediaType5", true, pBytes1 ) ) + return false; + + // sub level + + if ( !m_aTestHelper.checkStream( xTempSubStorage2_renamed, "SubStream1", "MediaType3", true, pBytes1 ) ) + return false; + + if ( !m_aTestHelper.checkEncrStream( xTempSubStorage2_renamed, "SubEncrStream1", "MediaType4", pBytes1, pPass1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xTempSubStorage2_target, "SubStream1_renamed", "MediaType3", true, pBytes1 ) ) + return false; + + if ( !m_aTestHelper.checkEncrStream( xTempSubStorage2_target, "SubEncrStream1_renamed", "MediaType4", pBytes1, pPass1 ) ) + return false; + + // root storage level + + if ( !m_aTestHelper.checkStream( xTempStorage2, "Stream1_renamed", "MediaType1", true, pBytes1 ) ) + return false; + + if ( !m_aTestHelper.checkEncrStream( xTempStorage2, "EncrStream1_renamed", "MediaType2", pBytes1, pPass1 ) ) + return false; + + // the storage is based on the temporary stream so it can be left undisposed, since it does not lock + // any resource, later the garbage collector will release the object and it must die by refcount + + return true; + } +} + diff --git a/package/qa/storages/RegressionTest_i30677.java b/package/qa/storages/RegressionTest_i30677.java new file mode 100644 index 0000000000..faee91afe5 --- /dev/null +++ b/package/qa/storages/RegressionTest_i30677.java @@ -0,0 +1,281 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.io.XStream; +import com.sun.star.io.XInputStream; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class RegressionTest_i30677 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public RegressionTest_i30677( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "RegressionTest_i30677: " ); + } + + public boolean test() + { + try + { + XStream xTempFileStream = m_aTestHelper.CreateTempFileStream( m_xMSF ); + if ( xTempFileStream == null ) + return false; + + // create storage based on the temporary stream + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) xTempFileStream; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open a new subsubstorage + XStorage xTempSubSubStorage = m_aTestHelper.openSubStorage( xTempSubStorage, + "SubSubStorage1", + ElementModes.WRITE ); + if ( xTempSubSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubSubStorage, "SubSubStream1", "MediaType1", true, pBytes1 ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempStorage, + "MediaType2", + true, + ElementModes.WRITE ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + "MediaType3", + false, + ElementModes.WRITE ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubSubStorage, + "MediaType4", + false, + ElementModes.WRITE ) ) + return false; + + + // commit the storages + + + // commit lowlevel substorage first + if ( !m_aTestHelper.commitStorage( xTempSubSubStorage ) ) + return false; + + // commit substorage + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + // commit substorage to let the renaming take place + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + + // dispose the storages + + + // dispose lowerest substorage + if ( !m_aTestHelper.disposeStorage( xTempSubSubStorage ) ) + return false; + + // dispose substorage + if ( !m_aTestHelper.disposeStorage( xTempSubStorage ) ) + return false; + + // dispose the temporary storage + if ( !m_aTestHelper.disposeStorage( xTempStorage ) ) + return false; + + + // reopen the storage and rewrite the stream + + + oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open the substorages + + xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open the lowlevel substorages + + xTempSubSubStorage = m_aTestHelper.openSubStorage( xTempSubStorage, + "SubSubStorage1", + ElementModes.WRITE ); + if ( xTempSubSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + byte pBytes2[] = { 2, 2, 2, 2, 2 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubSubStorage, "SubSubStream1", "MediaType1", true, pBytes2 ) ) + return false; + + + // commit the storages + + + // commit lowlevel substorage first + if ( !m_aTestHelper.commitStorage( xTempSubSubStorage ) ) + return false; + + // commit substorage + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + // commit substorage to let the renaming take place + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + + // dispose the storages + + + // dispose lowerest substorage + if ( !m_aTestHelper.disposeStorage( xTempSubSubStorage ) ) + return false; + + // dispose substorage + if ( !m_aTestHelper.disposeStorage( xTempSubStorage ) ) + return false; + + // dispose the temporary storage + if ( !m_aTestHelper.disposeStorage( xTempStorage ) ) + return false; + + + // reopen the storages and check the contents + + + pArgs[1] = Integer.valueOf( ElementModes.READ ); + oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open the substorages + + xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.READ ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open the lowlevel substorages + + xTempSubSubStorage = m_aTestHelper.openSubStorage( xTempSubStorage, + "SubSubStorage1", + ElementModes.READ ); + if ( xTempSubSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xTempSubSubStorage, "MediaType4", false, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStorageProperties( xTempSubStorage, "MediaType3", false, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStorageProperties( xTempStorage, "MediaType2", true, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStream( xTempSubSubStorage, "SubSubStream1", "MediaType1", true, pBytes2 ) ) + return false; + + // the root storage is based on the temporary stream so it can be left undisposed, since it does not lock + // any resource, later the garbage collector will release the object and it must die by refcount + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } +} + diff --git a/package/qa/storages/RegressionTest_i35095.java b/package/qa/storages/RegressionTest_i35095.java new file mode 100644 index 0000000000..0c979466c3 --- /dev/null +++ b/package/qa/storages/RegressionTest_i35095.java @@ -0,0 +1,184 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.io.XStream; +import com.sun.star.io.XInputStream; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class RegressionTest_i35095 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public RegressionTest_i35095( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "RegressionTest_i35095: " ); + } + + public boolean test() + { + try + { + XStream xTempFileStream = m_aTestHelper.CreateTempFileStream( m_xMSF ); + if ( xTempFileStream == null ) + return false; + + // create storage based on the temporary stream + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) xTempFileStream; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + byte pBytes[] = new byte[36000]; + for ( int nInd = 0; nInd < 36000; nInd++ ) + pBytes[nInd] = (byte)( nInd % 128 ); + + String sPass = "12345"; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToEncrSubstream( xTempStorage, "SubStream1", "MediaType1", true, pBytes, sPass ) ) + return false; + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToEncrSubstream( xTempSubStorage, "SubStream2", "MediaType2", false, pBytes, sPass ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempStorage, + "MediaType3", + true, + ElementModes.WRITE ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + "MediaType4", + false, + ElementModes.WRITE ) ) + return false; + + // commit substorage first + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + // dispose used storage to free resources + if ( !m_aTestHelper.disposeStorage( xTempStorage ) ) + return false; + + + // now check all the written information + // and the raw stream contents + + + // close the output part of the temporary stream + // the output part must present since we already wrote to the stream + if ( !m_aTestHelper.closeOutput( xTempFileStream ) ) + return false; + + XInputStream xTempInStream = m_aTestHelper.getInputStream( xTempFileStream ); + if ( xTempInStream == null ) + return false; + + // open input stream + // since no mode is provided the result storage must be opened readonly + Object pOneArg[] = new Object[1]; + pOneArg[0] = (Object) xTempInStream; + + Object oResultStorage = m_xStorageFactory.createInstanceWithArguments( pOneArg ); + XStorage xResultStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oResultStorage ); + if ( xResultStorage == null ) + { + m_aTestHelper.Error( "Can't open storage based on input stream!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultStorage, "MediaType3", true, ElementModes.READ ) ) + return false; + + // open existing substorage + XStorage xResultSubStorage = m_aTestHelper.openSubStorage( xResultStorage, + "SubStorage1", + ElementModes.READ ); + if ( xResultSubStorage == null ) + { + m_aTestHelper.Error( "Can't open existing substorage!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultSubStorage, "MediaType4", false, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.compareRawMethodsOnEncrStream( xResultStorage, "SubStream1" ) ) + return false; + + if ( !m_aTestHelper.compareRawMethodsOnEncrStream( xResultSubStorage, "SubStream2" ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xResultStorage ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } + +} + diff --git a/package/qa/storages/RegressionTest_i46848.java b/package/qa/storages/RegressionTest_i46848.java new file mode 100644 index 0000000000..9252766abc --- /dev/null +++ b/package/qa/storages/RegressionTest_i46848.java @@ -0,0 +1,209 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.io.XStream; +import com.sun.star.io.XInputStream; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class RegressionTest_i46848 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public RegressionTest_i46848( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "RegressionTest_i46848: " ); + } + + public boolean test() + { + try + { + XStream xTempFileStream = m_aTestHelper.CreateTempFileStream( m_xMSF ); + if ( xTempFileStream == null ) + return false; + + // create storage based on the temporary stream + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) xTempFileStream; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, "SubStream1", "MediaType1", true, pBytes1 ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempStorage, + "MediaType2", + true, + ElementModes.WRITE ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + "MediaType3", + false, + ElementModes.WRITE ) ) + return false; + + // commit substorage first + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + // dispose substorage + if ( !m_aTestHelper.disposeStorage( xTempSubStorage ) ) + return false; + + // dispose the temporary storage + if ( !m_aTestHelper.disposeStorage( xTempStorage ) ) + return false; + + + // create a new storage based on the stream and change the mediatype of the substorage + // as described in the bug description + + + oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open the substorage + xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + "ChangedMediaType3", + false, + ElementModes.WRITE ) ) + return false; + + // commit substorage first + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + // dispose substorage + if ( !m_aTestHelper.disposeStorage( xTempSubStorage ) ) + return false; + + // dispose the temporary storage + if ( !m_aTestHelper.disposeStorage( xTempStorage ) ) + return false; + + + // create a new readonly storage based on the stream and check the contents + + + pArgs[1] = Integer.valueOf( ElementModes.READ ); + oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open the substorage + xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.READ ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xTempStorage, "MediaType2", true, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStorageProperties( xTempSubStorage, "ChangedMediaType3", false, ElementModes.READ ) ) + return false; + + // the MediaType and the contents must be up to date + if ( !m_aTestHelper.checkStream( xTempSubStorage, "SubStream1", "MediaType1", true, pBytes1 ) ) + return false; + + // dispose used storage to free resources + if ( !m_aTestHelper.disposeStorage( xTempStorage ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } +} + diff --git a/package/qa/storages/RegressionTest_i49755.java b/package/qa/storages/RegressionTest_i49755.java new file mode 100644 index 0000000000..d2b32b8b93 --- /dev/null +++ b/package/qa/storages/RegressionTest_i49755.java @@ -0,0 +1,290 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.io.XStream; +import com.sun.star.io.XInputStream; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class RegressionTest_i49755 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public RegressionTest_i49755( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "RegressionTest_i49755: " ); + } + + public boolean test() + { + try + { + XStream xTempFileStream = m_aTestHelper.CreateTempFileStream( m_xMSF ); + if ( xTempFileStream == null ) + return false; + + // create storage based on the temporary stream + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) xTempFileStream; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempStorage, + "MediaType1", + true, + ElementModes.WRITE ) ) + return false; + + + byte pBytes[] = new byte[36000]; + for ( int nInd = 0; nInd < 36000; nInd++ ) + pBytes[nInd] = (byte)( nInd % 128 ); + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + "MediaType2", + false, + ElementModes.WRITE ) ) + return false; + + // open a new substorage + XStorage xTempSubSubStorage = m_aTestHelper.openSubStorage( xTempSubStorage, + "SubStorage2", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubSubStorage, + "MediaType3", + false, + ElementModes.WRITE ) ) + return false; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubSubStorage, "SubStream1", "MediaType4", true, pBytes ) ) + return false; + + // open a new substorage + XStorage xTempSubStorage1 = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage3", + ElementModes.WRITE ); + if ( xTempSubStorage1 == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage1, + "MediaType5", + false, + ElementModes.WRITE ) ) + return false; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage1, "SubStream2", "MediaType4", false, pBytes ) ) + return false; + + + // commit substorages first + if ( !m_aTestHelper.commitStorage( xTempSubSubStorage ) ) + return false; + + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + if ( !m_aTestHelper.commitStorage( xTempSubStorage1 ) ) + return false; + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + // dispose used storage to free resources + if ( !m_aTestHelper.disposeStorage( xTempStorage ) ) + return false; + + + // now change the contents of the second substorage + // without changing of the contents of the first substorage + + + Object oStep2TempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xStep2TempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oStep2TempStorage ); + if ( xStep2TempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + XStorage xStep2TempSubStorage1 = m_aTestHelper.openSubStorage( xStep2TempStorage, + "SubStorage3", + ElementModes.WRITE ); + if ( xStep2TempSubStorage1 == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xStep2TempSubStorage1, "SubStream2", "MediaType4", false, pBytes ) ) + return false; + + if ( !m_aTestHelper.commitStorage( xStep2TempSubStorage1 ) ) + return false; + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xStep2TempStorage ) ) + return false; + + // dispose used storage to free resources + if ( !m_aTestHelper.disposeStorage( xStep2TempStorage ) ) + return false; + + + + // now check all the written information + // and the raw stream contents + + + // close the output part of the temporary stream + // the output part must present since we already wrote to the stream + if ( !m_aTestHelper.closeOutput( xTempFileStream ) ) + return false; + + XInputStream xTempInStream = m_aTestHelper.getInputStream( xTempFileStream ); + if ( xTempInStream == null ) + return false; + + // open input stream + // since no mode is provided the result storage must be opened readonly + Object pOneArg[] = new Object[1]; + pOneArg[0] = (Object) xTempInStream; + + Object oResultStorage = m_xStorageFactory.createInstanceWithArguments( pOneArg ); + XStorage xResultStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oResultStorage ); + if ( xResultStorage == null ) + { + m_aTestHelper.Error( "Can't open storage based on input stream!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultStorage, "MediaType1", true, ElementModes.READ ) ) + return false; + + // open existing substorage + XStorage xResultSubStorage = m_aTestHelper.openSubStorage( xResultStorage, + "SubStorage1", + ElementModes.READ ); + if ( xResultSubStorage == null ) + { + m_aTestHelper.Error( "Can't open existing substorage!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultSubStorage, "MediaType2", false, ElementModes.READ ) ) + return false; + + // open existing substorage + XStorage xResultSubSubStorage = m_aTestHelper.openSubStorage( xResultSubStorage, + "SubStorage2", + ElementModes.READ ); + if ( xResultSubSubStorage == null ) + { + m_aTestHelper.Error( "Can't open existing substorage!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultSubSubStorage, "MediaType3", false, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResultSubSubStorage, "SubStream1", "MediaType4", true, pBytes ) ) + return false; + + + + XStorage xResultSubStorage1 = m_aTestHelper.openSubStorage( xResultStorage, + "SubStorage3", + ElementModes.READ ); + if ( xResultSubStorage1 == null ) + { + m_aTestHelper.Error( "Can't open existing substorage!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultSubStorage1, "MediaType5", false, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResultSubStorage1, "SubStream2", "MediaType4", false, pBytes ) ) + return false; + + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xResultStorage ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } + +} + diff --git a/package/qa/storages/RegressionTest_i55821.java b/package/qa/storages/RegressionTest_i55821.java new file mode 100644 index 0000000000..b4be15db48 --- /dev/null +++ b/package/qa/storages/RegressionTest_i55821.java @@ -0,0 +1,129 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.io.XStream; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class RegressionTest_i55821 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public RegressionTest_i55821( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "RegressionTest_i55821: " ); + } + + public boolean test() + { + try + { + + // create a temporary stream and a storage based on it + // fill the storage with the data that will be used for testing + + + XStream xTempFileStream = m_aTestHelper.CreateTempFileStream( m_xMSF ); + if ( xTempFileStream == null ) + return false; + + // create storage based on the temporary stream + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) xTempFileStream; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + String sPass = "12345"; + byte pBytes[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + // the stream will not be encrypted + if ( !m_aTestHelper.WriteBytesToEncrSubstream( xTempStorage, "SubStream1", "MediaType1", false, pBytes, sPass ) ) + return false; + + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + if ( !m_aTestHelper.WriteBytesToEncrSubstream( xTempStorage, "SubStream2", "MediaType2", false, pBytes, sPass ) ) + return false; + + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xTempStorage ) ) + return false; + + + // reopen the target storage readonly, and check contents + + + // the temporary file must not be locked any more after storage disposing + pArgs[1] = Integer.valueOf( ElementModes.READ ); + Object oResultStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xResultStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oResultStorage ); + if ( xResultStorage == null ) + { + m_aTestHelper.Error( "Can't reopen storage based on temporary file!" ); + return false; + } + + if ( !m_aTestHelper.checkEncrStream( xResultStorage, "SubStream1", "MediaType1", pBytes, sPass ) ) + return false; + + if ( !m_aTestHelper.checkEncrStream( xResultStorage, "SubStream2", "MediaType2", pBytes, sPass ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xResultStorage ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } +} + diff --git a/package/qa/storages/RegressionTest_i59886.java b/package/qa/storages/RegressionTest_i59886.java new file mode 100644 index 0000000000..90bf87a7e3 --- /dev/null +++ b/package/qa/storages/RegressionTest_i59886.java @@ -0,0 +1,261 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.io.XStream; +import com.sun.star.io.XInputStream; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class RegressionTest_i59886 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public RegressionTest_i59886( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "RegressionTest_i59886: " ); + } + + public boolean test() + { + try + { + XStream xTempFileStream = m_aTestHelper.CreateTempFileStream( m_xMSF ); + if ( xTempFileStream == null ) + return false; + + // create storage based on the temporary stream + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) xTempFileStream; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + byte pBytes[] = new byte[36000]; + for ( int nInd = 0; nInd < 36000; nInd++ ) + pBytes[nInd] = (byte)( nInd % 128 ); + + String sPass = "12345"; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToEncrSubstream( xTempStorage, "SubStream1", "MediaType1", true, pBytes, sPass ) ) + return false; + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToEncrSubstream( xTempSubStorage, "SubStream2", "MediaType2", false, pBytes, sPass ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempStorage, + "MediaType3", + true, + ElementModes.WRITE ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + "MediaType4", + false, + ElementModes.WRITE ) ) + return false; + + // commit substorage first + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + // dispose used storage to free resources + if ( !m_aTestHelper.disposeStorage( xTempStorage ) ) + return false; + + + // now reopen the storage, set the common storage key + // and copy the storage + + + Object oStep2TempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xStep2TempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oStep2TempStorage ); + if ( xStep2TempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + + XStorage xStep2TempSubStorage = m_aTestHelper.openSubStorage( xStep2TempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xStep2TempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // set the common storage password + XEncryptionProtectedSource xEncr = (XEncryptionProtectedSource) UnoRuntime.queryInterface( XEncryptionProtectedSource.class, xStep2TempStorage ); + if ( xEncr == null ) + { + m_aTestHelper.Error( "The storage does not support encryption access!" ); + return false; + } + try + { + xEncr.setEncryptionPassword( sPass ); + } + catch( Exception e ) + { + m_aTestHelper.Error( "Can not set the common storage password!" ); + return false; + } + + // open the stream for writing and read them so that the cache is created, but do not change + byte pDummyData[][] = new byte[1][3]; + XStream xTempStream1 = m_aTestHelper.OpenStream( xStep2TempStorage, "SubStream1", ElementModes.WRITE ); + XStream xTempStream2 = m_aTestHelper.OpenStream( xStep2TempSubStorage, "SubStream2", ElementModes.WRITE ); + if ( xTempStream1 == null || xTempStream2 == null ) + return false; + + XInputStream xTempInStream1 = xTempStream1.getInputStream(); + XInputStream xTempInStream2 = xTempStream2.getInputStream(); + if ( xTempInStream1 == null || xTempInStream2 == null ) + { + m_aTestHelper.Error( "No input stream is available!" ); + return false; + } + + xTempInStream1.readBytes( pDummyData, 3 ); + xTempInStream2.readBytes( pDummyData, 3 ); + + + // create temporary storage, it will be checked later + Object oTargetStorage = m_xStorageFactory.createInstance(); + XStorage xTargetStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTargetStorage ); + if ( xTargetStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // copy the current storage to the target + try + { + xStep2TempStorage.copyToStorage( xTargetStorage ); + } + catch( Exception e ) + { + m_aTestHelper.Error( "Can not copy the storage with common storage password!" ); + return false; + } + + // dispose used storage to free resources + if ( !m_aTestHelper.disposeStorage( xStep2TempStorage ) ) + return false; + + + // now check all the information in the copy + + + if ( !m_aTestHelper.checkStorageProperties( xTargetStorage, "MediaType3", true, ElementModes.WRITE ) ) + return false; + + // open existing substorage + XStorage xTargetSubStorage = m_aTestHelper.openSubStorage( xTargetStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTargetSubStorage == null ) + { + m_aTestHelper.Error( "Can't open existing substorage!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xTargetSubStorage, "MediaType4", false, ElementModes.WRITE ) ) + return false; + + // set the common storage password + XEncryptionProtectedSource xTargetEncr = (XEncryptionProtectedSource) UnoRuntime.queryInterface( XEncryptionProtectedSource.class, xTargetStorage ); + if ( xTargetEncr == null ) + { + m_aTestHelper.Error( "The storage does not support encryption access!" ); + return false; + } + try + { + xTargetEncr.setEncryptionPassword( sPass ); + } + catch( Exception e ) + { + m_aTestHelper.Error( "Can not set the common storage password!" ); + return false; + } + + // check the streams + if ( !m_aTestHelper.checkStream( xTargetStorage, "SubStream1", "MediaType1", true, pBytes ) ) + return false; + if ( !m_aTestHelper.checkStream( xTargetSubStorage, "SubStream2", "MediaType2", true, pBytes ) ) + return false; + + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xTargetStorage ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } +} + diff --git a/package/qa/storages/RegressionTest_i61909.java b/package/qa/storages/RegressionTest_i61909.java new file mode 100644 index 0000000000..a73d8677f2 --- /dev/null +++ b/package/qa/storages/RegressionTest_i61909.java @@ -0,0 +1,185 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import java.net.URI; +import java.io.File; +import java.io.FileInputStream; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipEntry; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.io.XStream; +import com.sun.star.io.XInputStream; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class RegressionTest_i61909 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public RegressionTest_i61909( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "RegressionTest_i61909: " ); + } + + public boolean test() + { + try + { + String sTempFileURL = m_aTestHelper.CreateTempFile( m_xMSF ); + if ( sTempFileURL == null || sTempFileURL == "" ) + { + m_aTestHelper.Error( "No valid temporary file was created!" ); + return false; + } + + // create storage based on the temporary stream + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) sTempFileURL; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + byte pBytes[] = new byte[36000]; + for ( int nInd = 0; nInd < 36000; nInd++ ) + pBytes[nInd] = (byte)( nInd % 128 ); + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempStorage, "SubStream1", "MediaType1", true, pBytes ) ) + return false; + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, "SubStream2", "MediaType2", true, pBytes ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempStorage, + "MediaType3", + true, + ElementModes.WRITE ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + "MediaType4", + false, + ElementModes.WRITE ) ) + return false; + + // commit substorage first + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + // dispose used storage to free resources + if ( !m_aTestHelper.disposeStorage( xTempStorage ) ) + return false; + + + // now reopen the storage, and insert a new stream + + + Object oStep2TempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xStep2TempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oStep2TempStorage ); + if ( xStep2TempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xStep2TempStorage, "SubStream3", "MediaType5", true, pBytes ) ) + return false; + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xStep2TempStorage ) ) + return false; + + // dispose used storage to free resources + if ( !m_aTestHelper.disposeStorage( xStep2TempStorage ) ) + return false; + + + // now access the stream using ZipInputStream + + + URI aUri = new URI( sTempFileURL ); + File aFile = new File( aUri ); + FileInputStream aFileStream = new FileInputStream( aFile ); + ZipInputStream aZipStream = new ZipInputStream( aFileStream ); + + ZipEntry aEntry; + int nNumber = 0; + m_aTestHelper.Message( "Available entries:" ); + while ( ( aEntry = aZipStream.getNextEntry() ) != null ) + { + nNumber++; + m_aTestHelper.Message( aEntry.getName() ); + } + + if ( nNumber != 6 ) + { + m_aTestHelper.Error( "Wrong number of entries: " + nNumber + ", Expected: 6" ); + return false; + } + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } +} + diff --git a/package/qa/storages/RegressionTest_i84234.java b/package/qa/storages/RegressionTest_i84234.java new file mode 100644 index 0000000000..7bd8073c64 --- /dev/null +++ b/package/qa/storages/RegressionTest_i84234.java @@ -0,0 +1,152 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.io.XStream; +import com.sun.star.io.XInputStream; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class RegressionTest_i84234 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public RegressionTest_i84234( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "RegressionTest_i84234: " ); + } + + public boolean test() + { + try + { + XStream xTempFileStream = m_aTestHelper.CreateTempFileStream( m_xMSF ); + if ( xTempFileStream == null ) + return false; + + // create storage based on the temporary stream + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) xTempFileStream; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempStorage, "SubStream1", "text/xml", false, pBytes1 ) ) + return false; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, "SubStream2", "text/xml", false, pBytes1 ) ) + return false; + + + + // commit the storages and dispose them + + + // commit substorage + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + // dispose substorage + if ( !m_aTestHelper.disposeStorage( xTempSubStorage ) ) + return false; + + // commit storage + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + // dispose storage + if ( !m_aTestHelper.disposeStorage( xTempStorage ) ) + return false; + + + // reopen the storages in readwrite mode and check Compressed flag + + + oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open a new substorage + xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + if ( !m_aTestHelper.checkStream( xTempStorage, "SubStream1", "text/xml", false, pBytes1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xTempSubStorage, "SubStream2", "text/xml", false, pBytes1 ) ) + return false; + + // the root storage is based on the temporary stream so it can be left undisposed, since it does not lock + // any resource, later the garbage collector will release the object and it must die by refcount + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } +} + diff --git a/package/qa/storages/StorageTest.java b/package/qa/storages/StorageTest.java new file mode 100644 index 0000000000..e35fd55993 --- /dev/null +++ b/package/qa/storages/StorageTest.java @@ -0,0 +1,25 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +public interface StorageTest +{ + boolean test(); +} + diff --git a/package/qa/storages/StorageUnitTest.java b/package/qa/storages/StorageUnitTest.java new file mode 100644 index 0000000000..a8e433c3ee --- /dev/null +++ b/package/qa/storages/StorageUnitTest.java @@ -0,0 +1,319 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +package complex.storages; + +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XMultiComponentFactory; +import com.sun.star.connection.XConnector; +import com.sun.star.connection.XConnection; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.uno.XNamingService; +import com.sun.star.uno.XComponentContext; + +import com.sun.star.container.*; +import com.sun.star.beans.*; +import com.sun.star.lang.*; + +import complexlib.ComplexTestCase; + +import complex.storages.*; + +import util.utils; +import java.util.*; +import java.io.*; + +/* This unit test for storage objects is designed to + * test most important statements from storage service + * specification. + * + * Regression tests are added to extend the tested + * functionalities. + */ +public class StorageUnitTest extends ComplexTestCase +{ + private XMultiServiceFactory m_xMSF = null; + private XSingleServiceFactory m_xStorageFactory = null; + + public String[] getTestMethodNames() + { + return new String[] { + "ExecuteTest01", + "ExecuteTest02", + "ExecuteTest03", + "ExecuteTest04", + "ExecuteTest05", + "ExecuteTest06", + "ExecuteTest07", + "ExecuteTest08", + "ExecuteTest09", + "ExecuteTest10", + "ExecuteTest11", + "ExecuteTest12", + "ExecuteTest13", + "ExecuteTest14", + "ExecuteTest15", + "ExecuteTest16", + "ExecuteTest17", + "ExecuteTest18", + "ExecuteRegressionTest_114358", + "ExecuteRegressionTest_i29169", + "ExecuteRegressionTest_i30400", + "ExecuteRegressionTest_i29321", + "ExecuteRegressionTest_i30677", + "ExecuteRegressionTest_i27773", + "ExecuteRegressionTest_i46848", + "ExecuteRegressionTest_i55821", + "ExecuteRegressionTest_i35095", + "ExecuteRegressionTest_i49755", + "ExecuteRegressionTest_i59886", + "ExecuteRegressionTest_i61909", + "ExecuteRegressionTest_i84234", + "ExecuteRegressionTest_125919" + }; + } + + public String getTestObjectName() + { + return "StorageUnitTest"; + } + + public void before() + { + m_xMSF = (XMultiServiceFactory)param.getMSF(); + if ( m_xMSF == null ) + { + failed( "Can't create service factory!" ); + return; + } + + try { + Object oStorageFactory = m_xMSF.createInstance( "com.sun.star.embed.StorageFactory" ); + m_xStorageFactory = (XSingleServiceFactory)UnoRuntime.queryInterface( XSingleServiceFactory.class, + oStorageFactory ); + } + catch( Exception e ) + { + failed( "Can't create storage factory!" ); + return; + } + + if ( m_xStorageFactory == null ) + { + failed( "Can't create service factory!" ); + return; + } + } + + public void ExecuteTest01() + { + StorageTest aTest = new Test01( m_xMSF, m_xStorageFactory, log ); + assure( "Test01 failed!", aTest.test() ); + } + + public void ExecuteTest02() + { + StorageTest aTest = new Test02( m_xMSF, m_xStorageFactory, log ); + assure( "Test02 failed!", aTest.test() ); + } + + public void ExecuteTest03() + { + StorageTest aTest = new Test03( m_xMSF, m_xStorageFactory, log ); + assure( "Test03 failed!", aTest.test() ); + } + + public void ExecuteTest04() + { + StorageTest aTest = new Test04( m_xMSF, m_xStorageFactory, log ); + assure( "Test04 failed!", aTest.test() ); + } + + public void ExecuteTest05() + { + StorageTest aTest = new Test05( m_xMSF, m_xStorageFactory, log ); + assure( "Test05 failed!", aTest.test() ); + } + + public void ExecuteTest06() + { + StorageTest aTest = new Test06( m_xMSF, m_xStorageFactory, log ); + assure( "Test06 failed!", aTest.test() ); + } + + public void ExecuteTest07() + { + StorageTest aTest = new Test07( m_xMSF, m_xStorageFactory, log ); + assure( "Test07 failed!", aTest.test() ); + } + + public void ExecuteTest08() + { + StorageTest aTest = new Test08( m_xMSF, m_xStorageFactory, log ); + assure( "Test08 failed!", aTest.test() ); + } + + public void ExecuteTest09() + { + StorageTest aTest = new Test09( m_xMSF, m_xStorageFactory, log ); + assure( "Test09 failed!", aTest.test() ); + } + + public void ExecuteTest10() + { + StorageTest aTest = new Test10( m_xMSF, m_xStorageFactory, log ); + assure( "Test10 failed!", aTest.test() ); + } + + public void ExecuteTest11() + { + StorageTest aTest = new Test11( m_xMSF, m_xStorageFactory, log ); + assure( "Test11 failed!", aTest.test() ); + } + + public void ExecuteTest12() + { + StorageTest aTest = new Test12( m_xMSF, m_xStorageFactory, log ); + assure( "Test12 failed!", aTest.test() ); + } + + public void ExecuteTest13() + { + StorageTest aTest = new Test13( m_xMSF, m_xStorageFactory, log ); + assure( "Test13 failed!", aTest.test() ); + } + + public void ExecuteTest14() + { + StorageTest aTest = new Test14( m_xMSF, m_xStorageFactory, log ); + assure( "Test14 failed!", aTest.test() ); + } + + public void ExecuteTest15() + { + StorageTest aTest = new Test15( m_xMSF, m_xStorageFactory, log ); + assure( "Test15 failed!", aTest.test() ); + } + + public void ExecuteTest16() + { + StorageTest aTest = new Test16( m_xMSF, m_xStorageFactory, log ); + assure( "Test16 failed!", aTest.test() ); + } + + public void ExecuteTest17() + { + StorageTest aTest = new Test17( m_xMSF, m_xStorageFactory, log ); + assure( "Test17 failed!", aTest.test() ); + } + + public void ExecuteTest18() + { + StorageTest aTest = new Test18( m_xMSF, m_xStorageFactory, log ); + assure( "Test18 failed!", aTest.test() ); + } + + public void ExecuteRegressionTest_114358() + { + StorageTest aTest = new RegressionTest_114358( m_xMSF, m_xStorageFactory, log ); + assure( "RegressionTest_114358 failed!", aTest.test() ); + } + + public void ExecuteRegressionTest_i29169() + { + StorageTest aTest = new RegressionTest_i29169( m_xMSF, m_xStorageFactory, log ); + assure( "RegressionTest_i29169 failed!", aTest.test() ); + } + + public void ExecuteRegressionTest_i30400() + { + StorageTest aTest = new RegressionTest_i30400( m_xMSF, m_xStorageFactory, log ); + assure( "RegressionTest_i30400 failed!", aTest.test() ); + } + + public void ExecuteRegressionTest_i29321() + { + StorageTest aTest = new RegressionTest_i29321( m_xMSF, m_xStorageFactory, log ); + assure( "RegressionTest_i29321 failed!", aTest.test() ); + } + + public void ExecuteRegressionTest_i30677() + { + StorageTest aTest = new RegressionTest_i30677( m_xMSF, m_xStorageFactory, log ); + assure( "RegressionTest_i30677 failed!", aTest.test() ); + } + + public void ExecuteRegressionTest_i27773() + { + StorageTest aTest = new RegressionTest_i27773( m_xMSF, m_xStorageFactory, log ); + assure( "RegressionTest_i27773 failed!", aTest.test() ); + } + + public void ExecuteRegressionTest_i46848() + { + StorageTest aTest = new RegressionTest_i46848( m_xMSF, m_xStorageFactory, log ); + assure( "RegressionTest_i46848 failed!", aTest.test() ); + } + + public void ExecuteRegressionTest_i55821() + { + StorageTest aTest = new RegressionTest_i55821( m_xMSF, m_xStorageFactory, log ); + assure( "RegressionTest_i55821 failed!", aTest.test() ); + } + + public void ExecuteRegressionTest_i35095() + { + StorageTest aTest = new RegressionTest_i35095( m_xMSF, m_xStorageFactory, log ); + assure( "RegressionTest_i35095 failed!", aTest.test() ); + } + + public void ExecuteRegressionTest_i49755() + { + StorageTest aTest = new RegressionTest_i49755( m_xMSF, m_xStorageFactory, log ); + assure( "RegressionTest_i49755 failed!", aTest.test() ); + } + + public void ExecuteRegressionTest_i59886() + { + StorageTest aTest = new RegressionTest_i59886( m_xMSF, m_xStorageFactory, log ); + assure( "RegressionTest_i59886 failed!", aTest.test() ); + } + + public void ExecuteRegressionTest_i61909() + { + StorageTest aTest = new RegressionTest_i61909( m_xMSF, m_xStorageFactory, log ); + assure( "RegressionTest_i61909 failed!", aTest.test() ); + } + + public void ExecuteRegressionTest_i84234() + { + StorageTest aTest = new RegressionTest_i84234( m_xMSF, m_xStorageFactory, log ); + assure( "RegressionTest_i84234 failed!", aTest.test() ); + } + + public void ExecuteRegressionTest_125919() + { + StorageTest aTest = new RegressionTest_125919( m_xMSF, m_xStorageFactory, log ); + assure( "RegressionTest_125919 failed!", aTest.test() ); + } +} + diff --git a/package/qa/storages/Test01.java b/package/qa/storages/Test01.java new file mode 100644 index 0000000000..a5d76bcca0 --- /dev/null +++ b/package/qa/storages/Test01.java @@ -0,0 +1,195 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class Test01 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public Test01( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "Test01: " ); + } + + public boolean test() + { + try + { + String sTempFileURL = m_aTestHelper.CreateTempFile( m_xMSF ); + if ( sTempFileURL == null || sTempFileURL == "" ) + { + m_aTestHelper.Error( "No valid temporary file was created!" ); + return false; + } + + // create temporary storage based on arbitrary medium + // after such a storage is closed it is lost + Object oTempStorage = m_xStorageFactory.createInstance(); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + byte pBigBytes[] = new byte[33000]; + for ( int nInd = 0; nInd < 33000; nInd++ ) + pBigBytes[nInd] = (byte)( nInd % 128 ); + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, "BigSubStream1", "MediaType1", true, pBigBytes ) ) + return false; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, "BigSubStream2", "MediaType2", false, pBigBytes ) ) + return false; + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, "SubStream1", "MediaType1", true, pBytes1 ) ) + return false; + + byte pBytes2[] = { 2, 2, 2, 2, 2 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, "SubStream2", "MediaType2", false, pBytes2 ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempStorage, + "MediaType3", + true, + ElementModes.WRITE ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + "MediaType4", + false, + ElementModes.WRITE ) ) + return false; + + // create temporary storage based on a previously created temporary file + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) sTempFileURL; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempFileStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempFileStorage = (XStorage)UnoRuntime.queryInterface( XStorage.class, oTempFileStorage ); + if ( xTempFileStorage == null ) + { + m_aTestHelper.Error( "Can't create storage based on temporary file!" ); + return false; + } + + // copy xTempStorage to xTempFileStorage + // xTempFileStorage will be automatically committed + if ( !m_aTestHelper.copyStorage( xTempStorage, xTempFileStorage ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xTempStorage ) || !m_aTestHelper.disposeStorage( xTempFileStorage ) ) + return false; + + + // now check all the written and copied information + + + // the temporary file must not be locked any more after storage disposing + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + Object oResultStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xResultStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oResultStorage ); + if ( xResultStorage == null ) + { + m_aTestHelper.Error( "Can't reopen storage based on temporary file!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultStorage, "MediaType3", true, ElementModes.WRITE ) ) + return false; + + // open existing substorage + XStorage xResultSubStorage = m_aTestHelper.openSubStorage( xResultStorage, + "SubStorage1", + ElementModes.READ ); + if ( xResultSubStorage == null ) + { + m_aTestHelper.Error( "Can't open existing substorage!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultSubStorage, "MediaType4", false, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResultSubStorage, "BigSubStream1", "MediaType1", true, pBigBytes ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResultSubStorage, "BigSubStream2", "MediaType2", false, pBigBytes ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResultSubStorage, "SubStream1", "MediaType1", true, pBytes1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResultSubStorage, "SubStream2", "MediaType2", false, pBytes2 ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xResultStorage ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } + +} + diff --git a/package/qa/storages/Test02.java b/package/qa/storages/Test02.java new file mode 100644 index 0000000000..2e3e712549 --- /dev/null +++ b/package/qa/storages/Test02.java @@ -0,0 +1,181 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.io.XStream; +import com.sun.star.io.XInputStream; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class Test02 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public Test02( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "Test02: " ); + } + + public boolean test() + { + try + { + XStream xTempFileStream = m_aTestHelper.CreateTempFileStream( m_xMSF ); + if ( xTempFileStream == null ) + return false; + + // create storage based on the temporary stream + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) xTempFileStream; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + byte pBigBytes[] = new byte[33000]; + for ( int nInd = 0; nInd < 33000; nInd++ ) + pBigBytes[nInd] = (byte)( nInd % 128 ); + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, "BigSubStream1", "MediaType1", true, pBigBytes ) ) + return false; + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, "SubStream1", "MediaType1", true, pBytes1 ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempStorage, + "MediaType2", + true, + ElementModes.WRITE ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + "MediaType3", + false, + ElementModes.WRITE ) ) + return false; + + // commit substorage first + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + // dispose used storage to free resources + if ( !m_aTestHelper.disposeStorage( xTempStorage ) ) + return false; + + + + // now check all the written information + + + // close the output part of the temporary stream + // the output part must present since we already wrote to the stream + if ( !m_aTestHelper.closeOutput( xTempFileStream ) ) + return false; + + XInputStream xTempInStream = m_aTestHelper.getInputStream( xTempFileStream ); + if ( xTempInStream == null ) + return false; + + + // open input stream + // since no mode is provided the result storage must be opened readonly + Object pOneArg[] = new Object[1]; + pOneArg[0] = (Object) xTempInStream; + + Object oResultStorage = m_xStorageFactory.createInstanceWithArguments( pOneArg ); + XStorage xResultStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oResultStorage ); + if ( xResultStorage == null ) + { + m_aTestHelper.Error( "Can't open storage based on input stream!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultStorage, "MediaType2", true, ElementModes.READ ) ) + return false; + + // open existing substorage + XStorage xResultSubStorage = m_aTestHelper.openSubStorage( xResultStorage, + "SubStorage1", + ElementModes.READ ); + if ( xResultSubStorage == null ) + { + m_aTestHelper.Error( "Can't open existing substorage!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultSubStorage, "MediaType3", false, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResultSubStorage, "BigSubStream1", "MediaType1", true, pBigBytes ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResultSubStorage, "SubStream1", "MediaType1", true, pBytes1 ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } + +} + diff --git a/package/qa/storages/Test03.java b/package/qa/storages/Test03.java new file mode 100644 index 0000000000..8e7c1ee304 --- /dev/null +++ b/package/qa/storages/Test03.java @@ -0,0 +1,249 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; + +import com.sun.star.embed.*; +import com.sun.star.container.XNameAccess; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class Test03 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public Test03( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "Test03: " ); + } + + public boolean test() + { + try + { + // create temporary storage based on arbitrary medium + // after such a storage is closed it is lost + Object oTempStorage = m_xStorageFactory.createInstance(); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + byte pBigBytes[] = new byte[33000]; + for ( int nInd = 0; nInd < 33000; nInd++ ) + pBigBytes[nInd] = (byte)( nInd % 128 ); + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempStorage, "BigSubStream1", "MediaType1", true, pBigBytes ) ) + return false; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, "BigSubStream2", "MediaType2", false, pBigBytes ) ) + return false; + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempStorage, "SubStream1", "MediaType1", true, pBytes1 ) ) + return false; + + byte pBytes2[] = { 2, 2, 2, 2, 2 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, "SubStream2", "MediaType2", false, pBytes2 ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + "MediaType3", + false, + ElementModes.WRITE ) ) + return false; + + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + if ( !m_aTestHelper.disposeStorage( xTempSubStorage ) ) + return false; + + + // check storage hierarchy tree + + + // check that isStorageElement() and isStreamElement reacts to nonexistent object correctly + try { + xTempStorage.isStorageElement( "does not exist" ); + m_aTestHelper.Error( "Nonexistent element doesn't detected by isStorageElement() call!" ); + return false; + } + catch( com.sun.star.container.NoSuchElementException ne ) + { + } + catch( Exception e ) + { + m_aTestHelper.Error( "Wrong exception is thrown by isStorageElement() call: " + e ); + return false; + } + + try { + xTempStorage.isStreamElement( "does not exist" ); + m_aTestHelper.Error( "Nonexistent element doesn't detected by isStreamElement() call!" ); + return false; + } + catch( com.sun.star.container.NoSuchElementException ne ) + { + } + catch( Exception e ) + { + m_aTestHelper.Error( "Wrong exception is thrown by isStreamElement() call: " + e ); + return false; + } + + XNameAccess xRootNameAccess = (XNameAccess) UnoRuntime.queryInterface( XNameAccess.class, xTempStorage ); + if ( xRootNameAccess == null ) + { + m_aTestHelper.Error( "Root storage doesn't support XNameAccess!" ); + return false; + } + + try { + if ( !xTempStorage.isStorageElement( "SubStorage1" ) || xTempStorage.isStreamElement( "SubStorage1" ) ) + { + m_aTestHelper.Error( "Child 'SubStorage1' can not be detected as storage!" ); + return false; + } + + if ( xTempStorage.isStorageElement( "SubStream1" ) || !xTempStorage.isStreamElement( "SubStream1" ) ) + { + m_aTestHelper.Error( "Child 'SubStream1' can not be detected as stream!" ); + return false; + } + } + catch( Exception e ) + { + m_aTestHelper.Error( "Child's type can not be detected, exception: " + e ); + return false; + } + + + // check that root storage contents are represented correctly + String sRootCont[] = xRootNameAccess.getElementNames(); + + if ( sRootCont.length != 3 ) + { + m_aTestHelper.Error( "Root storage contains wrong amount of children!" ); + return false; + } + + int nFlag = 0; + for ( int nInd = 0; nInd < sRootCont.length; nInd++ ) + { + if ( sRootCont[nInd].equals( "SubStorage1" ) ) + nFlag |= 1; + else if ( sRootCont[nInd].equals( "SubStream1" ) ) + nFlag |= 2; + else if ( sRootCont[nInd].equals( "BigSubStream1" ) ) + nFlag |= 4; + } + + if ( nFlag != 7 || !( xRootNameAccess.hasByName( "BigSubStream1" ) && xRootNameAccess.hasByName( "SubStream1" ) && xRootNameAccess.hasByName( "SubStorage1" ) ) ) + { + m_aTestHelper.Error( "Root storage contains wrong list of children!" ); + return false; + } + + // get storage through XNameAccess + XStorage xResultSubStorage = getStorageFromNameAccess( xRootNameAccess, "SubStorage1" ); + if ( xResultSubStorage == null ) + return false; + + if ( !m_aTestHelper.checkStorageProperties( xResultSubStorage, "MediaType3", false, ElementModes.READ ) ) + return false; + + XNameAccess xChildAccess = (XNameAccess) UnoRuntime.queryInterface( XNameAccess.class, xResultSubStorage ); + if ( xChildAccess == null ) + { + m_aTestHelper.Error( "Child storage doesn't support XNameAccess!" ); + return false; + } + + if ( !( xChildAccess.hasByName( "SubStream2" ) && xChildAccess.hasByName( "BigSubStream2" ) ) + || !xResultSubStorage.isStreamElement( "SubStream2" ) + || !xResultSubStorage.isStreamElement( "BigSubStream2" ) ) + { + m_aTestHelper.Error( "'SubStream2' can not be detected as child stream element of 'SubStorage1'!" ); + return false; + } + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } + + public XStorage getStorageFromNameAccess( XNameAccess xAccess, String sName ) + { + try + { + Object oStorage = xAccess.getByName( sName ); + XStorage xResult = (XStorage) UnoRuntime.queryInterface( XStorage.class, oStorage ); + + if ( xResult != null ) + return xResult; + else + m_aTestHelper.Error( "Can't retrieve substorage '" + sName + "' through XNameAccess!" ); + } + catch( Exception e ) + { + m_aTestHelper.Error( "Can't retrieve substorage '" + sName + "' through XNameAccess, exception: " + e ); + } + + return null; + } + +} + diff --git a/package/qa/storages/Test04.java b/package/qa/storages/Test04.java new file mode 100644 index 0000000000..78af017897 --- /dev/null +++ b/package/qa/storages/Test04.java @@ -0,0 +1,325 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; +import com.sun.star.lang.DisposedException; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; + +import com.sun.star.container.XNameAccess; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class Test04 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public Test04( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "Test04: " ); + } + + public boolean test() + { + try + { + String sTempFileURL = m_aTestHelper.CreateTempFile( m_xMSF ); + if ( sTempFileURL == null || sTempFileURL == "" ) + { + m_aTestHelper.Error( "No valid temporary file was created!" ); + return false; + } + + // create temporary storage based on arbitrary medium + // after such a storage is closed it is lost + Object oTempStorage = m_xStorageFactory.createInstance(); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open substorages and create streams there + + // first substorage of the root storage + XStorage xTempSubStorage1 = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage1 == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + byte pBigBytes[] = new byte[33000]; + for ( int nInd = 0; nInd < 33000; nInd++ ) + pBigBytes[nInd] = (byte)( nInd % 128 ); + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage1, "BigSubStream1", "MediaType1", true, pBigBytes ) ) + return false; + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage1, "SubStream1", "MediaType1", true, pBytes1 ) ) + return false; + + // second substorage of the root storage + XStorage xTempSubStorage2 = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage2", + ElementModes.WRITE ); + if ( xTempSubStorage2 == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage2, "BigSubStream2", "MediaType2", false, pBigBytes ) ) + return false; + + byte pBytes2[] = { 2, 2, 2, 2, 2 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage2, "SubStream2", "MediaType2", false, pBytes2 ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempStorage, + "MediaType3", + true, + ElementModes.WRITE ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage1, + "MediaType4", + false, + ElementModes.WRITE ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage2, + "MediaType5", + false, + ElementModes.WRITE ) ) + return false; + + // create temporary storage based on a previously created temporary file + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) sTempFileURL; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempFileStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempFileStorage = (XStorage)UnoRuntime.queryInterface( XStorage.class, oTempFileStorage ); + if ( xTempFileStorage == null ) + { + m_aTestHelper.Error( "Can't create storage based on temporary file!" ); + return false; + } + + if ( !m_aTestHelper.copyElementTo( xTempStorage, "SubStorage1", xTempFileStorage ) ) + return false; + + // if storage is not committed before disposing all the changes will be lost + if ( !m_aTestHelper.commitStorage( xTempSubStorage2 ) ) + return false; + + // a storage must be disposed before moving/removing otherwise the access will be denied + if ( !m_aTestHelper.disposeStorage( xTempSubStorage2 ) ) + return false; + + if ( !m_aTestHelper.moveElementTo( xTempStorage, "SubStorage2", xTempFileStorage ) ) + return false; + + // SubStorage2 must be removed and disposed now + try + { + xTempSubStorage2.isStreamElement( "SubStream2" ); + m_aTestHelper.Error( "SubStorage2 must be disposed already!" ); + return false; + } + catch( com.sun.star.lang.DisposedException de ) + { + } + catch( Exception e ) + { + m_aTestHelper.Error( "Wrong exception in case of disposed storage, exception: " + e ); + return false; + } + + if ( !m_aTestHelper.copyElementTo( xTempSubStorage1, "SubStream1", xTempFileStorage ) ) + return false; + + if ( !m_aTestHelper.renameElement( xTempFileStorage, "SubStream1", "SubStream1_copy" ) ) + return false; + + if ( !m_aTestHelper.moveElementTo( xTempSubStorage1, "SubStream1", xTempFileStorage ) ) + return false; + + if ( !m_aTestHelper.copyElementTo( xTempSubStorage1, "BigSubStream1", xTempFileStorage ) ) + return false; + + if ( !m_aTestHelper.renameElement( xTempFileStorage, "BigSubStream1", "BigSubStream1_copy" ) ) + return false; + + if ( !m_aTestHelper.moveElementTo( xTempSubStorage1, "BigSubStream1", xTempFileStorage ) ) + return false; + + if ( !m_aTestHelper.commitStorage( xTempFileStorage ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xTempStorage ) || !m_aTestHelper.disposeStorage( xTempFileStorage ) ) + return false; + + + // now check all the written and copied information + + + // the temporary file must not be locked any more after storage disposing + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + Object oResStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xResStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oResStorage ); + if ( xResStorage == null ) + { + m_aTestHelper.Error( "Can't reopen storage based on temporary file!" ); + return false; + } + + // open and check SubStorage1 + XStorage xResSubStorage1 = m_aTestHelper.openSubStorage( xResStorage, + "SubStorage1", + ElementModes.READ ); + if ( xResSubStorage1 == null ) + { + m_aTestHelper.Error( "Can't open existing substorage!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResSubStorage1, "MediaType4", false, ElementModes.READ ) ) + return false; + + + // open and check SubStorage2 + XStorage xResSubStorage2 = m_aTestHelper.openSubStorage( xResStorage, + "SubStorage2", + ElementModes.READ ); + if ( xResSubStorage2 == null ) + { + m_aTestHelper.Error( "Can't open existing substorage!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResSubStorage2, "MediaType5", false, ElementModes.READ ) ) + return false; + + + // check all the result streams + + if ( !m_aTestHelper.checkStream( xResStorage, "SubStream1", "MediaType1", true, pBytes1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResStorage, "BigSubStream1", "MediaType1", true, pBigBytes ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResStorage, "SubStream1_copy", "MediaType1", true, pBytes1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResStorage, "BigSubStream1_copy", "MediaType1", true, pBigBytes ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResSubStorage1, "SubStream1", "MediaType1", true, pBytes1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResSubStorage1, "BigSubStream1", "MediaType1", true, pBigBytes ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResSubStorage2, "SubStream2", "MediaType2", false, pBytes2 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResSubStorage2, "BigSubStream2", "MediaType2", false, pBigBytes ) ) + return false; + + // the storage must be disposed before removing + if ( !m_aTestHelper.disposeStorage( xResSubStorage2 ) ) + return false; + + // remove element and check that it was removed completely + if ( !m_aTestHelper.removeElement( xResStorage, "SubStorage2" ) ) + return false; + + try + { + XNameAccess xResAccess = (XNameAccess) UnoRuntime.queryInterface( XNameAccess.class, xResStorage ); + if ( xResAccess.hasByName( "SubStorage2" ) ) + m_aTestHelper.Error( "SubStorage2 must be removed already!" ); + } + catch( Exception e ) + { + m_aTestHelper.Error( "Can't get access to root storage, exception: " + e ); + return false; + } + + try + { + xResSubStorage2.isStreamElement( "SubStream2" ); + + m_aTestHelper.Error( "SubStorage2 must be disposed already!" ); + return false; + } + catch( com.sun.star.lang.DisposedException de ) + { + } + catch( Exception e ) + { + m_aTestHelper.Error( "Wrong exception in case of disposed storage, exception: " + e ); + return false; + } + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xResStorage ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } + +} + diff --git a/package/qa/storages/Test05.java b/package/qa/storages/Test05.java new file mode 100644 index 0000000000..9138b4f86b --- /dev/null +++ b/package/qa/storages/Test05.java @@ -0,0 +1,317 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.io.XStream; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class Test05 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public Test05( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "Test05: " ); + } + + public boolean test() + { + try + { + String sTempFileURL = m_aTestHelper.CreateTempFile( m_xMSF ); + if ( sTempFileURL == null || sTempFileURL == "" ) + { + m_aTestHelper.Error( "No valid temporary file was created!" ); + return false; + } + + // create temporary storage based on a previously created temporary file + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) sTempFileURL; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempFileStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempFileStorage = (XStorage)UnoRuntime.queryInterface( XStorage.class, oTempFileStorage ); + if ( xTempFileStorage == null ) + { + m_aTestHelper.Error( "Can't create storage based on temporary file!" ); + return false; + } + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempFileStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open a new substorage + XStorage xSubSubStorage = m_aTestHelper.openSubStorage( xTempSubStorage, + "SubSubStorage1", + ElementModes.WRITE ); + if ( xSubSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + byte pBigBytes[] = new byte[33000]; + for ( int nInd = 0; nInd < 33000; nInd++ ) + pBigBytes[nInd] = (byte)( nInd % 128 ); + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xSubSubStorage, "BigSubStream1", "MediaType1", true, pBigBytes ) ) + return false; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xSubSubStorage, "BigSubStream2", "MediaType2", false, pBigBytes ) ) + return false; + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xSubSubStorage, "SubStream1", "MediaType1", true, pBytes1 ) ) + return false; + + byte pBytes2[] = { 2, 2, 2, 2, 2 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xSubSubStorage, "SubStream2", "MediaType2", false, pBytes2 ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempFileStorage, + "MediaType3", + true, + ElementModes.WRITE ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + "MediaType4", + false, + ElementModes.WRITE ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xSubSubStorage, + "MediaType5", + false, + ElementModes.WRITE ) ) + return false; + + + // commit all the storages + if ( !m_aTestHelper.commitStorage( xSubSubStorage ) ) + return false; + + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + if ( !m_aTestHelper.commitStorage( xTempFileStorage ) ) + return false; + + // try to open an opened substorage, open call must fail + if ( !m_aTestHelper.cantOpenStorage( xTempFileStorage, "SubStorage1" ) ) + return false; + + + // reopen created streams + XStream xSubStream1 = m_aTestHelper.OpenStream( xSubSubStorage, + "SubStream1", + ElementModes.WRITE | ElementModes.NOCREATE ); + XStream xBigSubStream1 = m_aTestHelper.OpenStream( xSubSubStorage, + "BigSubStream1", + ElementModes.WRITE | ElementModes.NOCREATE ); + XStream xSubStream2 = m_aTestHelper.OpenStream( xSubSubStorage, + "SubStream2", + ElementModes.READ | ElementModes.NOCREATE ); + XStream xBigSubStream2 = m_aTestHelper.OpenStream( xSubSubStorage, + "BigSubStream2", + ElementModes.READ | ElementModes.NOCREATE ); + + if ( xSubStream1 == null || xBigSubStream1 == null || xSubStream2 == null || xBigSubStream2 == null ) + return false; + + // it should be possible to have more than one copy of stream for reading + XStream xSubStream2clone = m_aTestHelper.OpenStream( xSubSubStorage, + "SubStream2", + ElementModes.READ | ElementModes.NOCREATE ); + XStream xBigSubStream2clone = m_aTestHelper.OpenStream( xSubSubStorage, + "BigSubStream2", + ElementModes.READ | ElementModes.NOCREATE ); + if ( xSubStream2clone == null || xBigSubStream2clone == null ) + return false; + + + // so now the first streams can not be open neither for reading nor for writing + if ( !m_aTestHelper.cantOpenStream( xSubSubStorage, "SubStream1", ElementModes.WRITE ) + || !m_aTestHelper.cantOpenStream( xSubSubStorage, "SubStream1", ElementModes.READ ) + || !m_aTestHelper.cantOpenStream( xSubSubStorage, "BigSubStream1", ElementModes.WRITE ) + || !m_aTestHelper.cantOpenStream( xSubSubStorage, "BigSubStream1", ElementModes.READ ) ) + return false; + + // the second streams can not be open for writing + if ( !m_aTestHelper.cantOpenStream( xSubSubStorage, "SubStream2", ElementModes.WRITE ) + || !m_aTestHelper.cantOpenStream( xSubSubStorage, "BigSubStream2", ElementModes.WRITE ) ) + return false; + + + // dispose xTestSubStorage, all the subtree must be disposed + if ( !m_aTestHelper.disposeStorage( xTempSubStorage ) ) + return false; + + // check that subtree was disposed correctly + try + { + xSubSubStorage.isStreamElement( "SubStream1" ); + m_aTestHelper.Error( "Substorage was not disposed!" ); + return false; + } + catch ( com.sun.star.lang.DisposedException de ) + {} + catch ( Exception e ) + { + m_aTestHelper.Error( "Wrong exception is thrown by disposed storage: " + e ); + return false; + } + + try + { + xSubStream1.getInputStream(); + m_aTestHelper.Error( "Writeable substream was not disposed!" ); + return false; + } + catch ( com.sun.star.lang.DisposedException de ) + {} + catch ( Exception e ) + { + m_aTestHelper.Error( "Wrong exception is thrown by disposed stream: " + e ); + return false; + } + + try + { + xSubStream2.getInputStream(); + m_aTestHelper.Error( "Readonly substream was not disposed!" ); + return false; + } + catch ( com.sun.star.lang.DisposedException de ) + {} + catch ( Exception e ) + { + m_aTestHelper.Error( "Wrong exception is thrown by disposed stream: " + e ); + return false; + } + + + // dispose root storage + if ( !m_aTestHelper.disposeStorage( xTempFileStorage ) ) + return false; + + + + // now check all the written and copied information + + + pArgs[1] = Integer.valueOf( ElementModes.READ ); + Object oResultStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xResultStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oResultStorage ); + if ( xResultStorage == null ) + { + m_aTestHelper.Error( "Can't reopen storage based on temporary file!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultStorage, "MediaType3", true, ElementModes.READ ) ) + return false; + + // open existing substorage + XStorage xResSubStorage = m_aTestHelper.openSubStorage( xResultStorage, + "SubStorage1", + ElementModes.READ ); + if ( xResSubStorage == null ) + { + m_aTestHelper.Error( "Can't open existing substorage 'SubSubStorage'!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResSubStorage, "MediaType4", false, ElementModes.READ ) ) + return false; + + // open existing substorage + XStorage xResSubSubStorage = m_aTestHelper.openSubStorage( xResSubStorage, + "SubSubStorage1", + ElementModes.READ ); + if ( xResSubSubStorage == null ) + { + m_aTestHelper.Error( "Can't open existing substorage 'SubSubStorage'!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResSubSubStorage, "MediaType5", false, ElementModes.READ ) ) + return false; + + // check substreams + if ( !m_aTestHelper.checkStream( xResSubSubStorage, "SubStream1", "MediaType1", true, pBytes1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResSubSubStorage, "BigSubStream1", "MediaType1", true, pBigBytes ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResSubSubStorage, "SubStream2", "MediaType2", false, pBytes2 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResSubSubStorage, "BigSubStream2", "MediaType2", false, pBigBytes ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xResultStorage ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } + +} + diff --git a/package/qa/storages/Test06.java b/package/qa/storages/Test06.java new file mode 100644 index 0000000000..7d379f1823 --- /dev/null +++ b/package/qa/storages/Test06.java @@ -0,0 +1,297 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; + +import com.sun.star.lang.IllegalArgumentException; +import com.sun.star.container.NoSuchElementException; +import com.sun.star.container.ElementExistException; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class Test06 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public Test06( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "Test06: " ); + } + + public boolean test() + { + try + { + // create temporary storage based on arbitrary medium + // after such a storage is closed it is lost + Object oTempStorage = m_xStorageFactory.createInstance(); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + try + { + xTempStorage.copyToStorage( null ); + m_aTestHelper.Error( "The method must throw an exception because of illegal parameter!" ); + return false; + } + catch( com.sun.star.lang.IllegalArgumentException iae ) + {} + catch( com.sun.star.uno.Exception ue ) + {} + catch( Exception e ) + { + m_aTestHelper.Error( "Unexpected exception because of illegal parameter : " + e ); + return false; + } + + // open new substorages + XStorage xTempSubStorage1 = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + XStorage xTempSubStorage2 = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage2", + ElementModes.WRITE ); + if ( xTempSubStorage1 == null || xTempSubStorage2 == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // in case stream is open for reading it must exist + try + { + xTempSubStorage1.openStreamElement( "NonExistingStream", ElementModes.READ ); + m_aTestHelper.Error( "The method must throw an exception in case of try to open nonexistent stream for reading!" ); + return false; + } + catch( com.sun.star.uno.Exception ue ) + {} + catch( Exception e ) + { + m_aTestHelper.Error( "Unexpected exception in case of try to open nonexistent stream for reading : " + e ); + return false; + } + + // in case a storage is open for reading it must exist + try + { + xTempSubStorage1.openStreamElement( "NonExistingStorage", ElementModes.READ ); + m_aTestHelper.Error( "The method must throw an exception in case of try to open nonexistent storage for reading!" ); + return false; + } + catch( com.sun.star.uno.Exception ue ) + {} + catch( Exception e ) + { + m_aTestHelper.Error( "Unexpected exception in case of try to open nonexistent storage for reading : " + e ); + return false; + } + + // in case of removing nonexistent element an exception must be thrown + try + { + xTempSubStorage1.removeElement( "NonExistingElement" ); + m_aTestHelper.Error( "An exception must be thrown in case of removing nonexistent element!" ); + return false; + } + catch( com.sun.star.container.NoSuchElementException ne ) + {} + catch( Exception e ) + { + m_aTestHelper.Error( "Unexpected exception in case of try to remove nonexistent element : " + e ); + return false; + } + + // in case of renaming of nonexistent element an exception must be thrown + try + { + xTempSubStorage1.renameElement( "NonExistingElement", "NewName" ); + m_aTestHelper.Error( "An exception must be thrown in case of renaming nonexistent element!" ); + return false; + } + catch( com.sun.star.container.NoSuchElementException ne ) + {} + catch( Exception e ) + { + m_aTestHelper.Error( "Unexpected exception in case of try to rename nonexistent element : " + e ); + return false; + } + + // in case of renaming to a name of existent element an exception must be thrown + try + { + xTempStorage.renameElement( "SubStorage1", "SubStorage2" ); + m_aTestHelper.Error( "An exception must be thrown in case of renaming to the name of existent element!" ); + return false; + } + catch( com.sun.star.container.ElementExistException ee ) + {} + catch( Exception e ) + { + m_aTestHelper.Error( "Unexpected exception in case of try to rename to the name of existent element : " + e ); + return false; + } + + // in case of copying target storage must be provided + try + { + xTempStorage.copyElementTo( "SubStorage1", null, "SubStorage1" ); + m_aTestHelper.Error( "An exception must be thrown in case empty reference is provided as target for copying!" ); + return false; + } + catch( com.sun.star.lang.IllegalArgumentException iae ) + {} + catch( com.sun.star.uno.Exception ue ) + {} + catch( Exception e ) + { + m_aTestHelper.Error( "Unexpected exception in case empty reference is provided as target for copying : " + e ); + return false; + } + + // in case of moving target storage must be provided + try + { + xTempStorage.moveElementTo( "SubStorage1", null, "SubStorage1" ); + m_aTestHelper.Error( "An exception must be thrown in case empty reference is provided as target for moving!" ); + return false; + } + catch( com.sun.star.lang.IllegalArgumentException iae ) + {} + catch( com.sun.star.uno.Exception ue ) + {} + catch( Exception e ) + { + m_aTestHelper.Error( "Unexpected exception in case empty reference is provided as target for moving : " + e ); + return false; + } + + + // prepare target for further testings + + // create new temporary storage based on arbitrary medium + Object oTargetStorage = m_xStorageFactory.createInstance(); + XStorage xTargetStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTargetStorage ); + if ( xTargetStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open a new substorage + XStorage xTargetSubStorage = m_aTestHelper.openSubStorage( xTargetStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTargetSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // in case of copying of nonexistent element an exception must be thrown + try + { + xTempStorage.copyElementTo( "Nonexistent element", xTargetStorage, "Target" ); + m_aTestHelper.Error( "An exception must be thrown in case of copying of nonexistent element!" ); + return false; + } + catch( com.sun.star.container.NoSuchElementException ne ) + {} + catch( Exception e ) + { + m_aTestHelper.Error( "Unexpected exception in case of copying of nonexistent element: " + e ); + return false; + } + + // in case of moving of nonexistent element an exception must be thrown + try + { + xTempStorage.moveElementTo( "Nonexistent element", xTargetStorage, "Target" ); + m_aTestHelper.Error( "An exception must be thrown in case of moving of nonexistent element!" ); + return false; + } + catch( com.sun.star.container.NoSuchElementException ne ) + {} + catch( Exception e ) + { + m_aTestHelper.Error( "Unexpected exception in case of moving of nonexistent element: " + e ); + return false; + } + + // in case target for copying already exists an exception must be thrown + try + { + xTempStorage.copyElementTo( "SubStorage1", xTargetStorage, "SubStorage1" ); + m_aTestHelper.Error( "An exception must be thrown in case target for copying already exists!" ); + return false; + } + catch( com.sun.star.container.ElementExistException ee ) + {} + catch( Exception e ) + { + m_aTestHelper.Error( "Unexpected exception in case target for copying already exists: " + e ); + return false; + } + + // in case target for moving already exists an exception must be thrown + try + { + xTempStorage.moveElementTo( "SubStorage1", xTargetStorage, "SubStorage1" ); + m_aTestHelper.Error( "An exception must be thrown in case target for moving already exists!" ); + return false; + } + catch( com.sun.star.container.ElementExistException ee ) + {} + catch( Exception e ) + { + m_aTestHelper.Error( "Unexpected exception in case target for moving already exists: " + e ); + return false; + } + + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } + +} + diff --git a/package/qa/storages/Test07.java b/package/qa/storages/Test07.java new file mode 100644 index 0000000000..db0cca5b03 --- /dev/null +++ b/package/qa/storages/Test07.java @@ -0,0 +1,180 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class Test07 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public Test07( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "Test07: " ); + } + + public boolean test() + { + try + { + String sTempFileURL = m_aTestHelper.CreateTempFile( m_xMSF ); + if ( sTempFileURL == null || sTempFileURL == "" ) + { + m_aTestHelper.Error( "No valid temporary file was created!" ); + return false; + } + + // create temporary storage based on arbitrary medium + // after such a storage is closed it is lost + Object oTempStorage = m_xStorageFactory.createInstance(); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + byte pBigBytes[] = new byte[33000]; + for ( int nInd = 0; nInd < 33000; nInd++ ) + pBigBytes[nInd] = (byte)( nInd % 128 ); + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + String sPass1 = "12345"; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToEncrSubstream( xTempStorage, "BigSubStream1", "MediaType1", true, pBigBytes, sPass1 ) ) + return false; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToEncrSubstream( xTempStorage, "SubStream1", "MediaType1", true, pBytes1, sPass1 ) ) + return false; + + byte pBytes2[] = { 2, 2, 2, 2, 2 }; + String sPass2 = "54321"; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToEncrSubstream( xTempStorage, "BigSubStream2", "MediaType2", false, pBigBytes, sPass2 ) ) + return false; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToEncrSubstream( xTempStorage, "SubStream2", "MediaType2", false, pBytes2, sPass2 ) ) + return false; + + // create temporary storage based on a previously created temporary file + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) sTempFileURL; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempFileStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempFileStorage = (XStorage)UnoRuntime.queryInterface( XStorage.class, oTempFileStorage ); + if ( xTempFileStorage == null ) + { + m_aTestHelper.Error( "Can't create storage based on temporary file!" ); + return false; + } + + // copy xTempStorage to xTempFileStorage + // xTempFileStorage will be automatically committed + if ( !m_aTestHelper.copyStorage( xTempStorage, xTempFileStorage ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xTempStorage ) || !m_aTestHelper.disposeStorage( xTempFileStorage ) ) + return false; + + + // now check all the written and copied information + + + // the temporary file must not be locked any more after storage disposing + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + Object oResultStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xResultStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oResultStorage ); + if ( xResultStorage == null ) + { + m_aTestHelper.Error( "Can't reopen storage based on temporary file!" ); + return false; + } + + Object o2CopyStorage = m_xStorageFactory.createInstance(); + XStorage x2CopyStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, o2CopyStorage ); + if ( x2CopyStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + if ( !m_aTestHelper.copyStorage( xResultStorage, x2CopyStorage ) ) + return false; + + if ( !m_aTestHelper.checkEncrStream( xResultStorage, "SubStream1", "MediaType1", pBytes1, sPass1 ) ) + return false; + + if ( !m_aTestHelper.checkEncrStream( xResultStorage, "BigSubStream1", "MediaType1", pBigBytes, sPass1 ) ) + return false; + + if ( !m_aTestHelper.checkEncrStream( xResultStorage, "SubStream2", "MediaType2", pBytes2, sPass2 ) ) + return false; + + if ( !m_aTestHelper.checkEncrStream( xResultStorage, "BigSubStream2", "MediaType2", pBigBytes, sPass2 ) ) + return false; + + if ( !m_aTestHelper.checkEncrStream( x2CopyStorage, "SubStream1", "MediaType1", pBytes1, sPass1 ) ) + return false; + + if ( !m_aTestHelper.checkEncrStream( x2CopyStorage, "BigSubStream1", "MediaType1", pBigBytes, sPass1 ) ) + return false; + + if ( !m_aTestHelper.checkEncrStream( x2CopyStorage, "SubStream2", "MediaType2", pBytes2, sPass2 ) ) + return false; + + if ( !m_aTestHelper.checkEncrStream( x2CopyStorage, "BigSubStream2", "MediaType2", pBigBytes, sPass2 ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xResultStorage ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } + +} + diff --git a/package/qa/storages/Test08.java b/package/qa/storages/Test08.java new file mode 100644 index 0000000000..9113ee3854 --- /dev/null +++ b/package/qa/storages/Test08.java @@ -0,0 +1,248 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class Test08 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public Test08( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "Test08: " ); + } + + public boolean test() + { + try + { + + // create temporary storage based on arbitrary medium + // after such a storage is closed it is lost + Object oTempStorage = m_xStorageFactory.createInstance(); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // set the global password for the root storage + XEncryptionProtectedSource xTempStorageEncryption = + (XEncryptionProtectedSource) UnoRuntime.queryInterface( XEncryptionProtectedSource.class, xTempStorage ); + + if ( xTempStorageEncryption == null ) + { + m_aTestHelper.Message( "Optional interface XEncryptionProtectedSource is not implemented, feature can not be tested!" ); + return true; + } + + String sPass1 = "123"; + String sPass2 = "321"; + + try { + xTempStorageEncryption.setEncryptionPassword( sPass1 ); + } + catch( Exception e ) + { + m_aTestHelper.Error( "Can't set a common encryption key for the storage, exception:" + e ); + return false; + } + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + byte pBigBytes[] = new byte[33000]; + for ( int nInd = 0; nInd < 33000; nInd++ ) + pBigBytes[nInd] = (byte)( nInd % 128 ); + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + // the stream will be encrypted with common password + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + if ( !m_aTestHelper.WBToSubstrOfEncr( xTempSubStorage, "SubStream1", "MediaType1", true, pBytes1, true ) ) + return false; + if ( !m_aTestHelper.WBToSubstrOfEncr( xTempSubStorage, "BigSubStream1", "MediaType1", true, pBigBytes, true ) ) + return false; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + // the stream will not be encrypted + byte pBytes2[] = { 2, 2, 2, 2, 2 }; + if ( !m_aTestHelper.WBToSubstrOfEncr( xTempSubStorage, "SubStream2", "MediaType2", false, pBytes2, false ) ) + return false; + if ( !m_aTestHelper.WBToSubstrOfEncr( xTempSubStorage, "BigSubStream2", "MediaType2", false, pBigBytes, false ) ) + return false; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + // the stream will be compressed with own password + byte pBytes3[] = { 3, 3, 3, 3, 3 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + // the stream will not be encrypted + if ( !m_aTestHelper.WriteBytesToEncrSubstream( xTempSubStorage, "SubStream3", "MediaType3", false, pBytes3, sPass2 ) ) + return false; + if ( !m_aTestHelper.WriteBytesToEncrSubstream( xTempSubStorage, "BigSubStream3", "MediaType3", false, pBigBytes, sPass2 ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempStorage, + "MediaType4", + true, + ElementModes.WRITE ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + "MediaType5", + false, + ElementModes.WRITE ) ) + return false; + + // create temporary file + String sTempFileURL = m_aTestHelper.CreateTempFile( m_xMSF ); + if ( sTempFileURL == null || sTempFileURL == "" ) + { + m_aTestHelper.Error( "No valid temporary file was created!" ); + return false; + } + + // create temporary storage based on a previously created temporary file + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) sTempFileURL; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempFileStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempFileStorage = (XStorage)UnoRuntime.queryInterface( XStorage.class, oTempFileStorage ); + if ( xTempFileStorage == null ) + { + m_aTestHelper.Error( "Can't create storage based on temporary file!" ); + return false; + } + + // copy xTempStorage to xTempFileStorage + // xTempFileStorage will be automatically committed + if ( !m_aTestHelper.copyStorage( xTempStorage, xTempFileStorage ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xTempStorage ) || !m_aTestHelper.disposeStorage( xTempFileStorage ) ) + return false; + + + // now check all the written and copied information + + + // the temporary file must not be locked any more after storage disposing + pArgs[1] = Integer.valueOf( ElementModes.READ ); + Object oResultStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xResultStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oResultStorage ); + if ( xResultStorage == null ) + { + m_aTestHelper.Error( "Can't reopen storage based on temporary file!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultStorage, "MediaType4", true, ElementModes.READ ) ) + return false; + + // open existing substorage + XStorage xResultSubStorage = m_aTestHelper.openSubStorage( xResultStorage, + "SubStorage1", + ElementModes.READ ); + if ( xResultSubStorage == null ) + { + m_aTestHelper.Error( "Can't open existing substorage!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultSubStorage, "MediaType5", false, ElementModes.READ ) ) + return false; + + // set the global password for the root storage + XEncryptionProtectedSource xResultStorageEncryption = + (XEncryptionProtectedSource) UnoRuntime.queryInterface( XEncryptionProtectedSource.class, xResultStorage ); + + if ( xResultStorageEncryption == null ) + { + m_aTestHelper.Error( "XEncryptionProtectedSource was successfully used already, so it must be supported!" ); + return false; + } + + try { + xResultStorageEncryption.setEncryptionPassword( sPass2 ); + } + catch( Exception e ) + { + m_aTestHelper.Error( "Can't set a common encryption key for the storage, exception:" + e ); + return false; + } + + if ( !m_aTestHelper.checkEncrStream( xResultSubStorage, "SubStream1", "MediaType1", pBytes1, sPass1 ) ) + return false; + if ( !m_aTestHelper.checkEncrStream( xResultSubStorage, "BigSubStream1", "MediaType1", pBigBytes, sPass1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResultSubStorage, "SubStream2", "MediaType2", false, pBytes2 ) ) + return false; + if ( !m_aTestHelper.checkStream( xResultSubStorage, "BigSubStream2", "MediaType2", false, pBigBytes ) ) + return false; + + // the common root storage password should allow to open this stream + if ( !m_aTestHelper.checkStream( xResultSubStorage, "SubStream3", "MediaType3", true, pBytes3 ) ) + return false; + if ( !m_aTestHelper.checkStream( xResultSubStorage, "BigSubStream3", "MediaType3", true, pBigBytes ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xResultStorage ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } +} + diff --git a/package/qa/storages/Test09.java b/package/qa/storages/Test09.java new file mode 100644 index 0000000000..3790ecd8b9 --- /dev/null +++ b/package/qa/storages/Test09.java @@ -0,0 +1,156 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class Test09 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public Test09( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "Test09: " ); + } + + public boolean test() + { + try + { + + // create temporary storage based on arbitrary medium + // after such a storage is closed it is lost + Object oTempStorage = m_xStorageFactory.createInstance(); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + String sPass1 = "123"; + String sPass2 = "321"; + byte pBytes[] = { 1, 1, 1, 1, 1 }; + byte pBigBytes[] = new byte[33000]; + for ( int nInd = 0; nInd < 33000; nInd++ ) + pBigBytes[nInd] = (byte)( nInd % 128 ); + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + // the stream will not be encrypted + if ( !m_aTestHelper.WriteBytesToEncrSubstream( xTempStorage, "SubStream1", "MediaType1", false, pBytes, sPass1 ) ) + return false; + if ( !m_aTestHelper.WriteBytesToEncrSubstream( xTempStorage, "BigSubStream1", "MediaType1", false, pBigBytes, sPass1 ) ) + return false; + + // create temporary file + String sTempFileURL = m_aTestHelper.CreateTempFile( m_xMSF ); + if ( sTempFileURL == null || sTempFileURL == "" ) + { + m_aTestHelper.Error( "No valid temporary file was created!" ); + return false; + } + + // create temporary storage based on a previously created temporary file + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) sTempFileURL; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempFileStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempFileStorage = (XStorage)UnoRuntime.queryInterface( XStorage.class, oTempFileStorage ); + if ( xTempFileStorage == null ) + { + m_aTestHelper.Error( "Can't create storage based on temporary file!" ); + return false; + } + + // copy xTempStorage to xTempFileStorage + // xTempFileStorage will be automatically committed + if ( !m_aTestHelper.copyStorage( xTempStorage, xTempFileStorage ) ) + return false; + + // change password of the substream of new storage based on file + int nResult = m_aTestHelper.ChangeStreamPass( xTempFileStorage, "SubStream1", sPass1, sPass2 ); + if ( nResult == 0 ) + return false; // test failed + else if ( nResult == -1 ) + return true; // tested optional feature is not supported + + // change password of the substream of new storage based on file + nResult = m_aTestHelper.ChangeStreamPass( xTempFileStorage, "BigSubStream1", sPass1, sPass2 ); + if ( nResult == 0 ) + return false; // test failed + else if ( nResult == -1 ) + return true; // tested optional feature is not supported + + if ( !m_aTestHelper.commitStorage( xTempFileStorage ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xTempStorage ) || !m_aTestHelper.disposeStorage( xTempFileStorage ) ) + return false; + + + // now check all the written and copied information + + + // the temporary file must not be locked any more after storage disposing + pArgs[1] = Integer.valueOf( ElementModes.READ ); + Object oResultStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xResultStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oResultStorage ); + if ( xResultStorage == null ) + { + m_aTestHelper.Error( "Can't reopen storage based on temporary file!" ); + return false; + } + + if ( !m_aTestHelper.checkEncrStream( xResultStorage, "SubStream1", "MediaType1", pBytes, sPass2 ) ) + return false; + if ( !m_aTestHelper.checkEncrStream( xResultStorage, "BigSubStream1", "MediaType1", pBigBytes, sPass2 ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xResultStorage ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } +} + diff --git a/package/qa/storages/Test10.java b/package/qa/storages/Test10.java new file mode 100644 index 0000000000..6b4a5bb411 --- /dev/null +++ b/package/qa/storages/Test10.java @@ -0,0 +1,250 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; + +import com.sun.star.container.XNameAccess; +import com.sun.star.io.XStream; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class Test10 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public Test10( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "Test10: " ); + } + + public boolean test() + { + try + { + // create temporary storage based on arbitrary medium + // after such a storage is closed it is lost + Object oTempStorage = m_xStorageFactory.createInstance(); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + byte pBigBytes[] = new byte[33000]; + for ( int nInd = 0; nInd < 33000; nInd++ ) + pBigBytes[nInd] = (byte)( nInd % 128 ); + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempStorage, "SubStream1", "MediaType1", true, pBytes1 ) ) + return false; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempStorage, "BigSubStream1", "MediaType1", true, pBigBytes ) ) + return false; + + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + byte pBytes2[] = { 2, 2, 2, 2, 2 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, "SubStream2", "MediaType2", true, pBytes2 ) ) + return false; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, "BigSubStream2", "MediaType2", true, pBigBytes ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempStorage, + "MediaType3", + true, + ElementModes.WRITE ) ) + return false; + + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + "MediaType4", + false, + ElementModes.WRITE ) ) + return false; + + + // check cloning at current state + + + // the new storage still was not committed so the clone must be empty + XStorage xClonedSubStorage = m_aTestHelper.cloneSubStorage( m_xStorageFactory, xTempStorage, "SubStorage1" ); + + if ( xClonedSubStorage == null ) + { + m_aTestHelper.Error( "The result of clone is empty!" ); + return false; + } + + XNameAccess xClonedNameAccess = (XNameAccess) UnoRuntime.queryInterface( XNameAccess.class, xClonedSubStorage ); + if ( xClonedNameAccess == null ) + { + m_aTestHelper.Error( "XNameAccess is not implemented by the clone!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xClonedSubStorage, "", true, ElementModes.WRITE ) ) + return false; + + if ( xClonedNameAccess.hasElements() ) + { + m_aTestHelper.Error( "The new substorage still was not committed so it must be empty!" ); + return false; + } + + if ( !m_aTestHelper.disposeStorage( xClonedSubStorage ) ) + return false; + + xClonedSubStorage = null; + xClonedNameAccess = null; + + // the new stream was opened, written and closed, that means flashed + // so the clone must contain all the information + XStream xClonedSubStream = m_aTestHelper.cloneSubStream( xTempStorage, "SubStream1" ); + if ( !m_aTestHelper.InternalCheckStream( xClonedSubStream, "SubStream1", "MediaType1", true, pBytes1, true ) ) + return false; + + XStream xClonedBigSubStream = m_aTestHelper.cloneSubStream( xTempStorage, "BigSubStream1" ); + if ( !m_aTestHelper.InternalCheckStream( xClonedBigSubStream, "BigSubStream1", "MediaType1", true, pBigBytes, true ) ) + return false; + + if ( !m_aTestHelper.disposeStream( xClonedSubStream, "SubStream1" ) ) + return false; + + if ( !m_aTestHelper.disposeStream( xClonedBigSubStream, "BigSubStream1" ) ) + return false; + + + // commit substorage and check cloning + + + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + xClonedSubStorage = m_aTestHelper.cloneSubStorage( m_xStorageFactory, xTempStorage, "SubStorage1" ); + if ( xClonedSubStorage == null ) + { + m_aTestHelper.Error( "The result of clone is empty!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xClonedSubStorage, "MediaType4", true, ElementModes.WRITE ) ) + return false; + + if ( !m_aTestHelper.checkStream( xClonedSubStorage, "SubStream2", "MediaType2", true, pBytes2 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xClonedSubStorage, "BigSubStream2", "MediaType2", true, pBigBytes ) ) + return false; + + XStorage xCloneOfRoot = m_aTestHelper.cloneStorage( m_xStorageFactory, xTempStorage ); + if ( xCloneOfRoot == null ) + { + m_aTestHelper.Error( "The result of root clone is empty!" ); + return false; + } + + XNameAccess xCloneOfRootNA = (XNameAccess) UnoRuntime.queryInterface( XNameAccess.class, xCloneOfRoot ); + if ( xCloneOfRootNA == null ) + { + m_aTestHelper.Error( "XNameAccess is not implemented by the root clone!" ); + return false; + } + + if ( xCloneOfRootNA.hasElements() ) + { + m_aTestHelper.Error( "The root storage still was not committed so it's clone must be empty!" ); + return false; + } + + if ( !m_aTestHelper.disposeStorage( xCloneOfRoot ) ) + return false; + + xCloneOfRoot = null; + + + // commit root storage and check cloning + + + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + xCloneOfRoot = m_aTestHelper.cloneStorage( m_xStorageFactory, xTempStorage ); + if ( xCloneOfRoot == null ) + { + m_aTestHelper.Error( "The result of root clone is empty!" ); + return false; + } + + XStorage xSubStorageOfClone = xCloneOfRoot.openStorageElement( "SubStorage1", ElementModes.READ ); + if ( xSubStorageOfClone == null ) + { + m_aTestHelper.Error( "The result of root clone is wrong!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xSubStorageOfClone, "MediaType4", false, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStream( xSubStorageOfClone, "SubStream2", "MediaType2", true, pBytes2 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xSubStorageOfClone, "BigSubStream2", "MediaType2", true, pBigBytes ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } +} + diff --git a/package/qa/storages/Test11.java b/package/qa/storages/Test11.java new file mode 100644 index 0000000000..02320db81d --- /dev/null +++ b/package/qa/storages/Test11.java @@ -0,0 +1,236 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; + +import com.sun.star.container.XNameAccess; +import com.sun.star.io.XStream; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class Test11 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public Test11( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "Test11: " ); + } + + public boolean test() + { + try + { + // create temporary storage based on arbitrary medium + // after such a storage is closed it is lost + Object oTempStorage = m_xStorageFactory.createInstance(); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + byte pBigBytes[] = new byte[33000]; + for ( int nInd = 0; nInd < 33000; nInd++ ) + pBigBytes[nInd] = (byte)( nInd % 128 ); + + String sPass1 = "111111111"; + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToEncrSubstream( xTempStorage, "SubStream1", "MediaType1", true, pBytes1, sPass1 ) ) + return false; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToEncrSubstream( xTempStorage, "BigSubStream1", "MediaType1", true, pBigBytes, sPass1 ) ) + return false; + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + String sPass2 = "2222222222"; + byte pBytes2[] = { 2, 2, 2, 2, 2 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToEncrSubstream( xTempSubStorage, "SubStream2", "MediaType2", true, pBytes2, sPass2 ) ) + return false; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToEncrSubstream( xTempSubStorage, "BigSubStream2", "MediaType2", true, pBigBytes, sPass2 ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempStorage, + "MediaType3", + true, + ElementModes.WRITE ) ) + return false; + + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + "MediaType4", + false, + ElementModes.WRITE ) ) + return false; + + + // check cloning at current state + + + // the new storage still was not committed so the clone must be empty + XStorage xClonedSubStorage = m_aTestHelper.cloneSubStorage( m_xStorageFactory, xTempStorage, "SubStorage1" ); + + if ( xClonedSubStorage == null ) + { + m_aTestHelper.Error( "The result of clone is empty!" ); + return false; + } + + XNameAccess xClonedNameAccess = (XNameAccess) UnoRuntime.queryInterface( XNameAccess.class, xClonedSubStorage ); + if ( xClonedNameAccess == null ) + { + m_aTestHelper.Error( "XNameAccess is not implemented by the clone!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xClonedSubStorage, "", true, ElementModes.WRITE ) ) + return false; + + if ( xClonedNameAccess.hasElements() ) + { + m_aTestHelper.Error( "The new substorage still was not committed so it must be empty!" ); + return false; + } + + if ( !m_aTestHelper.disposeStorage( xClonedSubStorage ) ) + return false; + + xClonedSubStorage = null; + xClonedNameAccess = null; + + // the new stream was opened, written and closed, that means flashed + // so the clone must contain all the information + XStream xClonedSubStream = m_aTestHelper.cloneEncrSubStream( xTempStorage, "SubStream1", sPass1 ); + if ( !m_aTestHelper.InternalCheckStream( xClonedSubStream, "SubStream1", "MediaType1", true, pBytes1, true ) ) + return false; + + XStream xClonedBigSubStream = m_aTestHelper.cloneEncrSubStream( xTempStorage, "BigSubStream1", sPass1 ); + if ( !m_aTestHelper.InternalCheckStream( xClonedBigSubStream, "BigSubStream1", "MediaType1", true, pBigBytes, true ) ) + return false; + + if ( !m_aTestHelper.disposeStream( xClonedSubStream, "SubStream1" ) ) + return false; + + if ( !m_aTestHelper.disposeStream( xClonedBigSubStream, "BigSubStream1" ) ) + return false; + + + // commit substorage and check cloning + + + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + xClonedSubStorage = m_aTestHelper.cloneSubStorage( m_xStorageFactory, xTempStorage, "SubStorage1" ); + if ( xClonedSubStorage == null ) + { + m_aTestHelper.Error( "The result of clone is empty!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xClonedSubStorage, "MediaType4", true, ElementModes.WRITE ) ) + return false; + + if ( !m_aTestHelper.checkEncrStream( xClonedSubStorage, "SubStream2", "MediaType2", pBytes2, sPass2 ) ) + return false; + + if ( !m_aTestHelper.checkEncrStream( xClonedSubStorage, "BigSubStream2", "MediaType2", pBigBytes, sPass2 ) ) + return false; + + + // commit the root storage and check cloning + + + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + XStorage xCloneOfRoot = m_aTestHelper.cloneStorage( m_xStorageFactory, xTempStorage ); + if ( xCloneOfRoot == null ) + { + m_aTestHelper.Error( "The result of root clone is empty!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xCloneOfRoot, "MediaType3", true, ElementModes.WRITE ) ) + return false; + + if ( !m_aTestHelper.checkEncrStream( xCloneOfRoot, "SubStream1", "MediaType1", pBytes1, sPass1 ) ) + return false; + + if ( !m_aTestHelper.checkEncrStream( xCloneOfRoot, "BigSubStream1", "MediaType1", pBigBytes, sPass1 ) ) + return false; + + XStorage xSubStorageOfClone = xCloneOfRoot.openStorageElement( "SubStorage1", ElementModes.READ ); + if ( xSubStorageOfClone == null ) + { + m_aTestHelper.Error( "The result of root clone is wrong!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xSubStorageOfClone, "MediaType4", false, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkEncrStream( xSubStorageOfClone, "SubStream2", "MediaType2", pBytes2, sPass2 ) ) + return false; + + if ( !m_aTestHelper.checkEncrStream( xSubStorageOfClone, "BigSubStream2", "MediaType2", pBigBytes, sPass2 ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } +} + diff --git a/package/qa/storages/Test12.java b/package/qa/storages/Test12.java new file mode 100644 index 0000000000..13850daf47 --- /dev/null +++ b/package/qa/storages/Test12.java @@ -0,0 +1,258 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.io.XStream; +import com.sun.star.io.XInputStream; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class Test12 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public Test12( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "Test12: " ); + } + + public boolean test() + { + try + { + XStream xTempFileStream = m_aTestHelper.CreateTempFileStream( m_xMSF ); + if ( xTempFileStream == null ) + return false; + + // create storage based on the temporary stream + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) xTempFileStream; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + byte pBigBytes[] = new byte[33000]; + for ( int nInd = 0; nInd < 33000; nInd++ ) + pBigBytes[nInd] = (byte)( nInd % 128 ); + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, "SubStream1", "MediaType1", true, pBytes1 ) ) + return false; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, "BigSubStream1", "MediaType1", true, pBigBytes ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempStorage, + "MediaType2", + true, + ElementModes.WRITE ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + "MediaType3", + false, + ElementModes.WRITE ) ) + return false; + + // commit substorage first + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + // dispose substorage + if ( !m_aTestHelper.disposeStorage( xTempSubStorage ) ) + return false; + + + // check substorage + + + if ( !checkSubStorages( xTempStorage, pBytes1, pBigBytes ) ) + return false; + + // dispose used storage to free resources + if ( !m_aTestHelper.disposeStorage( xTempStorage ) ) + return false; + + + // now check all the written information with readwrite access + + + Object oResWriteStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xResWriteStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oResWriteStorage ); + if ( xResWriteStorage == null ) + { + m_aTestHelper.Error( "Can't open storage based on input stream!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResWriteStorage, "MediaType2", true, ElementModes.WRITE ) ) + return false; + + if( !checkSubStorages( xResWriteStorage, pBytes1, pBigBytes ) ) + return false; + + // try to open for writing after opening for reading + XStorage xResWSubStorage = m_aTestHelper.openSubStorage( xResWriteStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xResWSubStorage == null ) + { + m_aTestHelper.Error( "Can't open substorage for writing after it was opened for reading!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResWSubStorage, "MediaType3", false, ElementModes.WRITE ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResWSubStorage, "SubStream1", "MediaType1", true, pBytes1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResWSubStorage, "BigSubStream1", "MediaType1", true, pBigBytes ) ) + return false; + + // dispose used storage to free resources + if ( !m_aTestHelper.disposeStorage( xResWriteStorage ) ) + return false; + + + + // now check all the written information with readonly access + + + // close the output part of the temporary stream + // the output part must present since we already wrote to the stream + if ( !m_aTestHelper.closeOutput( xTempFileStream ) ) + return false; + + XInputStream xTempInStream = m_aTestHelper.getInputStream( xTempFileStream ); + if ( xTempInStream == null ) + return false; + + // open input stream + // since no mode is provided the result storage must be opened readonly + Object pOneArg[] = new Object[1]; + pOneArg[0] = (Object) xTempInStream; + + Object oResultStorage = m_xStorageFactory.createInstanceWithArguments( pOneArg ); + XStorage xResultStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oResultStorage ); + if ( xResultStorage == null ) + { + m_aTestHelper.Error( "Can't open storage based on input stream!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultStorage, "MediaType2", true, ElementModes.READ ) ) + return false; + + if( !checkSubStorages( xResultStorage, pBytes1, pBigBytes ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } + + private boolean checkSubStorages( XStorage xStorage, byte[] pBytes1, byte[] pBigBytes ) + { + XStorage xReadSubStorage1 = m_aTestHelper.openSubStorage( xStorage, + "SubStorage1", + ElementModes.READ ); + + XStorage xReadSubStorage2 = m_aTestHelper.openSubStorage( xStorage, + "SubStorage1", + ElementModes.READ ); + + if ( xReadSubStorage1 == null || xReadSubStorage2 == null ) + { + m_aTestHelper.Error( "Can't open substorage for reading!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xReadSubStorage1, "MediaType3", false, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStorageProperties( xReadSubStorage2, "MediaType3", false, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStream( xReadSubStorage1, "SubStream1", "MediaType1", true, pBytes1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xReadSubStorage1, "BigSubStream1", "MediaType1", true, pBigBytes ) ) + return false; + + if ( !m_aTestHelper.checkStream( xReadSubStorage2, "SubStream1", "MediaType1", true, pBytes1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xReadSubStorage2, "BigSubStream1", "MediaType1", true, pBigBytes ) ) + return false; + + if ( !m_aTestHelper.disposeStorage( xReadSubStorage1 ) ) + return false; + + if ( !m_aTestHelper.disposeStorage( xReadSubStorage2 ) ) + return false; + + return true; + } +} + diff --git a/package/qa/storages/Test13.java b/package/qa/storages/Test13.java new file mode 100644 index 0000000000..649f9d88b4 --- /dev/null +++ b/package/qa/storages/Test13.java @@ -0,0 +1,233 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class Test13 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public Test13( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "Test13: " ); + } + + public boolean test() + { + String aStreamPrefix = ""; + for ( int nInd = 0; nInd < 4; ++nInd, aStreamPrefix += "SubStorage" + nInd ) + if ( !testForPath( aStreamPrefix ) ) + return false; + + return true; + } + + public boolean testForPath( String aStreamPrefix ) + { + try + { + String aSubStream1Path = aStreamPrefix + "SubStream1"; + String aSubStream2Path = aStreamPrefix + "SubStream2"; + String aSubStream3Path = aStreamPrefix + "SubStream3"; + String aBigSubStream1Path = aStreamPrefix + "BigSubStream1"; + String aBigSubStream2Path = aStreamPrefix + "BigSubStream2"; + String aBigSubStream3Path = aStreamPrefix + "BigSubStream3"; + + String sTempFileURL = m_aTestHelper.CreateTempFile( m_xMSF ); + if ( sTempFileURL == null || sTempFileURL == "" ) + { + m_aTestHelper.Error( "No valid temporary file was created!" ); + return false; + } + + // create temporary storage based on a previously created temporary file + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) sTempFileURL; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempFileStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempFileStorage = (XStorage)UnoRuntime.queryInterface( XStorage.class, oTempFileStorage ); + if ( xTempFileStorage == null ) + { + m_aTestHelper.Error( "Can't create storage based on temporary file!" ); + return false; + } + + byte pBigBytes[] = new byte[33000]; + for ( int nInd = 0; nInd < 33000; nInd++ ) + pBigBytes[nInd] = (byte)( nInd % 128 ); + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + + // open a new substream hierarchically, set "MediaType" and "Compressed" properties to it, write some bytes + // and commit + if ( !m_aTestHelper.WriteBytesToStreamH( xTempFileStorage, aSubStream1Path, "MediaType1", true, pBytes1, true ) ) + return false; + if ( !m_aTestHelper.WriteBytesToStreamH( xTempFileStorage, aBigSubStream1Path, "MediaType1", true, pBigBytes, true ) ) + return false; + + byte pBytes2[] = { 2, 2, 2, 2, 2 }; + + // open a new substream hierarchically, set "MediaType" and "Compressed" properties to it, write some bytes + // and commit + if ( !m_aTestHelper.WriteBytesToStreamH( xTempFileStorage, aSubStream2Path, "MediaType2", false, pBytes2, true ) ) + return false; + if ( !m_aTestHelper.WriteBytesToStreamH( xTempFileStorage, aBigSubStream2Path, "MediaType2", false, pBigBytes, true ) ) + return false; + + // open a new substream hierarchically, set "MediaType" and "Compressed" properties to it, write some bytes + // and don't commit + if ( !m_aTestHelper.WriteBytesToStreamH( xTempFileStorage, aSubStream3Path, "MediaType2", false, pBytes2, false ) ) + return false; + if ( !m_aTestHelper.WriteBytesToStreamH( xTempFileStorage, aBigSubStream3Path, "MediaType2", false, pBigBytes, false ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempFileStorage, + "MediaType3", + true, + ElementModes.WRITE ) ) + return false; + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xTempFileStorage ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xTempFileStorage ) ) + return false; + + + // now reopen the storage, + // check all the written and copied information + // and change it + + + // the temporary file must not be locked any more after storage disposing + oTempFileStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + xTempFileStorage = (XStorage)UnoRuntime.queryInterface( XStorage.class, oTempFileStorage ); + if ( xTempFileStorage == null ) + { + m_aTestHelper.Error( "Can't create storage based on temporary file!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xTempFileStorage, "MediaType3", true, ElementModes.WRITE ) ) + return false; + + if ( !m_aTestHelper.checkStreamH( xTempFileStorage, aSubStream1Path, "MediaType1", true, pBytes1 ) ) + return false; + + if ( !m_aTestHelper.checkStreamH( xTempFileStorage, aBigSubStream1Path, "MediaType1", true, pBigBytes ) ) + return false; + + if ( !m_aTestHelper.checkStreamH( xTempFileStorage, aSubStream2Path, "MediaType2", false, pBytes2 ) ) + return false; + + if ( !m_aTestHelper.checkStreamH( xTempFileStorage, aBigSubStream2Path, "MediaType2", false, pBigBytes ) ) + return false; + + if ( !m_aTestHelper.cantOpenStreamH( xTempFileStorage, aSubStream3Path, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.cantOpenStreamH( xTempFileStorage, aBigSubStream3Path, ElementModes.READ ) ) + return false; + + // open existing substream hierarchically, set "MediaType" and "Compressed" properties to it, write some bytes + // and commit + if ( !m_aTestHelper.WriteBytesToStreamH( xTempFileStorage, aSubStream1Path, "MediaType3", true, pBytes2, true ) ) + return false; + if ( !m_aTestHelper.WriteBytesToStreamH( xTempFileStorage, aBigSubStream1Path, "MediaType3", true, pBigBytes, true ) ) + return false; + + + // open existing substream hierarchically, set "MediaType" and "Compressed" properties to it, write some bytes + // and don't commit + if ( !m_aTestHelper.WriteBytesToStreamH( xTempFileStorage, aSubStream2Path, "MediaType3", true, pBytes1, false ) ) + return false; + if ( !m_aTestHelper.WriteBytesToStreamH( xTempFileStorage, aBigSubStream2Path, "MediaType3", true, pBigBytes, false ) ) + return false; + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xTempFileStorage ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xTempFileStorage ) ) + return false; + + + // now reopen the storage, + // check all the written information + + + // the temporary file must not be locked any more after storage disposing + pArgs[1] = Integer.valueOf( ElementModes.READ ); + Object oResultStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xResultStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oResultStorage ); + if ( xResultStorage == null ) + { + m_aTestHelper.Error( "Can't reopen storage based on temporary file!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultStorage, "MediaType3", true, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStreamH( xResultStorage, aSubStream1Path, "MediaType3", true, pBytes2 ) ) + return false; + if ( !m_aTestHelper.checkStreamH( xResultStorage, aBigSubStream1Path, "MediaType3", true, pBigBytes ) ) + return false; + + // the following stream was not committed last time, so the last change must be lost + if ( !m_aTestHelper.checkStreamH( xResultStorage, aBigSubStream2Path, "MediaType2", false, pBigBytes ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xResultStorage ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } + +} + diff --git a/package/qa/storages/Test14.java b/package/qa/storages/Test14.java new file mode 100644 index 0000000000..04d4a87874 --- /dev/null +++ b/package/qa/storages/Test14.java @@ -0,0 +1,206 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class Test14 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public Test14( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "Test14: " ); + } + + public boolean test() + { + String aStreamPrefix = ""; + for ( int nInd = 0; nInd < 4; ++nInd, aStreamPrefix += "SubStorage" + nInd ) + if ( !testForPath( aStreamPrefix ) ) + return false; + + return true; + } + + public boolean testForPath( String aStreamPrefix ) + { + try + { + String aSubStream1Path = aStreamPrefix + "SubStream1"; + String aSubStream2Path = aStreamPrefix + "SubStream2"; + String aSubStream3Path = aStreamPrefix + "SubStream3"; + + String sTempFileURL = m_aTestHelper.CreateTempFile( m_xMSF ); + if ( sTempFileURL == null || sTempFileURL == "" ) + { + m_aTestHelper.Error( "No valid temporary file was created!" ); + return false; + } + + // create temporary storage based on a previously created temporary file + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) sTempFileURL; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempFileStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempFileStorage = (XStorage)UnoRuntime.queryInterface( XStorage.class, oTempFileStorage ); + if ( xTempFileStorage == null ) + { + m_aTestHelper.Error( "Can't create storage based on temporary file!" ); + return false; + } + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + String sPass1 = "12345"; + + // open a new substream hierarchically, set "MediaType" and "Compressed" properties to it, write some bytes + // and commit + if ( !m_aTestHelper.WriteBytesToEncrStreamH( xTempFileStorage, aSubStream1Path, "MediaType1", true, pBytes1, sPass1, true ) ) + return false; + + byte pBytes2[] = { 2, 2, 2, 2, 2 }; + String sPass2 = "54321"; + + // open a new substream hierarchically, set "MediaType" and "Compressed" properties to it, write some bytes + // and commit + if ( !m_aTestHelper.WriteBytesToEncrStreamH( xTempFileStorage, aSubStream2Path, "MediaType2", false, pBytes2, sPass2, true ) ) + return false; + + // open a new substream hierarchically, set "MediaType" and "Compressed" properties to it, write some bytes + // and don't commit + if ( !m_aTestHelper.WriteBytesToEncrStreamH( xTempFileStorage, aSubStream3Path, "MediaType2", false, pBytes2, sPass2, false ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempFileStorage, + "MediaType3", + true, + ElementModes.WRITE ) ) + return false; + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xTempFileStorage ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xTempFileStorage ) ) + return false; + + + // now reopen the storage, + // check all the written and copied information + // and change it + + + // the temporary file must not be locked any more after storage disposing + oTempFileStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + xTempFileStorage = (XStorage)UnoRuntime.queryInterface( XStorage.class, oTempFileStorage ); + if ( xTempFileStorage == null ) + { + m_aTestHelper.Error( "Can't create storage based on temporary file!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xTempFileStorage, "MediaType3", true, ElementModes.WRITE ) ) + return false; + + if ( !m_aTestHelper.checkEncrStreamH( xTempFileStorage, aSubStream1Path, "MediaType1", pBytes1, sPass1 ) ) + return false; + + if ( !m_aTestHelper.checkEncrStreamH( xTempFileStorage, aSubStream2Path, "MediaType2", pBytes2, sPass2 ) ) + return false; + + if ( !m_aTestHelper.cantOpenEncrStreamH( xTempFileStorage, aSubStream3Path, ElementModes.READ, sPass2 ) ) + return false; + + // open existing substream hierarchically, set "MediaType" and "Compressed" properties to it, write some bytes + // and commit + if ( !m_aTestHelper.WriteBytesToEncrStreamH( xTempFileStorage, aSubStream1Path, "MediaType3", true, pBytes2, sPass1, true ) ) + return false; + + // open existing substream hierarchically, set "MediaType" and "Compressed" properties to it, write some bytes + // and don't commit + if ( !m_aTestHelper.WriteBytesToEncrStreamH( xTempFileStorage, aSubStream2Path, "MediaType3", true, pBytes1, sPass2, false ) ) + return false; + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xTempFileStorage ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xTempFileStorage ) ) + return false; + + + // now reopen the storage, + // check all the written information + + + // the temporary file must not be locked any more after storage disposing + pArgs[1] = Integer.valueOf( ElementModes.READ ); + Object oResultStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xResultStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oResultStorage ); + if ( xResultStorage == null ) + { + m_aTestHelper.Error( "Can't reopen storage based on temporary file!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultStorage, "MediaType3", true, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkEncrStreamH( xResultStorage, aSubStream1Path, "MediaType3", pBytes2, sPass1 ) ) + return false; + + // the following stream was not committed last time, so the last change must be lost + if ( !m_aTestHelper.checkEncrStreamH( xResultStorage, aSubStream2Path, "MediaType2", pBytes2, sPass2 ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xResultStorage ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } + +} + diff --git a/package/qa/storages/Test15.java b/package/qa/storages/Test15.java new file mode 100644 index 0000000000..a5ac5d0ac7 --- /dev/null +++ b/package/qa/storages/Test15.java @@ -0,0 +1,286 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class Test15 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public Test15( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "Test15: " ); + } + + public boolean test() + { + String aStreamPrefix = ""; + for ( int nInd = 0; nInd < 4; ++nInd, aStreamPrefix += "SubStorage" + nInd ) + if ( !testForPath( aStreamPrefix ) ) + return false; + + return true; + } + + public boolean testForPath( String aStreamPrefix ) + { + try + { + String aSubStream1Path = aStreamPrefix + "SubStream1"; + String aSubStream2Path = aStreamPrefix + "SubStream2"; + String aSubStream3Path = aStreamPrefix + "SubStream3"; + String aSubStream4Path = aStreamPrefix + "SubStream4"; + + String sTempFileURL = m_aTestHelper.CreateTempFile( m_xMSF ); + if ( sTempFileURL == null || sTempFileURL == "" ) + { + m_aTestHelper.Error( "No valid temporary file was created!" ); + return false; + } + + // create temporary storage based on a previously created temporary file + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) sTempFileURL; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempFileStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempFileStorage = (XStorage)UnoRuntime.queryInterface( XStorage.class, oTempFileStorage ); + if ( xTempFileStorage == null ) + { + m_aTestHelper.Error( "Can't create storage based on temporary file!" ); + return false; + } + + // set the global password for the root storage + XEncryptionProtectedSource xTempStorageEncryption = + (XEncryptionProtectedSource) UnoRuntime.queryInterface( XEncryptionProtectedSource.class, xTempFileStorage ); + + if ( xTempStorageEncryption == null ) + { + m_aTestHelper.Message( "Optional interface XEncryptionProtectedSource is not implemented, feature can not be tested!" ); + return true; + } + + String sPass1 = "12345"; + String sPass2 = "54321"; + + try { + xTempStorageEncryption.setEncryptionPassword( sPass1 ); + } + catch( Exception e ) + { + m_aTestHelper.Error( "Can't set a common encryption key for the storage, exception:" + e ); + return false; + } + + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + byte pBytes2[] = { 2, 2, 2, 2, 2 }; + + // open a new substream hierarchically, set "MediaType" and "Compressed" properties to it, write some bytes + // and commit + if ( !m_aTestHelper.WBToSubstrOfEncrH( xTempFileStorage, aSubStream1Path, "MediaType1", true, pBytes1, true, true ) ) + return false; + + // open a new substream hierarchically, set "MediaType" and "Compressed" properties to it, write some bytes + // and commit + if ( !m_aTestHelper.WriteBytesToEncrStreamH( xTempFileStorage, aSubStream2Path, "MediaType2", false, pBytes2, sPass2, true ) ) + return false; + + // open a new substream hierarchically, set "MediaType" and "Compressed" properties to it, write some bytes + // and commit + if ( !m_aTestHelper.WriteBytesToEncrStreamH( xTempFileStorage, aSubStream3Path, "MediaType3", false, pBytes2, sPass2, true ) ) + return false; + + // open a new substream hierarchically, set "MediaType" and "Compressed" properties to it, write some bytes + // and don't commit + if ( !m_aTestHelper.WBToSubstrOfEncrH( xTempFileStorage, aSubStream4Path, "MediaType2", true, pBytes1, true, false ) ) + return false; + + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempFileStorage, + "MediaType3", + true, + ElementModes.WRITE ) ) + return false; + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xTempFileStorage ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xTempFileStorage ) ) + return false; + + + // now reopen the storage, + // check all the written and copied information + // and change it + + + // the temporary file must not be locked any more after storage disposing + oTempFileStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + xTempFileStorage = (XStorage)UnoRuntime.queryInterface( XStorage.class, oTempFileStorage ); + if ( xTempFileStorage == null ) + { + m_aTestHelper.Error( "Can't create storage based on temporary file!" ); + return false; + } + + // set the global password for the root storage + xTempStorageEncryption = + (XEncryptionProtectedSource) UnoRuntime.queryInterface( XEncryptionProtectedSource.class, xTempFileStorage ); + + if ( xTempStorageEncryption == null ) + { + m_aTestHelper.Error( "XEncryptionProtectedSource is supported, but can not be retrieved!" ); + return false; + } + + try { + xTempStorageEncryption.setEncryptionPassword( sPass2 ); + } + catch( Exception e ) + { + m_aTestHelper.Error( "Can't set a common encryption key for the storage, exception:" + e ); + return false; + } + + + if ( !m_aTestHelper.checkStorageProperties( xTempFileStorage, "MediaType3", true, ElementModes.WRITE ) ) + return false; + + if ( !m_aTestHelper.checkEncrStreamH( xTempFileStorage, aSubStream1Path, "MediaType1", pBytes1, sPass1 ) ) + return false; + + if ( !m_aTestHelper.checkStreamH( xTempFileStorage, aSubStream2Path, "MediaType2", true, pBytes2 ) ) + return false; + + if ( !m_aTestHelper.checkStreamH( xTempFileStorage, aSubStream3Path, "MediaType3", true, pBytes2 ) ) + return false; + + if ( !m_aTestHelper.cantOpenEncrStreamH( xTempFileStorage, aSubStream4Path, ElementModes.READ, sPass1 ) ) + return false; + + // open existing substream hierarchically, set "MediaType" and "Compressed" properties to it, write some bytes + // and commit + if ( !m_aTestHelper.WriteBytesToEncrStreamH( xTempFileStorage, aSubStream1Path, "MediaType4", true, pBytes2, sPass1, true ) ) + return false; + + // open existing substream hierarchically, set "MediaType" and "Compressed" properties to it, write some bytes + // and don't commit + if ( !m_aTestHelper.WriteBytesToStreamH( xTempFileStorage, aSubStream2Path, "MediaType5", true, pBytes1, true ) ) + return false; + + // change the password of the existing stream + if ( m_aTestHelper.ChangeStreamPassH( xTempFileStorage, aSubStream2Path, sPass2, sPass1, true ) != 1 ) + return false; + + // open existing substream hierarchically, set "MediaType" and "Compressed" properties to it, write some bytes + // and don't commit + if ( !m_aTestHelper.WriteBytesToStreamH( xTempFileStorage, aSubStream3Path, "MediaType5", true, pBytes1, false ) ) + return false; + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xTempFileStorage ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xTempFileStorage ) ) + return false; + + + // now reopen the storage, + // check all the written information + + + // the temporary file must not be locked any more after storage disposing + pArgs[1] = Integer.valueOf( ElementModes.READ ); + Object oResultStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xResultStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oResultStorage ); + if ( xResultStorage == null ) + { + m_aTestHelper.Error( "Can't reopen storage based on temporary file!" ); + return false; + } + + // set the global password for the root storage + xTempStorageEncryption = + (XEncryptionProtectedSource) UnoRuntime.queryInterface( XEncryptionProtectedSource.class, xResultStorage ); + + if ( xTempStorageEncryption == null ) + { + m_aTestHelper.Error( "XEncryptionProtectedSource is supported, but can not be retrieved!" ); + return false; + } + + try { + xTempStorageEncryption.setEncryptionPassword( sPass1 ); + } + catch( Exception e ) + { + m_aTestHelper.Error( "Can't set a common encryption key for the storage, exception:" + e ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultStorage, "MediaType3", true, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStreamH( xResultStorage, aSubStream1Path, "MediaType4", true, pBytes2 ) ) + return false; + + if ( !m_aTestHelper.checkStreamH( xResultStorage, aSubStream2Path, "MediaType5", true, pBytes1 ) ) + return false; + + if ( !m_aTestHelper.checkEncrStreamH( xResultStorage, aSubStream3Path, "MediaType3", pBytes2, sPass2 ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xResultStorage ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } + +} + diff --git a/package/qa/storages/Test16.java b/package/qa/storages/Test16.java new file mode 100644 index 0000000000..36e945e74d --- /dev/null +++ b/package/qa/storages/Test16.java @@ -0,0 +1,177 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class Test16 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public Test16( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "Test16: " ); + } + + public boolean test() + { + try + { + String sTempFileURL = m_aTestHelper.CreateTempFile( m_xMSF ); + if ( sTempFileURL == null || sTempFileURL == "" ) + { + m_aTestHelper.Error( "No valid temporary file was created!" ); + return false; + } + + // create temporary storage based on arbitrary medium + // after such a storage is closed it is lost + Object oTempStorage = m_xStorageFactory.createInstance(); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage\u0442\u0435\u0441\u04421", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, "SubStream\u0442\u0435\u0441\u04421", "MediaType1", true, pBytes1 ) ) + return false; + + byte pBytes2[] = { 2, 2, 2, 2, 2 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, "SubStream\u0442\u0435\u0441\u04422", "MediaType2", false, pBytes2 ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempStorage, + "MediaType3", + true, + ElementModes.WRITE ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + "MediaType4", + false, + ElementModes.WRITE ) ) + return false; + + // create temporary storage based on a previously created temporary file + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) sTempFileURL; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempFileStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempFileStorage = (XStorage)UnoRuntime.queryInterface( XStorage.class, oTempFileStorage ); + if ( xTempFileStorage == null ) + { + m_aTestHelper.Error( "Can't create storage based on temporary file!" ); + return false; + } + + // copy xTempStorage to xTempFileStorage + // xTempFileStorage will be automatically committed + if ( !m_aTestHelper.copyStorage( xTempStorage, xTempFileStorage ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xTempStorage ) || !m_aTestHelper.disposeStorage( xTempFileStorage ) ) + return false; + + + // now check all the written and copied information + + + // the temporary file must not be locked any more after storage disposing + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + Object oResultStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xResultStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oResultStorage ); + if ( xResultStorage == null ) + { + m_aTestHelper.Error( "Can't reopen storage based on temporary file!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultStorage, "MediaType3", true, ElementModes.WRITE ) ) + return false; + + // open existing substorage + XStorage xResultSubStorage = m_aTestHelper.openSubStorage( xResultStorage, + "SubStorage\u0442\u0435\u0441\u04421", + ElementModes.READ ); + if ( xResultSubStorage == null ) + { + m_aTestHelper.Error( "Can't open existing substorage!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultSubStorage, "MediaType4", false, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResultSubStorage, "SubStream\u0442\u0435\u0441\u04421", "MediaType1", true, pBytes1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResultSubStorage, "SubStream\u0442\u0435\u0441\u04422", "MediaType2", false, pBytes2 ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xResultStorage ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } + +} + diff --git a/package/qa/storages/Test17.java b/package/qa/storages/Test17.java new file mode 100644 index 0000000000..b81fc240a7 --- /dev/null +++ b/package/qa/storages/Test17.java @@ -0,0 +1,160 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.io.XStream; +import com.sun.star.io.XInputStream; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class Test17 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public Test17( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "Test17: " ); + } + + public boolean test() + { + try + { + XStream xTempFileStream = m_aTestHelper.CreateTempFileStream( m_xMSF ); + if ( xTempFileStream == null ) + return false; + + // create storage based on the temporary stream + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) xTempFileStream; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + String pNames[] = { "SubStream1", "SubStream2", "SubStream3", "SubStream4", "SubStream5", "SubStream6", "SubStream7" }; + + for ( int nInd = 0; nInd < pNames.length; nInd++ ) + { + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstream( xTempSubStorage, pNames[nInd], "MediaType1", true, pBytes1 ) ) + return false; + + // commit substorage first + if ( !m_aTestHelper.commitStorage( xTempSubStorage ) ) + return false; + + // dispose used storage to free resources + if ( !m_aTestHelper.disposeStorage( xTempSubStorage ) ) + return false; + } + + // commit the root storage so the contents must be stored now + if ( !m_aTestHelper.commitStorage( xTempStorage ) ) + return false; + + // dispose used storage to free resources + if ( !m_aTestHelper.disposeStorage( xTempStorage ) ) + return false; + + + + // now check all the written information + + + // close the output part of the temporary stream + // the output part must present since we already wrote to the stream + if ( !m_aTestHelper.closeOutput( xTempFileStream ) ) + return false; + + XInputStream xTempInStream = m_aTestHelper.getInputStream( xTempFileStream ); + if ( xTempInStream == null ) + return false; + + + // open input stream + // since no mode is provided the result storage must be opened readonly + Object pOneArg[] = new Object[1]; + pOneArg[0] = (Object) xTempInStream; + + Object oResultStorage = m_xStorageFactory.createInstanceWithArguments( pOneArg ); + XStorage xResultStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oResultStorage ); + if ( xResultStorage == null ) + { + m_aTestHelper.Error( "Can't open storage based on input stream!" ); + return false; + } + + // open existing substorage + XStorage xResultSubStorage = m_aTestHelper.openSubStorage( xResultStorage, + "SubStorage1", + ElementModes.READ ); + if ( xResultSubStorage == null ) + { + m_aTestHelper.Error( "Can't open existing substorage!" ); + return false; + } + + for ( int nInd = 0; nInd < pNames.length; nInd++ ) + if ( !m_aTestHelper.checkStream( xResultSubStorage, pNames[nInd], "MediaType1", true, pBytes1 ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } + +} + diff --git a/package/qa/storages/Test18.java b/package/qa/storages/Test18.java new file mode 100644 index 0000000000..3e0f246d1d --- /dev/null +++ b/package/qa/storages/Test18.java @@ -0,0 +1,190 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.XInterface; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XSingleServiceFactory; + +import com.sun.star.bridge.XUnoUrlResolver; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; + +import com.sun.star.embed.*; + +import share.LogWriter; +import complex.storages.TestHelper; +import complex.storages.StorageTest; + +public class Test18 implements StorageTest { + + XMultiServiceFactory m_xMSF; + XSingleServiceFactory m_xStorageFactory; + TestHelper m_aTestHelper; + + public Test18( XMultiServiceFactory xMSF, XSingleServiceFactory xStorageFactory, LogWriter aLogWriter ) + { + m_xMSF = xMSF; + m_xStorageFactory = xStorageFactory; + m_aTestHelper = new TestHelper( aLogWriter, "Test18: " ); + } + + public boolean test() + { + try + { + // test the default value of Compressed property + String sTempFileURL = m_aTestHelper.CreateTempFile( m_xMSF ); + if ( sTempFileURL == null || sTempFileURL == "" ) + { + m_aTestHelper.Error( "No valid temporary file was created!" ); + return false; + } + + // create temporary storage based on arbitrary medium + // after such a storage is closed it is lost + Object oTempStorage = m_xStorageFactory.createInstance(); + XStorage xTempStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xTempStorage == null ) + { + m_aTestHelper.Error( "Can't create temporary storage representation!" ); + return false; + } + + // open a new substorage + XStorage xTempSubStorage = m_aTestHelper.openSubStorage( xTempStorage, + "SubStorage1", + ElementModes.WRITE ); + if ( xTempSubStorage == null ) + { + m_aTestHelper.Error( "Can't create substorage!" ); + return false; + } + + byte pBytes1[] = { 1, 1, 1, 1, 1 }; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstreamDefaultCompressed( xTempSubStorage, "SubStream1", "image/jpeg", pBytes1 ) ) + return false; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstreamDefaultCompressed( xTempSubStorage, "SubStream2", "image/png", pBytes1 ) ) + return false; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstreamDefaultCompressed( xTempSubStorage, "SubStream3", "image/gif", pBytes1 ) ) + return false; + + // open a new substream, set "MediaType" and "Compressed" properties to it and write some bytes + if ( !m_aTestHelper.WriteBytesToSubstreamDefaultCompressed( xTempSubStorage, "SubStream4", "MediaType1", pBytes1 ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempStorage, + "MediaType3", + true, + ElementModes.WRITE ) ) + return false; + + // set "MediaType" property for storages and check that "IsRoot" and "OpenMode" properties are set correctly + if ( !m_aTestHelper.setStorageTypeAndCheckProps( xTempSubStorage, + "MediaType4", + false, + ElementModes.WRITE ) ) + return false; + + // create temporary storage based on a previously created temporary file + Object pArgs[] = new Object[2]; + pArgs[0] = (Object) sTempFileURL; + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + + Object oTempFileStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xTempFileStorage = (XStorage)UnoRuntime.queryInterface( XStorage.class, oTempFileStorage ); + if ( xTempFileStorage == null ) + { + m_aTestHelper.Error( "Can't create storage based on temporary file!" ); + return false; + } + + // copy xTempStorage to xTempFileStorage + // xTempFileStorage will be automatically committed + if ( !m_aTestHelper.copyStorage( xTempStorage, xTempFileStorage ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xTempStorage ) || !m_aTestHelper.disposeStorage( xTempFileStorage ) ) + return false; + + + // now check all the written and copied information + + + // the temporary file must not be locked any more after storage disposing + pArgs[1] = Integer.valueOf( ElementModes.WRITE ); + Object oResultStorage = m_xStorageFactory.createInstanceWithArguments( pArgs ); + XStorage xResultStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oResultStorage ); + if ( xResultStorage == null ) + { + m_aTestHelper.Error( "Can't reopen storage based on temporary file!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultStorage, "MediaType3", true, ElementModes.WRITE ) ) + return false; + + // open existing substorage + XStorage xResultSubStorage = m_aTestHelper.openSubStorage( xResultStorage, + "SubStorage1", + ElementModes.READ ); + if ( xResultSubStorage == null ) + { + m_aTestHelper.Error( "Can't open existing substorage!" ); + return false; + } + + if ( !m_aTestHelper.checkStorageProperties( xResultSubStorage, "MediaType4", false, ElementModes.READ ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResultSubStorage, "SubStream1", "image/jpeg", false, pBytes1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResultSubStorage, "SubStream2", "image/png", false, pBytes1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResultSubStorage, "SubStream3", "image/gif", false, pBytes1 ) ) + return false; + + if ( !m_aTestHelper.checkStream( xResultSubStorage, "SubStream4", "MediaType1", true, pBytes1 ) ) + return false; + + // dispose used storages to free resources + if ( !m_aTestHelper.disposeStorage( xResultStorage ) ) + return false; + + return true; + } + catch( Exception e ) + { + m_aTestHelper.Error( "Exception: " + e ); + return false; + } + } + +} + diff --git a/package/qa/storages/TestHelper.java b/package/qa/storages/TestHelper.java new file mode 100644 index 0000000000..d6a4fd280d --- /dev/null +++ b/package/qa/storages/TestHelper.java @@ -0,0 +1,1679 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.storages; + +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.uno.AnyConverter; + +import com.sun.star.lang.*; +import com.sun.star.embed.*; +import com.sun.star.packages.*; +import com.sun.star.io.*; +import com.sun.star.beans.*; + +import share.LogWriter; + +public class TestHelper { + + LogWriter m_aLogWriter; + String m_sTestPrefix; + + public TestHelper( LogWriter aLogWriter, String sTestPrefix ) + { + m_aLogWriter = aLogWriter; + m_sTestPrefix = sTestPrefix; + } + + public boolean WriteBytesToStream( XStream xStream, + String sStreamName, + String sMediaType, + boolean bCompressed, + byte[] pBytes ) + { + // get output stream of substream + XOutputStream xOutput = xStream.getOutputStream(); + if ( xOutput == null ) + { + Error( "Can't get XOutputStream implementation from substream '" + sStreamName + "'!" ); + return false; + } + + // get XTruncate implementation from output stream + XTruncate xTruncate = (XTruncate) UnoRuntime.queryInterface( XTruncate.class, xOutput ); + if ( xTruncate == null ) + { + Error( "Can't get XTruncate implementation from substream '" + sStreamName + "'!" ); + return false; + } + + // write requested byte sequence + try + { + xTruncate.truncate(); + xOutput.writeBytes( pBytes ); + } + catch( Exception e ) + { + Error( "Can't write to stream '" + sStreamName + "', exception: " + e ); + return false; + } + + // get access to the XPropertySet interface + XPropertySet xPropSet = (XPropertySet) UnoRuntime.queryInterface( XPropertySet.class, xStream ); + if ( xPropSet == null ) + { + Error( "Can't get XPropertySet implementation from substream '" + sStreamName + "'!" ); + return false; + } + + // set properties to the stream + try + { + xPropSet.setPropertyValue( "MediaType", sMediaType ); + xPropSet.setPropertyValue( "Compressed", Boolean.valueOf( bCompressed ) ); + } + catch( Exception e ) + { + Error( "Can't set properties to substream '" + sStreamName + "', exception: " + e ); + return false; + } + + // check size property of the stream + try + { + long nSize = AnyConverter.toLong( xPropSet.getPropertyValue( "Size" ) ); + if ( nSize != pBytes.length ) + { + Error( "The 'Size' property of substream '" + sStreamName + "' contains wrong value!" ); + return false; + } + } + catch( Exception e ) + { + Error( "Can't get 'Size' property from substream '" + sStreamName + "', exception: " + e ); + return false; + } + + return true; + } + + public boolean WriteBytesToSubstreamDefaultCompressed( XStorage xStorage, + String sStreamName, + String sMediaType, + byte[] pBytes ) + { + // open substream element + XStream xSubStream = null; + try + { + Object oSubStream = xStorage.openStreamElement( sStreamName, ElementModes.WRITE ); + xSubStream = (XStream) UnoRuntime.queryInterface( XStream.class, oSubStream ); + if ( xSubStream == null ) + { + Error( "Can't create substream '" + sStreamName + "'!" ); + return false; + } + } + catch( Exception e ) + { + Error( "Can't create substream '" + sStreamName + "', exception : " + e + "!" ); + return false; + } + + // get output stream of substream + XOutputStream xOutput = xSubStream.getOutputStream(); + if ( xOutput == null ) + { + Error( "Can't get XOutputStream implementation from substream '" + sStreamName + "'!" ); + return false; + } + + // get XTruncate implementation from output stream + XTruncate xTruncate = (XTruncate) UnoRuntime.queryInterface( XTruncate.class, xOutput ); + if ( xTruncate == null ) + { + Error( "Can't get XTruncate implementation from substream '" + sStreamName + "'!" ); + return false; + } + + // write requested byte sequence + try + { + xTruncate.truncate(); + xOutput.writeBytes( pBytes ); + } + catch( Exception e ) + { + Error( "Can't write to stream '" + sStreamName + "', exception: " + e ); + return false; + } + + // get access to the XPropertySet interface + XPropertySet xPropSet = (XPropertySet) UnoRuntime.queryInterface( XPropertySet.class, xSubStream ); + if ( xPropSet == null ) + { + Error( "Can't get XPropertySet implementation from substream '" + sStreamName + "'!" ); + return false; + } + + // set properties to the stream + // do not set the compressed property + try + { + xPropSet.setPropertyValue( "MediaType", sMediaType ); + } + catch( Exception e ) + { + Error( "Can't set properties to substream '" + sStreamName + "', exception: " + e ); + return false; + } + + // check size property of the stream + try + { + long nSize = AnyConverter.toLong( xPropSet.getPropertyValue( "Size" ) ); + if ( nSize != pBytes.length ) + { + Error( "The 'Size' property of substream '" + sStreamName + "' contains wrong value!" ); + return false; + } + } + catch( Exception e ) + { + Error( "Can't get 'Size' property from substream '" + sStreamName + "', exception: " + e ); + return false; + } + + // free the stream resources, garbage collector may remove the object too late + if ( !disposeStream( xSubStream, sStreamName ) ) + return false; + + return true; + } + + public boolean WriteBytesToSubstream( XStorage xStorage, + String sStreamName, + String sMediaType, + boolean bCompressed, + byte[] pBytes ) + { + // open substream element + XStream xSubStream = null; + try + { + Object oSubStream = xStorage.openStreamElement( sStreamName, ElementModes.WRITE ); + xSubStream = (XStream) UnoRuntime.queryInterface( XStream.class, oSubStream ); + if ( xSubStream == null ) + { + Error( "Can't create substream '" + sStreamName + "'!" ); + return false; + } + } + catch( Exception e ) + { + Error( "Can't create substream '" + sStreamName + "', exception : " + e + "!" ); + return false; + } + + if ( !WriteBytesToStream( xSubStream, sStreamName, sMediaType, bCompressed, pBytes ) ) + return false; + + // free the stream resources, garbage collector may remove the object too late + if ( !disposeStream( xSubStream, sStreamName ) ) + return false; + + return true; + } + + public boolean WriteBytesToEncrSubstream( XStorage xStorage, + String sStreamName, + String sMediaType, + boolean bCompressed, + byte[] pBytes, + String sPass ) + { + // open substream element + XStream xSubStream = null; + try + { + Object oSubStream = xStorage.openEncryptedStreamElement( sStreamName, ElementModes.WRITE, sPass ); + xSubStream = (XStream) UnoRuntime.queryInterface( XStream.class, oSubStream ); + if ( xSubStream == null ) + { + Error( "Can't create substream '" + sStreamName + "'!" ); + return false; + } + } + catch( Exception e ) + { + Error( "Can't create substream '" + sStreamName + "', exception : " + e + "!" ); + return false; + } + + if ( !WriteBytesToStream( xSubStream, sStreamName, sMediaType, bCompressed, pBytes ) ) + return false; + + // free the stream resources, garbage collector may remove the object too late + if ( !disposeStream( xSubStream, sStreamName ) ) + return false; + + return true; + } + + public boolean WBToSubstrOfEncr( XStorage xStorage, + String sStreamName, + String sMediaType, + boolean bCompressed, + byte[] pBytes, + boolean bEncrypted ) + { + // open substream element + XStream xSubStream = null; + try + { + Object oSubStream = xStorage.openStreamElement( sStreamName, ElementModes.WRITE ); + xSubStream = (XStream) UnoRuntime.queryInterface( XStream.class, oSubStream ); + if ( xSubStream == null ) + { + Error( "Can't create substream '" + sStreamName + "'!" ); + return false; + } + } + catch( Exception e ) + { + Error( "Can't create substream '" + sStreamName + "', exception : " + e + "!" ); + return false; + } + + // get access to the XPropertySet interface + XPropertySet xPropSet = (XPropertySet) UnoRuntime.queryInterface( XPropertySet.class, xSubStream ); + if ( xPropSet == null ) + { + Error( "Can't get XPropertySet implementation from substream '" + sStreamName + "'!" ); + return false; + } + + // set properties to the stream + try + { + xPropSet.setPropertyValue( "UseCommonStoragePasswordEncryption", Boolean.valueOf( bEncrypted ) ); + } + catch( Exception e ) + { + Error( "Can't set 'UseCommonStoragePasswordEncryption' property to substream '" + sStreamName + "', exception: " + e ); + return false; + } + + if ( !WriteBytesToStream( xSubStream, sStreamName, sMediaType, bCompressed, pBytes ) ) + return false; + + // free the stream resources, garbage collector may remove the object too late + if ( !disposeStream( xSubStream, sStreamName ) ) + return false; + + return true; + } + + public boolean WriteBytesToStreamH( XStorage xStorage, + String sStreamPath, + String sMediaType, + boolean bCompressed, + byte[] pBytes, + boolean bCommit ) + { + // open substream element + XStream xSubStream = null; + try + { + XHierarchicalStorageAccess xHStorage = + (XHierarchicalStorageAccess) UnoRuntime.queryInterface( XHierarchicalStorageAccess.class, xStorage ); + if ( xHStorage == null ) + { + Error( "The storage does not support hierarchical access!" ); + return false; + } + + Object oSubStream = xHStorage.openStreamElementByHierarchicalName( sStreamPath, ElementModes.WRITE ); + xSubStream = (XStream) UnoRuntime.queryInterface( XStream.class, oSubStream ); + if ( xSubStream == null ) + { + Error( "Can't create substream '" + sStreamPath + "'!" ); + return false; + } + } + catch( Exception e ) + { + Error( "Can't create substream '" + sStreamPath + "', exception : " + e + "!" ); + return false; + } + + if ( !WriteBytesToStream( xSubStream, sStreamPath, sMediaType, bCompressed, pBytes ) ) + return false; + + XTransactedObject xTransact = + (XTransactedObject) UnoRuntime.queryInterface( XTransactedObject.class, xSubStream ); + if ( xTransact == null ) + { + Error( "Substream '" + sStreamPath + "', stream opened for writing must be transacted!" ); + return false; + } + + if ( bCommit ) + { + try { + xTransact.commit(); + } catch( Exception e ) + { + Error( "Can't commit storage after substream '" + sStreamPath + "' change, exception : " + e + "!" ); + return false; + } + } + + // free the stream resources, garbage collector may remove the object too late + if ( !disposeStream( xSubStream, sStreamPath ) ) + return false; + + return true; + } + + public boolean WriteBytesToEncrStreamH( XStorage xStorage, + String sStreamPath, + String sMediaType, + boolean bCompressed, + byte[] pBytes, + String sPass, + boolean bCommit ) + { + // open substream element + XStream xSubStream = null; + try + { + XHierarchicalStorageAccess xHStorage = + (XHierarchicalStorageAccess) UnoRuntime.queryInterface( XHierarchicalStorageAccess.class, xStorage ); + if ( xHStorage == null ) + { + Error( "The storage does not support hierarchical access!" ); + return false; + } + + Object oSubStream = xHStorage.openEncryptedStreamElementByHierarchicalName( sStreamPath, + ElementModes.WRITE, + sPass ); + xSubStream = (XStream) UnoRuntime.queryInterface( XStream.class, oSubStream ); + if ( xSubStream == null ) + { + Error( "Can't create substream '" + sStreamPath + "'!" ); + return false; + } + } + catch( Exception e ) + { + Error( "Can't create substream '" + sStreamPath + "', exception : " + e + "!" ); + return false; + } + + if ( !WriteBytesToStream( xSubStream, sStreamPath, sMediaType, bCompressed, pBytes ) ) + return false; + + XTransactedObject xTransact = + (XTransactedObject) UnoRuntime.queryInterface( XTransactedObject.class, xSubStream ); + if ( xTransact == null ) + { + Error( "Substream '" + sStreamPath + "', stream opened for writing must be transacted!" ); + return false; + } + + if ( bCommit ) + { + try { + xTransact.commit(); + } catch( Exception e ) + { + Error( "Can't commit storage after substream '" + sStreamPath + "' change, exception : " + e + "!" ); + return false; + } + } + + // free the stream resources, garbage collector may remove the object too late + if ( !disposeStream( xSubStream, sStreamPath ) ) + return false; + + return true; + } + + public boolean WBToSubstrOfEncrH( XStorage xStorage, + String sStreamPath, + String sMediaType, + boolean bCompressed, + byte[] pBytes, + boolean bEncrypted, + boolean bCommit ) + { + // open substream element + XStream xSubStream = null; + try + { + XHierarchicalStorageAccess xHStorage = + (XHierarchicalStorageAccess) UnoRuntime.queryInterface( XHierarchicalStorageAccess.class, xStorage ); + if ( xHStorage == null ) + { + Error( "The storage does not support hierarchical access!" ); + return false; + } + + Object oSubStream = xHStorage.openStreamElementByHierarchicalName( sStreamPath, ElementModes.WRITE ); + xSubStream = (XStream) UnoRuntime.queryInterface( XStream.class, oSubStream ); + if ( xSubStream == null ) + { + Error( "Can't create substream '" + sStreamPath + "'!" ); + return false; + } + } + catch( Exception e ) + { + Error( "Can't create substream '" + sStreamPath + "', exception : " + e + "!" ); + return false; + } + + // get access to the XPropertySet interface + XPropertySet xPropSet = (XPropertySet) UnoRuntime.queryInterface( XPropertySet.class, xSubStream ); + if ( xPropSet == null ) + { + Error( "Can't get XPropertySet implementation from substream '" + sStreamPath + "'!" ); + return false; + } + + // set properties to the stream + try + { + xPropSet.setPropertyValue( "UseCommonStoragePasswordEncryption", Boolean.valueOf( bEncrypted ) ); + } + catch( Exception e ) + { + Error( "Can't set 'UseCommonStoragePasswordEncryption' property to substream '" + sStreamPath + "', exception: " + e ); + return false; + } + + if ( !WriteBytesToStream( xSubStream, sStreamPath, sMediaType, bCompressed, pBytes ) ) + return false; + + XTransactedObject xTransact = + (XTransactedObject) UnoRuntime.queryInterface( XTransactedObject.class, xSubStream ); + if ( xTransact == null ) + { + Error( "Substream '" + sStreamPath + "', stream opened for writing must be transacted!" ); + return false; + } + + if ( bCommit ) + { + try { + xTransact.commit(); + } catch( Exception e ) + { + Error( "Can't commit storage after substream '" + sStreamPath + "' change, exception : " + e + "!" ); + return false; + } + } + + // free the stream resources, garbage collector may remove the object too late + if ( !disposeStream( xSubStream, sStreamPath ) ) + return false; + + return true; + } + + public int ChangeStreamPass( XStorage xStorage, + String sStreamName, + String sOldPass, + String sNewPass ) + { + // open substream element + XStream xSubStream = null; + try + { + Object oSubStream = xStorage.openEncryptedStreamElement( sStreamName, ElementModes.WRITE, sOldPass ); + xSubStream = (XStream) UnoRuntime.queryInterface( XStream.class, oSubStream ); + if ( xSubStream == null ) + { + Error( "Can't open substream '" + sStreamName + "'!" ); + return 0; + } + } + catch( Exception e ) + { + Error( "Can't open substream '" + sStreamName + "', exception : " + e + "!" ); + return 0; + } + + + // change the password for the stream + XEncryptionProtectedSource xStreamEncryption = + (XEncryptionProtectedSource) UnoRuntime.queryInterface( XEncryptionProtectedSource.class, xSubStream ); + + if ( xStreamEncryption == null ) + { + Message( "Optional interface XEncryptionProtectedSource is not implemented, feature can not be tested!" ); + return -1; + } + + try { + xStreamEncryption.setEncryptionPassword( sNewPass ); + } + catch( Exception e ) + { + Error( "Can't change encryption key of the substream '" + sStreamName + "', exception:" + e ); + return 0; + } + + // free the stream resources, garbage collector may remove the object too late + if ( !disposeStream( xSubStream, sStreamName ) ) + return 0; + + return 1; + } + + public int ChangeStreamPassH( XStorage xStorage, + String sPath, + String sOldPass, + String sNewPass, + boolean bCommit ) + { + // open substream element + XHierarchicalStorageAccess xHStorage = + (XHierarchicalStorageAccess) UnoRuntime.queryInterface( XHierarchicalStorageAccess.class, xStorage ); + if ( xHStorage == null ) + { + Error( "The storage does not support hierarchical access!" ); + return 0; + } + + XStream xSubStream = null; + try + { + Object oSubStream = xHStorage.openEncryptedStreamElementByHierarchicalName( sPath, ElementModes.WRITE, sOldPass ); + xSubStream = (XStream) UnoRuntime.queryInterface( XStream.class, oSubStream ); + if ( xSubStream == null ) + { + Error( "Can't open encrypted substream '" + sPath + "'!" ); + return 0; + } + } + catch( Exception e ) + { + Error( "Can't open encrypted substream '" + sPath + "', exception : " + e + "!" ); + return 0; + } + + // change the password for the stream + XEncryptionProtectedSource xStreamEncryption = + (XEncryptionProtectedSource) UnoRuntime.queryInterface( XEncryptionProtectedSource.class, xSubStream ); + + if ( xStreamEncryption == null ) + { + Message( "Optional interface XEncryptionProtectedSource is not implemented, feature can not be tested!" ); + return -1; + } + + try { + xStreamEncryption.setEncryptionPassword( sNewPass ); + } + catch( Exception e ) + { + Error( "Can't change encryption key of the substream '" + sPath + "', exception:" + e ); + return 0; + } + + XTransactedObject xTransact = + (XTransactedObject) UnoRuntime.queryInterface( XTransactedObject.class, xSubStream ); + if ( xTransact == null ) + { + Error( "Substream '" + sPath + "', stream opened for writing must be transacted!" ); + return 0; + } + + if ( bCommit ) + { + try { + xTransact.commit(); + } catch( Exception e ) + { + Error( "Can't commit storage after substream '" + sPath + "' change, exception : " + e + "!" ); + return 0; + } + } + + // free the stream resources, garbage collector may remove the object too late + if ( !disposeStream( xSubStream, sPath ) ) + return 0; + + return 1; + } + + public boolean setStorageTypeAndCheckProps( XStorage xStorage, String sMediaType, boolean bIsRoot, int nMode ) + { + boolean bOk = false; + + // get access to the XPropertySet interface + XPropertySet xPropSet = (XPropertySet) UnoRuntime.queryInterface( XPropertySet.class, xStorage ); + if ( xPropSet != null ) + { + try + { + // set "MediaType" property to the stream + xPropSet.setPropertyValue( "MediaType", sMediaType ); + + // get "IsRoot" and "OpenMode" properties and control there values + boolean bPropIsRoot = AnyConverter.toBoolean( xPropSet.getPropertyValue( "IsRoot" ) ); + int nPropMode = AnyConverter.toInt( xPropSet.getPropertyValue( "OpenMode" ) ); + + bOk = true; + if ( bPropIsRoot != bIsRoot ) + { + Error( "'IsRoot' property contains wrong value!" ); + bOk = false; + } + + if ( ( bIsRoot + && ( nPropMode | ElementModes.READ ) != ( nMode | ElementModes.READ ) ) + || ( !bIsRoot && ( nPropMode & nMode ) != nMode ) ) + { + Error( "'OpenMode' property contains wrong value, expected " + nMode + ", in reality " + nPropMode + "!" ); + bOk = false; + } + } + catch( Exception e ) + { + Error( "Can't control properties of substorage, exception: " + e ); + } + } + else + { + Error( "Can't get XPropertySet implementation from storage!" ); + } + + return bOk; + } + + public boolean checkStorageProperties( XStorage xStorage, String sMediaType, boolean bIsRoot, int nMode ) + { + boolean bOk = false; + + // get access to the XPropertySet interface + XPropertySet xPropSet = (XPropertySet) UnoRuntime.queryInterface( XPropertySet.class, xStorage ); + if ( xPropSet != null ) + { + try + { + // get "MediaType", "IsRoot" and "OpenMode" properties and control there values + String sPropMediaType = AnyConverter.toString( xPropSet.getPropertyValue( "MediaType" ) ); + boolean bPropIsRoot = AnyConverter.toBoolean( xPropSet.getPropertyValue( "IsRoot" ) ); + int nPropMode = AnyConverter.toInt( xPropSet.getPropertyValue( "OpenMode" ) ); + + bOk = true; + if ( !sPropMediaType.equals( sMediaType ) ) + { + Error( "'MediaType' property contains wrong value, expected '" + + sMediaType + "', set '" + sPropMediaType + "' !" ); + bOk = false; + } + + if ( bPropIsRoot != bIsRoot ) + { + Error( "'IsRoot' property contains wrong value!" ); + bOk = false; + } + + if ( ( bIsRoot + && ( nPropMode | ElementModes.READ ) != ( nMode | ElementModes.READ ) ) + || ( !bIsRoot && ( nPropMode & nMode ) != nMode ) ) + { + Error( "'OpenMode' property contains wrong value, expected " + nMode + ", in reality " + nPropMode + "!" ); + bOk = false; + } + } + catch( Exception e ) + { + Error( "Can't get properties of substorage, exception: " + e ); + } + } + else + { + Error( "Can't get XPropertySet implementation from storage!" ); + } + + return bOk; + } + + public boolean InternalCheckStream( XStream xStream, + String sName, + String sMediaType, + boolean bCompressed, + byte[] pBytes, + boolean bCheckCompressed ) + { + // get input stream of substream + XInputStream xInput = xStream.getInputStream(); + if ( xInput == null ) + { + Error( "Can't get XInputStream implementation from substream '" + sName + "'!" ); + return false; + } + + byte pContents[][] = new byte[1][]; // ??? + + // read contents + try + { + xInput.readBytes( pContents, pBytes.length + 1 ); + } + catch( Exception e ) + { + Error( "Can't read from stream '" + sName + "', exception: " + e ); + return false; + } + + // check size of stream data + if ( pContents.length == 0 ) + { + Error( "SubStream '" + sName + "' reading produced disaster!" ); + return false; + } + + if ( pBytes.length != pContents[0].length ) + { + Error( "SubStream '" + sName + "' contains wrong amount of data! (" + pContents[0].length + "/" + pBytes.length + ")" ); + return false; + } + + // check stream data + for ( int ind = 0; ind < pBytes.length; ind++ ) + { + if ( pBytes[ind] != pContents[0][ind] ) + { + Error( "SubStream '" + sName + "' contains wrong data! ( byte num. " + + ind + " should be " + pBytes[ind] + " but it is " + pContents[0][ind] + ")" ); + return false; + } + } + + // check properties + boolean bOk = false; + + // get access to the XPropertySet interface + XPropertySet xPropSet = (XPropertySet) UnoRuntime.queryInterface( XPropertySet.class, xStream ); + if ( xPropSet != null ) + { + try + { + // get "MediaType" and "Size" properties and control there values + String sPropMediaType = AnyConverter.toString( xPropSet.getPropertyValue( "MediaType" ) ); + long nPropSize = AnyConverter.toLong( xPropSet.getPropertyValue( "Size" ) ); + boolean bPropCompress = AnyConverter.toBoolean( xPropSet.getPropertyValue( "Compressed" ) ); + + bOk = true; + if ( !sPropMediaType.equals( sMediaType ) ) + { + Error( "'MediaType' property contains wrong value for stream '" + sName + "',\nexpected: '" + + sMediaType + "', set: '" + sPropMediaType + "'!" ); + bOk = false; + } + + if ( nPropSize != pBytes.length ) + { + Error( "'Size' property contains wrong value for stream'" + sName + "'!" ); + bOk = false; + } + + if ( bCheckCompressed && bPropCompress != bCompressed ) + { + Error( "'Compressed' property contains wrong value for stream'" + sName + "'!" ); + bOk = false; + } + } + catch( Exception e ) + { + Error( "Can't get properties of substream '" + sName + "', exception: " + e ); + } + } + else + { + Error( "Can't get XPropertySet implementation from stream '" + sName + "'!" ); + } + + return bOk; + } + + public boolean checkStream( XStorage xParentStorage, + String sName, + String sMediaType, + boolean bCompressed, + byte[] pBytes ) + { + // open substream element first + XStream xSubStream = null; + try + { + Object oSubStream = xParentStorage.openStreamElement( sName, ElementModes.READ ); + xSubStream = (XStream) UnoRuntime.queryInterface( XStream.class, oSubStream ); + if ( xSubStream == null ) + { + Error( "Can't open substream '" + sName + "'!" ); + return false; + } + } + catch( Exception e ) + { + Error( "Can't open substream '" + sName + "', exception : " + e + "!" ); + return false; + } + + boolean bResult = InternalCheckStream( xSubStream, sName, sMediaType, bCompressed, pBytes, true ); + + // free the stream resources, garbage collector may remove the object too late + if ( !disposeStream( xSubStream, sName ) ) + return false; + + return bResult; + } + + public boolean checkEncrStream( XStorage xParentStorage, + String sName, + String sMediaType, + byte[] pBytes, + String sPass ) + { + // Important: a common password for any of parent storage should not be set or + // should be different from sPass + + try + { + Object oSubStream = xParentStorage.openStreamElement( sName, ElementModes.READ ); + Error( "Encrypted stream '" + sName + "' was opened without password!" ); + return false; + } + catch( WrongPasswordException wpe ) + {} + catch( Exception e ) + { + Error( "Unexpected exception in case of opening of encrypted stream '" + sName + "' without password: " + e + "!" ); + return false; + } + + String sWrongPass = "11"; + sWrongPass += sPass; + try + { + Object oSubStream = xParentStorage.openEncryptedStreamElement( sName, ElementModes.READ, sWrongPass ); + Error( "Encrypted stream '" + sName + "' was opened with wrong password!" ); + return false; + } + catch( WrongPasswordException wpe ) + {} + catch( Exception e ) + { + Error( "Unexpected exception in case of opening of encrypted stream '" + sName + "' with wrong password: " + e + "!" ); + return false; + } + + XStream xSubStream = null; + try + { + Object oSubStream = xParentStorage.openEncryptedStreamElement( sName, ElementModes.READ, sPass ); + xSubStream = (XStream) UnoRuntime.queryInterface( XStream.class, oSubStream ); + if ( xSubStream == null ) + { + Error( "Can't open encrypted substream '" + sName + "'!" ); + return false; + } + } + catch( Exception e ) + { + Error( "Can't open encrypted substream '" + sName + "', exception : " + e + "!" ); + return false; + } + + // encrypted streams will be compressed always, so after the storing this property is always true, + // although before the storing it can be set to false ( it is not always clear whether a stream is encrypted + // before the storing ) + boolean bResult = InternalCheckStream( xSubStream, sName, sMediaType, true, pBytes, false ); + + // free the stream resources, garbage collector may remove the object too late + if ( !disposeStream( xSubStream, sName ) ) + return false; + + return bResult; + } + + public boolean checkStreamH( XStorage xParentStorage, + String sPath, + String sMediaType, + boolean bCompressed, + byte[] pBytes ) + { + // open substream element first + XStream xSubStream = null; + try + { + XHierarchicalStorageAccess xHStorage = + (XHierarchicalStorageAccess) UnoRuntime.queryInterface( XHierarchicalStorageAccess.class, xParentStorage ); + if ( xHStorage == null ) + { + Error( "The storage does not support hierarchical access!" ); + return false; + } + + Object oSubStream = xHStorage.openStreamElementByHierarchicalName( sPath, ElementModes.READ ); + xSubStream = (XStream) UnoRuntime.queryInterface( XStream.class, oSubStream ); + if ( xSubStream == null ) + { + Error( "Can't open substream '" + sPath + "'!" ); + return false; + } + } + catch( Exception e ) + { + Error( "Can't open substream '" + sPath + "', exception : " + e + "!" ); + return false; + } + + boolean bResult = InternalCheckStream( xSubStream, sPath, sMediaType, bCompressed, pBytes, true ); + + // free the stream resources, garbage collector may remove the object too late + if ( !disposeStream( xSubStream, sPath ) ) + return false; + + return bResult; + } + + public boolean checkEncrStreamH( XStorage xParentStorage, + String sPath, + String sMediaType, + byte[] pBytes, + String sPass ) + { + // Important: a common password for any of parent storage should not be set or + // should be different from sPass + XHierarchicalStorageAccess xHStorage = + (XHierarchicalStorageAccess) UnoRuntime.queryInterface( XHierarchicalStorageAccess.class, xParentStorage ); + if ( xHStorage == null ) + { + Error( "The storage does not support hierarchical access!" ); + return false; + } + + try + { + Object oSubStream = xHStorage.openStreamElementByHierarchicalName( sPath, ElementModes.READ ); + XStream xSubStream = (XStream) UnoRuntime.queryInterface( XStream.class, oSubStream ); + Error( "Encrypted substream '" + sPath + "' was opened without password!" ); + return false; + } + catch( WrongPasswordException wpe ) + {} + catch( Exception e ) + { + Error( "Unexpected exception in case of opening of encrypted stream '" + sPath + "' without password: " + e + "!" ); + return false; + } + + String sWrongPass = "11"; + sWrongPass += sPass; + try + { + Object oSubStream = xHStorage.openEncryptedStreamElementByHierarchicalName( sPath, ElementModes.READ, sWrongPass ); + XStream xSubStream = (XStream) UnoRuntime.queryInterface( XStream.class, oSubStream ); + Error( "Encrypted substream '" + sPath + "' was opened with wrong password!" ); + return false; + } + catch( WrongPasswordException wpe ) + {} + catch( Exception e ) + { + Error( "Unexpected exception in case of opening of encrypted stream '" + sPath + "' with wrong password: " + e + "!" ); + return false; + } + + XStream xSubStream = null; + try + { + Object oSubStream = xHStorage.openEncryptedStreamElementByHierarchicalName( sPath, ElementModes.READ, sPass ); + xSubStream = (XStream) UnoRuntime.queryInterface( XStream.class, oSubStream ); + if ( xSubStream == null ) + { + Error( "Can't open encrypted substream '" + sPath + "'!" ); + return false; + } + } + catch( Exception e ) + { + Error( "Can't open encrypted substream '" + sPath + "', exception : " + e + "!" ); + return false; + } + + // encrypted streams will be compressed always, so after the storing this property is always true, + // although before the storing it can be set to false ( it is not always clear whether a stream is encrypted + // before the storing ) + boolean bResult = InternalCheckStream( xSubStream, sPath, sMediaType, true, pBytes, false ); + + // free the stream resources, garbage collector may remove the object too late + if ( !disposeStream( xSubStream, sPath ) ) + return false; + + return bResult; + } + + public boolean copyStorage( XStorage xSourceStorage, XStorage xDestStorage ) + { + // copy xSourceStorage to xDestStorage + try + { + xSourceStorage.copyToStorage( xDestStorage ); + } + catch( Exception e ) + { + Error( "Storage copying failed, exception: " + e ); + return false; + } + + return true; + } + + public boolean commitStorage( XStorage xStorage ) + { + // XTransactedObject must be supported by storages + XTransactedObject xTransact = (XTransactedObject) UnoRuntime.queryInterface( XTransactedObject.class, xStorage ); + if ( xTransact == null ) + { + Error( "Storage doesn't implement transacted access!" ); + return false; + } + + try + { + xTransact.commit(); + } + catch( Exception e ) + { + Error( "Storage commit failed, exception:" + e ); + return false; + } + + return true; + } + + public boolean disposeStream( XStream xStream, String sStreamName ) + { + XComponent xComponent = (XComponent) UnoRuntime.queryInterface( XComponent.class, xStream ); + if ( xComponent == null ) + { + Error( "Can't get XComponent implementation from substream '" + sStreamName + "'!" ); + return false; + } + + try + { + xComponent.dispose(); + } + catch( Exception e ) + { + Error( "Substream '" + sStreamName + "' disposing throws exception: " + e ); + return false; + } + + return true; + } + + public boolean disposeStorage( XStorage xStorage ) + { + // dispose the storage + XComponent xComponent = (XComponent) UnoRuntime.queryInterface( XComponent.class, xStorage ); + if ( xComponent == null ) + { + Error( "Can't retrieve XComponent implementation from storage!" ); + return false; + } + + try + { + xComponent.dispose(); + } + catch( Exception e ) + { + Error( "Storage disposing failed!" ); + return false; + } + + return true; + } + + public XInputStream getInputStream( XStream xStream ) + { + XInputStream xInTemp = null; + try + { + xInTemp = xStream.getInputStream(); + if ( xInTemp == null ) + Error( "Can't get the input part of a stream!" ); + } + catch ( Exception e ) + { + Error( "Can't get the input part of a stream, exception :" + e ); + } + + return xInTemp; + } + + public boolean closeOutput( XStream xStream ) + { + XOutputStream xOutTemp = null; + try + { + xOutTemp = xStream.getOutputStream(); + if ( xOutTemp == null ) + { + Error( "Can't get the output part of a stream!" ); + return false; + } + } + catch ( Exception e ) + { + Error( "Can't get the output part of a stream, exception :" + e ); + return false; + } + + try + { + xOutTemp.closeOutput(); + } + catch ( Exception e ) + { + Error( "Can't close output part of a stream, exception :" + e ); + return false; + } + + return true; + } + + public XStorage openSubStorage( XStorage xStorage, String sName, int nMode ) + { + // open existing substorage + try + { + Object oSubStorage = xStorage.openStorageElement( sName, nMode ); + XStorage xSubStorage = (XStorage) UnoRuntime.queryInterface( XStorage.class, oSubStorage ); + return xSubStorage; + } + catch( Exception e ) + { + Error( "Can't open substorage '" + sName + "', exception: " + e ); + } + + return null; + } + + public XStream CreateTempFileStream( XMultiServiceFactory xMSF ) + { + // try to get temporary file representation + XStream xTempFileStream = null; + try + { + Object oTempFile = xMSF.createInstance( "com.sun.star.io.TempFile" ); + xTempFileStream = (XStream)UnoRuntime.queryInterface( XStream.class, oTempFile ); + } + catch( Exception e ) + {} + + if ( xTempFileStream == null ) + Error( "Can't create temporary file!" ); + + return xTempFileStream; + } + + public String CreateTempFile( XMultiServiceFactory xMSF ) + { + String sResult = null; + + // try to get temporary file representation + XPropertySet xTempFileProps = null; + try + { + Object oTempFile = xMSF.createInstance( "com.sun.star.io.TempFile" ); + xTempFileProps = (XPropertySet)UnoRuntime.queryInterface( XPropertySet.class, oTempFile ); + } + catch( Exception e ) + {} + + if ( xTempFileProps != null ) + { + try + { + xTempFileProps.setPropertyValue( "RemoveFile", Boolean.FALSE ); + sResult = AnyConverter.toString( xTempFileProps.getPropertyValue( "Uri" ) ); + } + catch( Exception e ) + { + Error( "Can't control TempFile properties, exception: " + e ); + } + } + else + { + Error( "Can't create temporary file representation!" ); + } + + // close temporary file explicitly + try + { + XStream xStream = (XStream)UnoRuntime.queryInterface( XStream.class, xTempFileProps ); + if ( xStream != null ) + { + XOutputStream xOut = xStream.getOutputStream(); + if ( xOut != null ) + xOut.closeOutput(); + + XInputStream xIn = xStream.getInputStream(); + if ( xIn != null ) + xIn.closeInput(); + } + else + Error( "Can't close TempFile!" ); + } + catch( Exception e ) + { + Error( "Can't close TempFile, exception: " + e ); + } + + return sResult; + } + + public boolean copyElementTo( XStorage xSource, String sName, XStorage xDest ) + { + // copy element with name sName from xSource to xDest + try + { + xSource.copyElementTo( sName, xDest, sName ); + } + catch( Exception e ) + { + Error( "Element copying failed, exception: " + e ); + return false; + } + + return true; + } + + public boolean copyElementTo( XStorage xSource, String sName, XStorage xDest, String sTargetName ) + { + // copy element with name sName from xSource to xDest + try + { + xSource.copyElementTo( sName, xDest, sTargetName ); + } + catch( Exception e ) + { + Error( "Element copying failed, exception: " + e ); + return false; + } + + return true; + } + + public boolean moveElementTo( XStorage xSource, String sName, XStorage xDest ) + { + // move element with name sName from xSource to xDest + try + { + xSource.moveElementTo( sName, xDest, sName ); + } + catch( Exception e ) + { + Error( "Element moving failed, exception: " + e ); + return false; + } + + return true; + } + + public boolean renameElement( XStorage xStorage, String sOldName, String sNewName ) + { + // rename element with name sOldName to sNewName + try + { + xStorage.renameElement( sOldName, sNewName ); + } + catch( Exception e ) + { + Error( "Element renaming failed, exception: " + e ); + return false; + } + + return true; + } + + public boolean removeElement( XStorage xStorage, String sName ) + { + // remove element with name sName + try + { + xStorage.removeElement( sName ); + } + catch( Exception e ) + { + Error( "Element removing failed, exception: " + e ); + return false; + } + + return true; + } + + public XStream OpenStream( XStorage xStorage, + String sStreamName, + int nMode ) + { + // open substream element + XStream xSubStream = null; + try + { + Object oSubStream = xStorage.openStreamElement( sStreamName, nMode ); + xSubStream = (XStream) UnoRuntime.queryInterface( XStream.class, oSubStream ); + if ( xSubStream == null ) + Error( "Can't create substream '" + sStreamName + "'!" ); + } + catch( Exception e ) + { + Error( "Can't create substream '" + sStreamName + "', exception : " + e + "!" ); + } + + return xSubStream; + } + + public boolean compareRawMethodsOnEncrStream( XStorage xStorage, String sStreamName ) + { + + XStorageRawAccess xRawStorage; + try + { + xRawStorage = (XStorageRawAccess) UnoRuntime.queryInterface( XStorageRawAccess.class, xStorage ); + } + catch( Exception e ) + { + Error( "Can't get raw access to the storage, exception : " + e + "!" ); + return false; + } + + if ( xRawStorage == null ) + { + Error( "Can't get raw access to the storage!" ); + return false; + } + + XInputStream xHeadRawStream = null; + try + { + xHeadRawStream = xRawStorage.getRawEncrStreamElement( sStreamName ); + } + catch( Exception e ) + { + Error( "Can't open encrypted stream '" + sStreamName + "' in raw mode with header, exception : " + e + "!" ); + } + + XInputStream xPlainRawStream = null; + try + { + xPlainRawStream = xRawStorage.getPlainRawStreamElement( sStreamName ); + } + catch( Exception e ) + { + Error( "Can't open encrypted stream '" + sStreamName + "' in raw mode with header, exception : " + e + "!" ); + } + + if ( xHeadRawStream == null || xPlainRawStream == null ) + { + Error( "Can't open encrypted stream '" + sStreamName + "' in raw modes!" ); + return false; + } + + try + { + byte pData[][] = new byte[1][38]; + if ( xHeadRawStream.readBytes( pData, 38 ) != 38 ) + { + Error( "Can't read header of encrypted stream '" + sStreamName + "' raw representations!" ); + return false; + } + + if ( pData[0][0] != 0x4d || pData[0][1] != 0x4d || pData[0][2] != 0x02 || pData[0][3] != 0x05 ) + { + Error( "No signature in the header of encrypted stream '" + sStreamName + "' raw representations!" ); + return false; + } + + int nVariableHeaderLength = + ( pData[0][30] + pData[0][31] * 0x100 ) // salt length + + ( pData[0][32] + pData[0][33] * 0x100 ) // iv length + + ( pData[0][34] + pData[0][35] * 0x100 ) // digest length + + ( pData[0][36] + pData[0][37] * 0x100 ); // mediatype length + + xHeadRawStream.skipBytes( nVariableHeaderLength ); + + byte pRawData1[][] = new byte[1][32000]; + byte pRawData2[][] = new byte[1][32000]; + int nRead1 = 0; + int nRead2 = 0; + + do + { + nRead1 = xHeadRawStream.readBytes( pRawData1, 32000 ); + nRead2 = xPlainRawStream.readBytes( pRawData2, 32000 ); + + if ( nRead1 != nRead2 ) + { + Error( "The encrypted stream '" + sStreamName + "' raw representations have different size! nRead1 - nRead2 = " + ( Integer.valueOf( nRead1 - nRead2 ) ).toString() ); + return false; + } + + for ( int nInd = 0; nInd < nRead1; nInd++ ) + if ( pRawData1[0][nInd] != pRawData2[0][nInd] ) + { + Error( "The encrypted stream '" + sStreamName + "' raw representations have different data!" ); + return false; + } + } + while( nRead1 == 32000 ); + } + catch ( Exception e ) + { + Error( "Can't compare stream '" + sStreamName + "' raw representations, exception : " + e + "!" ); + return false; + } + + return true; + } + + public boolean cantOpenStorage( XStorage xStorage, String sName ) + { + // try to open an opened substorage, open call must fail + try + { + Object oDummyStorage = xStorage.openStorageElement( sName, ElementModes.READ ); + Error( "The trying to reopen opened substorage '" + sName + "' must fail!" ); + } + catch( Exception e ) + { + return true; + } + + return false; + } + + public boolean cantOpenStream( XStorage xStorage, String sName, int nMode ) + { + // try to open the substream with specified mode must fail + try + { + Object oDummyStream = xStorage.openStreamElement( sName, nMode ); + Error( "The trying to open substream '" + sName + "' must fail!" ); + } + catch( Exception e ) + { + return true; + } + + return false; + } + + public boolean cantOpenStreamH( XStorage xStorage, String sPath, int nMode ) + { + // try to open the substream with specified mode must fail + + XHierarchicalStorageAccess xHStorage = + (XHierarchicalStorageAccess) UnoRuntime.queryInterface( XHierarchicalStorageAccess.class, xStorage ); + if ( xHStorage == null ) + { + Error( "The storage does not support hierarchical access!" ); + return false; + } + + try + { + Object oDummyStream = xHStorage.openStreamElementByHierarchicalName( sPath, nMode ); + Error( "The trying to open substream '" + sPath + "' must fail!" ); + } + catch( Exception e ) + { + return true; + } + + return false; + } + + public boolean cantOpenEncrStreamH( XStorage xStorage, String sPath, int nMode, String aPass ) + { + // try to open the substream with specified mode must fail + + XHierarchicalStorageAccess xHStorage = + (XHierarchicalStorageAccess) UnoRuntime.queryInterface( XHierarchicalStorageAccess.class, xStorage ); + if ( xHStorage == null ) + { + Error( "The storage does not support hierarchical access!" ); + return false; + } + + try + { + Object oDummyStream = xHStorage.openEncryptedStreamElementByHierarchicalName( sPath, nMode, aPass ); + Error( "The trying to open substream '" + sPath + "' must fail!" ); + } + catch( WrongPasswordException wpe ) + { + Error( "The substream '" + sPath + "' must not exist!" ); + return false; + } + catch( Exception e ) + { + return true; + } + + return false; + } + + public XStorage cloneStorage( XSingleServiceFactory xFactory, XStorage xStorage ) + { + // create a copy of a last committed version of specified storage + XStorage xResult = null; + try + { + Object oTempStorage = xFactory.createInstance(); + xResult = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xResult != null ) + xStorage.copyLastCommitTo( xResult ); + } + catch( Exception e ) + { + Error( "Can't clone storage, exception: " + e ); + return null; + } + + return xResult; + } + + public XStorage cloneSubStorage( XSingleServiceFactory xFactory, XStorage xStorage, String sName ) + { + // create a copy of a last committed version of specified substorage + XStorage xResult = null; + try + { + Object oTempStorage = xFactory.createInstance(); + xResult = (XStorage) UnoRuntime.queryInterface( XStorage.class, oTempStorage ); + if ( xResult != null ) + xStorage.copyStorageElementLastCommitTo( sName, xResult ); + } + catch( Exception e ) + { + Error( "Can't clone substorage '" + sName + "', exception: " + e ); + return null; + } + + return xResult; + } + + public XStream cloneSubStream( XStorage xStorage, String sName ) + { + // clone existing substream + try + { + XStream xStream = xStorage.cloneStreamElement( sName ); + return xStream; + } + catch( Exception e ) + { + Error( "Can't clone substream '" + sName + "', exception: " + e ); + } + + return null; + } + + public XStream cloneEncrSubStream( XStorage xStorage, String sName, String sPass ) + { + // clone existing substream + try + { + XStream xStream = xStorage.cloneEncryptedStreamElement( sName, sPass ); + return xStream; + } + catch( Exception e ) + { + Error( "Can't clone encrypted substream '" + sName + "', exception: " + e ); + } + + return null; + } + + public void Error( String sError ) + { + m_aLogWriter.println( m_sTestPrefix + "Error: " + sError ); + } + + public void Message( String sMessage ) + { + m_aLogWriter.println( m_sTestPrefix + sMessage ); + } +} + diff --git a/package/qa/storages/makefile.mk b/package/qa/storages/makefile.mk new file mode 100644 index 0000000000..d412846031 --- /dev/null +++ b/package/qa/storages/makefile.mk @@ -0,0 +1,109 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + + + +PRJ = ..$/.. +TARGET = StorageUnitTest +PRJNAME = package +PACKAGE = complex$/storages + +# --- Settings ----------------------------------------------------- +.INCLUDE: settings.mk + + +#----- compile .java files ----------------------------------------- + +JARFILES = ridl.jar unoil.jar jurt.jar juh.jar java_uno.jar OOoRunner.jar + +JAVAFILES =\ + StorageUnitTest.java\ + StorageTest.java\ + TestHelper.java\ + BorderedStream.java\ + Test01.java\ + Test02.java\ + Test03.java\ + Test04.java\ + Test05.java\ + Test06.java\ + Test07.java\ + Test08.java\ + Test09.java\ + Test10.java\ + Test11.java\ + Test12.java\ + Test13.java\ + Test14.java\ + Test15.java\ + Test16.java\ + Test17.java\ + Test18.java\ + RegressionTest_114358.java\ + RegressionTest_i29169.java\ + RegressionTest_i30400.java\ + RegressionTest_i29321.java\ + RegressionTest_i30677.java\ + RegressionTest_i27773.java\ + RegressionTest_i46848.java\ + RegressionTest_i55821.java\ + RegressionTest_i35095.java\ + RegressionTest_i49755.java\ + RegressionTest_i59886.java\ + RegressionTest_i61909.java\ + RegressionTest_i84234.java\ + RegressionTest_125919.java + +JAVACLASSFILES = $(foreach,i,$(JAVAFILES) $(CLASSDIR)$/$(PACKAGE)$/$(i:b).class) + +#----- make a jar from compiled files ------------------------------ + +MAXLINELENGTH = 100000 + +JARCLASSDIRS = $(PACKAGE) +JARTARGET = $(TARGET).jar +JARCOMPRESS = TRUE + +# --- Parameters for the test -------------------------------------- + +# start an office if the parameter is set for the makefile +.IF "$(OFFICE)" == "" +CT_APPEXECCOMMAND = +.ELSE +CT_APPEXECCOMMAND = -AppExecutionCommand "$(OFFICE)$/soffice --accept=socket,host=localhost,port=8100;urp;" +.ENDIF + +# test base is java complex +CT_TESTBASE = -TestBase java_complex + +# test looks something like the.full.package.TestName +CT_TEST = -o $(PACKAGE:s\$/\.\).$(JAVAFILES:b) + +# start the runner application +CT_APP = org.openoffice.Runner + +# --- Targets ------------------------------------------------------ + +.INCLUDE: target.mk + +RUN: run + +run: + java -cp $(CLASSPATH) $(CT_APP) $(CT_TESTBASE) $(CT_APPEXECCOMMAND) $(CT_TEST) + + diff --git a/package/source/manifest/ManifestDefines.hxx b/package/source/manifest/ManifestDefines.hxx new file mode 100644 index 0000000000..dbe7b985b8 --- /dev/null +++ b/package/source/manifest/ManifestDefines.hxx @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#pragma once + +#include <rtl/ustring.hxx> + +inline constexpr OUString MANIFEST_NSPREFIX = u"manifest:"_ustr; +inline constexpr OUString ELEMENT_MANIFEST = u"manifest:manifest"_ustr; +inline constexpr OUString ATTRIBUTE_XMLNS = u"xmlns:manifest"_ustr; +inline constexpr OUString ATTRIBUTE_XMLNS_LOEXT = u"xmlns:loext"_ustr; +inline constexpr OUString MANIFEST_NAMESPACE = u"http://openoffice.org/2001/manifest"_ustr; +inline constexpr OUString MANIFEST_OASIS_NAMESPACE = u"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"_ustr; +inline constexpr OUString MANIFEST_LOEXT_NAMESPACE = u"urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0"_ustr; +inline constexpr OUString MANIFEST_DOCTYPE = u"<!DOCTYPE manifest:manifest PUBLIC \"-//OpenOffice.org//DTD Manifest 1.0//EN\" \"Manifest.dtd\">"_ustr; + +inline constexpr OUString ELEMENT_FILE_ENTRY = u"manifest:file-entry"_ustr; +inline constexpr OUString ATTRIBUTE_FULL_PATH = u"manifest:full-path"_ustr; +inline constexpr OUString ATTRIBUTE_VERSION = u"manifest:version"_ustr; +inline constexpr OUString ATTRIBUTE_MEDIA_TYPE = u"manifest:media-type"_ustr; +inline constexpr OUString ATTRIBUTE_SIZE = u"manifest:size"_ustr; +inline constexpr OUString ELEMENT_MANIFEST_KEYINFO = u"loext:keyinfo"_ustr; +inline constexpr OUString ELEMENT_ENCRYPTED_KEYINFO = u"loext:KeyInfo"_ustr; +inline constexpr OUString ELEMENT_ENCRYPTEDKEY = u"loext:encrypted-key"_ustr; +inline constexpr OUString ELEMENT_ENCRYPTIONMETHOD = u"loext:encryption-method"_ustr; +inline constexpr OUString ELEMENT_PGPDATA = u"loext:PGPData"_ustr; +inline constexpr OUString ELEMENT_PGPKEYID = u"loext:PGPKeyID"_ustr; +inline constexpr OUString ELEMENT_PGPKEYPACKET = u"loext:PGPKeyPacket"_ustr; +inline constexpr OUString ATTRIBUTE_ALGORITHM = u"loext:PGPAlgorithm"_ustr; +inline constexpr OUString ELEMENT_CIPHERDATA = u"loext:CipherData"_ustr; +inline constexpr OUString ELEMENT_CIPHERVALUE = u"loext:CipherValue"_ustr; +inline constexpr OUString ELEMENT_MANIFEST13_KEYINFO = u"manifest:keyinfo"_ustr; +inline constexpr OUString ELEMENT_ENCRYPTEDKEY13 = u"manifest:encrypted-key"_ustr; +inline constexpr OUString ELEMENT_ENCRYPTIONMETHOD13 = u"manifest:encryption-method"_ustr; +inline constexpr OUString ELEMENT_PGPDATA13 = u"manifest:PGPData"_ustr; +inline constexpr OUString ELEMENT_PGPKEYID13 = u"manifest:PGPKeyID"_ustr; +inline constexpr OUString ELEMENT_PGPKEYPACKET13 = u"manifest:PGPKeyPacket"_ustr; +inline constexpr OUString ATTRIBUTE_ALGORITHM13 = u"manifest:PGPAlgorithm"_ustr; +inline constexpr OUString ELEMENT_CIPHERDATA13 = u"manifest:CipherData"_ustr; +inline constexpr OUString ELEMENT_CIPHERVALUE13 = u"manifest:CipherValue"_ustr; + +inline constexpr OUString ELEMENT_ENCRYPTION_DATA = u"manifest:encryption-data"_ustr; +inline constexpr OUString ATTRIBUTE_CHECKSUM_TYPE = u"manifest:checksum-type"_ustr; +inline constexpr OUString ATTRIBUTE_CHECKSUM = u"manifest:checksum"_ustr; + +inline constexpr OUString ELEMENT_ALGORITHM = u"manifest:algorithm"_ustr; +inline constexpr OUString ATTRIBUTE_ALGORITHM_NAME = u"manifest:algorithm-name"_ustr; +inline constexpr OUString ATTRIBUTE_INITIALISATION_VECTOR = u"manifest:initialisation-vector"_ustr; + +inline constexpr OUString ELEMENT_START_KEY_GENERATION = u"manifest:start-key-generation"_ustr; +inline constexpr OUString ATTRIBUTE_START_KEY_GENERATION_NAME = u"manifest:start-key-generation-name"_ustr; +inline constexpr OUString ATTRIBUTE_KEY_SIZE = u"manifest:key-size"_ustr; + +inline constexpr OUString ELEMENT_KEY_DERIVATION = u"manifest:key-derivation"_ustr; +inline constexpr OUString ATTRIBUTE_KEY_DERIVATION_NAME = u"manifest:key-derivation-name"_ustr; +inline constexpr OUString ATTRIBUTE_SALT = u"manifest:salt"_ustr; +inline constexpr OUString ATTRIBUTE_ITERATION_COUNT = u"manifest:iteration-count"_ustr; +inline constexpr OUString ATTRIBUTE_ARGON2_T_LO= u"loext:argon2-iterations"_ustr; +inline constexpr OUString ATTRIBUTE_ARGON2_M_LO= u"loext:argon2-memory"_ustr; +inline constexpr OUString ATTRIBUTE_ARGON2_P_LO= u"loext:argon2-lanes"_ustr; + +/// OFFICE-3708: wrong URL cited in ODF 1.2 and used since OOo 3.4 beta +inline constexpr OUString SHA256_URL_ODF12 = u"http://www.w3.org/2000/09/xmldsig#sha256"_ustr; +inline constexpr OUString SHA256_URL = u"http://www.w3.org/2001/04/xmlenc#sha256"_ustr; +inline constexpr OUString SHA1_NAME = u"SHA1"_ustr; +inline constexpr OUString SHA1_URL = u"http://www.w3.org/2000/09/xmldsig#sha1"_ustr; + +inline constexpr OUString SHA1_1K_NAME = u"SHA1/1K"_ustr; +inline constexpr OUString SHA1_1K_URL = u"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0#sha1-1k"_ustr; +inline constexpr OUString SHA256_1K_URL = u"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0#sha256-1k"_ustr; + +inline constexpr OUString BLOWFISH_NAME = u"Blowfish CFB"_ustr; +inline constexpr OUString BLOWFISH_URL = u"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0#blowfish"_ustr; +inline constexpr OUString AES128_URL = u"http://www.w3.org/2001/04/xmlenc#aes128-cbc"_ustr; +inline constexpr OUString AES192_URL = u"http://www.w3.org/2001/04/xmlenc#aes192-cbc"_ustr; +inline constexpr OUString AES256_URL = u"http://www.w3.org/2001/04/xmlenc#aes256-cbc"_ustr; +inline constexpr OUString AESGCM128_URL = u"http://www.w3.org/2009/xmlenc11#aes128-gcm"_ustr; +inline constexpr OUString AESGCM192_URL = u"http://www.w3.org/2009/xmlenc11#aes192-gcm"_ustr; +inline constexpr OUString AESGCM256_URL = u"http://www.w3.org/2009/xmlenc11#aes256-gcm"_ustr; + +inline constexpr OUString PBKDF2_NAME = u"PBKDF2"_ustr; +inline constexpr OUString PGP_NAME = u"PGP"_ustr; +inline constexpr OUString PBKDF2_URL = u"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0#pbkdf2"_ustr; +inline constexpr OUString ARGON2ID_URL = u"urn:oasis:names:tc:opendocument:xmlns:manifest:1.5#argon2id"_ustr; +inline constexpr OUString ARGON2ID_URL_LO = u"urn:org:documentfoundation:names:experimental:office:manifest:argon2id"_ustr; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/manifest/ManifestExport.cxx b/package/source/manifest/ManifestExport.cxx new file mode 100644 index 0000000000..1d51e223e4 --- /dev/null +++ b/package/source/manifest/ManifestExport.cxx @@ -0,0 +1,558 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/xml/sax/XExtendedDocumentHandler.hpp> +#include <com/sun/star/xml/crypto/DigestID.hpp> +#include <com/sun/star/xml/crypto/CipherID.hpp> +#include <com/sun/star/xml/crypto/KDFID.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/uno/RuntimeException.hpp> + +#include "ManifestDefines.hxx" +#include "ManifestExport.hxx" + +#include <osl/diagnose.h> +#include <rtl/ustrbuf.hxx> +#include <rtl/ref.hxx> +#include <sal/log.hxx> +#include <comphelper/base64.hxx> +#include <comphelper/documentconstants.hxx> +#include <comphelper/attributelist.hxx> + +using namespace ::com::sun::star; + +#if OSL_DEBUG_LEVEL > 0 +#define THROW_WHERE SAL_WHERE +#else +#define THROW_WHERE "" +#endif + +ManifestExport::ManifestExport( uno::Reference< xml::sax::XDocumentHandler > const & xHandler, const uno::Sequence< uno::Sequence < beans::PropertyValue > >& rManList ) +{ + static constexpr OUStringLiteral sKeyInfo ( u"KeyInfo" ); + static constexpr OUStringLiteral sPgpKeyIDProperty ( u"KeyId" ); + static constexpr OUStringLiteral sPgpKeyPacketProperty ( u"KeyPacket" ); + static constexpr OUStringLiteral sCipherValueProperty ( u"CipherValue" ); + static constexpr OUString sFullPathProperty ( u"FullPath"_ustr ); + static constexpr OUString sVersionProperty ( u"Version"_ustr ); + static constexpr OUString sMediaTypeProperty ( u"MediaType"_ustr ); + static constexpr OUStringLiteral sIterationCountProperty ( u"IterationCount" ); + static constexpr OUStringLiteral sDerivedKeySizeProperty ( u"DerivedKeySize" ); + static constexpr OUStringLiteral sSaltProperty ( u"Salt" ); + static constexpr OUStringLiteral sInitialisationVectorProperty( u"InitialisationVector" ); + static constexpr OUStringLiteral sSizeProperty ( u"Size" ); + static constexpr OUStringLiteral sDigestProperty ( u"Digest" ); + static constexpr OUStringLiteral sEncryptionAlgProperty ( u"EncryptionAlgorithm" ); + static constexpr OUStringLiteral sStartKeyAlgProperty ( u"StartKeyAlgorithm" ); + static constexpr OUStringLiteral sDigestAlgProperty ( u"DigestAlgorithm" ); + + static constexpr OUString sWhiteSpace ( u" "_ustr ); + + const OUString sSHA256_URL_ODF12 ( SHA256_URL_ODF12 ); + const OUString sSHA1_Name ( SHA1_NAME ); + + const OUString sSHA1_1k_Name ( SHA1_1K_NAME ); + const OUString sSHA256_1k_URL ( SHA256_1K_URL ); + + const OUString sBlowfish_Name ( BLOWFISH_NAME ); + const OUString sAES256_URL ( AES256_URL ); + + const OUString sPBKDF2_Name ( PBKDF2_NAME ); + const OUString sPGP_Name ( PGP_NAME ); + + rtl::Reference<::comphelper::AttributeList> pRootAttrList = new ::comphelper::AttributeList; + + // find the mediatype of the document if any + OUString aDocMediaType; + OUString aDocVersion; + bool isWholesomeEncryption(false); + const uno::Sequence<beans::PropertyValue>* pRootFolderPropSeq = nullptr; + for (const uno::Sequence < beans::PropertyValue >& rSequence : rManList) + { + OUString aMediaType; + OUString aPath; + OUString aVersion; + + for (const beans::PropertyValue& rValue : rSequence) + { + if (rValue.Name == sMediaTypeProperty ) + { + rValue.Value >>= aMediaType; + } + else if (rValue.Name == sFullPathProperty ) + { + rValue.Value >>= aPath; + } + else if (rValue.Name == sVersionProperty ) + { + rValue.Value >>= aVersion; + } + + if ( !aPath.isEmpty() && !aMediaType.isEmpty() && !aVersion.isEmpty() ) + break; + } + + if ( aPath == "/" ) + { + assert(aDocMediaType.isEmpty()); + // unfortunately no aMediaType in some cases where non-documents + // are stored as StorageFormats::PACKAGE instead of sensible + // StorageFormats::ZIP, such as SvxXMLXTableExportComponent and + // SwXMLTextBlocks, which results in an empty "mimetype" etc but + // can't be easily fixed; try to exclude these cases by checking + // for aVersion, but of course then forgetting to set both version + // and type on an actual document can't be found :( + assert(!aMediaType.isEmpty() || aVersion.isEmpty()); + aDocMediaType = aMediaType; + aDocVersion = aVersion; + pRootFolderPropSeq = &rSequence; + } + + if (aPath == "encrypted-package") + { + isWholesomeEncryption = true; + assert(aDocMediaType.isEmpty() || aDocMediaType == aMediaType); + } + } + assert(pRootFolderPropSeq); + + bool bProvideDTD = false; + bool bAcceptNonemptyVersion = false; + bool bStoreStartKeyGeneration = false; + if ( !aDocMediaType.isEmpty() ) + { + if ( aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_TEXT_ASCII + || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_TEXT_WEB_ASCII + || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_TEXT_GLOBAL_ASCII + || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_DRAWING_ASCII + || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_PRESENTATION_ASCII + || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_SPREADSHEET_ASCII + || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_CHART_ASCII + || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_DATABASE_ASCII + || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_FORMULA_ASCII + || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_TEXT_TEMPLATE_ASCII + || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_TEXT_GLOBAL_TEMPLATE_ASCII + || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_DRAWING_TEMPLATE_ASCII + || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_PRESENTATION_TEMPLATE_ASCII + || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_SPREADSHEET_TEMPLATE_ASCII + || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_CHART_TEMPLATE_ASCII + || aDocMediaType == MIMETYPE_OASIS_OPENDOCUMENT_FORMULA_TEMPLATE_ASCII ) + + { + // oasis format + pRootAttrList->AddAttribute ( ATTRIBUTE_XMLNS, + MANIFEST_OASIS_NAMESPACE ); + bAcceptNonemptyVersion = true; + if ( aDocVersion.compareTo( ODFVER_012_TEXT ) >= 0 ) + { + // this is ODF12 or later generation, let encrypted + // streams contain start-key-generation entry + bStoreStartKeyGeneration = true; + pRootAttrList->AddAttribute ( ATTRIBUTE_VERSION, aDocVersion ); + // plus gpg4libre extensions - loext NS for that + pRootAttrList->AddAttribute ( ATTRIBUTE_XMLNS_LOEXT, + MANIFEST_LOEXT_NAMESPACE ); + } + } + else + { + // even if it is no SO6 format the namespace must be specified + // thus SO6 format is used as default one + pRootAttrList->AddAttribute ( ATTRIBUTE_XMLNS, + MANIFEST_NAMESPACE ); + + bProvideDTD = true; + } + } + + xHandler->startDocument(); + uno::Reference < xml::sax::XExtendedDocumentHandler > xExtHandler ( xHandler, uno::UNO_QUERY ); + if ( xExtHandler.is() && bProvideDTD ) + { + xExtHandler->unknown ( MANIFEST_DOCTYPE ); + xHandler->ignorableWhitespace ( sWhiteSpace ); + } + xHandler->startElement( ELEMENT_MANIFEST, pRootAttrList ); + + const uno::Any *pKeyInfoProperty = nullptr; + if ( pRootFolderPropSeq ) + { + // do we have package-wide encryption info? + for (const beans::PropertyValue& rValue : *pRootFolderPropSeq) + { + if (rValue.Name == sKeyInfo ) + pKeyInfoProperty = &rValue.Value; + } + + if ( pKeyInfoProperty ) + { + // no start-key-generation needed, our session key has + // max size already + bStoreStartKeyGeneration = false; + + // yeah, so that goes directly below the manifest:manifest + // element + OUStringBuffer aBuffer; + + xHandler->ignorableWhitespace ( sWhiteSpace ); + + // ==== manifest:keyinfo & children + bool const isODF13(aDocVersion.compareTo(ODFVER_013_TEXT) >= 0); + if (!isODF13) + { + xHandler->startElement(ELEMENT_MANIFEST_KEYINFO, nullptr); + } + xHandler->ignorableWhitespace ( sWhiteSpace ); + + uno::Sequence< uno::Sequence < beans::NamedValue > > aKeyInfoSequence; + *pKeyInfoProperty >>= aKeyInfoSequence; + for (const uno::Sequence<beans::NamedValue>& rKeyInfoSequence : std::as_const(aKeyInfoSequence)) + { + uno::Sequence < sal_Int8 > aPgpKeyID; + uno::Sequence < sal_Int8 > aPgpKeyPacket; + uno::Sequence < sal_Int8 > aCipherValue; + for (const beans::NamedValue& rNValue : rKeyInfoSequence) + { + if (rNValue.Name == sPgpKeyIDProperty ) + rNValue.Value >>= aPgpKeyID; + else if (rNValue.Name == sPgpKeyPacketProperty ) + rNValue.Value >>= aPgpKeyPacket; + else if (rNValue.Name == sCipherValueProperty ) + rNValue.Value >>= aCipherValue; + } + + if (aPgpKeyID.hasElements() && aCipherValue.hasElements() ) + { + // ==== manifest:encrypted-key & children - one for each recipient + xHandler->startElement(isODF13 ? ELEMENT_ENCRYPTEDKEY13 : ELEMENT_ENCRYPTEDKEY, nullptr); + xHandler->ignorableWhitespace ( sWhiteSpace ); + + rtl::Reference<::comphelper::AttributeList> pNewAttrList = new ::comphelper::AttributeList; + // TODO: the algorithm should rather be configurable + pNewAttrList->AddAttribute( + isODF13 ? ATTRIBUTE_ALGORITHM13 : ATTRIBUTE_ALGORITHM, + "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p" ); + xHandler->startElement(isODF13 ? ELEMENT_ENCRYPTIONMETHOD13 : ELEMENT_ENCRYPTIONMETHOD, pNewAttrList); + xHandler->endElement(isODF13 ? ELEMENT_ENCRYPTIONMETHOD13 : ELEMENT_ENCRYPTIONMETHOD); + xHandler->ignorableWhitespace ( sWhiteSpace ); + + // note: the mismatch here corresponds to ODF 1.3 cs01 schema + xHandler->startElement(isODF13 ? ELEMENT_MANIFEST13_KEYINFO : ELEMENT_MANIFEST_KEYINFO, nullptr); + xHandler->ignorableWhitespace ( sWhiteSpace ); + + xHandler->startElement(isODF13 ? ELEMENT_PGPDATA13 : ELEMENT_PGPDATA, nullptr); + xHandler->ignorableWhitespace ( sWhiteSpace ); + + xHandler->startElement(isODF13 ? ELEMENT_PGPKEYID13 : ELEMENT_PGPKEYID, nullptr); + ::comphelper::Base64::encode(aBuffer, aPgpKeyID); + xHandler->characters( aBuffer.makeStringAndClear() ); + xHandler->endElement(isODF13 ? ELEMENT_PGPKEYID13 : ELEMENT_PGPKEYID); + xHandler->ignorableWhitespace ( sWhiteSpace ); + + // key packet is optional + if (aPgpKeyPacket.hasElements()) + { + xHandler->startElement(isODF13 ? ELEMENT_PGPKEYPACKET13 : ELEMENT_PGPKEYPACKET, nullptr); + ::comphelper::Base64::encode(aBuffer, aPgpKeyPacket); + xHandler->characters( aBuffer.makeStringAndClear() ); + xHandler->endElement(isODF13 ? ELEMENT_PGPKEYPACKET13 : ELEMENT_PGPKEYPACKET); + xHandler->ignorableWhitespace ( sWhiteSpace ); + } + + xHandler->endElement(isODF13 ? ELEMENT_PGPDATA13 : ELEMENT_PGPDATA); + xHandler->ignorableWhitespace ( sWhiteSpace ); + + xHandler->endElement(isODF13 ? ELEMENT_MANIFEST13_KEYINFO : ELEMENT_MANIFEST_KEYINFO); + xHandler->ignorableWhitespace ( sWhiteSpace ); + + xHandler->startElement(isODF13 ? ELEMENT_CIPHERDATA13 : ELEMENT_CIPHERDATA, nullptr); + xHandler->ignorableWhitespace ( sWhiteSpace ); + + xHandler->startElement(isODF13 ? ELEMENT_CIPHERVALUE13 : ELEMENT_CIPHERVALUE, nullptr); + ::comphelper::Base64::encode(aBuffer, aCipherValue); + xHandler->characters( aBuffer.makeStringAndClear() ); + xHandler->endElement(isODF13 ? ELEMENT_CIPHERVALUE13 : ELEMENT_CIPHERVALUE); + xHandler->ignorableWhitespace ( sWhiteSpace ); + + xHandler->endElement(isODF13 ? ELEMENT_CIPHERDATA13 : ELEMENT_CIPHERDATA); + xHandler->ignorableWhitespace ( sWhiteSpace ); + + xHandler->endElement(isODF13 ? ELEMENT_ENCRYPTEDKEY13 : ELEMENT_ENCRYPTEDKEY); + xHandler->ignorableWhitespace ( sWhiteSpace ); + } + } + + if (!isODF13) + { + xHandler->endElement(ELEMENT_MANIFEST_KEYINFO); + } + xHandler->ignorableWhitespace ( sWhiteSpace ); + } + } + + // now write individual file entries + for (const uno::Sequence<beans::PropertyValue>& rSequence : rManList) + { + if (&rSequence == pRootFolderPropSeq && isWholesomeEncryption) + { + continue; // no root document, but embedded package => omit + } + rtl::Reference<::comphelper::AttributeList> pAttrList = new ::comphelper::AttributeList; + OUString fullPath; + OUString aString; + const uno::Any *pVector = nullptr, *pSalt = nullptr, *pIterationCount = nullptr, *pDigest = nullptr, *pDigestAlg = nullptr, *pEncryptAlg = nullptr, *pStartKeyAlg = nullptr, *pDerivedKeySize = nullptr; + uno::Any const* pKDF = nullptr; + uno::Any const* pArgon2Args = nullptr; + for (const beans::PropertyValue& rValue : rSequence) + { + if (rValue.Name == sMediaTypeProperty ) + { + rValue.Value >>= aString; + pAttrList->AddAttribute ( ATTRIBUTE_MEDIA_TYPE, aString ); + } + else if (rValue.Name == sVersionProperty ) + { + rValue.Value >>= aString; + // the version is stored only if it is not empty + if ( bAcceptNonemptyVersion && !aString.isEmpty() ) + pAttrList->AddAttribute ( ATTRIBUTE_VERSION, aString ); + } + else if (rValue.Name == sFullPathProperty ) + { + rValue.Value >>= fullPath; + pAttrList->AddAttribute(ATTRIBUTE_FULL_PATH, fullPath); + } + else if (rValue.Name == sSizeProperty ) + { + sal_Int64 nSize = 0; + rValue.Value >>= nSize; + pAttrList->AddAttribute ( ATTRIBUTE_SIZE, OUString::number( nSize ) ); + } + else if (rValue.Name == sInitialisationVectorProperty ) + pVector = &rValue.Value; + else if (rValue.Name == sSaltProperty ) + pSalt = &rValue.Value; + else if (rValue.Name == sIterationCountProperty ) + pIterationCount = &rValue.Value; + else if (rValue.Name == sDigestProperty ) + pDigest = &rValue.Value; + else if (rValue.Name == sDigestAlgProperty ) + pDigestAlg = &rValue.Value; + else if (rValue.Name == sEncryptionAlgProperty ) + pEncryptAlg = &rValue.Value; + else if (rValue.Name == sStartKeyAlgProperty ) + pStartKeyAlg = &rValue.Value; + else if (rValue.Name == sDerivedKeySizeProperty ) + pDerivedKeySize = &rValue.Value; + else if (rValue.Name == "KeyDerivationFunction") { + pKDF = &rValue.Value; + } else if (rValue.Name == "Argon2Args") { + pArgon2Args = &rValue.Value; + } + } + assert(!fullPath.isEmpty()); + if (isWholesomeEncryption) + { // there may be signatures in META-INF too + assert(fullPath == "encrypted-package" || fullPath.startsWith("META-INF/")); + } + + xHandler->ignorableWhitespace ( sWhiteSpace ); + xHandler->startElement( ELEMENT_FILE_ENTRY , pAttrList); + if (pVector && pEncryptAlg && pDerivedKeySize && pKDF + && ((pSalt && pStartKeyAlg && (pIterationCount || pArgon2Args)) + || pKeyInfoProperty)) + { + // ==== Encryption Data + rtl::Reference<::comphelper::AttributeList> pNewAttrList = new ::comphelper::AttributeList; + OUStringBuffer aBuffer; + uno::Sequence < sal_Int8 > aSequence; + + xHandler->ignorableWhitespace ( sWhiteSpace ); + + // ==== Digest + if (pDigest && pDigestAlg && pDigestAlg->hasValue()) + { + OUString sChecksumType; + sal_Int32 nDigestAlgID = 0; + *pDigestAlg >>= nDigestAlgID; + if ( nDigestAlgID == xml::crypto::DigestID::SHA256_1K ) + sChecksumType = sSHA256_1k_URL; + else if ( nDigestAlgID == xml::crypto::DigestID::SHA1_1K ) + sChecksumType = sSHA1_1k_Name; + else + throw uno::RuntimeException( THROW_WHERE "Unexpected digest algorithm is provided!" ); + + pNewAttrList->AddAttribute(ATTRIBUTE_CHECKSUM_TYPE, sChecksumType); + *pDigest >>= aSequence; + ::comphelper::Base64::encode(aBuffer, aSequence); + pNewAttrList->AddAttribute(ATTRIBUTE_CHECKSUM, aBuffer.makeStringAndClear()); + } + + xHandler->startElement( ELEMENT_ENCRYPTION_DATA , pNewAttrList); + + // ==== Algorithm + pNewAttrList = new ::comphelper::AttributeList; + + sal_Int32 nEncAlgID = 0; + sal_Int32 nDerivedKeySize = 0; + *pEncryptAlg >>= nEncAlgID; + *pDerivedKeySize >>= nDerivedKeySize; + + OUString sEncAlgName; + if ( nEncAlgID == xml::crypto::CipherID::AES_CBC_W3C_PADDING ) + { + OSL_ENSURE( nDerivedKeySize, "Unexpected key size is provided!" ); + if ( nDerivedKeySize != 32 ) + throw uno::RuntimeException( THROW_WHERE "Unexpected key size is provided!" ); + + sEncAlgName = sAES256_URL; + } + else if (nEncAlgID == xml::crypto::CipherID::AES_GCM_W3C) + { + assert(bStoreStartKeyGeneration || pKeyInfoProperty); + SAL_WARN_IF(nDerivedKeySize != 32, "package.manifest", "Unexpected key size is provided!"); + if (nDerivedKeySize != 32) + { + throw uno::RuntimeException(THROW_WHERE "Unexpected key size is provided!"); + } + sEncAlgName = AESGCM256_URL; + } + else if ( nEncAlgID == xml::crypto::CipherID::BLOWFISH_CFB_8 ) + { + sEncAlgName = sBlowfish_Name; + } + else + throw uno::RuntimeException( THROW_WHERE "Unexpected encryption algorithm is provided!" ); + + pNewAttrList->AddAttribute ( ATTRIBUTE_ALGORITHM_NAME, sEncAlgName ); + + *pVector >>= aSequence; + ::comphelper::Base64::encode(aBuffer, aSequence); + pNewAttrList->AddAttribute ( ATTRIBUTE_INITIALISATION_VECTOR, aBuffer.makeStringAndClear() ); + + xHandler->ignorableWhitespace ( sWhiteSpace ); + xHandler->startElement( ELEMENT_ALGORITHM , pNewAttrList); + xHandler->ignorableWhitespace ( sWhiteSpace ); + xHandler->endElement( ELEMENT_ALGORITHM ); + + if ( bStoreStartKeyGeneration ) + { + // ==== Start Key Generation + pNewAttrList = new ::comphelper::AttributeList; + + OUString sStartKeyAlg; + OUString sStartKeySize; + sal_Int32 nStartKeyAlgID = 0; + *pStartKeyAlg >>= nStartKeyAlgID; + if ( nStartKeyAlgID == xml::crypto::DigestID::SHA256 ) + { + if (nEncAlgID == xml::crypto::CipherID::AES_GCM_W3C) + { // new encryption is incompatible anyway, use W3C URL + sStartKeyAlg = SHA256_URL; + } + else // to interop with ODF <= 1.4 consumers use bad ODF URL + { + sStartKeyAlg = sSHA256_URL_ODF12; + } + aBuffer.append( sal_Int32(32) ); + sStartKeySize = aBuffer.makeStringAndClear(); + } + else if ( nStartKeyAlgID == xml::crypto::DigestID::SHA1 ) + { + sStartKeyAlg = sSHA1_Name; + aBuffer.append( sal_Int32(20) ); + sStartKeySize = aBuffer.makeStringAndClear(); + } + else + throw uno::RuntimeException( THROW_WHERE "Unexpected start key algorithm is provided!" ); + + pNewAttrList->AddAttribute ( ATTRIBUTE_START_KEY_GENERATION_NAME, sStartKeyAlg ); + pNewAttrList->AddAttribute ( ATTRIBUTE_KEY_SIZE, sStartKeySize ); + + xHandler->ignorableWhitespace ( sWhiteSpace ); + xHandler->startElement( ELEMENT_START_KEY_GENERATION , pNewAttrList); + xHandler->ignorableWhitespace ( sWhiteSpace ); + xHandler->endElement( ELEMENT_START_KEY_GENERATION ); + } + + // ==== Key Derivation + pNewAttrList = new ::comphelper::AttributeList; + + if (pKeyInfoProperty) + { + assert(pKDF->get<sal_Int32>() == xml::crypto::KDFID::PGP_RSA_OAEP_MGF1P); + pNewAttrList->AddAttribute(ATTRIBUTE_KEY_DERIVATION_NAME, + sPGP_Name); + } + else + { + if (pKDF->get<sal_Int32>() == xml::crypto::KDFID::Argon2id) + { + pNewAttrList->AddAttribute(ATTRIBUTE_KEY_DERIVATION_NAME, + ARGON2ID_URL_LO); + + uno::Sequence<sal_Int32> args; + *pArgon2Args >>= args; + assert(args.getLength() == 3); + pNewAttrList->AddAttribute(ATTRIBUTE_ARGON2_T_LO, OUString::number(args[0])); + pNewAttrList->AddAttribute(ATTRIBUTE_ARGON2_M_LO, OUString::number(args[1])); + pNewAttrList->AddAttribute(ATTRIBUTE_ARGON2_P_LO, OUString::number(args[2])); + } + else + { + assert(pKDF->get<sal_Int32>() == xml::crypto::KDFID::PBKDF2); + pNewAttrList->AddAttribute(ATTRIBUTE_KEY_DERIVATION_NAME, + sPBKDF2_Name); + + sal_Int32 nCount = 0; + *pIterationCount >>= nCount; + aBuffer.append(nCount); + pNewAttrList->AddAttribute(ATTRIBUTE_ITERATION_COUNT, aBuffer.makeStringAndClear()); + } + + *pSalt >>= aSequence; + ::comphelper::Base64::encode(aBuffer, aSequence); + pNewAttrList->AddAttribute ( ATTRIBUTE_SALT, aBuffer.makeStringAndClear() ); + } + + // ODF 1.3 specifies the default as 16 so have to write it for PGP + if (bStoreStartKeyGeneration || pKeyInfoProperty) + { + aBuffer.append(nDerivedKeySize); + pNewAttrList->AddAttribute(ATTRIBUTE_KEY_SIZE, aBuffer.makeStringAndClear()); + } + + xHandler->ignorableWhitespace(sWhiteSpace); + xHandler->startElement(ELEMENT_KEY_DERIVATION, pNewAttrList); + xHandler->ignorableWhitespace(sWhiteSpace); + xHandler->endElement(ELEMENT_KEY_DERIVATION); + + xHandler->ignorableWhitespace ( sWhiteSpace ); + xHandler->endElement( ELEMENT_ENCRYPTION_DATA ); + } + xHandler->ignorableWhitespace ( sWhiteSpace ); + xHandler->endElement( ELEMENT_FILE_ENTRY ); + } + xHandler->ignorableWhitespace ( sWhiteSpace ); + xHandler->endElement( ELEMENT_MANIFEST ); + xHandler->endDocument(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/manifest/ManifestExport.hxx b/package/source/manifest/ManifestExport.hxx new file mode 100644 index 0000000000..0148ea62c8 --- /dev/null +++ b/package/source/manifest/ManifestExport.hxx @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_PACKAGE_SOURCE_MANIFEST_MANIFESTEXPORT_HXX +#define INCLUDED_PACKAGE_SOURCE_MANIFEST_MANIFESTEXPORT_HXX + +#include <com/sun/star/uno/Sequence.h> +#include <com/sun/star/uno/Reference.h> + +namespace com::sun::star { + namespace beans { struct PropertyValue;} + namespace xml::sax { class XDocumentHandler; } +} +class ManifestExport +{ +public: + ManifestExport(css::uno::Reference < css::xml::sax::XDocumentHandler > const & xHandler, const css::uno::Sequence < css::uno::Sequence < css::beans::PropertyValue > > &rManList ); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/manifest/ManifestImport.cxx b/package/source/manifest/ManifestImport.cxx new file mode 100644 index 0000000000..f6f4ce36f4 --- /dev/null +++ b/package/source/manifest/ManifestImport.cxx @@ -0,0 +1,599 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "ManifestImport.hxx" +#include "ManifestDefines.hxx" +#include <PackageConstants.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> +#include <com/sun/star/xml/sax/XAttributeList.hpp> +#include <com/sun/star/xml/crypto/DigestID.hpp> +#include <com/sun/star/xml/crypto/CipherID.hpp> +#include <com/sun/star/xml/crypto/KDFID.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <comphelper/base64.hxx> +#include <comphelper/sequence.hxx> + +using namespace com::sun::star::uno; +using namespace com::sun::star::beans; +using namespace com::sun::star; + +constexpr OUStringLiteral gsFullPathProperty ( u"FullPath" ); +constexpr OUStringLiteral gsMediaTypeProperty ( u"MediaType" ); +constexpr OUStringLiteral gsVersionProperty ( u"Version" ); +constexpr OUStringLiteral gsIterationCountProperty ( u"IterationCount" ); +constexpr OUStringLiteral gsDerivedKeySizeProperty ( u"DerivedKeySize" ); +constexpr OUStringLiteral gsSaltProperty ( u"Salt" ); +constexpr OUStringLiteral gsInitialisationVectorProperty ( u"InitialisationVector" ); +constexpr OUStringLiteral gsSizeProperty ( u"Size" ); +constexpr OUStringLiteral gsDigestProperty ( u"Digest" ); +constexpr OUString gsEncryptionAlgProperty ( u"EncryptionAlgorithm"_ustr ); +constexpr OUString gsStartKeyAlgProperty ( u"StartKeyAlgorithm"_ustr ); +constexpr OUString gsDigestAlgProperty ( u"DigestAlgorithm"_ustr ); + +ManifestImport::ManifestImport( std::vector < Sequence < PropertyValue > > & rNewManVector ) + : bIgnoreEncryptData ( false ) + , bPgpEncryption ( false ) + , nDerivedKeySize( 0 ) + , rManVector ( rNewManVector ) +{ + aStack.reserve( 10 ); +} + +ManifestImport::~ManifestImport() +{ +} + +void SAL_CALL ManifestImport::startDocument( ) +{ +} + +void SAL_CALL ManifestImport::endDocument( ) +{ +} + +void ManifestImport::doFileEntry(StringHashMap &rConvertedAttribs) +{ + aSequence.resize(PKG_SIZE_ENCR_MNFST); + + aSequence[PKG_MNFST_FULLPATH].Name = gsFullPathProperty; + aSequence[PKG_MNFST_FULLPATH].Value <<= rConvertedAttribs[ATTRIBUTE_FULL_PATH]; + aSequence[PKG_MNFST_MEDIATYPE].Name = gsMediaTypeProperty; + aSequence[PKG_MNFST_MEDIATYPE].Value <<= rConvertedAttribs[ATTRIBUTE_MEDIA_TYPE]; + + OUString sVersion = rConvertedAttribs[ATTRIBUTE_VERSION]; + if ( sVersion.getLength() ) { + aSequence[PKG_MNFST_VERSION].Name = gsVersionProperty; + aSequence[PKG_MNFST_VERSION].Value <<= sVersion; + } + + OUString sSize = rConvertedAttribs[ATTRIBUTE_SIZE]; + if ( sSize.getLength() ) { + sal_Int64 nSize = sSize.toInt64(); + aSequence[PKG_MNFST_UCOMPSIZE].Name = gsSizeProperty; + aSequence[PKG_MNFST_UCOMPSIZE].Value <<= nSize; + } +} + +void ManifestImport::doEncryptedKey(StringHashMap &) +{ + aKeyInfoSequence.clear(); + aKeyInfoSequence.resize(3); +} + +void ManifestImport::doEncryptionMethod(StringHashMap &rConvertedAttribs, + const OUString& rAlgoAttrName) +{ + OUString aString = rConvertedAttribs[rAlgoAttrName]; + if ( aKeyInfoSequence.size() != 3 + || aString != "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p" ) + { + bIgnoreEncryptData = true; + } +} + +void ManifestImport::doEncryptedCipherValue() +{ + if ( aKeyInfoSequence.size() == 3 ) + { + aKeyInfoSequence[2].Name = "CipherValue"; + uno::Sequence < sal_Int8 > aDecodeBuffer; + ::comphelper::Base64::decode(aDecodeBuffer, aCurrentCharacters); + aKeyInfoSequence[2].Value <<= aDecodeBuffer; + aCurrentCharacters.setLength(0); // consumed + } + else + bIgnoreEncryptData = true; +} + +void ManifestImport::doEncryptedKeyId() +{ + if ( aKeyInfoSequence.size() == 3 ) + { + aKeyInfoSequence[0].Name = "KeyId"; + uno::Sequence < sal_Int8 > aDecodeBuffer; + ::comphelper::Base64::decode(aDecodeBuffer, aCurrentCharacters); + aKeyInfoSequence[0].Value <<= aDecodeBuffer; + aCurrentCharacters.setLength(0); // consumed + } + else + bIgnoreEncryptData = true; +} + +void ManifestImport::doEncryptedKeyPacket() +{ + if ( aKeyInfoSequence.size() == 3 ) + { + aKeyInfoSequence[1].Name = "KeyPacket"; + uno::Sequence < sal_Int8 > aDecodeBuffer; + ::comphelper::Base64::decode(aDecodeBuffer, aCurrentCharacters); + aKeyInfoSequence[1].Value <<= aDecodeBuffer; + aCurrentCharacters.setLength(0); // consumed + } + else + bIgnoreEncryptData = true; +} + +void ManifestImport::doEncryptionData(StringHashMap &rConvertedAttribs) +{ + // If this element exists, then this stream is encrypted and we need + // to import the initialisation vector, salt and iteration count used + nDerivedKeySize = 0; + OUString aString = rConvertedAttribs[ATTRIBUTE_CHECKSUM_TYPE]; + if ( bIgnoreEncryptData ) + return; + + if ( aString == SHA1_1K_NAME || aString == SHA1_1K_URL ) { + aSequence[PKG_MNFST_DIGESTALG].Name = gsDigestAlgProperty; + aSequence[PKG_MNFST_DIGESTALG].Value <<= xml::crypto::DigestID::SHA1_1K; + } else if ( aString == SHA256_1K_URL ) { + aSequence[PKG_MNFST_DIGESTALG].Name = gsDigestAlgProperty; + aSequence[PKG_MNFST_DIGESTALG].Value <<= xml::crypto::DigestID::SHA256_1K; + } + // note: digest is now *optional* - expected not to be used with AEAD + + if (aSequence[PKG_MNFST_DIGESTALG].Value.hasValue()) { + aString = rConvertedAttribs[ATTRIBUTE_CHECKSUM]; + uno::Sequence < sal_Int8 > aDecodeBuffer; + ::comphelper::Base64::decode(aDecodeBuffer, aString); + aSequence[PKG_MNFST_DIGEST].Name = gsDigestProperty; + aSequence[PKG_MNFST_DIGEST].Value <<= aDecodeBuffer; + } +} + +void ManifestImport::doAlgorithm(StringHashMap &rConvertedAttribs) +{ + if ( bIgnoreEncryptData ) + return; + + OUString aString = rConvertedAttribs[ATTRIBUTE_ALGORITHM_NAME]; + if ( aString == BLOWFISH_NAME || aString == BLOWFISH_URL ) { + aSequence[PKG_MNFST_ENCALG].Name = gsEncryptionAlgProperty; + aSequence[PKG_MNFST_ENCALG].Value <<= xml::crypto::CipherID::BLOWFISH_CFB_8; + } else if (aString == AESGCM256_URL) { + aSequence[PKG_MNFST_ENCALG].Name = gsEncryptionAlgProperty; + aSequence[PKG_MNFST_ENCALG].Value <<= xml::crypto::CipherID::AES_GCM_W3C; + SAL_INFO_IF(nDerivedKeySize != 0 && nDerivedKeySize != 32, "package.manifest", "Unexpected derived key length!"); + nDerivedKeySize = 32; + } else if (aString == AESGCM192_URL) { + aSequence[PKG_MNFST_ENCALG].Name = gsEncryptionAlgProperty; + aSequence[PKG_MNFST_ENCALG].Value <<= xml::crypto::CipherID::AES_GCM_W3C; + SAL_INFO_IF(nDerivedKeySize != 0 && nDerivedKeySize != 24, "package.manifest", "Unexpected derived key length!"); + nDerivedKeySize = 24; + } else if (aString == AESGCM128_URL) { + aSequence[PKG_MNFST_ENCALG].Name = gsEncryptionAlgProperty; + aSequence[PKG_MNFST_ENCALG].Value <<= xml::crypto::CipherID::AES_GCM_W3C; + SAL_INFO_IF(nDerivedKeySize != 0 && nDerivedKeySize != 16, "package.manifest", "Unexpected derived key length!"); + nDerivedKeySize = 16; + } else if ( aString == AES256_URL ) { + aSequence[PKG_MNFST_ENCALG].Name = gsEncryptionAlgProperty; + aSequence[PKG_MNFST_ENCALG].Value <<= xml::crypto::CipherID::AES_CBC_W3C_PADDING; + OSL_ENSURE( !nDerivedKeySize || nDerivedKeySize == 32, "Unexpected derived key length!" ); + nDerivedKeySize = 32; + } else if ( aString == AES192_URL ) { + aSequence[PKG_MNFST_ENCALG].Name = gsEncryptionAlgProperty; + aSequence[PKG_MNFST_ENCALG].Value <<= xml::crypto::CipherID::AES_CBC_W3C_PADDING; + OSL_ENSURE( !nDerivedKeySize || nDerivedKeySize == 24, "Unexpected derived key length!" ); + nDerivedKeySize = 24; + } else if ( aString == AES128_URL ) { + aSequence[PKG_MNFST_ENCALG].Name = gsEncryptionAlgProperty; + aSequence[PKG_MNFST_ENCALG].Value <<= xml::crypto::CipherID::AES_CBC_W3C_PADDING; + OSL_ENSURE( !nDerivedKeySize || nDerivedKeySize == 16, "Unexpected derived key length!" ); + nDerivedKeySize = 16; + } else + bIgnoreEncryptData = true; + + if ( !bIgnoreEncryptData ) { + aString = rConvertedAttribs[ATTRIBUTE_INITIALISATION_VECTOR]; + uno::Sequence < sal_Int8 > aDecodeBuffer; + ::comphelper::Base64::decode(aDecodeBuffer, aString); + aSequence[PKG_MNFST_INIVECTOR].Name = gsInitialisationVectorProperty; + aSequence[PKG_MNFST_INIVECTOR].Value <<= aDecodeBuffer; + } +} + +void ManifestImport::doKeyDerivation(StringHashMap &rConvertedAttribs) +{ + if ( bIgnoreEncryptData ) + return; + + OUString aString = rConvertedAttribs[ATTRIBUTE_KEY_DERIVATION_NAME]; + if (aString == PBKDF2_NAME || aString == PBKDF2_URL + || aString == ARGON2ID_URL || aString == ARGON2ID_URL_LO) + { + aSequence[PKG_MNFST_KDF].Name = "KeyDerivationFunction"; + if (aString == ARGON2ID_URL || aString == ARGON2ID_URL_LO) + { + aSequence[PKG_MNFST_KDF].Value <<= xml::crypto::KDFID::Argon2id; + + aString = rConvertedAttribs[ATTRIBUTE_ARGON2_T_LO]; + sal_Int32 const t(aString.toInt32()); + aString = rConvertedAttribs[ATTRIBUTE_ARGON2_M_LO]; + sal_Int32 const m(aString.toInt32()); + aString = rConvertedAttribs[ATTRIBUTE_ARGON2_P_LO]; + sal_Int32 const p(aString.toInt32()); + if (0 < t && 0 < m && 0 < p) + { + aSequence[PKG_MNFST_ARGON2ARGS].Name = "Argon2Args"; + aSequence[PKG_MNFST_ARGON2ARGS].Value <<= uno::Sequence{t,m,p}; + } + else + { + SAL_INFO("package.manifest", "invalid argon2 arguments"); + bIgnoreEncryptData = true; + } + } + else + { + aSequence[PKG_MNFST_KDF].Value <<= xml::crypto::KDFID::PBKDF2; + + aString = rConvertedAttribs[ATTRIBUTE_ITERATION_COUNT]; + aSequence[PKG_MNFST_ITERATION].Name = gsIterationCountProperty; + aSequence[PKG_MNFST_ITERATION].Value <<= aString.toInt32(); + } + + aString = rConvertedAttribs[ATTRIBUTE_SALT]; + uno::Sequence < sal_Int8 > aDecodeBuffer; + ::comphelper::Base64::decode(aDecodeBuffer, aString); + aSequence[PKG_MNFST_SALT].Name = gsSaltProperty; + aSequence[PKG_MNFST_SALT].Value <<= aDecodeBuffer; + + aString = rConvertedAttribs[ATTRIBUTE_KEY_SIZE]; + if ( aString.getLength() ) { + sal_Int32 nKey = aString.toInt32(); + OSL_ENSURE( !nDerivedKeySize || nKey == nDerivedKeySize , "Provided derived key length differs from the expected one!" ); + nDerivedKeySize = nKey; + } else if ( !nDerivedKeySize ) + nDerivedKeySize = 16; + else if ( nDerivedKeySize != 16 ) + OSL_ENSURE( false, "Default derived key length differs from the expected one!" ); + + aSequence[PKG_MNFST_DERKEYSIZE].Name = gsDerivedKeySizeProperty; + aSequence[PKG_MNFST_DERKEYSIZE].Value <<= nDerivedKeySize; + } else if ( bPgpEncryption ) { + if (aString == "PGP") { + aSequence[PKG_MNFST_KDF].Name = "KeyDerivationFunction"; + aSequence[PKG_MNFST_KDF].Value <<= xml::crypto::KDFID::PGP_RSA_OAEP_MGF1P; + } else { + bIgnoreEncryptData = true; + } + } else + bIgnoreEncryptData = true; +} + +void ManifestImport::doStartKeyAlg(StringHashMap &rConvertedAttribs) +{ + OUString aString = rConvertedAttribs[ATTRIBUTE_START_KEY_GENERATION_NAME]; + if (aString == SHA256_URL || aString == SHA256_URL_ODF12) { + aSequence[PKG_MNFST_STARTALG].Name = gsStartKeyAlgProperty; + aSequence[PKG_MNFST_STARTALG].Value <<= xml::crypto::DigestID::SHA256; + } else if ( aString == SHA1_NAME || aString == SHA1_URL ) { + aSequence[PKG_MNFST_STARTALG].Name = gsStartKeyAlgProperty; + aSequence[PKG_MNFST_STARTALG].Value <<= xml::crypto::DigestID::SHA1; + } else + bIgnoreEncryptData = true; +} + +void SAL_CALL ManifestImport::startElement( const OUString& aName, const uno::Reference< xml::sax::XAttributeList >& xAttribs ) +{ + StringHashMap aConvertedAttribs; + OUString aConvertedName = PushNameAndNamespaces( aName, xAttribs, aConvertedAttribs ); + + size_t nLevel = aStack.size(); + + assert(nLevel >= 1); + + switch (nLevel) { + case 1: { + if (aConvertedName != ELEMENT_MANIFEST) //manifest:manifest + aStack.back().m_bValid = false; + break; + } + case 2: { + if (aConvertedName == ELEMENT_FILE_ENTRY) //manifest:file-entry + doFileEntry(aConvertedAttribs); + else if (aConvertedName == ELEMENT_MANIFEST_KEYINFO) //loext:keyinfo + ; + else if (aConvertedName == ELEMENT_ENCRYPTEDKEY13) //manifest:encrypted-key + doEncryptedKey(aConvertedAttribs); + else + aStack.back().m_bValid = false; + break; + } + case 3: { + ManifestStack::reverse_iterator aIter = aStack.rbegin(); + ++aIter; + + if (!aIter->m_bValid) + aStack.back().m_bValid = false; + else if (aConvertedName == ELEMENT_ENCRYPTION_DATA) //manifest:encryption-data + doEncryptionData(aConvertedAttribs); + else if (aConvertedName == ELEMENT_ENCRYPTEDKEY) //loext:encrypted-key + doEncryptedKey(aConvertedAttribs); + else if (aConvertedName == ELEMENT_ENCRYPTIONMETHOD13) //manifest:encryption-method + doEncryptionMethod(aConvertedAttribs, ATTRIBUTE_ALGORITHM13); + else if (aConvertedName == ELEMENT_MANIFEST13_KEYINFO) //manifest:keyinfo + ; + else if (aConvertedName == ELEMENT_CIPHERDATA13) //manifest:CipherData + ; + else + aStack.back().m_bValid = false; + break; + } + case 4: { + ManifestStack::reverse_iterator aIter = aStack.rbegin(); + ++aIter; + + if (!aIter->m_bValid) + aStack.back().m_bValid = false; + else if (aConvertedName == ELEMENT_ALGORITHM) //manifest:algorithm, + doAlgorithm(aConvertedAttribs); + else if (aConvertedName == ELEMENT_KEY_DERIVATION) //manifest:key-derivation, + doKeyDerivation(aConvertedAttribs); + else if (aConvertedName == ELEMENT_START_KEY_GENERATION) //manifest:start-key-generation + doStartKeyAlg(aConvertedAttribs); + else if (aConvertedName == ELEMENT_ENCRYPTIONMETHOD) //loext:encryption-method + doEncryptionMethod(aConvertedAttribs, ATTRIBUTE_ALGORITHM); + else if (aConvertedName == ELEMENT_ENCRYPTED_KEYINFO) //loext:KeyInfo + ; + else if (aConvertedName == ELEMENT_CIPHERDATA) //loext:CipherData + ; + else if (aConvertedName == ELEMENT_PGPDATA13) //manifest:PGPData + ; + else if (aConvertedName == ELEMENT_CIPHERVALUE13) //manifest:CipherValue + // ciphervalue action happens on endElement + aCurrentCharacters = ""; + else + aStack.back().m_bValid = false; + break; + } + case 5: { + ManifestStack::reverse_iterator aIter = aStack.rbegin(); + ++aIter; + + if (!aIter->m_bValid) + aStack.back().m_bValid = false; + else if (aConvertedName == ELEMENT_PGPDATA) //loext:PGPData + ; + else if (aConvertedName == ELEMENT_CIPHERVALUE) //loext:CipherValue + // ciphervalue action happens on endElement + aCurrentCharacters = ""; + else if (aConvertedName == ELEMENT_PGPKEYID13) //manifest:PGPKeyID + // ciphervalue action happens on endElement + aCurrentCharacters = ""; + else if (aConvertedName == ELEMENT_PGPKEYPACKET13) //manifest:PGPKeyPacket + // ciphervalue action happens on endElement + aCurrentCharacters = ""; + else + aStack.back().m_bValid = false; + break; + } + case 6: { + ManifestStack::reverse_iterator aIter = aStack.rbegin(); + ++aIter; + + if (!aIter->m_bValid) + aStack.back().m_bValid = false; + else if (aConvertedName == ELEMENT_PGPKEYID) //loext:PGPKeyID + // ciphervalue action happens on endElement + aCurrentCharacters = ""; + else if (aConvertedName == ELEMENT_PGPKEYPACKET) //loext:PGPKeyPacket + // ciphervalue action happens on endElement + aCurrentCharacters = ""; + else + aStack.back().m_bValid = false; + break; + } + default: + aStack.back().m_bValid = false; + break; + } +} + +namespace +{ +bool isEmpty(const css::beans::PropertyValue &rProp) +{ + return rProp.Name.isEmpty(); +} +} + +void SAL_CALL ManifestImport::endElement( const OUString& aName ) +{ + size_t nLevel = aStack.size(); + + assert(nLevel >= 1); + + OUString aConvertedName = ConvertName( aName ); + if ( aStack.empty() || aStack.rbegin()->m_aConvertedName != aConvertedName ) + return; + + if ( aConvertedName == ELEMENT_FILE_ENTRY && aStack.back().m_bValid ) { + // the first entry gets KeyInfo element if any, for PGP encryption + if (!bIgnoreEncryptData && !aKeys.empty() && rManVector.empty()) + { + aSequence[PKG_MNFST_KEYINFO].Name = "KeyInfo"; + aSequence[PKG_MNFST_KEYINFO].Value <<= comphelper::containerToSequence(aKeys); + } + std::erase_if(aSequence, isEmpty); + + bIgnoreEncryptData = false; + rManVector.push_back ( comphelper::containerToSequence(aSequence) ); + + aSequence.clear(); + } + else if ( (aConvertedName == ELEMENT_ENCRYPTEDKEY + || aConvertedName == ELEMENT_ENCRYPTEDKEY13) + && aStack.back().m_bValid ) { + if ( !bIgnoreEncryptData ) + { + aKeys.push_back( comphelper::containerToSequence(aKeyInfoSequence) ); + bPgpEncryption = true; + } + aKeyInfoSequence.clear(); + } + + // end element handling for elements with cdata + switch (nLevel) { + case 4: { + if (aConvertedName == ELEMENT_CIPHERVALUE13) //manifest:CipherValue + doEncryptedCipherValue(); + else + aStack.back().m_bValid = false; + break; + } + case 5: { + if (aConvertedName == ELEMENT_CIPHERVALUE) //loext:CipherValue + doEncryptedCipherValue(); + else if (aConvertedName == ELEMENT_PGPKEYID13) //manifest:PGPKeyID + doEncryptedKeyId(); + else if (aConvertedName == ELEMENT_PGPKEYPACKET13) //manifest:PGPKeyPacket + doEncryptedKeyPacket(); + else + aStack.back().m_bValid = false; + break; + } + case 6: { + if (aConvertedName == ELEMENT_PGPKEYID) //loext:PGPKeyID + doEncryptedKeyId(); + else if (aConvertedName == ELEMENT_PGPKEYPACKET) //loext:PGPKeyPacket + doEncryptedKeyPacket(); + else + aStack.back().m_bValid = false; + break; + } + } + + aStack.pop_back(); +} + +void SAL_CALL ManifestImport::characters( const OUString& aChars ) +{ + aCurrentCharacters.append(aChars); +} + +void SAL_CALL ManifestImport::ignorableWhitespace( const OUString& /*aWhitespaces*/ ) +{ +} + +void SAL_CALL ManifestImport::processingInstruction( const OUString& /*aTarget*/, const OUString& /*aData*/ ) +{ +} + +void SAL_CALL ManifestImport::setDocumentLocator( const uno::Reference< xml::sax::XLocator >& /*xLocator*/ ) +{ +} + +OUString ManifestImport::PushNameAndNamespaces( const OUString& aName, const uno::Reference< xml::sax::XAttributeList >& xAttribs, StringHashMap& o_aConvertedAttribs ) +{ + StringHashMap aNamespaces; + ::std::vector< ::std::pair< OUString, OUString > > aAttribsStrs; + + if ( xAttribs.is() ) { + sal_Int16 nAttrCount = xAttribs.is() ? xAttribs->getLength() : 0; + aAttribsStrs.reserve( nAttrCount ); + + for( sal_Int16 nInd = 0; nInd < nAttrCount; nInd++ ) { + OUString aAttrName = xAttribs->getNameByIndex( nInd ); + OUString aAttrValue = xAttribs->getValueByIndex( nInd ); + if ( aAttrName.getLength() >= 5 + && aAttrName.startsWith("xmlns") + && ( aAttrName.getLength() == 5 || aAttrName[5] == ':' ) ) { + // this is a namespace declaration + OUString aNsName( ( aAttrName.getLength() == 5 ) ? OUString() : aAttrName.copy( 6 ) ); + aNamespaces[aNsName] = aAttrValue; + } else { + // this is no namespace declaration + aAttribsStrs.emplace_back( aAttrName, aAttrValue ); + } + } + } + + OUString aConvertedName = ConvertNameWithNamespace( aName, aNamespaces ); + if ( !aConvertedName.getLength() ) + aConvertedName = ConvertName( aName ); + + aStack.emplace_back( aConvertedName, std::move(aNamespaces) ); + + for (const std::pair<OUString,OUString> & rAttribsStr : aAttribsStrs) { + // convert the attribute names on filling + o_aConvertedAttribs[ConvertName( rAttribsStr.first )] = rAttribsStr.second; + } + + return aConvertedName; +} + +OUString ManifestImport::ConvertNameWithNamespace( const OUString& aName, const StringHashMap& aNamespaces ) +{ + OUString aNsAlias; + OUString aPureName = aName; + + sal_Int32 nInd = aName.indexOf( ':' ); + if ( nInd != -1 && nInd < aName.getLength() ) { + aNsAlias = aName.copy( 0, nInd ); + aPureName = aName.copy( nInd + 1 ); + } + + OUString aResult; + + StringHashMap::const_iterator aIter = aNamespaces.find( aNsAlias ); + if ( aIter != aNamespaces.end() + && ( aIter->second == MANIFEST_NAMESPACE || aIter->second == MANIFEST_OASIS_NAMESPACE ) ) { + // no check for manifest.xml consistency currently since the old versions have supported inconsistent documents as well + aResult = MANIFEST_NSPREFIX + aPureName; + } + + return aResult; +} + +OUString ManifestImport::ConvertName( const OUString& aName ) +{ + OUString aConvertedName; + for ( ManifestStack::reverse_iterator aIter = aStack.rbegin(); !aConvertedName.getLength() && aIter != aStack.rend(); ++aIter ) { + if ( !aIter->m_aNamespaces.empty() ) + aConvertedName = ConvertNameWithNamespace( aName, aIter->m_aNamespaces ); + } + + if ( !aConvertedName.getLength() ) + aConvertedName = aName; + + return aConvertedName; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/manifest/ManifestImport.hxx b/package/source/manifest/ManifestImport.hxx new file mode 100644 index 0000000000..fd86e02e4f --- /dev/null +++ b/package/source/manifest/ManifestImport.hxx @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_PACKAGE_SOURCE_MANIFEST_MANIFESTIMPORT_HXX +#define INCLUDED_PACKAGE_SOURCE_MANIFEST_MANIFESTIMPORT_HXX + +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/xml/sax/XDocumentHandler.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <unordered_map> +#include <utility> +#include <vector> +#include <rtl/ustrbuf.hxx> + +namespace com::sun::star { + namespace xml::sax { class XAttributeList; } + namespace beans { struct PropertyValue; } +} + +typedef std::unordered_map< OUString, OUString > StringHashMap; + +struct ManifestScopeEntry +{ + OUString m_aConvertedName; + StringHashMap m_aNamespaces; + bool m_bValid; + + ManifestScopeEntry( OUString aConvertedName, StringHashMap&& aNamespaces ) + : m_aConvertedName(std::move( aConvertedName )) + , m_aNamespaces( std::move(aNamespaces) ) + , m_bValid( true ) + {} +}; + +typedef ::std::vector< ManifestScopeEntry > ManifestStack; + +class ManifestImport final : public cppu::WeakImplHelper < css::xml::sax::XDocumentHandler > +{ + std::vector< css::beans::NamedValue > aKeyInfoSequence; + std::vector< css::uno::Sequence< css::beans::NamedValue > > aKeys; + std::vector< css::beans::PropertyValue > aSequence; + OUStringBuffer aCurrentCharacters{64}; + ManifestStack aStack; + bool bIgnoreEncryptData; + bool bPgpEncryption; + sal_Int32 nDerivedKeySize; + ::std::vector < css::uno::Sequence < css::beans::PropertyValue > > & rManVector; + + + OUString PushNameAndNamespaces( const OUString& aName, + const css::uno::Reference< css::xml::sax::XAttributeList >& xAttribs, + StringHashMap& o_aConvertedAttribs ); + static OUString ConvertNameWithNamespace( const OUString& aName, const StringHashMap& aNamespaces ); + OUString ConvertName( const OUString& aName ); + +public: + ManifestImport( std::vector < css::uno::Sequence < css::beans::PropertyValue > > & rNewVector ); + virtual ~ManifestImport() override; + virtual void SAL_CALL startDocument( ) override; + virtual void SAL_CALL endDocument( ) override; + virtual void SAL_CALL startElement( const OUString& aName, const css::uno::Reference< css::xml::sax::XAttributeList >& xAttribs ) override; + virtual void SAL_CALL endElement( const OUString& aName ) override; + virtual void SAL_CALL characters( const OUString& aChars ) override; + virtual void SAL_CALL ignorableWhitespace( const OUString& aWhitespaces ) override; + virtual void SAL_CALL processingInstruction( const OUString& aTarget, const OUString& aData ) override; + virtual void SAL_CALL setDocumentLocator( const css::uno::Reference< css::xml::sax::XLocator >& xLocator ) override; + +private: + /// @throws css::uno::RuntimeException + void doFileEntry(StringHashMap &rConvertedAttribs); + /// @throws css::uno::RuntimeException + void doEncryptionData(StringHashMap &rConvertedAttribs); + /// @throws css::uno::RuntimeException + void doAlgorithm(StringHashMap &rConvertedAttribs); + /// @throws css::uno::RuntimeException + void doKeyDerivation(StringHashMap &rConvertedAttribs); + /// @throws css::uno::RuntimeException + void doStartKeyAlg(StringHashMap &rConvertedAttribs); + void doEncryptedKey(StringHashMap &); + void doEncryptionMethod(StringHashMap &, const OUString &); + void doEncryptedCipherValue(); + void doEncryptedKeyId(); + void doEncryptedKeyPacket(); +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/manifest/ManifestReader.cxx b/package/source/manifest/ManifestReader.cxx new file mode 100644 index 0000000000..2a60feff02 --- /dev/null +++ b/package/source/manifest/ManifestReader.cxx @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "ManifestReader.hxx" +#include "ManifestImport.hxx" +#include <comphelper/diagnose_ex.hxx> +#include <comphelper/sequence.hxx> +#include <cppuhelper/factory.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/xml/sax/XDocumentHandler.hpp> +#include <com/sun/star/xml/sax/SAXParseException.hpp> +#include <com/sun/star/xml/sax/Parser.hpp> +#include <vector> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::registry; +using namespace ::com::sun::star::packages; +using namespace ::com::sun::star::xml::sax; + +ManifestReader::ManifestReader( const Reference < XComponentContext > & xContext ) +: m_xContext ( xContext ) +{ +} +ManifestReader::~ManifestReader() +{ +} +Sequence< Sequence< PropertyValue > > SAL_CALL ManifestReader::readManifestSequence( const Reference< XInputStream >& rStream ) +{ + Sequence < Sequence < PropertyValue > > aManifestSequence; + Reference < XParser > xParser = Parser::create(m_xContext); + try + { + std::vector < Sequence < PropertyValue > > aManVector; + Reference < XDocumentHandler > xFilter = new ManifestImport( aManVector ); + InputSource aParserInput; + aParserInput.aInputStream = rStream; + aParserInput.sSystemId = "META-INF/manifest.xml"; + xParser->setDocumentHandler ( xFilter ); + xParser->parseStream( aParserInput ); + aManifestSequence = comphelper::containerToSequence(aManVector); + } + catch (const SAXParseException&) + { + TOOLS_WARN_EXCEPTION("package", "ignoring"); + } + catch (const SAXException&) + { + TOOLS_WARN_EXCEPTION("package", "ignoring"); + } + catch (const IOException&) + { + TOOLS_WARN_EXCEPTION("package", "ignoring"); + } + xParser->setDocumentHandler ( Reference < XDocumentHandler > () ); + return aManifestSequence; +} +// Component functions + + +OUString ManifestReader::getImplementationName() +{ + return "com.sun.star.packages.manifest.comp.ManifestReader"; +} + +sal_Bool SAL_CALL ManifestReader::supportsService(OUString const & rServiceName) +{ + return cppu::supportsService(this, rServiceName ); +} + +Sequence < OUString > ManifestReader::getSupportedServiceNames() +{ + return { "com.sun.star.packages.manifest.ManifestReader" }; +} + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +package_ManifestReader_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new ManifestReader(context)); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/manifest/ManifestReader.hxx b/package/source/manifest/ManifestReader.hxx new file mode 100644 index 0000000000..f85644162d --- /dev/null +++ b/package/source/manifest/ManifestReader.hxx @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_PACKAGE_SOURCE_MANIFEST_MANIFESTREADER_HXX +#define INCLUDED_PACKAGE_SOURCE_MANIFEST_MANIFESTREADER_HXX + +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/packages/manifest/XManifestReader.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> + +namespace com::sun::star { + namespace lang { class XSingleServiceFactory; } + namespace uno { class XComponentContext; } +} + +class ManifestReader: public ::cppu::WeakImplHelper +< + css::packages::manifest::XManifestReader, + css::lang::XServiceInfo +> +{ +private: + css::uno::Reference< css::uno::XComponentContext > m_xContext; +public: + ManifestReader( const css::uno::Reference< css::uno::XComponentContext > & xContext ); + virtual ~ManifestReader() override; + + // XManifestReader + virtual css::uno::Sequence< css::uno::Sequence< css::beans::PropertyValue > > SAL_CALL readManifestSequence( const css::uno::Reference< css::io::XInputStream >& rStream ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/manifest/ManifestWriter.cxx b/package/source/manifest/ManifestWriter.cxx new file mode 100644 index 0000000000..5515cc26df --- /dev/null +++ b/package/source/manifest/ManifestWriter.cxx @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "ManifestWriter.hxx" +#include "ManifestExport.hxx" +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/factory.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <com/sun/star/xml/sax/Writer.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/xml/sax/SAXException.hpp> + +#include <sal/log.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::registry; +using namespace ::com::sun::star::packages; +using namespace ::com::sun::star::xml::sax; + +#if OSL_DEBUG_LEVEL > 0 +#define THROW_WHERE SAL_WHERE +#else +#define THROW_WHERE "" +#endif + +ManifestWriter::ManifestWriter( const Reference < XComponentContext > & xContext ) +: m_xContext ( xContext ) +{ +} +ManifestWriter::~ManifestWriter() +{ +} + +// XManifestWriter methods +void SAL_CALL ManifestWriter::writeManifestSequence( const Reference< XOutputStream >& rStream, const Sequence< Sequence< PropertyValue > >& rSequence ) +{ + Reference < XWriter > xSource = Writer::create( m_xContext ); + xSource->setOutputStream ( rStream ); + try { + ManifestExport( xSource, rSequence); + } + catch( SAXException& ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException( THROW_WHERE, + nullptr, anyEx ); + } +} + +OUString ManifestWriter::getImplementationName() +{ + return "com.sun.star.packages.manifest.comp.ManifestWriter"; +} + +sal_Bool SAL_CALL ManifestWriter::supportsService(OUString const & rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} +Sequence < OUString > ManifestWriter::getSupportedServiceNames() +{ + return { "com.sun.star.packages.manifest.ManifestWriter" }; +} + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +package_ManifestWriter_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new ManifestWriter(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/manifest/ManifestWriter.hxx b/package/source/manifest/ManifestWriter.hxx new file mode 100644 index 0000000000..ca08bb0e40 --- /dev/null +++ b/package/source/manifest/ManifestWriter.hxx @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_PACKAGE_SOURCE_MANIFEST_MANIFESTWRITER_HXX +#define INCLUDED_PACKAGE_SOURCE_MANIFEST_MANIFESTWRITER_HXX + +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/packages/manifest/XManifestWriter.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> + +namespace com::sun::star { + namespace lang { class XSingleServiceFactory; } + namespace uno { class XComponentContext; } +} + +class ManifestWriter: public ::cppu::WeakImplHelper +< + css::packages::manifest::XManifestWriter, + css::lang::XServiceInfo +> +{ +private: + css::uno::Reference< css::uno::XComponentContext > m_xContext; +public: + ManifestWriter( const css::uno::Reference< css::uno::XComponentContext > & xContext ); + virtual ~ManifestWriter() override; + + // XManifestWriter + virtual void SAL_CALL writeManifestSequence( const css::uno::Reference< css::io::XOutputStream >& rStream, const css::uno::Sequence< css::uno::Sequence< css::beans::PropertyValue > >& rSequence ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/xstor/disposelistener.cxx b/package/source/xstor/disposelistener.cxx new file mode 100644 index 0000000000..b7ee409574 --- /dev/null +++ b/package/source/xstor/disposelistener.cxx @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "disposelistener.hxx" +#include "xstorage.hxx" + +using namespace ::com::sun::star; + +OChildDispListener_Impl::OChildDispListener_Impl( OStorage& aStorage ) +: m_pStorage( &aStorage ) +{} + +OChildDispListener_Impl::~OChildDispListener_Impl() +{} + +void OChildDispListener_Impl::OwnerIsDisposed() +{ + std::scoped_lock aGuard( m_aMutex ); + m_pStorage = nullptr; +} + +void SAL_CALL OChildDispListener_Impl::disposing( const lang::EventObject& Source ) +{ + std::scoped_lock aGuard( m_aMutex ); + // ObjectIsDisposed must not contain any locking! + if ( m_pStorage && Source.Source.is() ) + m_pStorage->ChildIsDisposed( Source.Source ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/xstor/disposelistener.hxx b/package/source/xstor/disposelistener.hxx new file mode 100644 index 0000000000..b635b58ce3 --- /dev/null +++ b/package/source/xstor/disposelistener.hxx @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_PACKAGE_SOURCE_XSTOR_DISPOSELISTENER_HXX +#define INCLUDED_PACKAGE_SOURCE_XSTOR_DISPOSELISTENER_HXX + +#include <com/sun/star/lang/XEventListener.hpp> +#include <cppuhelper/implbase.hxx> +#include <mutex> + +class OStorage; +class OChildDispListener_Impl : public ::cppu::WeakImplHelper<css::lang::XEventListener> +{ + std::mutex m_aMutex; + OStorage* m_pStorage; + +public: + explicit OChildDispListener_Impl(OStorage& aStorage); + virtual ~OChildDispListener_Impl() override; + + void OwnerIsDisposed(); + + virtual void SAL_CALL disposing(const css::lang::EventObject& Source) override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/xstor/ocompinstream.cxx b/package/source/xstor/ocompinstream.cxx new file mode 100644 index 0000000000..a7dd8ab219 --- /dev/null +++ b/package/source/xstor/ocompinstream.cxx @@ -0,0 +1,597 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "ocompinstream.hxx" +#include <com/sun/star/embed/StorageFormats.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <comphelper/sequence.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> +#include <utility> + +#include "owriteablestream.hxx" + +using namespace ::com::sun::star; + +OInputCompStream::OInputCompStream( OWriteStream_Impl& aImpl, + uno::Reference < io::XInputStream > xStream, + const uno::Sequence< beans::PropertyValue >& aProps, + sal_Int32 nStorageType ) +: m_pImpl( &aImpl ) +, m_xMutex( m_pImpl->m_xMutex ) +, m_xStream(std::move( xStream )) +, m_aProperties( aProps ) +, m_bDisposed( false ) +, m_nStorageType( nStorageType ) +{ + OSL_ENSURE( m_pImpl->m_xMutex.is(), "No mutex is provided!" ); + if ( !m_pImpl->m_xMutex.is() ) + throw uno::RuntimeException(); // just a disaster + + assert(m_xStream.is()); +} + +OInputCompStream::OInputCompStream( uno::Reference < io::XInputStream > xStream, + const uno::Sequence< beans::PropertyValue >& aProps, + sal_Int32 nStorageType ) +: m_pImpl( nullptr ) +, m_xMutex( new comphelper::RefCountedMutex ) +, m_xStream(std::move( xStream )) +, m_aProperties( aProps ) +, m_bDisposed( false ) +, m_nStorageType( nStorageType ) +{ + assert(m_xStream.is()); +} + +OInputCompStream::~OInputCompStream() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( !m_bDisposed ) + { + osl_atomic_increment(&m_refCount); + dispose(); + } +} + +uno::Any SAL_CALL OInputCompStream::queryInterface( const uno::Type& rType ) +{ + // common interfaces + uno::Any aReturn = ::cppu::queryInterface + ( rType + , static_cast<io::XInputStream*> ( this ) + , static_cast<io::XStream*> ( this ) + , static_cast<lang::XComponent*> ( this ) + , static_cast<beans::XPropertySet*> ( this ) + , static_cast<embed::XExtendedStorageStream*> ( this ) ); + + if ( aReturn.hasValue() ) + return aReturn ; + + if ( m_nStorageType == embed::StorageFormats::OFOPXML ) + { + aReturn = ::cppu::queryInterface + ( rType + , static_cast<embed::XRelationshipAccess*> ( this ) ); + + if ( aReturn.hasValue() ) + return aReturn ; + } + + return OWeakObject::queryInterface( rType ); +} + +sal_Int32 SAL_CALL OInputCompStream::readBytes( uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + return m_xStream->readBytes( aData, nBytesToRead ); +} + +sal_Int32 SAL_CALL OInputCompStream::readSomeBytes( uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + return m_xStream->readSomeBytes( aData, nMaxBytesToRead ); + +} + +void SAL_CALL OInputCompStream::skipBytes( sal_Int32 nBytesToSkip ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + m_xStream->skipBytes( nBytesToSkip ); + +} + +sal_Int32 SAL_CALL OInputCompStream::available( ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + return m_xStream->available(); + +} + +void SAL_CALL OInputCompStream::closeInput( ) +{ + dispose(); +} + +uno::Reference< io::XInputStream > SAL_CALL OInputCompStream::getInputStream() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + return this; +} + +uno::Reference< io::XOutputStream > SAL_CALL OInputCompStream::getOutputStream() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + return uno::Reference< io::XOutputStream >(); +} + +void OInputCompStream::InternalDispose() +{ + // can be called only by OWriteStream_Impl + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + if ( m_bDisposed ) + return; + + // the source object is also a kind of locker for the current object + // since the listeners could dispose the object while being notified + lang::EventObject aSource( getXWeak() ); + + if ( m_pInterfaceContainer ) + m_pInterfaceContainer->disposeAndClear( aSource ); + + try + { + m_xStream->closeInput(); + } + catch( uno::Exception& ) + {} + + m_pImpl = nullptr; + m_bDisposed = true; +} + +void SAL_CALL OInputCompStream::dispose( ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + if ( m_bDisposed ) + return; + + if ( m_pInterfaceContainer ) + { + lang::EventObject aSource( getXWeak() ); + m_pInterfaceContainer->disposeAndClear( aSource ); + } + + m_xStream->closeInput(); + + if ( m_pImpl ) + { + m_pImpl->InputStreamDisposed( this ); + m_pImpl = nullptr; + } + + m_bDisposed = true; +} + +void SAL_CALL OInputCompStream::addEventListener( const uno::Reference< lang::XEventListener >& xListener ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( !m_pInterfaceContainer ) + m_pInterfaceContainer.reset( new ::comphelper::OInterfaceContainerHelper3<css::lang::XEventListener>( m_xMutex->GetMutex() ) ); + + m_pInterfaceContainer->addInterface( xListener ); +} + +void SAL_CALL OInputCompStream::removeEventListener( const uno::Reference< lang::XEventListener >& xListener ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( m_pInterfaceContainer ) + m_pInterfaceContainer->removeInterface( xListener ); +} + +sal_Bool SAL_CALL OInputCompStream::hasByID( const OUString& sID ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException(); + + try + { + getRelationshipByID( sID ); + return true; + } + catch( container::NoSuchElementException& ) + {} + + return false; +} + +namespace +{ + +const beans::StringPair* lcl_findPairByName(const uno::Sequence<beans::StringPair>& rSeq, const OUString& rName) +{ + return std::find_if(rSeq.begin(), rSeq.end(), + [&rName](const beans::StringPair& rPair) { return rPair.First == rName; }); +} + +} + +OUString SAL_CALL OInputCompStream::getTargetByID( const OUString& sID ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException(); + + const uno::Sequence< beans::StringPair > aSeq = getRelationshipByID( sID ); + auto pRel = lcl_findPairByName(aSeq, "Target"); + if (pRel != aSeq.end()) + return pRel->Second; + + return OUString(); +} + +OUString SAL_CALL OInputCompStream::getTypeByID( const OUString& sID ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException(); + + const uno::Sequence< beans::StringPair > aSeq = getRelationshipByID( sID ); + auto pRel = lcl_findPairByName(aSeq, "Type"); + if (pRel != aSeq.end()) + return pRel->Second; + + return OUString(); +} + +uno::Sequence< beans::StringPair > SAL_CALL OInputCompStream::getRelationshipByID( const OUString& sID ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException(); + + // TODO/LATER: in future the unification of the ID could be checked + const uno::Sequence< uno::Sequence< beans::StringPair > > aSeq = getAllRelationships(); + const beans::StringPair aIDRel("Id", sID); + auto pRel = std::find_if(aSeq.begin(), aSeq.end(), + [&aIDRel](const uno::Sequence<beans::StringPair>& rRel){ + return std::find(rRel.begin(), rRel.end(), aIDRel) != rRel.end(); }); + if (pRel != aSeq.end()) + return *pRel; + + throw container::NoSuchElementException(); +} + +uno::Sequence< uno::Sequence< beans::StringPair > > SAL_CALL OInputCompStream::getRelationshipsByType( const OUString& sType ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException(); + + // TODO/LATER: in future the unification of the ID could be checked + const uno::Sequence< uno::Sequence< beans::StringPair > > aSeq = getAllRelationships(); + const beans::StringPair aTypeRel("Type", sType); + std::vector< uno::Sequence<beans::StringPair> > aResult; + aResult.reserve(aSeq.getLength()); + + std::copy_if(aSeq.begin(), aSeq.end(), std::back_inserter(aResult), + [&aTypeRel](const uno::Sequence<beans::StringPair>& rRel) { + return std::find(rRel.begin(), rRel.end(), aTypeRel) != rRel.end(); }); + + return comphelper::containerToSequence(aResult); +} + +uno::Sequence< uno::Sequence< beans::StringPair > > SAL_CALL OInputCompStream::getAllRelationships() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException(); + + // TODO/LATER: in future the information could be taken directly from m_pImpl when possible + auto pProp = std::find_if(std::cbegin(m_aProperties), std::cend(m_aProperties), + [](const beans::PropertyValue& rProp) { return rProp.Name == "RelationsInfo"; }); + if (pProp != std::cend(m_aProperties)) + { + uno::Sequence< uno::Sequence< beans::StringPair > > aResult; + if (pProp->Value >>= aResult) + return aResult; + } + + throw io::IOException("relations info could not be read"); // the relations info could not be read +} + +void SAL_CALL OInputCompStream::insertRelationshipByID( const OUString& /*sID*/, const uno::Sequence< beans::StringPair >& /*aEntry*/, sal_Bool /*bReplace*/ ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException(); + + throw io::IOException(); // TODO: Access denied +} + +void SAL_CALL OInputCompStream::removeRelationshipByID( const OUString& /*sID*/ ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException(); + + throw io::IOException(); // TODO: Access denied +} + +void SAL_CALL OInputCompStream::insertRelationships( const uno::Sequence< uno::Sequence< beans::StringPair > >& /*aEntries*/, sal_Bool /*bReplace*/ ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException(); + + throw io::IOException(); // TODO: Access denied +} + +void SAL_CALL OInputCompStream::clearRelationships() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException(); + + throw io::IOException(); // TODO: Access denied +} + +uno::Reference< beans::XPropertySetInfo > SAL_CALL OInputCompStream::getPropertySetInfo() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + //TODO: + return uno::Reference< beans::XPropertySetInfo >(); +} + +void SAL_CALL OInputCompStream::setPropertyValue( const OUString& aPropertyName, const uno::Any& /*aValue*/ ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + // all the provided properties are accessible + if (std::any_of(std::cbegin(m_aProperties), std::cend(m_aProperties), + [&aPropertyName](const beans::PropertyValue& rProp) { return rProp.Name == aPropertyName; })) + throw beans::PropertyVetoException(); // TODO + + throw beans::UnknownPropertyException(aPropertyName); // TODO +} + +uno::Any SAL_CALL OInputCompStream::getPropertyValue( const OUString& aProp ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + OUString aPropertyName; + if ( aProp == "IsEncrypted" ) + aPropertyName = "Encrypted"; + else + aPropertyName = aProp; + + if ( aPropertyName == "RelationsInfo" ) + throw beans::UnknownPropertyException(aPropertyName); // TODO + + // all the provided properties are accessible + auto pProp = std::find_if(std::cbegin(m_aProperties), std::cend(m_aProperties), + [&aPropertyName](const beans::PropertyValue& rProp) { return rProp.Name == aPropertyName; }); + if (pProp != std::cend(m_aProperties)) + return pProp->Value; + + throw beans::UnknownPropertyException(aPropertyName); // TODO +} + +void SAL_CALL OInputCompStream::addPropertyChangeListener( + const OUString& /*aPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/ ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + //TODO: +} + +void SAL_CALL OInputCompStream::removePropertyChangeListener( + const OUString& /*aPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*aListener*/ ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + //TODO: +} + +void SAL_CALL OInputCompStream::addVetoableChangeListener( + const OUString& /*PropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*aListener*/ ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + //TODO: +} + +void SAL_CALL OInputCompStream::removeVetoableChangeListener( + const OUString& /*PropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*aListener*/ ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + //TODO: +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/xstor/ocompinstream.hxx b/package/source/xstor/ocompinstream.hxx new file mode 100644 index 0000000000..effbc4be2f --- /dev/null +++ b/package/source/xstor/ocompinstream.hxx @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_PACKAGE_SOURCE_XSTOR_OCOMPINSTREAM_HXX +#define INCLUDED_PACKAGE_SOURCE_XSTOR_OCOMPINSTREAM_HXX + +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/embed/XExtendedStorageStream.hpp> +#include <com/sun/star/embed/XRelationshipAccess.hpp> +#include <cppuhelper/implbase.hxx> +#include <comphelper/interfacecontainer3.hxx> +#include <comphelper/refcountedmutex.hxx> +#include <rtl/ref.hxx> +#include <memory> + + +struct OWriteStream_Impl; + +class OInputCompStream : public cppu::WeakImplHelper < css::io::XInputStream + ,css::embed::XExtendedStorageStream + ,css::embed::XRelationshipAccess + ,css::beans::XPropertySet > +{ +protected: + OWriteStream_Impl* m_pImpl; + rtl::Reference<comphelper::RefCountedMutex> m_xMutex; + css::uno::Reference < css::io::XInputStream > m_xStream; + std::unique_ptr<::comphelper::OInterfaceContainerHelper3<css::lang::XEventListener>> m_pInterfaceContainer; + css::uno::Sequence < css::beans::PropertyValue > m_aProperties; + bool m_bDisposed; + sal_Int32 m_nStorageType; + +public: + OInputCompStream( OWriteStream_Impl& pImpl, + css::uno::Reference< css::io::XInputStream > xStream, + const css::uno::Sequence< css::beans::PropertyValue >& aProps, + sal_Int32 nStorageType ); + + OInputCompStream( css::uno::Reference< css::io::XInputStream > xStream, + const css::uno::Sequence< css::beans::PropertyValue >& aProps, + sal_Int32 nStorageType ); + + virtual ~OInputCompStream() override; + + void InternalDispose(); + + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type& rType ) override; + + // XInputStream + virtual sal_Int32 SAL_CALL readBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) override; + virtual sal_Int32 SAL_CALL readSomeBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) override; + virtual void SAL_CALL skipBytes( sal_Int32 nBytesToSkip ) override; + virtual sal_Int32 SAL_CALL available( ) override; + virtual void SAL_CALL closeInput( ) override; + + //XStream + virtual css::uno::Reference< css::io::XInputStream > SAL_CALL getInputStream( ) override; + virtual css::uno::Reference< css::io::XOutputStream > SAL_CALL getOutputStream( ) override; + + //XComponent + virtual void SAL_CALL dispose( ) override; + virtual void SAL_CALL addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) override; + virtual void SAL_CALL removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) override; + + //XRelationshipAccess + virtual sal_Bool SAL_CALL hasByID( const OUString& sID ) override; + virtual OUString SAL_CALL getTargetByID( const OUString& sID ) override; + virtual OUString SAL_CALL getTypeByID( const OUString& sID ) override; + virtual css::uno::Sequence< css::beans::StringPair > SAL_CALL getRelationshipByID( const OUString& sID ) override; + virtual css::uno::Sequence< css::uno::Sequence< css::beans::StringPair > > SAL_CALL getRelationshipsByType( const OUString& sType ) override; + virtual css::uno::Sequence< css::uno::Sequence< css::beans::StringPair > > SAL_CALL getAllRelationships( ) override; + virtual void SAL_CALL insertRelationshipByID( const OUString& sID, const css::uno::Sequence< css::beans::StringPair >& aEntry, sal_Bool bReplace ) override; + virtual void SAL_CALL removeRelationshipByID( const OUString& sID ) override; + virtual void SAL_CALL insertRelationships( const css::uno::Sequence< css::uno::Sequence< css::beans::StringPair > >& aEntries, sal_Bool bReplace ) override; + virtual void SAL_CALL clearRelationships( ) override; + + //XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo() override; + virtual void SAL_CALL setPropertyValue( const OUString& aPropertyName, const css::uno::Any& aValue ) override; + virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& PropertyName ) override; + virtual void SAL_CALL addPropertyChangeListener( const OUString& aPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener ) override; + virtual void SAL_CALL removePropertyChangeListener( const OUString& aPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener >& aListener ) override; + virtual void SAL_CALL addVetoableChangeListener( const OUString& PropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override; + virtual void SAL_CALL removeVetoableChangeListener( const OUString& PropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override; + +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/xstor/ohierarchyholder.cxx b/package/source/xstor/ohierarchyholder.cxx new file mode 100644 index 0000000000..d9b5b13743 --- /dev/null +++ b/package/source/xstor/ohierarchyholder.cxx @@ -0,0 +1,326 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/embed/XHierarchicalStorageAccess2.hpp> +#include <com/sun/star/embed/XTransactedObject.hpp> +#include <com/sun/star/embed/XTransactionBroadcaster.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <cppuhelper/exc_hlp.hxx> +#include <o3tl/string_view.hxx> + +#include "ohierarchyholder.hxx" + +using namespace ::com::sun::star; + +// OHierarchyHolder_Impl + +uno::Reference< embed::XExtendedStorageStream > OHierarchyHolder_Impl::GetStreamHierarchically( sal_Int32 nStorageMode, std::vector<OUString>& aListPath, sal_Int32 nStreamMode, const ::comphelper::SequenceAsHashMap& aEncryptionData ) +{ + uno::Reference< embed::XStorage > xOwnStor( m_xWeakOwnStorage.get(), uno::UNO_QUERY_THROW ); + + if ( !( nStorageMode & embed::ElementModes::WRITE ) && ( nStreamMode & embed::ElementModes::WRITE ) ) + throw io::IOException("invalid storage/stream mode combo"); + + uno::Reference< embed::XExtendedStorageStream > xResult = + m_xChild->GetStreamHierarchically( nStorageMode, aListPath, nStreamMode, aEncryptionData ); + if ( !xResult.is() ) + throw uno::RuntimeException(); + + return xResult; +} + +void OHierarchyHolder_Impl::RemoveStreamHierarchically( std::vector<OUString>& aListPath ) +{ + uno::Reference< embed::XStorage > xOwnStor( m_xWeakOwnStorage.get(), uno::UNO_QUERY_THROW ); + + m_xChild->RemoveStreamHierarchically( aListPath ); +} + +// static +std::vector<OUString> OHierarchyHolder_Impl::GetListPathFromString( std::u16string_view aPath ) +{ + std::vector<OUString> aResult; + sal_Int32 nIndex = 0; + do + { + OUString aName( o3tl::getToken(aPath, 0, '/', nIndex ) ); + if ( aName.isEmpty() ) + throw lang::IllegalArgumentException(); + + aResult.push_back( aName ); + } + while ( nIndex >= 0 ); + + return aResult; +} + +// OHierarchyElement_Impl + +uno::Reference< embed::XExtendedStorageStream > OHierarchyElement_Impl::GetStreamHierarchically( sal_Int32 nStorageMode, std::vector<OUString>& aListPath, sal_Int32 nStreamMode, const ::comphelper::SequenceAsHashMap& aEncryptionData ) +{ + std::unique_lock aGuard( m_aMutex ); + + if ( !( nStorageMode & embed::ElementModes::WRITE ) && ( nStreamMode & embed::ElementModes::WRITE ) ) + throw io::IOException("invalid storage/stream mode combo"); + + if ( aListPath.empty() ) + throw uno::RuntimeException(); + + OUString aNextName = *(aListPath.begin()); + aListPath.erase( aListPath.begin() ); + + uno::Reference< embed::XExtendedStorageStream > xResult; + + uno::Reference< embed::XStorage > xOwnStor = m_xOwnStorage.is() ? m_xOwnStorage + : uno::Reference< embed::XStorage >( m_xWeakOwnStorage.get(), uno::UNO_QUERY_THROW ); + + if ( aListPath.empty() ) + { + if ( aEncryptionData.empty() ) + { + uno::Reference< embed::XHierarchicalStorageAccess > xHStorage( xOwnStor, uno::UNO_QUERY_THROW ); + xResult = xHStorage->openStreamElementByHierarchicalName( aNextName, nStreamMode ); + } + else + { + uno::Reference< embed::XHierarchicalStorageAccess2 > xHStorage( xOwnStor, uno::UNO_QUERY_THROW ); + xResult = xHStorage->openEncryptedStreamByHierarchicalName( aNextName, nStreamMode, aEncryptionData.getAsConstNamedValueList() ); + } + + uno::Reference< embed::XTransactedObject > xTransact( xResult, uno::UNO_QUERY ); + if ( xTransact.is() ) + { + // the existence of the transacted object means that the stream is opened for writing also + // so the whole chain must be committed + uno::Reference< embed::XTransactionBroadcaster > xTrBroadcast( xTransact, uno::UNO_QUERY_THROW ); + xTrBroadcast->addTransactionListener( static_cast< embed::XTransactionListener* >( this ) ); + } + else + { + uno::Reference< lang::XComponent > xStreamComp( xResult, uno::UNO_QUERY_THROW ); + xStreamComp->addEventListener( static_cast< lang::XEventListener* >( this ) ); + } + + m_aOpenStreams.emplace_back( xResult ); + } + else + { + bool bNewElement = false; + ::rtl::Reference< OHierarchyElement_Impl > aElement; + OHierarchyElementList_Impl::iterator aIter = m_aChildren.find( aNextName ); + if ( aIter != m_aChildren.end() ) + aElement = aIter->second; + + if ( !aElement.is() ) + { + bNewElement = true; + uno::Reference< embed::XStorage > xChildStorage = xOwnStor->openStorageElement( aNextName, nStorageMode ); + if ( !xChildStorage.is() ) + throw uno::RuntimeException(); + + aElement = new OHierarchyElement_Impl( xChildStorage ); + } + + xResult = aElement->GetStreamHierarchically( nStorageMode, aListPath, nStreamMode, aEncryptionData ); + if ( !xResult.is() ) + throw uno::RuntimeException(); + + if ( bNewElement ) + { + m_aChildren[aNextName] = aElement; + aElement->SetParent( this ); + } + } + + // the subelement was opened successfully, remember the storage to let it be locked + m_xOwnStorage = xOwnStor; + + return xResult; +} + +void OHierarchyElement_Impl::RemoveStreamHierarchically( std::vector<OUString>& aListPath ) +{ + std::unique_lock aGuard( m_aMutex ); + + if ( aListPath.empty() ) + throw uno::RuntimeException(); + + OUString aNextName = *(aListPath.begin()); + aListPath.erase( aListPath.begin() ); + + uno::Reference< embed::XStorage > xOwnStor = m_xOwnStorage.is() ? m_xOwnStorage + : uno::Reference< embed::XStorage >( m_xWeakOwnStorage.get(), uno::UNO_QUERY_THROW ); + + if ( aListPath.empty() ) + { + xOwnStor->removeElement( aNextName ); + } + else + { + ::rtl::Reference< OHierarchyElement_Impl > aElement; + OHierarchyElementList_Impl::iterator aIter = m_aChildren.find( aNextName ); + if ( aIter != m_aChildren.end() ) + aElement = aIter->second; + + if ( !aElement.is() ) + { + uno::Reference< embed::XStorage > xChildStorage = xOwnStor->openStorageElement( aNextName, + embed::ElementModes::READWRITE ); + if ( !xChildStorage.is() ) + throw uno::RuntimeException(); + + aElement = new OHierarchyElement_Impl( xChildStorage ); + } + + aElement->RemoveStreamHierarchically( aListPath ); + } + + uno::Reference< embed::XTransactedObject > xTransact( xOwnStor, uno::UNO_QUERY ); + if ( xTransact.is() ) + xTransact->commit(); + + TestForClosing(); +} + +void OHierarchyElement_Impl::Commit() +{ + ::rtl::Reference< OHierarchyElement_Impl > xKeepAlive( this ); + ::rtl::Reference< OHierarchyElement_Impl > aParent; + uno::Reference< embed::XStorage > xOwnStor; + + { + std::unique_lock aGuard( m_aMutex ); + aParent = m_rParent; + xOwnStor = m_xOwnStorage; + } + + if ( xOwnStor.is() ) + { + uno::Reference< embed::XTransactedObject > xTransact( xOwnStor, uno::UNO_QUERY_THROW ); + xTransact->commit(); + if ( aParent.is() ) + aParent->Commit(); + } +} + +void OHierarchyElement_Impl::TestForClosing() +{ + ::rtl::Reference< OHierarchyElement_Impl > xKeepAlive( this ); + { + std::unique_lock aGuard( m_aMutex ); + + if ( m_aOpenStreams.empty() && m_aChildren.empty() ) + { + if ( m_rParent.is() ) + { + // only the root storage should not be disposed, other storages can be disposed + if ( m_xOwnStorage.is() ) + { + try + { + m_xOwnStorage->dispose(); + } + catch( uno::Exception& ) + {} + } + + m_rParent->RemoveElement( this ); + } + + m_xOwnStorage.clear(); + } + } +} + +void SAL_CALL OHierarchyElement_Impl::disposing( const lang::EventObject& Source ) +{ + try + { + { + std::unique_lock aGuard(m_aMutex); + uno::Reference< embed::XExtendedStorageStream > xStream(Source.Source, uno::UNO_QUERY); + + std::erase_if(m_aOpenStreams, + [&xStream](const OWeakStorRefVector_Impl::value_type& rxStorage) { + return !rxStorage.get().is() || rxStorage.get() == xStream; }); + } + + TestForClosing(); + } + catch( uno::Exception& ex ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw lang::WrappedTargetRuntimeException( ex.Message, + nullptr, anyEx ); // no exception must happen here, usually an exception means disaster + } +} + +void OHierarchyElement_Impl::RemoveElement( const ::rtl::Reference< OHierarchyElement_Impl >& aRef ) +{ + { + std::unique_lock aGuard( m_aMutex ); + OHierarchyElementList_Impl::iterator aIter = m_aChildren.begin(); + while (aIter != m_aChildren.end()) + { + if (aIter->second == aRef ) + aIter = m_aChildren.erase(aIter); + else + ++aIter; + } + } + + TestForClosing(); +} + +// XTransactionListener +void SAL_CALL OHierarchyElement_Impl::preCommit( const css::lang::EventObject& /*aEvent*/ ) +{ +} + +void SAL_CALL OHierarchyElement_Impl::commited( const css::lang::EventObject& /*aEvent*/ ) +{ + try + { + Commit(); + } + catch( const uno::Exception& ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw lang::WrappedTargetRuntimeException( + "Can not commit storage sequence!", + uno::Reference< uno::XInterface >(), + anyEx ); + } +} + +void SAL_CALL OHierarchyElement_Impl::preRevert( const css::lang::EventObject& /*aEvent*/ ) +{ +} + +void SAL_CALL OHierarchyElement_Impl::reverted( const css::lang::EventObject& /*aEvent*/ ) +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/xstor/ohierarchyholder.hxx b/package/source/xstor/ohierarchyholder.hxx new file mode 100644 index 0000000000..c3ceddabd1 --- /dev/null +++ b/package/source/xstor/ohierarchyholder.hxx @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_PACKAGE_SOURCE_XSTOR_OHIERARCHYHOLDER_HXX +#define INCLUDED_PACKAGE_SOURCE_XSTOR_OHIERARCHYHOLDER_HXX + +#include <com/sun/star/embed/XStorage.hpp> +#include <com/sun/star/embed/XTransactionListener.hpp> +#include <com/sun/star/embed/XExtendedStorageStream.hpp> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/weakref.hxx> + +#include <comphelper/sequenceashashmap.hxx> + +#include <rtl/ref.hxx> +#include <mutex> +#include <unordered_map> +#include <utility> +#include <vector> + +class OHierarchyElement_Impl; + +typedef std::unordered_map< OUString, + ::rtl::Reference< OHierarchyElement_Impl > > OHierarchyElementList_Impl; + +typedef ::std::vector< css::uno::WeakReference< css::embed::XExtendedStorageStream > > + OWeakStorRefVector_Impl; + +class OHierarchyElement_Impl : public cppu::WeakImplHelper< css::embed::XTransactionListener > +{ + std::mutex m_aMutex; + + ::rtl::Reference< OHierarchyElement_Impl > m_rParent; + css::uno::Reference< css::embed::XStorage > m_xOwnStorage; + css::uno::WeakReference< css::embed::XStorage > m_xWeakOwnStorage; + + OHierarchyElementList_Impl m_aChildren; + + OWeakStorRefVector_Impl m_aOpenStreams; + +public: + explicit OHierarchyElement_Impl( css::uno::Reference< css::embed::XStorage > xStorage ) + : m_xOwnStorage(std::move( xStorage )) + {} + + explicit OHierarchyElement_Impl( css::uno::WeakReference< css::embed::XStorage > xWeakStorage ) + : m_xWeakOwnStorage(std::move( xWeakStorage )) + {} + + void Commit(); + + void SetParent( const ::rtl::Reference< OHierarchyElement_Impl >& rParent ) { m_rParent = rParent; } + + void TestForClosing(); + + void RemoveElement( const ::rtl::Reference< OHierarchyElement_Impl >& aRef ); + + css::uno::Reference< css::embed::XExtendedStorageStream > + GetStreamHierarchically( sal_Int32 nStorageMode, + std::vector<OUString>& aPath, + sal_Int32 nStreamMode, + const ::comphelper::SequenceAsHashMap& aEncryptionData ); + + void RemoveStreamHierarchically( std::vector<OUString>& aListPath ); + + // XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + + // XTransactionListener + virtual void SAL_CALL preCommit( const css::lang::EventObject& aEvent ) override; + virtual void SAL_CALL commited( const css::lang::EventObject& aEvent ) override; + virtual void SAL_CALL preRevert( const css::lang::EventObject& aEvent ) override; + virtual void SAL_CALL reverted( const css::lang::EventObject& aEvent ) override; + +}; + +class OHierarchyHolder_Impl : public ::cppu::OWeakObject +{ + css::uno::WeakReference< css::embed::XStorage > m_xWeakOwnStorage; + ::rtl::Reference< OHierarchyElement_Impl > m_xChild; +public: + explicit OHierarchyHolder_Impl( const css::uno::Reference< css::embed::XStorage >& xOwnStorage ) + : m_xWeakOwnStorage( xOwnStorage ) + , m_xChild( new OHierarchyElement_Impl( css::uno::WeakReference< css::embed::XStorage >( xOwnStorage ) ) ) + {} + + static std::vector<OUString> GetListPathFromString( std::u16string_view aPath ); + + css::uno::Reference< css::embed::XExtendedStorageStream > + GetStreamHierarchically( sal_Int32 nStorageMode, + std::vector<OUString>& aListPath, + sal_Int32 nStreamMode, + const ::comphelper::SequenceAsHashMap& aEncryptionData = ::comphelper::SequenceAsHashMap() ); + + void RemoveStreamHierarchically( std::vector<OUString>& aListPath ); +}; + +#endif // _OHIERARCHYHOLDER + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/xstor/oseekinstream.cxx b/package/source/xstor/oseekinstream.cxx new file mode 100644 index 0000000000..f5e3d644d6 --- /dev/null +++ b/package/source/xstor/oseekinstream.cxx @@ -0,0 +1,144 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/lang/DisposedException.hpp> +#include <cppuhelper/typeprovider.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> + +#include "oseekinstream.hxx" +#include "owriteablestream.hxx" + +using namespace ::com::sun::star; + +OInputSeekStream::OInputSeekStream( OWriteStream_Impl& pImpl, + uno::Reference < io::XInputStream > const & xStream, + const uno::Sequence< beans::PropertyValue >& aProps, + sal_Int32 nStorageType ) +: OInputCompStream( pImpl, xStream, aProps, nStorageType ) +{ + m_xSeekable.set( m_xStream, uno::UNO_QUERY ); + OSL_ENSURE( m_xSeekable.is(), "No seeking support!" ); +} + +OInputSeekStream::OInputSeekStream( uno::Reference < io::XInputStream > const & xStream, + const uno::Sequence< beans::PropertyValue >& aProps, + sal_Int32 nStorageType ) +: OInputCompStream( xStream, aProps, nStorageType ) +{ + m_xSeekable.set( m_xStream, uno::UNO_QUERY ); + OSL_ENSURE( m_xSeekable.is(), "No seeking support!" ); +} + +OInputSeekStream::~OInputSeekStream() +{ +} + +uno::Sequence< uno::Type > SAL_CALL OInputSeekStream::getTypes() +{ + static cppu::OTypeCollection aTypeCollection(cppu::UnoType<io::XSeekable>::get(), + OInputCompStream::getTypes()); + + return aTypeCollection.getTypes(); +} + +uno::Any SAL_CALL OInputSeekStream::queryInterface( const uno::Type& rType ) +{ + // Attention: + // Don't use mutex or guard in this method!!! Is a method of XInterface. + + uno::Any aReturn( ::cppu::queryInterface( rType, + static_cast< io::XSeekable* >( this ) ) ); + + if ( aReturn.hasValue() ) + { + return aReturn ; + } + + return OInputCompStream::queryInterface( rType ) ; +} + +void SAL_CALL OInputSeekStream::acquire() + noexcept +{ + OInputCompStream::acquire(); +} + +void SAL_CALL OInputSeekStream::release() + noexcept +{ + OInputCompStream::release(); +} + +void SAL_CALL OInputSeekStream::seek( sal_Int64 location ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( !m_xSeekable.is() ) + { + SAL_INFO("package.xstor", "No seekable!"); + throw uno::RuntimeException(); + } + + m_xSeekable->seek( location ); +} + +sal_Int64 SAL_CALL OInputSeekStream::getPosition() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( !m_xSeekable.is() ) + { + SAL_INFO("package.xstor", "No seekable!"); + throw uno::RuntimeException(); + } + + return m_xSeekable->getPosition(); +} + +sal_Int64 SAL_CALL OInputSeekStream::getLength() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + if ( m_bDisposed ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( !m_xSeekable.is() ) + { + SAL_INFO("package.xstor", "No seekable!"); + throw uno::RuntimeException(); + } + + return m_xSeekable->getLength(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/xstor/oseekinstream.hxx b/package/source/xstor/oseekinstream.hxx new file mode 100644 index 0000000000..a77289e9a3 --- /dev/null +++ b/package/source/xstor/oseekinstream.hxx @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_PACKAGE_SOURCE_XSTOR_OSEEKINSTREAM_HXX +#define INCLUDED_PACKAGE_SOURCE_XSTOR_OSEEKINSTREAM_HXX + +#include <com/sun/star/io/XSeekable.hpp> + +#include "ocompinstream.hxx" + +class OInputSeekStream final : public OInputCompStream + , public css::io::XSeekable +{ + css::uno::Reference < css::io::XSeekable > m_xSeekable; + +public: + OInputSeekStream( OWriteStream_Impl& pImpl, + css::uno::Reference < css::io::XInputStream > const & xStream, + const css::uno::Sequence< css::beans::PropertyValue >& aProps, + sal_Int32 nStorageType ); + + OInputSeekStream( css::uno::Reference < css::io::XInputStream > const & xStream, + const css::uno::Sequence< css::beans::PropertyValue >& aProps, + sal_Int32 nStorageType ); + + virtual ~OInputSeekStream() override; + + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes() override; + + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type& rType ) override; + virtual void SAL_CALL acquire() noexcept override; + virtual void SAL_CALL release() noexcept override; + + //XSeekable + virtual void SAL_CALL seek( sal_Int64 location ) override; + virtual sal_Int64 SAL_CALL getPosition() override; + virtual sal_Int64 SAL_CALL getLength() override; + +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/xstor/owriteablestream.cxx b/package/source/xstor/owriteablestream.cxx new file mode 100644 index 0000000000..68bf5d1688 --- /dev/null +++ b/package/source/xstor/owriteablestream.cxx @@ -0,0 +1,3189 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <cassert> +#include <memory> +#include <sal/log.hxx> + +#include <com/sun/star/packages/NoEncryptionException.hpp> +#include <com/sun/star/packages/WrongPasswordException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/ucb/SimpleFileAccess.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/XTypeProvider.hpp> +#include <com/sun/star/io/NotConnectedException.hpp> +#include <com/sun/star/io/TempFile.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/embed/StorageFormats.hpp> +#include <com/sun/star/embed/StorageWrappedTargetException.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <cppuhelper/typeprovider.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <osl/diagnose.h> + +#include <comphelper/processfactory.hxx> +#include <comphelper/servicehelper.hxx> +#include <comphelper/storagehelper.hxx> +#include <comphelper/ofopxmlhelper.hxx> +#include <comphelper/refcountedmutex.hxx> +#include <comphelper/sequence.hxx> + +#include <comphelper/diagnose_ex.hxx> + +#include <PackageConstants.hxx> +#include <utility> + +#include "selfterminatefilestream.hxx" +#include "owriteablestream.hxx" +#include "oseekinstream.hxx" +#include "xstorage.hxx" + +// since the copying uses 32000 blocks usually, it makes sense to have a smaller size +#define MAX_STORCACHE_SIZE 30000 + +using namespace ::com::sun::star; + +namespace package +{ + +static void CopyInputToOutput( + const css::uno::Reference< css::io::XInputStream >& xInput, + SvStream& rOutput ) +{ + static const sal_Int32 nConstBufferSize = 32000; + + if (auto pByteReader = dynamic_cast< comphelper::ByteReader* >( xInput.get() )) + { + sal_Int32 nRead; + sal_Int8 aTempBuf[ nConstBufferSize ]; + do + { + nRead = pByteReader->readSomeBytes ( aTempBuf, nConstBufferSize ); + rOutput.WriteBytes ( aTempBuf, nRead ); + } + while ( nRead == nConstBufferSize ); + } + else + { + sal_Int32 nRead; + uno::Sequence < sal_Int8 > aSequence ( nConstBufferSize ); + + do + { + nRead = xInput->readBytes ( aSequence, nConstBufferSize ); + rOutput.WriteBytes ( aSequence.getConstArray(), nRead ); + } + while ( nRead == nConstBufferSize ); + } +} + +bool PackageEncryptionDataLessOrEqual( const ::comphelper::SequenceAsHashMap& aHash1, const ::comphelper::SequenceAsHashMap& aHash2 ) +{ + // tdf#93389: aHash2 may contain more than in aHash1, if it contains also data for other package + // formats (as in case of autorecovery) + bool bResult = !aHash1.empty() && aHash1.size() <= aHash2.size(); + for ( ::comphelper::SequenceAsHashMap::const_iterator aIter = aHash1.begin(); + bResult && aIter != aHash1.end(); + ++aIter ) + { + uno::Sequence< sal_Int8 > aKey1; + bResult = ( ( aIter->second >>= aKey1 ) && aKey1.hasElements() ); + if ( bResult ) + { + const uno::Sequence< sal_Int8 > aKey2 = aHash2.getUnpackedValueOrDefault( aIter->first.maString, uno::Sequence< sal_Int8 >() ); + bResult = aKey1.getLength() == aKey2.getLength() + && std::equal(std::cbegin(aKey1), std::cend(aKey1), aKey2.begin(), aKey2.end()); + } + } + + return bResult; +} + +} // namespace package + +namespace +{ + +void SetEncryptionKeyProperty_Impl( const uno::Reference< beans::XPropertySet >& xPropertySet, + const uno::Sequence< beans::NamedValue >& aKey ) +{ + SAL_WARN_IF( !xPropertySet.is(), "package.xstor", "No property set is provided!" ); + if ( !xPropertySet.is() ) + throw uno::RuntimeException(); + + try { + xPropertySet->setPropertyValue( STORAGE_ENCRYPTION_KEYS_PROPERTY, uno::Any( aKey ) ); + } + catch ( const uno::Exception& ex ) + { + TOOLS_WARN_EXCEPTION( "package.xstor", "Can't write encryption related properties"); + throw io::IOException(ex.Message); // TODO + } +} + +uno::Any GetEncryptionKeyProperty_Impl( const uno::Reference< beans::XPropertySet >& xPropertySet ) +{ + SAL_WARN_IF( !xPropertySet.is(), "package.xstor", "No property set is provided!" ); + if ( !xPropertySet.is() ) + throw uno::RuntimeException(); + + try { + return xPropertySet->getPropertyValue(STORAGE_ENCRYPTION_KEYS_PROPERTY); + } + catch ( const uno::Exception& ex ) + { + TOOLS_WARN_EXCEPTION( "package.xstor", "Can't get encryption related properties"); + throw io::IOException(ex.Message); // TODO + } +} + +bool SequencesEqual( const uno::Sequence< sal_Int8 >& aSequence1, const uno::Sequence< sal_Int8 >& aSequence2 ) +{ + return aSequence1.getLength() == aSequence2.getLength() + && std::equal(aSequence1.begin(), aSequence1.end(), aSequence2.begin(), aSequence2.end()); +} + +bool SequencesEqual( const uno::Sequence< beans::NamedValue >& aSequence1, const uno::Sequence< beans::NamedValue >& aSequence2 ) +{ + if ( aSequence1.getLength() != aSequence2.getLength() ) + return false; + + for ( const auto& rProp1 : aSequence1 ) + { + bool bHasMember = false; + uno::Sequence< sal_Int8 > aMember1; + sal_Int32 nMember1 = 0; + if ( rProp1.Value >>= aMember1 ) + { + for ( const auto& rProp2 : aSequence2 ) + { + if ( rProp1.Name == rProp2.Name ) + { + bHasMember = true; + + uno::Sequence< sal_Int8 > aMember2; + if ( !( rProp2.Value >>= aMember2 ) || !SequencesEqual( aMember1, aMember2 ) ) + return false; + } + } + } + else if ( rProp1.Value >>= nMember1 ) + { + for ( const auto& rProp2 : aSequence2 ) + { + if ( rProp1.Name == rProp2.Name ) + { + bHasMember = true; + + sal_Int32 nMember2 = 0; + if ( !( rProp2.Value >>= nMember2 ) || nMember1 != nMember2 ) + return false; + } + } + } + else + return false; + + if ( !bHasMember ) + return false; + } + + return true; +} + +uno::Reference< io::XStream > CreateMemoryStream( const uno::Reference< uno::XComponentContext >& rContext ) +{ + static constexpr OUStringLiteral sName(u"com.sun.star.comp.MemoryStream"); + return uno::Reference< io::XStream >( + rContext->getServiceManager()->createInstanceWithContext(sName, rContext), + uno::UNO_QUERY_THROW); +} + +const beans::StringPair* lcl_findPairByName(const uno::Sequence<beans::StringPair>& rSeq, const OUString& rName) +{ + return std::find_if(rSeq.begin(), rSeq.end(), + [&rName](const beans::StringPair& rPair) { return rPair.First == rName; }); +} + +} // anonymous namespace + +OWriteStream_Impl::OWriteStream_Impl( OStorage_Impl* pParent, + const uno::Reference< packages::XDataSinkEncrSupport >& xPackageStream, + const uno::Reference< lang::XSingleServiceFactory >& xPackage, + uno::Reference< uno::XComponentContext > xContext, + bool bForceEncrypted, + sal_Int32 nStorageType, + bool bDefaultCompress, + uno::Reference< io::XInputStream > xRelInfoStream ) +: m_xMutex( new comphelper::RefCountedMutex ) +, m_pAntiImpl( nullptr ) +, m_bHasDataToFlush( false ) +, m_bFlushed( false ) +, m_xPackageStream( xPackageStream ) +, m_xContext(std::move( xContext )) +, m_pParent( pParent ) +, m_bForceEncrypted( bForceEncrypted ) +, m_bUseCommonEncryption( !bForceEncrypted && nStorageType == embed::StorageFormats::PACKAGE ) +, m_bHasCachedEncryptionData( false ) +, m_bCompressedSetExplicit( !bDefaultCompress ) +, m_xPackage( xPackage ) +, m_bHasInsertedStreamOptimization( false ) +, m_nStorageType( nStorageType ) +, m_xOrigRelInfoStream(std::move( xRelInfoStream )) +, m_bOrigRelInfoBroken( false ) +, m_nRelInfoStatus( RELINFO_NO_INIT ) +, m_nRelId( 1 ) +{ + SAL_WARN_IF( !xPackageStream.is(), "package.xstor", "No package stream is provided!" ); + SAL_WARN_IF( !xPackage.is(), "package.xstor", "No package component is provided!" ); + SAL_WARN_IF( !m_xContext.is(), "package.xstor", "No package stream is provided!" ); + OSL_ENSURE( pParent, "No parent storage is provided!" ); + OSL_ENSURE( m_nStorageType == embed::StorageFormats::OFOPXML || !m_xOrigRelInfoStream.is(), "The Relations info makes sense only for OFOPXML format!" ); +} + +OWriteStream_Impl::~OWriteStream_Impl() +{ + DisposeWrappers(); + + m_oTempFile.reset(); + + CleanCacheStream(); +} + +void OWriteStream_Impl::CleanCacheStream() +{ + if ( !m_xCacheStream.is() ) + return; + + try + { + uno::Reference< io::XInputStream > xInputCache = m_xCacheStream->getInputStream(); + if ( xInputCache.is() ) + xInputCache->closeInput(); + } + catch( const uno::Exception& ) + {} + + try + { + uno::Reference< io::XOutputStream > xOutputCache = m_xCacheStream->getOutputStream(); + if ( xOutputCache.is() ) + xOutputCache->closeOutput(); + } + catch( const uno::Exception& ) + {} + + m_xCacheStream.clear(); + m_xCacheSeek.clear(); +} + +void OWriteStream_Impl::InsertIntoPackageFolder( const OUString& aName, + const uno::Reference< container::XNameContainer >& xParentPackageFolder ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + SAL_WARN_IF( !m_bFlushed, "package.xstor", "This method must not be called for nonflushed streams!" ); + if ( m_bFlushed ) + { + SAL_WARN_IF( !m_xPackageStream.is(), "package.xstor", "An inserted stream is incomplete!" ); + uno::Reference< uno::XInterface > xTmp( m_xPackageStream, uno::UNO_QUERY_THROW ); + xParentPackageFolder->insertByName( aName, uno::Any( xTmp ) ); + + m_bFlushed = false; + m_bHasInsertedStreamOptimization = false; + } +} +bool OWriteStream_Impl::IsEncrypted() +{ + if ( m_nStorageType != embed::StorageFormats::PACKAGE ) + return false; + + if ( m_bForceEncrypted || m_bHasCachedEncryptionData ) + return true; + + if ( m_oTempFile.has_value() || m_xCacheStream.is() ) + return false; + + GetStreamProperties(); + + // the following value can not be cached since it can change after root commit + bool bWasEncr = false; + uno::Reference< beans::XPropertySet > xPropSet( m_xPackageStream, uno::UNO_QUERY ); + if ( xPropSet.is() ) + { + uno::Any aValue = xPropSet->getPropertyValue("WasEncrypted"); + if ( !( aValue >>= bWasEncr ) ) + { + SAL_WARN( "package.xstor", "The property WasEncrypted has wrong type!" ); + } + } + + bool bToBeEncr = false; + for ( const auto& rProp : std::as_const(m_aProps) ) + { + if ( rProp.Name == "Encrypted" ) + { + if ( !( rProp.Value >>= bToBeEncr ) ) + { + SAL_WARN( "package.xstor", "The property has wrong type!" ); + } + } + } + + // since a new key set to the package stream it should not be removed except the case when + // the stream becomes nonencrypted + uno::Sequence< beans::NamedValue > aKey; + if ( bToBeEncr ) + GetEncryptionKeyProperty_Impl( xPropSet ) >>= aKey; + + // If the properties must be investigated the stream is either + // was never changed or was changed, the parent was committed + // and the stream was closed. + // That means that if it is intended to use common storage key + // it is already has no encryption but is marked to be stored + // encrypted and the key is empty. + if ( !bWasEncr && bToBeEncr && !aKey.hasElements() ) + { + // the stream is intended to use common storage password + m_bUseCommonEncryption = true; + return false; + } + else + return bToBeEncr; +} + +void OWriteStream_Impl::SetDecrypted() +{ + SAL_WARN_IF( m_nStorageType != embed::StorageFormats::PACKAGE, "package.xstor", "The encryption is supported only for package storages!" ); + if ( m_nStorageType != embed::StorageFormats::PACKAGE ) + throw uno::RuntimeException(); + + GetStreamProperties(); + + // let the stream be modified + FillTempGetFileName(); + m_bHasDataToFlush = true; + + // remove encryption + m_bForceEncrypted = false; + m_bHasCachedEncryptionData = false; + m_aEncryptionData.clear(); + + for ( auto& rProp : asNonConstRange(m_aProps) ) + { + if ( rProp.Name == "Encrypted" ) + rProp.Value <<= false; + } +} + +void OWriteStream_Impl::SetEncrypted( const ::comphelper::SequenceAsHashMap& aEncryptionData ) +{ + SAL_WARN_IF( m_nStorageType != embed::StorageFormats::PACKAGE, "package.xstor", "The encryption is supported only for package storages!" ); + if ( m_nStorageType != embed::StorageFormats::PACKAGE ) + throw uno::RuntimeException(); + + if ( aEncryptionData.empty() ) + throw uno::RuntimeException(); + + GetStreamProperties(); + + // let the stream be modified + FillTempGetFileName(); + m_bHasDataToFlush = true; + + // introduce encryption info + for ( auto& rProp : asNonConstRange(m_aProps) ) + { + if ( rProp.Name == "Encrypted" ) + rProp.Value <<= true; + } + + m_bUseCommonEncryption = false; // very important to set it to false + + m_bHasCachedEncryptionData = true; + m_aEncryptionData = aEncryptionData; +} + +void OWriteStream_Impl::DisposeWrappers() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + if ( m_pAntiImpl ) + { + try { + m_pAntiImpl->dispose(); + } + catch ( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Quiet exception"); + } + + m_pAntiImpl = nullptr; + } + m_pParent = nullptr; + + if ( m_aInputStreamsVector.empty() ) + return; + + for ( auto& pStream : m_aInputStreamsVector ) + { + if ( pStream ) + { + pStream->InternalDispose(); + pStream = nullptr; + } + } + + m_aInputStreamsVector.clear(); +} + +void OWriteStream_Impl::GetFilledTempFileIfNo( const uno::Reference< io::XInputStream >& xStream ) +{ + if ( !m_oTempFile.has_value() ) + { + m_oTempFile.emplace(); + + try { + if ( xStream.is() ) + { + // the current position of the original stream should be still OK, copy further + package::CopyInputToOutput( xStream, *m_oTempFile->GetStream(StreamMode::READWRITE) ); + } + } + catch( const packages::WrongPasswordException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + m_oTempFile.reset(); + throw; + } + catch( const uno::Exception& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + m_oTempFile.reset(); + throw; + } + + if ( m_oTempFile.has_value() ) + CleanCacheStream(); + } +} + +void OWriteStream_Impl::FillTempGetFileName() +{ + // should try to create cache first, if the amount of contents is too big, the temp file should be taken + if ( !m_xCacheStream.is() && !m_oTempFile.has_value() ) + { + uno::Reference< io::XInputStream > xOrigStream = m_xPackageStream->getDataStream(); + if ( !xOrigStream.is() ) + { + // in case of new inserted package stream it is possible that input stream still was not set + uno::Reference< io::XStream > xCacheStream = CreateMemoryStream( m_xContext ); + SAL_WARN_IF( !xCacheStream.is(), "package.xstor", "If the stream can not be created an exception must be thrown!" ); + m_xCacheSeek.set( xCacheStream, uno::UNO_QUERY_THROW ); + m_xCacheStream = xCacheStream; + } + else + { + sal_Int32 nRead = 0; + uno::Sequence< sal_Int8 > aData( MAX_STORCACHE_SIZE + 1 ); + nRead = xOrigStream->readBytes( aData, MAX_STORCACHE_SIZE + 1 ); + if ( aData.getLength() > nRead ) + aData.realloc( nRead ); + + if ( nRead <= MAX_STORCACHE_SIZE ) + { + uno::Reference< io::XStream > xCacheStream = CreateMemoryStream( m_xContext ); + SAL_WARN_IF( !xCacheStream.is(), "package.xstor", "If the stream can not be created an exception must be thrown!" ); + + if ( nRead ) + { + uno::Reference< io::XOutputStream > xOutStream( xCacheStream->getOutputStream(), uno::UNO_SET_THROW ); + xOutStream->writeBytes( aData ); + } + m_xCacheSeek.set( xCacheStream, uno::UNO_QUERY_THROW ); + m_xCacheStream = xCacheStream; + m_xCacheSeek->seek( 0 ); + } + else if ( !m_oTempFile.has_value() ) + { + m_oTempFile.emplace(); + + try { + // copy stream contents to the file + SvStream* pStream = m_oTempFile->GetStream(StreamMode::READWRITE); + pStream->WriteBytes( aData.getConstArray(), aData.getLength() ); + + // the current position of the original stream should be still OK, copy further + package::CopyInputToOutput( xOrigStream, *pStream ); + } + catch( const packages::WrongPasswordException& ) + { + m_oTempFile.reset(); + throw; + } + catch( const uno::Exception& ) + { + m_oTempFile.reset(); + } + } + } + } +} + +uno::Reference< io::XStream > OWriteStream_Impl::GetTempFileAsStream() +{ + uno::Reference< io::XStream > xTempStream; + + if ( !m_xCacheStream.is() ) + { + if ( !m_oTempFile.has_value() ) + FillTempGetFileName(); + + if ( m_oTempFile.has_value() ) + { + // the temporary file is not used if the cache is used + try + { + SvStream* pStream = m_oTempFile->GetStream(StreamMode::READWRITE); + pStream->Seek(0); + xTempStream = new utl::OStreamWrapper(pStream, /*bOwner*/false); + } + catch( const uno::Exception& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Quiet exception"); + } + } + } + + if ( m_xCacheStream.is() ) + xTempStream = m_xCacheStream; + + // the method must always return a stream + // in case the stream can not be open + // an exception should be thrown + if ( !xTempStream.is() ) + throw io::IOException("no temp stream"); //TODO: + + return xTempStream; +} + +uno::Reference< io::XInputStream > OWriteStream_Impl::GetTempFileAsInputStream() +{ + uno::Reference< io::XInputStream > xInputStream; + + if ( !m_xCacheStream.is() ) + { + if ( !m_oTempFile.has_value() ) + FillTempGetFileName(); + + if ( m_oTempFile.has_value() ) + { + // the temporary file is not used if the cache is used + try + { + SvStream* pStream = m_oTempFile->GetStream(StreamMode::READWRITE); + pStream->Seek(0); + xInputStream = new utl::OStreamWrapper(pStream, /*bOwner*/false); + } + catch( const uno::Exception& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Quiet exception"); + } + } + } + + if ( m_xCacheStream.is() ) + xInputStream = m_xCacheStream->getInputStream(); + + // the method must always return a stream + // in case the stream can not be open + // an exception should be thrown + if ( !xInputStream.is() ) + throw io::IOException(); // TODO: + + return xInputStream; +} + +void OWriteStream_Impl::InsertStreamDirectly( const uno::Reference< io::XInputStream >& xInStream, + const uno::Sequence< beans::PropertyValue >& aProps ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ) ; + + // this call can be made only during parent storage commit + // the parent storage is responsible for the correct handling + // of deleted and renamed contents + + SAL_WARN_IF( !m_xPackageStream.is(), "package.xstor", "No package stream is set!" ); + + if ( m_bHasDataToFlush ) + throw io::IOException("m_bHasDataToFlush==true"); + + OSL_ENSURE( !m_oTempFile.has_value() && !m_xCacheStream.is(), "The temporary must not exist!" ); + + // use new file as current persistent representation + // the new file will be removed after it's stream is closed + m_xPackageStream->setDataStream( xInStream ); + + // copy properties to the package stream + uno::Reference< beans::XPropertySet > xPropertySet( m_xPackageStream, uno::UNO_QUERY_THROW ); + + // The storage-package communication has a problem + // the storage caches properties, thus if the package changes one of them itself + // the storage does not know about it + + // Depending from MediaType value the package can change the compressed property itself + // Thus if Compressed property is provided it must be set as the latest one + bool bCompressedIsSet = false; + bool bCompressed = false; + OUString aComprPropName( "Compressed" ); + OUString aMedTypePropName( "MediaType" ); + for ( const auto& rProp : aProps ) + { + if ( rProp.Name == aComprPropName ) + { + bCompressedIsSet = true; + rProp.Value >>= bCompressed; + } + else if ( ( m_nStorageType == embed::StorageFormats::OFOPXML || m_nStorageType == embed::StorageFormats::PACKAGE ) + && rProp.Name == aMedTypePropName ) + { + xPropertySet->setPropertyValue( rProp.Name, rProp.Value ); + } + else if ( m_nStorageType == embed::StorageFormats::PACKAGE && rProp.Name == "UseCommonStoragePasswordEncryption" ) + rProp.Value >>= m_bUseCommonEncryption; + else + throw lang::IllegalArgumentException(); + + // if there are cached properties update them + if ( rProp.Name == aMedTypePropName || rProp.Name == aComprPropName ) + for ( auto& rMemProp : asNonConstRange(m_aProps) ) + { + if ( rProp.Name == rMemProp.Name ) + rMemProp.Value = rProp.Value; + } + } + + if ( bCompressedIsSet ) + { + xPropertySet->setPropertyValue( aComprPropName, uno::Any( bCompressed ) ); + m_bCompressedSetExplicit = true; + } + + if ( m_bUseCommonEncryption ) + { + if ( m_nStorageType != embed::StorageFormats::PACKAGE ) + throw uno::RuntimeException(); + + // set to be encrypted but do not use encryption key + xPropertySet->setPropertyValue( STORAGE_ENCRYPTION_KEYS_PROPERTY, + uno::Any( uno::Sequence< beans::NamedValue >() ) ); + xPropertySet->setPropertyValue( "Encrypted", uno::Any( true ) ); + } + + // the stream should be free soon, after package is stored + m_bHasDataToFlush = false; + m_bFlushed = true; // will allow to use transaction on stream level if will need it + m_bHasInsertedStreamOptimization = true; +} + +void OWriteStream_Impl::Commit() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ) ; + + SAL_WARN_IF( !m_xPackageStream.is(), "package.xstor", "No package stream is set!" ); + + if ( !m_bHasDataToFlush ) + return; + + uno::Reference< packages::XDataSinkEncrSupport > xNewPackageStream; + uno::Sequence< uno::Any > aSeq{ uno::Any(false) }; + + if ( m_xCacheStream.is() ) + { + if ( m_pAntiImpl ) + m_pAntiImpl->DeInit(); + + uno::Reference< io::XInputStream > xInStream( m_xCacheStream->getInputStream(), uno::UNO_SET_THROW ); + + xNewPackageStream.set( m_xPackage->createInstanceWithArguments( aSeq ), uno::UNO_QUERY_THROW ); + + xNewPackageStream->setDataStream( xInStream ); + + m_xCacheStream.clear(); + m_xCacheSeek.clear(); + + } + else if ( m_oTempFile.has_value() ) + { + if ( m_pAntiImpl ) + m_pAntiImpl->DeInit(); + + uno::Reference< io::XInputStream > xInStream; + try + { + xInStream = new OSelfTerminateFileStream(m_xContext, std::move(*m_oTempFile)); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("package", ""); + } + + if ( !xInStream.is() ) + throw io::IOException(); + + xNewPackageStream.set( m_xPackage->createInstanceWithArguments( aSeq ), uno::UNO_QUERY_THROW ); + + // TODO/NEW: Let the temporary file be removed after commit + xNewPackageStream->setDataStream( xInStream ); + m_oTempFile.reset(); + } + else // if ( m_bHasInsertedStreamOptimization ) + { + // if the optimization is used the stream can be accessed directly + xNewPackageStream = m_xPackageStream; + } + + // copy properties to the package stream + uno::Reference< beans::XPropertySet > xPropertySet( xNewPackageStream, uno::UNO_QUERY_THROW ); + + for ( auto& rProp : asNonConstRange(m_aProps) ) + { + if ( rProp.Name == "Size" ) + { + if ( m_pAntiImpl && !m_bHasInsertedStreamOptimization && m_pAntiImpl->m_xSeekable.is() ) + { + rProp.Value <<= m_pAntiImpl->m_xSeekable->getLength(); + xPropertySet->setPropertyValue( rProp.Name, rProp.Value ); + } + } + else + xPropertySet->setPropertyValue( rProp.Name, rProp.Value ); + } + + if ( m_bUseCommonEncryption ) + { + if ( m_nStorageType != embed::StorageFormats::PACKAGE ) + throw uno::RuntimeException(); + + // set to be encrypted but do not use encryption key + xPropertySet->setPropertyValue( STORAGE_ENCRYPTION_KEYS_PROPERTY, + uno::Any( uno::Sequence< beans::NamedValue >() ) ); + xPropertySet->setPropertyValue( "Encrypted", + uno::Any( true ) ); + } + else if ( m_bHasCachedEncryptionData ) + { + if ( m_nStorageType != embed::StorageFormats::PACKAGE ) + throw uno::RuntimeException(); + + xPropertySet->setPropertyValue( STORAGE_ENCRYPTION_KEYS_PROPERTY, + uno::Any( m_aEncryptionData.getAsConstNamedValueList() ) ); + } + + // the stream should be free soon, after package is stored + m_xPackageStream = xNewPackageStream; + m_bHasDataToFlush = false; + m_bFlushed = true; // will allow to use transaction on stream level if will need it +} + +void OWriteStream_Impl::Revert() +{ + // can be called only from parent storage + // means complete reload of the stream + + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ) ; + + if ( !m_bHasDataToFlush ) + return; // nothing to do + + OSL_ENSURE( m_oTempFile.has_value() || m_xCacheStream.is(), "The temporary must exist!" ); + + if ( m_xCacheStream.is() ) + { + m_xCacheStream.clear(); + m_xCacheSeek.clear(); + } + + m_oTempFile.reset(); + + m_aProps.realloc( 0 ); + + m_bHasDataToFlush = false; + + m_bUseCommonEncryption = true; + m_bHasCachedEncryptionData = false; + m_aEncryptionData.clear(); + + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + return; + + // currently the relations storage is changed only on commit + m_xNewRelInfoStream.clear(); + m_aNewRelInfo = uno::Sequence< uno::Sequence< beans::StringPair > >(); + if ( m_xOrigRelInfoStream.is() ) + { + // the original stream is still here, that means that it was not parsed + m_aOrigRelInfo = uno::Sequence< uno::Sequence< beans::StringPair > >(); + m_nRelInfoStatus = RELINFO_NO_INIT; + } + else + { + // the original stream was already parsed + if ( !m_bOrigRelInfoBroken ) + m_nRelInfoStatus = RELINFO_READ; + else + m_nRelInfoStatus = RELINFO_BROKEN; + } +} + +uno::Sequence< beans::PropertyValue > const & OWriteStream_Impl::GetStreamProperties() +{ + if ( !m_aProps.hasElements() ) + m_aProps = ReadPackageStreamProperties(); + + return m_aProps; +} + +uno::Sequence< beans::PropertyValue > OWriteStream_Impl::InsertOwnProps( + const uno::Sequence< beans::PropertyValue >& aProps, + bool bUseCommonEncryption ) +{ + uno::Sequence< beans::PropertyValue > aResult( aProps ); + beans::PropertyValue aPropVal; + + if ( m_nStorageType == embed::StorageFormats::PACKAGE ) + { + aPropVal.Name = "UseCommonStoragePasswordEncryption"; + aPropVal.Value <<= bUseCommonEncryption; + } + else if ( m_nStorageType == embed::StorageFormats::OFOPXML ) + { + ReadRelInfoIfNecessary(); + + aPropVal.Name = "RelationsInfo"; + if ( m_nRelInfoStatus == RELINFO_READ ) + aPropVal.Value <<= m_aOrigRelInfo; + else if ( m_nRelInfoStatus == RELINFO_CHANGED_STREAM_READ || m_nRelInfoStatus == RELINFO_CHANGED ) + aPropVal.Value <<= m_aNewRelInfo; + else // m_nRelInfoStatus == RELINFO_CHANGED_BROKEN || m_nRelInfoStatus == RELINFO_BROKEN + throw io::IOException( "Wrong relinfo stream!" ); + } + if (!aPropVal.Name.isEmpty()) + { + sal_Int32 i = 0; + for (auto p = aResult.getConstArray(); i < aResult.getLength(); ++i) + if (p[i].Name == aPropVal.Name) + break; + if (i == aResult.getLength()) + aResult.realloc(i + 1); + aResult.getArray()[i] = aPropVal; + } + + return aResult; +} + +bool OWriteStream_Impl::IsTransacted() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ) ; + return ( m_pAntiImpl && m_pAntiImpl->m_bTransacted ); +} + +void OWriteStream_Impl::ReadRelInfoIfNecessary() +{ + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + return; + + if ( m_nRelInfoStatus == RELINFO_NO_INIT ) + { + try + { + // Init from original stream + if ( m_xOrigRelInfoStream.is() ) + m_aOrigRelInfo = ::comphelper::OFOPXMLHelper::ReadRelationsInfoSequence( + m_xOrigRelInfoStream, + u"_rels/*.rels", + m_xContext ); + + // in case of success the stream must be thrown away, that means that the OrigRelInfo is initialized + // the reason for this is that the original stream might not be seekable ( at the same time the new + // provided stream must be seekable ), so it must be read only once + m_xOrigRelInfoStream.clear(); + m_nRelInfoStatus = RELINFO_READ; + } + catch( const uno::Exception& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Quiet exception"); + + m_nRelInfoStatus = RELINFO_BROKEN; + m_bOrigRelInfoBroken = true; + } + } + else if ( m_nRelInfoStatus == RELINFO_CHANGED_STREAM ) + { + // Init from the new stream + try + { + if ( m_xNewRelInfoStream.is() ) + m_aNewRelInfo = ::comphelper::OFOPXMLHelper::ReadRelationsInfoSequence( + m_xNewRelInfoStream, + u"_rels/*.rels", + m_xContext ); + + m_nRelInfoStatus = RELINFO_CHANGED_STREAM_READ; + } + catch( const uno::Exception& ) + { + m_nRelInfoStatus = RELINFO_CHANGED_BROKEN; + } + } +} + +uno::Sequence< beans::PropertyValue > OWriteStream_Impl::ReadPackageStreamProperties() +{ + sal_Int32 nPropNum = 0; + if ( m_nStorageType == embed::StorageFormats::ZIP ) + nPropNum = 2; + else if ( m_nStorageType == embed::StorageFormats::OFOPXML ) + nPropNum = 3; + else if ( m_nStorageType == embed::StorageFormats::PACKAGE ) + nPropNum = 4; + assert(nPropNum >= 2); + uno::Sequence< beans::PropertyValue > aResult( nPropNum ); + auto aResultRange = asNonConstRange(aResult); + + // The "Compressed" property must be set after "MediaType" property, + // since the setting of the last one can change the value of the first one + static constexpr OUStringLiteral sMediaType = u"MediaType"; + static constexpr OUString sCompressed = u"Compressed"_ustr; + static constexpr OUString sSize = u"Size"_ustr; + static constexpr OUStringLiteral sEncrypted = u"Encrypted"; + if ( m_nStorageType == embed::StorageFormats::OFOPXML || m_nStorageType == embed::StorageFormats::PACKAGE ) + { + aResultRange[0].Name = sMediaType; + aResultRange[1].Name = sCompressed; + aResultRange[2].Name = sSize; + + if ( m_nStorageType == embed::StorageFormats::PACKAGE ) + aResultRange[3].Name = sEncrypted; + } + else + { + aResultRange[0].Name = sCompressed; + aResultRange[1].Name = sSize; + } + + // TODO: may be also raw stream should be marked + + uno::Reference< beans::XPropertySet > xPropSet( m_xPackageStream, uno::UNO_QUERY_THROW ); + for ( auto& rProp : aResultRange ) + { + try { + rProp.Value = xPropSet->getPropertyValue( rProp.Name ); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "package.xstor", "A property can't be retrieved" ); + } + } + + return aResult; +} + +void OWriteStream_Impl::CopyInternallyTo_Impl( const uno::Reference< io::XStream >& xDestStream, + const ::comphelper::SequenceAsHashMap& aEncryptionData ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ) ; + + SAL_WARN_IF( m_bUseCommonEncryption, "package.xstor", "The stream can not be encrypted!" ); + + if ( m_nStorageType != embed::StorageFormats::PACKAGE ) + throw packages::NoEncryptionException(); + + if ( m_pAntiImpl ) + { + m_pAntiImpl->CopyToStreamInternally_Impl( xDestStream ); + } + else + { + uno::Reference< io::XStream > xOwnStream = GetStream( embed::ElementModes::READ, aEncryptionData, false ); + if ( !xOwnStream.is() ) + throw io::IOException(); // TODO + + OStorage_Impl::completeStorageStreamCopy_Impl( xOwnStream, xDestStream, m_nStorageType, GetAllRelationshipsIfAny() ); + } + + uno::Reference< embed::XEncryptionProtectedSource2 > xEncr( xDestStream, uno::UNO_QUERY ); + if ( xEncr.is() ) + xEncr->setEncryptionData( aEncryptionData.getAsConstNamedValueList() ); +} + +uno::Sequence< uno::Sequence< beans::StringPair > > OWriteStream_Impl::GetAllRelationshipsIfAny() +{ + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + return uno::Sequence< uno::Sequence< beans::StringPair > >(); + + ReadRelInfoIfNecessary(); + + if ( m_nRelInfoStatus == RELINFO_READ ) + return m_aOrigRelInfo; + else if ( m_nRelInfoStatus == RELINFO_CHANGED_STREAM_READ || m_nRelInfoStatus == RELINFO_CHANGED ) + return m_aNewRelInfo; + else // m_nRelInfoStatus == RELINFO_CHANGED_BROKEN || m_nRelInfoStatus == RELINFO_BROKEN + throw io::IOException( "Wrong relinfo stream!" ); +} + +void OWriteStream_Impl::CopyInternallyTo_Impl( const uno::Reference< io::XStream >& xDestStream ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ) ; + + if ( m_pAntiImpl ) + { + m_pAntiImpl->CopyToStreamInternally_Impl( xDestStream ); + } + else + { + uno::Reference< io::XStream > xOwnStream = GetStream( embed::ElementModes::READ, false ); + if ( !xOwnStream.is() ) + throw io::IOException(); // TODO + + OStorage_Impl::completeStorageStreamCopy_Impl( xOwnStream, xDestStream, m_nStorageType, GetAllRelationshipsIfAny() ); + } +} + +uno::Reference< io::XStream > OWriteStream_Impl::GetStream( sal_Int32 nStreamMode, const ::comphelper::SequenceAsHashMap& aEncryptionData, bool bHierarchyAccess ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ) ; + + SAL_WARN_IF( !m_xPackageStream.is(), "package.xstor", "No package stream is set!" ); + + if ( m_pAntiImpl ) + throw io::IOException(); // TODO: + + if ( !IsEncrypted() ) + throw packages::NoEncryptionException(); + + uno::Reference< io::XStream > xResultStream; + + uno::Reference< beans::XPropertySet > xPropertySet( m_xPackageStream, uno::UNO_QUERY_THROW ); + + if ( m_bHasCachedEncryptionData ) + { + if ( !::package::PackageEncryptionDataLessOrEqual( m_aEncryptionData, aEncryptionData ) ) + throw packages::WrongPasswordException(); + + // the correct key must be set already + xResultStream = GetStream_Impl( nStreamMode, bHierarchyAccess ); + } + else + { + SetEncryptionKeyProperty_Impl( xPropertySet, aEncryptionData.getAsConstNamedValueList() ); + + try { + xResultStream = GetStream_Impl( nStreamMode, bHierarchyAccess ); + + m_bUseCommonEncryption = false; // very important to set it to false + m_bHasCachedEncryptionData = true; + m_aEncryptionData = aEncryptionData; + } + catch( const packages::WrongPasswordException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + SetEncryptionKeyProperty_Impl( xPropertySet, uno::Sequence< beans::NamedValue >() ); + throw; + } + catch ( const uno::Exception& ex ) + { + TOOLS_WARN_EXCEPTION("package.xstor", "GetStream: decrypting stream failed"); + SetEncryptionKeyProperty_Impl( xPropertySet, uno::Sequence< beans::NamedValue >() ); + throw io::IOException(ex.Message); // TODO: + } + } + + SAL_WARN_IF( !xResultStream.is(), "package.xstor", "In case stream can not be retrieved an exception must be thrown!" ); + + return xResultStream; +} + +uno::Reference< io::XStream > OWriteStream_Impl::GetStream( sal_Int32 nStreamMode, bool bHierarchyAccess ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ) ; + + SAL_WARN_IF( !m_xPackageStream.is(), "package.xstor", "No package stream is set!" ); + + if ( m_pAntiImpl ) + throw io::IOException(); // TODO: + + uno::Reference< io::XStream > xResultStream; + + if ( IsEncrypted() ) + { + ::comphelper::SequenceAsHashMap aGlobalEncryptionData; + try + { + aGlobalEncryptionData = GetCommonRootEncryptionData(); + } + catch( const packages::NoEncryptionException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw packages::WrongPasswordException(); + } + + xResultStream = GetStream( nStreamMode, aGlobalEncryptionData, bHierarchyAccess ); + } + else + xResultStream = GetStream_Impl( nStreamMode, bHierarchyAccess ); + + return xResultStream; +} + +uno::Reference< io::XStream > OWriteStream_Impl::GetStream_Impl( sal_Int32 nStreamMode, bool bHierarchyAccess ) +{ + // private method, no mutex is used + GetStreamProperties(); + + // TODO/LATER: this info might be read later, on demand in future + ReadRelInfoIfNecessary(); + + if ( ( nStreamMode & embed::ElementModes::READWRITE ) == embed::ElementModes::READ ) + { + uno::Reference< io::XInputStream > xInStream; + if ( m_xCacheStream.is() || m_oTempFile.has_value() ) + xInStream = GetTempFileAsInputStream(); //TODO: + else + xInStream = m_xPackageStream->getDataStream(); + + // The stream does not exist in the storage + if ( !xInStream.is() ) + throw io::IOException(); + + rtl::Reference<OInputCompStream> pStream = new OInputCompStream( *this, xInStream, InsertOwnProps( m_aProps, m_bUseCommonEncryption ), m_nStorageType ); + m_aInputStreamsVector.push_back( pStream.get() ); + return pStream; + } + else if ( ( nStreamMode & embed::ElementModes::READWRITE ) == embed::ElementModes::SEEKABLEREAD ) + { + if ( !m_xCacheStream.is() && !m_oTempFile.has_value() && !( m_xPackageStream->getDataStream().is() ) ) + { + // The stream does not exist in the storage + throw io::IOException(); + } + + uno::Reference< io::XInputStream > xInStream = GetTempFileAsInputStream(); //TODO: + + if ( !xInStream.is() ) + throw io::IOException(); + + rtl::Reference<OInputSeekStream> pStream = new OInputSeekStream( *this, xInStream, InsertOwnProps( m_aProps, m_bUseCommonEncryption ), m_nStorageType ); + m_aInputStreamsVector.push_back( pStream.get() ); + return pStream; + } + else if ( ( nStreamMode & embed::ElementModes::WRITE ) == embed::ElementModes::WRITE ) + { + if ( !m_aInputStreamsVector.empty() ) + throw io::IOException(); // TODO: + + uno::Reference< io::XStream > xStream; + if ( ( nStreamMode & embed::ElementModes::TRUNCATE ) == embed::ElementModes::TRUNCATE ) + { + m_oTempFile.reset(); + if ( m_xCacheStream.is() ) + CleanCacheStream(); + + m_bHasDataToFlush = true; + + // this call is triggered by the parent and it will recognize the change of the state + if ( m_pParent ) + m_pParent->m_bIsModified = true; + + xStream = CreateMemoryStream( m_xContext ); + m_xCacheSeek.set( xStream, uno::UNO_QUERY_THROW ); + m_xCacheStream = xStream; + } + else if ( !m_bHasInsertedStreamOptimization ) + { + if ( !m_oTempFile.has_value() && !m_xCacheStream.is() && !( m_xPackageStream->getDataStream().is() ) ) + { + // The stream does not exist in the storage + m_bHasDataToFlush = true; + + // this call is triggered by the parent and it will recognize the change of the state + if ( m_pParent ) + m_pParent->m_bIsModified = true; + xStream = GetTempFileAsStream(); + } + + // if the stream exists the temporary file is created on demand + // xStream = GetTempFileAsStream(); + } + + rtl::Reference<OWriteStream> tmp; + assert(m_xMutex.is() && "No mutex!"); + if ( !xStream.is() ) + tmp = new OWriteStream( *this, bHierarchyAccess ); + else + tmp = new OWriteStream( *this, xStream, bHierarchyAccess ); + + m_pAntiImpl = tmp.get(); + return tmp; + } + + throw lang::IllegalArgumentException(); // TODO +} + +uno::Reference< io::XInputStream > OWriteStream_Impl::GetPlainRawInStream() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ) ; + + SAL_WARN_IF( !m_xPackageStream.is(), "package.xstor", "No package stream is set!" ); + + // this method is used only internally, this stream object should not go outside of this implementation + // if ( m_pAntiImpl ) + // throw io::IOException(); // TODO: + + return m_xPackageStream->getPlainRawStream(); +} + +uno::Reference< io::XInputStream > OWriteStream_Impl::GetRawInStream() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ) ; + + SAL_WARN_IF( !m_xPackageStream.is(), "package.xstor", "No package stream is set!" ); + + if ( m_pAntiImpl ) + throw io::IOException(); // TODO: + + SAL_WARN_IF( !IsEncrypted(), "package.xstor", "Impossible to get raw representation for nonencrypted stream!" ); + if ( !IsEncrypted() ) + throw packages::NoEncryptionException(); + + return m_xPackageStream->getRawStream(); +} + +::comphelper::SequenceAsHashMap OWriteStream_Impl::GetCommonRootEncryptionData() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ) ; + + if ( m_nStorageType != embed::StorageFormats::PACKAGE || !m_pParent ) + throw packages::NoEncryptionException(); + + return m_pParent->GetCommonRootEncryptionData(); +} + +void OWriteStream_Impl::InputStreamDisposed( OInputCompStream* pStream ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + std::erase(m_aInputStreamsVector, pStream); +} + +void OWriteStream_Impl::CreateReadonlyCopyBasedOnData( const uno::Reference< io::XInputStream >& xDataToCopy, const uno::Sequence< beans::PropertyValue >& aProps, uno::Reference< io::XStream >& xTargetStream ) +{ + uno::Reference < io::XStream > xTempFile; + if ( !xTargetStream.is() ) + xTempFile = new utl::TempFileFastService; + else + xTempFile = xTargetStream; + + uno::Reference < io::XSeekable > xTempSeek( xTempFile, uno::UNO_QUERY_THROW ); + + uno::Reference < io::XOutputStream > xTempOut(xTempFile->getOutputStream(), uno::UNO_SET_THROW); + + if ( xDataToCopy.is() ) + ::comphelper::OStorageHelper::CopyInputToOutput( xDataToCopy, xTempOut ); + + xTempOut->closeOutput(); + xTempSeek->seek( 0 ); + + uno::Reference< io::XInputStream > xInStream = xTempFile->getInputStream(); + if ( !xInStream.is() ) + throw io::IOException(); + + // TODO: remember last state of m_bUseCommonEncryption + if ( !xTargetStream.is() ) + xTargetStream.set( + new OInputSeekStream( xInStream, InsertOwnProps( aProps, m_bUseCommonEncryption ), m_nStorageType ) ); +} + +void OWriteStream_Impl::GetCopyOfLastCommit( uno::Reference< io::XStream >& xTargetStream ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + SAL_WARN_IF( !m_xPackageStream.is(), "package.xstor", "The source stream for copying is incomplete!" ); + if ( !m_xPackageStream.is() ) + throw uno::RuntimeException(); + + uno::Reference< io::XInputStream > xDataToCopy; + if ( IsEncrypted() ) + { + // an encrypted stream must contain input stream + ::comphelper::SequenceAsHashMap aGlobalEncryptionData; + try + { + aGlobalEncryptionData = GetCommonRootEncryptionData(); + } + catch( const packages::NoEncryptionException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "No Element"); + throw packages::WrongPasswordException(); + } + + GetCopyOfLastCommit( xTargetStream, aGlobalEncryptionData ); + } + else + { + xDataToCopy = m_xPackageStream->getDataStream(); + + // in case of new inserted package stream it is possible that input stream still was not set + GetStreamProperties(); + + CreateReadonlyCopyBasedOnData( xDataToCopy, m_aProps, xTargetStream ); + } +} + +void OWriteStream_Impl::GetCopyOfLastCommit( uno::Reference< io::XStream >& xTargetStream, const ::comphelper::SequenceAsHashMap& aEncryptionData ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + SAL_WARN_IF( !m_xPackageStream.is(), "package.xstor", "The source stream for copying is incomplete!" ); + if ( !m_xPackageStream.is() ) + throw uno::RuntimeException(); + + if ( !IsEncrypted() ) + throw packages::NoEncryptionException(); + + uno::Reference< io::XInputStream > xDataToCopy; + + if ( m_bHasCachedEncryptionData ) + { + // TODO: introduce last committed cashed password information and use it here + // that means "use common pass" also should be remembered on flash + uno::Sequence< beans::NamedValue > aKey = aEncryptionData.getAsConstNamedValueList(); + + uno::Reference< beans::XPropertySet > xProps( m_xPackageStream, uno::UNO_QUERY_THROW ); + + bool bEncr = false; + xProps->getPropertyValue( "Encrypted" ) >>= bEncr; + if ( !bEncr ) + throw packages::NoEncryptionException(); + + uno::Sequence< beans::NamedValue > aPackKey; + xProps->getPropertyValue( STORAGE_ENCRYPTION_KEYS_PROPERTY ) >>= aPackKey; + if ( !SequencesEqual( aKey, aPackKey ) ) + throw packages::WrongPasswordException(); + + // the correct key must be set already + xDataToCopy = m_xPackageStream->getDataStream(); + } + else + { + uno::Reference< beans::XPropertySet > xPropertySet( m_xPackageStream, uno::UNO_QUERY ); + SetEncryptionKeyProperty_Impl( xPropertySet, aEncryptionData.getAsConstNamedValueList() ); + + try { + xDataToCopy = m_xPackageStream->getDataStream(); + + if ( !xDataToCopy.is() ) + { + SAL_WARN( "package.xstor", "Encrypted ZipStream must already have input stream inside!" ); + SetEncryptionKeyProperty_Impl( xPropertySet, uno::Sequence< beans::NamedValue >() ); + } + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "package.xstor", "Can't open encrypted stream"); + SetEncryptionKeyProperty_Impl( xPropertySet, uno::Sequence< beans::NamedValue >() ); + throw; + } + + SetEncryptionKeyProperty_Impl( xPropertySet, uno::Sequence< beans::NamedValue >() ); + } + + // in case of new inserted package stream it is possible that input stream still was not set + GetStreamProperties(); + + CreateReadonlyCopyBasedOnData( xDataToCopy, m_aProps, xTargetStream ); +} + +void OWriteStream_Impl::CommitStreamRelInfo( const uno::Reference< embed::XStorage >& xRelStorage, std::u16string_view aOrigStreamName, std::u16string_view aNewStreamName ) +{ + // at this point of time the old stream must be already cleaned + OSL_ENSURE( m_nStorageType == embed::StorageFormats::OFOPXML, "The method should be used only with OFOPXML format!" ); + + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + return; + + OSL_ENSURE( !aOrigStreamName.empty() && !aNewStreamName.empty() && xRelStorage.is(), + "Wrong relation persistence information is provided!" ); + + if ( !xRelStorage.is() || aOrigStreamName.empty() || aNewStreamName.empty() ) + throw uno::RuntimeException(); + + if ( m_nRelInfoStatus == RELINFO_BROKEN || m_nRelInfoStatus == RELINFO_CHANGED_BROKEN ) + throw io::IOException(); // TODO: + + OUString aOrigRelStreamName = OUString::Concat(aOrigStreamName) + ".rels"; + OUString aNewRelStreamName = OUString::Concat(aNewStreamName) + ".rels"; + + bool bRenamed = aOrigRelStreamName != aNewRelStreamName; + if ( m_nRelInfoStatus == RELINFO_CHANGED + || m_nRelInfoStatus == RELINFO_CHANGED_STREAM_READ + || m_nRelInfoStatus == RELINFO_CHANGED_STREAM ) + { + if ( bRenamed && xRelStorage->hasByName( aOrigRelStreamName ) ) + xRelStorage->removeElement( aOrigRelStreamName ); + + if ( m_nRelInfoStatus == RELINFO_CHANGED ) + { + if ( m_aNewRelInfo.hasElements() ) + { + uno::Reference< io::XStream > xRelsStream = + xRelStorage->openStreamElement( aNewRelStreamName, + embed::ElementModes::TRUNCATE | embed::ElementModes::READWRITE ); + + uno::Reference< io::XOutputStream > xOutStream = xRelsStream->getOutputStream(); + if ( !xOutStream.is() ) + throw uno::RuntimeException(); + + ::comphelper::OFOPXMLHelper::WriteRelationsInfoSequence( xOutStream, m_aNewRelInfo, m_xContext ); + + // set the mediatype + uno::Reference< beans::XPropertySet > xPropSet( xRelsStream, uno::UNO_QUERY_THROW ); + xPropSet->setPropertyValue( + "MediaType", + uno::Any( OUString("application/vnd.openxmlformats-package.relationships+xml" ) ) ); + + m_nRelInfoStatus = RELINFO_READ; + } + } + else if ( m_nRelInfoStatus == RELINFO_CHANGED_STREAM_READ + || m_nRelInfoStatus == RELINFO_CHANGED_STREAM ) + { + uno::Reference< io::XStream > xRelsStream = + xRelStorage->openStreamElement( aNewRelStreamName, + embed::ElementModes::TRUNCATE | embed::ElementModes::READWRITE ); + + uno::Reference< io::XOutputStream > xOutputStream = xRelsStream->getOutputStream(); + if ( !xOutputStream.is() ) + throw uno::RuntimeException(); + + uno::Reference< io::XSeekable > xSeek( m_xNewRelInfoStream, uno::UNO_QUERY_THROW ); + xSeek->seek( 0 ); + ::comphelper::OStorageHelper::CopyInputToOutput( m_xNewRelInfoStream, xOutputStream ); + xSeek->seek( 0 ); + + // set the mediatype + uno::Reference< beans::XPropertySet > xPropSet( xRelsStream, uno::UNO_QUERY_THROW ); + xPropSet->setPropertyValue("MediaType", + uno::Any( OUString("application/vnd.openxmlformats-package.relationships+xml" ) ) ); + + if ( m_nRelInfoStatus == RELINFO_CHANGED_STREAM ) + m_nRelInfoStatus = RELINFO_NO_INIT; + else + { + // the information is already parsed and the stream is stored, no need in temporary stream any more + m_xNewRelInfoStream.clear(); + m_nRelInfoStatus = RELINFO_READ; + } + } + + // the original stream makes no sense after this step + m_xOrigRelInfoStream = m_xNewRelInfoStream; + m_aOrigRelInfo = m_aNewRelInfo; + m_bOrigRelInfoBroken = false; + m_aNewRelInfo = uno::Sequence< uno::Sequence< beans::StringPair > >(); + m_xNewRelInfoStream.clear(); + } + else + { + // the stream is not changed but it might be renamed + if ( bRenamed && xRelStorage->hasByName( aOrigRelStreamName ) ) + xRelStorage->renameElement( aOrigRelStreamName, aNewRelStreamName ); + } +} + +// OWriteStream implementation + +OWriteStream::OWriteStream( OWriteStream_Impl& rImpl, bool bTransacted ) +: m_pImpl( &rImpl ) +, m_xSharedMutex( rImpl.m_xMutex ) +, m_aListenersContainer( rImpl.m_xMutex->GetMutex() ) +, m_nStorageType( m_pImpl->m_nStorageType ) +, m_bInStreamDisconnected( false ) +, m_bInitOnDemand( true ) +, m_nInitPosition( 0 ) +, m_bTransacted( bTransacted ) +{ +} + +OWriteStream::OWriteStream( OWriteStream_Impl& rImpl, uno::Reference< io::XStream > const & xStream, bool bTransacted ) +: m_pImpl( &rImpl ) +, m_xSharedMutex( rImpl.m_xMutex ) +, m_aListenersContainer( rImpl.m_xMutex->GetMutex() ) +, m_nStorageType( m_pImpl->m_nStorageType ) +, m_bInStreamDisconnected( false ) +, m_bInitOnDemand( false ) +, m_nInitPosition( 0 ) +, m_bTransacted( bTransacted ) +{ + if ( xStream.is() ) + { + m_xInStream = xStream->getInputStream(); + m_xOutStream = xStream->getOutputStream(); + m_xSeekable.set( xStream, uno::UNO_QUERY ); + OSL_ENSURE( m_xInStream.is() && m_xOutStream.is() && m_xSeekable.is(), "Stream implementation is incomplete!" ); + } +} + +OWriteStream::~OWriteStream() +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + if ( m_pImpl ) + { + osl_atomic_increment(&m_refCount); + try { + dispose(); + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Quiet exception"); + } + } +} + +void OWriteStream::DeInit() +{ + if ( !m_pImpl ) + return; // do nothing + + if ( m_xSeekable.is() ) + m_nInitPosition = m_xSeekable->getPosition(); + + m_xInStream.clear(); + m_xOutStream.clear(); + m_xSeekable.clear(); + m_bInitOnDemand = true; +} + +void OWriteStream::CheckInitOnDemand() +{ + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( !m_bInitOnDemand ) + return; + + SAL_INFO( "package.xstor", "package (mv76033) OWriteStream::CheckInitOnDemand, initializing" ); + uno::Reference< io::XStream > xStream = m_pImpl->GetTempFileAsStream(); + if ( xStream.is() ) + { + m_xInStream.set( xStream->getInputStream(), uno::UNO_SET_THROW ); + m_xOutStream.set( xStream->getOutputStream(), uno::UNO_SET_THROW ); + m_xSeekable.set( xStream, uno::UNO_QUERY_THROW ); + m_xSeekable->seek( m_nInitPosition ); + + m_nInitPosition = 0; + m_bInitOnDemand = false; + } +} + +void OWriteStream::CopyToStreamInternally_Impl( const uno::Reference< io::XStream >& xDest ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + CheckInitOnDemand(); + + if ( !m_xInStream.is() ) + throw uno::RuntimeException(); + + if ( !m_xSeekable.is() ) + throw uno::RuntimeException(); + + uno::Reference< beans::XPropertySet > xDestProps( xDest, uno::UNO_QUERY_THROW ); + + uno::Reference< io::XOutputStream > xDestOutStream = xDest->getOutputStream(); + if ( !xDestOutStream.is() ) + throw io::IOException(); // TODO + + sal_Int64 nCurPos = m_xSeekable->getPosition(); + m_xSeekable->seek( 0 ); + + uno::Exception eThrown; + bool bThrown = false; + try { + ::comphelper::OStorageHelper::CopyInputToOutput( m_xInStream, xDestOutStream ); + } + catch ( const uno::Exception& e ) + { + eThrown = e; + bThrown = true; + } + + // position-related section below is critical + // if it fails the stream will become invalid + try { + m_xSeekable->seek( nCurPos ); + } + catch ( const uno::Exception& ) + { + // TODO: set the stream in invalid state or dispose + TOOLS_WARN_EXCEPTION( "package.xstor", "The stream become invalid during copying" ); + throw uno::RuntimeException(); + } + + if ( bThrown ) + throw eThrown; + + // now the properties can be copied + // the order of the properties setting is not important for StorageStream API + OUString aPropName ("Compressed"); + xDestProps->setPropertyValue( aPropName, getPropertyValue( aPropName ) ); + if ( m_nStorageType == embed::StorageFormats::PACKAGE || m_nStorageType == embed::StorageFormats::OFOPXML ) + { + aPropName = "MediaType"; + xDestProps->setPropertyValue( aPropName, getPropertyValue( aPropName ) ); + + if ( m_nStorageType == embed::StorageFormats::PACKAGE ) + { + aPropName = "UseCommonStoragePasswordEncryption"; + xDestProps->setPropertyValue( aPropName, getPropertyValue( aPropName ) ); + } + } +} + +void OWriteStream::ModifyParentUnlockMutex_Impl(osl::ClearableMutexGuard& aGuard) +{ + if ( m_pImpl->m_pParent ) + { + if ( m_pImpl->m_pParent->HasModifiedListener() ) + { + uno::Reference< util::XModifiable > xParentModif( static_cast<util::XModifiable*>(m_pImpl->m_pParent->m_pAntiImpl) ); + aGuard.clear(); + xParentModif->setModified( true ); + } + else + m_pImpl->m_pParent->m_bIsModified = true; + } +} + +uno::Any SAL_CALL OWriteStream::queryInterface( const uno::Type& rType ) +{ + // common interfaces + uno::Any aReturn = ::cppu::queryInterface + ( rType + , static_cast<lang::XTypeProvider*> ( this ) + , static_cast<io::XInputStream*> ( this ) + , static_cast<io::XOutputStream*> ( this ) + , static_cast<io::XStream*> ( this ) + , static_cast<embed::XExtendedStorageStream*> ( this ) + , static_cast<io::XSeekable*> ( this ) + , static_cast<io::XTruncate*> ( this ) + , static_cast<lang::XComponent*> ( this ) + , static_cast<beans::XPropertySet*> ( this ) ); + + if ( aReturn.hasValue() ) + return aReturn ; + + if ( m_nStorageType == embed::StorageFormats::PACKAGE ) + { + aReturn = ::cppu::queryInterface + ( rType + , static_cast<embed::XEncryptionProtectedSource2*> ( this ) + , static_cast<embed::XEncryptionProtectedSource*> ( this ) ); + } + else if ( m_nStorageType == embed::StorageFormats::OFOPXML ) + { + aReturn = ::cppu::queryInterface + ( rType + , static_cast<embed::XRelationshipAccess*> ( this ) ); + } + + if ( aReturn.hasValue() ) + return aReturn ; + + if ( m_bTransacted ) + { + aReturn = ::cppu::queryInterface + ( rType + , static_cast<embed::XTransactedObject*> ( this ) + , static_cast<embed::XTransactionBroadcaster*> ( this ) ); + + if ( aReturn.hasValue() ) + return aReturn ; + } + + return OWeakObject::queryInterface( rType ); +} + +void SAL_CALL OWriteStream::acquire() noexcept +{ + OWeakObject::acquire(); +} + +void SAL_CALL OWriteStream::release() noexcept +{ + OWeakObject::release(); +} + +uno::Sequence< uno::Type > SAL_CALL OWriteStream::getTypes() +{ + if (! m_oTypeCollection) + { + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if (! m_oTypeCollection) + { + if ( m_bTransacted ) + { + if ( m_nStorageType == embed::StorageFormats::PACKAGE ) + { + ::cppu::OTypeCollection aTmpCollection + ( cppu::UnoType<lang::XTypeProvider>::get() + , cppu::UnoType<io::XInputStream>::get() + , cppu::UnoType<io::XOutputStream>::get() + , cppu::UnoType<io::XStream>::get() + , cppu::UnoType<io::XSeekable>::get() + , cppu::UnoType<io::XTruncate>::get() + , cppu::UnoType<lang::XComponent>::get() + , cppu::UnoType<embed::XEncryptionProtectedSource2>::get() + , cppu::UnoType<embed::XEncryptionProtectedSource>::get() + , cppu::UnoType<embed::XExtendedStorageStream>::get() + , cppu::UnoType<embed::XTransactedObject>::get() + , cppu::UnoType<embed::XTransactionBroadcaster>::get()); + + m_oTypeCollection.emplace( + cppu::UnoType<beans::XPropertySet>::get() + , aTmpCollection.getTypes()); + } + else if ( m_nStorageType == embed::StorageFormats::OFOPXML ) + { + m_oTypeCollection.emplace( + cppu::UnoType<lang::XTypeProvider>::get() + , cppu::UnoType<io::XInputStream>::get() + , cppu::UnoType<io::XOutputStream>::get() + , cppu::UnoType<io::XStream>::get() + , cppu::UnoType<io::XSeekable>::get() + , cppu::UnoType<io::XTruncate>::get() + , cppu::UnoType<lang::XComponent>::get() + , cppu::UnoType<embed::XRelationshipAccess>::get() + , cppu::UnoType<embed::XExtendedStorageStream>::get() + , cppu::UnoType<embed::XTransactedObject>::get() + , cppu::UnoType<embed::XTransactionBroadcaster>::get() + , cppu::UnoType<beans::XPropertySet>::get()); + } + else // if ( m_pData->m_nStorageType == embed::StorageFormats::ZIP ) + { + m_oTypeCollection.emplace( + cppu::UnoType<lang::XTypeProvider>::get() + , cppu::UnoType<io::XInputStream>::get() + , cppu::UnoType<io::XOutputStream>::get() + , cppu::UnoType<io::XStream>::get() + , cppu::UnoType<io::XSeekable>::get() + , cppu::UnoType<io::XTruncate>::get() + , cppu::UnoType<lang::XComponent>::get() + , cppu::UnoType<embed::XExtendedStorageStream>::get() + , cppu::UnoType<embed::XTransactedObject>::get() + , cppu::UnoType<embed::XTransactionBroadcaster>::get() + , cppu::UnoType<beans::XPropertySet>::get()); + } + } + else + { + if ( m_nStorageType == embed::StorageFormats::PACKAGE ) + { + m_oTypeCollection.emplace( + cppu::UnoType<lang::XTypeProvider>::get() + , cppu::UnoType<io::XInputStream>::get() + , cppu::UnoType<io::XOutputStream>::get() + , cppu::UnoType<io::XStream>::get() + , cppu::UnoType<io::XSeekable>::get() + , cppu::UnoType<io::XTruncate>::get() + , cppu::UnoType<lang::XComponent>::get() + , cppu::UnoType<embed::XEncryptionProtectedSource2>::get() + , cppu::UnoType<embed::XEncryptionProtectedSource>::get() + , cppu::UnoType<beans::XPropertySet>::get()); + } + else if ( m_nStorageType == embed::StorageFormats::OFOPXML ) + { + m_oTypeCollection.emplace( + cppu::UnoType<lang::XTypeProvider>::get() + , cppu::UnoType<io::XInputStream>::get() + , cppu::UnoType<io::XOutputStream>::get() + , cppu::UnoType<io::XStream>::get() + , cppu::UnoType<io::XSeekable>::get() + , cppu::UnoType<io::XTruncate>::get() + , cppu::UnoType<lang::XComponent>::get() + , cppu::UnoType<embed::XRelationshipAccess>::get() + , cppu::UnoType<beans::XPropertySet>::get()); + } + else // if ( m_pData->m_nStorageType == embed::StorageFormats::ZIP ) + { + m_oTypeCollection.emplace( + cppu::UnoType<lang::XTypeProvider>::get() + , cppu::UnoType<io::XInputStream>::get() + , cppu::UnoType<io::XOutputStream>::get() + , cppu::UnoType<io::XStream>::get() + , cppu::UnoType<io::XSeekable>::get() + , cppu::UnoType<io::XTruncate>::get() + , cppu::UnoType<lang::XComponent>::get() + , cppu::UnoType<beans::XPropertySet>::get()); + } + } + } + } + + return m_oTypeCollection->getTypes() ; +} + +uno::Sequence< sal_Int8 > SAL_CALL OWriteStream::getImplementationId() +{ + static const comphelper::UnoIdInit lcl_ImplId; + return lcl_ImplId.getSeq(); +} + +sal_Int32 SAL_CALL OWriteStream::readBytes( uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + CheckInitOnDemand(); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( !m_xInStream.is() ) + throw io::NotConnectedException(); + + return m_xInStream->readBytes( aData, nBytesToRead ); +} + +sal_Int32 SAL_CALL OWriteStream::readSomeBytes( uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + CheckInitOnDemand(); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( !m_xInStream.is() ) + throw io::NotConnectedException(); + + return m_xInStream->readSomeBytes( aData, nMaxBytesToRead ); +} + +void SAL_CALL OWriteStream::skipBytes( sal_Int32 nBytesToSkip ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + CheckInitOnDemand(); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( !m_xInStream.is() ) + throw io::NotConnectedException(); + + m_xInStream->skipBytes( nBytesToSkip ); +} + +sal_Int32 SAL_CALL OWriteStream::available( ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + CheckInitOnDemand(); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( !m_xInStream.is() ) + throw io::NotConnectedException(); + + return m_xInStream->available(); + +} + +void SAL_CALL OWriteStream::closeInput( ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( !m_bInitOnDemand && ( m_bInStreamDisconnected || !m_xInStream.is() ) ) + throw io::NotConnectedException(); + + // the input part of the stream stays open for internal purposes (to allow reading during copying) + // since it can not be reopened until output part is closed, it will be closed with output part. + m_bInStreamDisconnected = true; + // m_xInStream->closeInput(); + // m_xInStream.clear(); + + if ( !m_xOutStream.is() ) + dispose(); +} + +uno::Reference< io::XInputStream > SAL_CALL OWriteStream::getInputStream() +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( !m_bInitOnDemand && ( m_bInStreamDisconnected || !m_xInStream.is() ) ) + return uno::Reference< io::XInputStream >(); + + return this; +} + +uno::Reference< io::XOutputStream > SAL_CALL OWriteStream::getOutputStream() +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + try + { + CheckInitOnDemand(); + } + catch( const io::IOException& r ) + { + throw lang::WrappedTargetRuntimeException("OWriteStream::getOutputStream: Could not create backing temp file", + getXWeak(), css::uno::Any ( r ) ); + } + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( !m_xOutStream.is() ) + return uno::Reference< io::XOutputStream >(); + + return this; +} + +void SAL_CALL OWriteStream::writeBytes( const uno::Sequence< sal_Int8 >& aData ) +{ + osl::ClearableMutexGuard aGuard(m_xSharedMutex->GetMutex()); + + // the write method makes initialization itself, since it depends from the aData length + // NO CheckInitOnDemand()! + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( !m_bInitOnDemand ) + { + if ( !m_xOutStream.is() || !m_xSeekable.is()) + throw io::NotConnectedException(); + + if ( m_pImpl->m_xCacheStream.is() ) + { + // check whether the cache should be turned off + sal_Int64 nPos = m_xSeekable->getPosition(); + if ( nPos + aData.getLength() > MAX_STORCACHE_SIZE ) + { + // disconnect the cache and copy the data to the temporary file + m_xSeekable->seek( 0 ); + + // it is enough to copy the cached stream, the cache should already contain everything + m_pImpl->GetFilledTempFileIfNo( m_xInStream ); + if ( m_pImpl->m_oTempFile.has_value() ) + { + DeInit(); + // the last position is known and it is differs from the current stream position + m_nInitPosition = nPos; + } + } + } + } + + if ( m_bInitOnDemand ) + { + SAL_INFO( "package.xstor", "package (mv76033) OWriteStream::CheckInitOnDemand, initializing" ); + uno::Reference< io::XStream > xStream = m_pImpl->GetTempFileAsStream(); + if ( xStream.is() ) + { + m_xInStream.set( xStream->getInputStream(), uno::UNO_SET_THROW ); + m_xOutStream.set( xStream->getOutputStream(), uno::UNO_SET_THROW ); + m_xSeekable.set( xStream, uno::UNO_QUERY_THROW ); + m_xSeekable->seek( m_nInitPosition ); + + m_nInitPosition = 0; + m_bInitOnDemand = false; + } + } + + if ( !m_xOutStream.is() ) + throw io::NotConnectedException(); + + m_xOutStream->writeBytes( aData ); + m_pImpl->m_bHasDataToFlush = true; + + ModifyParentUnlockMutex_Impl( aGuard ); +} + +void OWriteStream::writeBytes( const sal_Int8* pData, sal_Int32 nBytesToWrite ) +{ + assert(nBytesToWrite >= 0); + + osl::ClearableMutexGuard aGuard(m_xSharedMutex->GetMutex()); + + // the write method makes initialization itself, since it depends from the aData length + // NO CheckInitOnDemand()! + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( !m_bInitOnDemand ) + { + if ( !m_xOutStream.is() || !m_xSeekable.is()) + throw io::NotConnectedException(); + + if ( m_pImpl->m_xCacheStream.is() ) + { + // check whether the cache should be turned off + sal_Int64 nPos = m_xSeekable->getPosition(); + if ( nPos + nBytesToWrite > MAX_STORCACHE_SIZE ) + { + // disconnect the cache and copy the data to the temporary file + m_xSeekable->seek( 0 ); + + // it is enough to copy the cached stream, the cache should already contain everything + m_pImpl->GetFilledTempFileIfNo( m_xInStream ); + if ( m_pImpl->m_oTempFile.has_value() ) + { + DeInit(); + // the last position is known and it is differs from the current stream position + m_nInitPosition = nPos; + } + } + } + } + + if ( m_bInitOnDemand ) + { + SAL_INFO( "package.xstor", "package (mv76033) OWriteStream::CheckInitOnDemand, initializing" ); + uno::Reference< io::XStream > xStream = m_pImpl->GetTempFileAsStream(); + if ( xStream.is() ) + { + m_xInStream.set( xStream->getInputStream(), uno::UNO_SET_THROW ); + m_xOutStream.set( xStream->getOutputStream(), uno::UNO_SET_THROW ); + m_xSeekable.set( xStream, uno::UNO_QUERY_THROW ); + m_xSeekable->seek( m_nInitPosition ); + + m_nInitPosition = 0; + m_bInitOnDemand = false; + } + } + + if ( !m_xOutStream.is() ) + throw io::NotConnectedException(); + + if (auto pByteWriter = dynamic_cast< comphelper::ByteWriter* >( m_xOutStream.get() )) + pByteWriter->writeBytes(pData, nBytesToWrite); + else + { + uno::Sequence<sal_Int8> aData(pData, nBytesToWrite); + m_xOutStream->writeBytes( aData ); + } + m_pImpl->m_bHasDataToFlush = true; + + ModifyParentUnlockMutex_Impl( aGuard ); +} + +void SAL_CALL OWriteStream::flush() +{ + // In case stream is flushed its current version becomes visible + // to the parent storage. Usually parent storage flushes the stream + // during own commit but a user can explicitly flush the stream + // so the changes will be available through cloning functionality. + + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( !m_bInitOnDemand ) + { + if ( !m_xOutStream.is() ) + throw io::NotConnectedException(); + + m_xOutStream->flush(); + m_pImpl->Commit(); + } +} + +void OWriteStream::CloseOutput_Impl() +{ + // all the checks must be done in calling method + + m_xOutStream->closeOutput(); + m_xOutStream.clear(); + + if ( m_bInitOnDemand ) + return; + + // after the stream is disposed it can be committed + // so transport correct size property + if ( !m_xSeekable.is() ) + throw uno::RuntimeException(); + + for ( auto& rProp : asNonConstRange(m_pImpl->m_aProps) ) + { + if ( rProp.Name == "Size" ) + rProp.Value <<= m_xSeekable->getLength(); + } +} + +void SAL_CALL OWriteStream::closeOutput() +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + CheckInitOnDemand(); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( !m_xOutStream.is() ) + throw io::NotConnectedException(); + + CloseOutput_Impl(); + + if ( m_bInStreamDisconnected || !m_xInStream.is() ) + dispose(); +} + +void SAL_CALL OWriteStream::seek( sal_Int64 location ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + CheckInitOnDemand(); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( !m_xSeekable.is() ) + throw uno::RuntimeException(); + + m_xSeekable->seek( location ); +} + +sal_Int64 SAL_CALL OWriteStream::getPosition() +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + CheckInitOnDemand(); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( !m_xSeekable.is() ) + throw uno::RuntimeException(); + + return m_xSeekable->getPosition(); +} + +sal_Int64 SAL_CALL OWriteStream::getLength() +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + CheckInitOnDemand(); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( !m_xSeekable.is() ) + throw uno::RuntimeException(); + + return m_xSeekable->getLength(); +} + +void SAL_CALL OWriteStream::truncate() +{ + osl::ClearableMutexGuard aGuard(m_xSharedMutex->GetMutex()); + + CheckInitOnDemand(); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( !m_xOutStream.is() ) + throw uno::RuntimeException(); + + uno::Reference< io::XTruncate > xTruncate( m_xOutStream, uno::UNO_QUERY_THROW ); + xTruncate->truncate(); + + m_pImpl->m_bHasDataToFlush = true; + + ModifyParentUnlockMutex_Impl( aGuard ); +} + +void SAL_CALL OWriteStream::dispose() +{ + // should be an internal method since it can be called only from parent storage + { + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( m_xOutStream.is() ) + CloseOutput_Impl(); + + if ( m_xInStream.is() ) + { + m_xInStream->closeInput(); + m_xInStream.clear(); + } + + m_xSeekable.clear(); + + m_pImpl->m_pAntiImpl = nullptr; + + if ( !m_bInitOnDemand ) + { + try + { + if ( !m_bTransacted ) + { + m_pImpl->Commit(); + } + else + { + // throw away all the changes + m_pImpl->Revert(); + } + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + throw lang::WrappedTargetRuntimeException("Can not commit/revert the storage!", + getXWeak(), + aCaught ); + } + } + + m_pImpl = nullptr; + } + + // the listener might try to get rid of parent storage, and the storage would delete this object; + // for now the listener is just notified at the end of the method to workaround the problem + // in future a more elegant way should be found + + lang::EventObject aSource( getXWeak() ); + m_aListenersContainer.disposeAndClear( aSource ); +} + +void SAL_CALL OWriteStream::addEventListener( + const uno::Reference< lang::XEventListener >& xListener ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + m_aListenersContainer.addInterface( cppu::UnoType<lang::XEventListener>::get(), + xListener ); +} + +void SAL_CALL OWriteStream::removeEventListener( + const uno::Reference< lang::XEventListener >& xListener ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + m_aListenersContainer.removeInterface( cppu::UnoType<lang::XEventListener>::get(), + xListener ); +} + +void SAL_CALL OWriteStream::setEncryptionPassword( const OUString& aPass ) +{ + osl::ClearableMutexGuard aGuard(m_xSharedMutex->GetMutex()); + + CheckInitOnDemand(); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + OSL_ENSURE( m_pImpl->m_xPackageStream.is(), "No package stream is set!" ); + + m_pImpl->SetEncrypted( ::comphelper::OStorageHelper::CreatePackageEncryptionData( aPass ) ); + + ModifyParentUnlockMutex_Impl( aGuard ); +} + +void SAL_CALL OWriteStream::removeEncryption() +{ + osl::ClearableMutexGuard aGuard(m_xSharedMutex->GetMutex()); + + CheckInitOnDemand(); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + OSL_ENSURE( m_pImpl->m_xPackageStream.is(), "No package stream is set!" ); + + m_pImpl->SetDecrypted(); + + ModifyParentUnlockMutex_Impl( aGuard ); +} + +void SAL_CALL OWriteStream::setEncryptionData( const uno::Sequence< beans::NamedValue >& aEncryptionData ) +{ + osl::ClearableMutexGuard aGuard(m_xSharedMutex->GetMutex()); + + CheckInitOnDemand(); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + OSL_ENSURE( m_pImpl->m_xPackageStream.is(), "No package stream is set!" ); + + m_pImpl->SetEncrypted( aEncryptionData ); + + ModifyParentUnlockMutex_Impl( aGuard ); +} + +sal_Bool SAL_CALL OWriteStream::hasEncryptionData() +{ + osl::ClearableMutexGuard aGuard(m_xSharedMutex->GetMutex()); + + if (!m_pImpl) + return false; + + bool bRet = false; + + try + { + bRet = m_pImpl->IsEncrypted(); + + if (!bRet && m_pImpl->m_bUseCommonEncryption && m_pImpl->m_pParent) + bRet = m_pImpl->m_pParent->m_bHasCommonEncryptionData; + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + throw lang::WrappedTargetRuntimeException( "Problems on hasEncryptionData!", + getXWeak(), + aCaught ); + } + + return bRet; +} + +sal_Bool SAL_CALL OWriteStream::hasByID( const OUString& sID ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException(); + + try + { + getRelationshipByID( sID ); + return true; + } + catch( const container::NoSuchElementException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "No Element"); + } + + return false; +} + +OUString SAL_CALL OWriteStream::getTargetByID( const OUString& sID ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException(); + + const uno::Sequence< beans::StringPair > aSeq = getRelationshipByID( sID ); + auto pRel = lcl_findPairByName(aSeq, "Target"); + if (pRel != aSeq.end()) + return pRel->Second; + + return OUString(); +} + +OUString SAL_CALL OWriteStream::getTypeByID( const OUString& sID ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException(); + + const uno::Sequence< beans::StringPair > aSeq = getRelationshipByID( sID ); + auto pRel = lcl_findPairByName(aSeq, "Type"); + if (pRel != aSeq.end()) + return pRel->Second; + + return OUString(); +} + +uno::Sequence< beans::StringPair > SAL_CALL OWriteStream::getRelationshipByID( const OUString& sID ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException(); + + // TODO/LATER: in future the unification of the ID could be checked + const uno::Sequence< uno::Sequence< beans::StringPair > > aSeq = getAllRelationships(); + const beans::StringPair aIDRel("Id", sID); + auto pRel = std::find_if(aSeq.begin(), aSeq.end(), + [&aIDRel](const uno::Sequence<beans::StringPair>& rRel) { + return std::find(rRel.begin(), rRel.end(), aIDRel) != rRel.end(); }); + if (pRel != aSeq.end()) + return *pRel; + + throw container::NoSuchElementException(); +} + +uno::Sequence< uno::Sequence< beans::StringPair > > SAL_CALL OWriteStream::getRelationshipsByType( const OUString& sType ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException(); + + // TODO/LATER: in future the unification of the ID could be checked + const uno::Sequence< uno::Sequence< beans::StringPair > > aSeq = getAllRelationships(); + const beans::StringPair aTypeRel("Type", sType); + std::vector< uno::Sequence<beans::StringPair> > aResult; + aResult.reserve(aSeq.getLength()); + + std::copy_if(aSeq.begin(), aSeq.end(), std::back_inserter(aResult), + [&aTypeRel](const uno::Sequence<beans::StringPair>& rRel) { + return std::find(rRel.begin(), rRel.end(), aTypeRel) != rRel.end(); }); + + return comphelper::containerToSequence(aResult); +} + +uno::Sequence< uno::Sequence< beans::StringPair > > SAL_CALL OWriteStream::getAllRelationships() +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException(); + + return m_pImpl->GetAllRelationshipsIfAny(); +} + +void SAL_CALL OWriteStream::insertRelationshipByID( const OUString& sID, const uno::Sequence< beans::StringPair >& aEntry, sal_Bool bReplace ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException(); + + const beans::StringPair aIDRel("Id", sID); + + uno::Sequence<beans::StringPair>* pPair = nullptr; + + // TODO/LATER: in future the unification of the ID could be checked + uno::Sequence< uno::Sequence< beans::StringPair > > aSeq = getAllRelationships(); + for ( sal_Int32 nInd = 0; nInd < aSeq.getLength(); nInd++ ) + { + const auto& rRel = aSeq[nInd]; + if (std::find(rRel.begin(), rRel.end(), aIDRel) != rRel.end()) + pPair = &aSeq.getArray()[nInd]; + } + + if ( pPair && !bReplace ) + throw container::ElementExistException(); // TODO + + if ( !pPair ) + { + sal_Int32 nIDInd = aSeq.getLength(); + aSeq.realloc( nIDInd + 1 ); + pPair = &aSeq.getArray()[nIDInd]; + } + + std::vector<beans::StringPair> aResult; + aResult.reserve(aEntry.getLength() + 1); + + aResult.push_back(aIDRel); + std::copy_if(aEntry.begin(), aEntry.end(), std::back_inserter(aResult), + [](const beans::StringPair& rRel) { return rRel.First != "Id"; }); + + *pPair = comphelper::containerToSequence(aResult); + + m_pImpl->m_aNewRelInfo = aSeq; + m_pImpl->m_xNewRelInfoStream.clear(); + m_pImpl->m_nRelInfoStatus = RELINFO_CHANGED; +} + +void SAL_CALL OWriteStream::removeRelationshipByID( const OUString& sID ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException(); + + uno::Sequence< uno::Sequence< beans::StringPair > > aSeq = getAllRelationships(); + const beans::StringPair aIDRel("Id", sID); + auto pRel = std::find_if(std::cbegin(aSeq), std::cend(aSeq), + [&aIDRel](const uno::Sequence< beans::StringPair >& rRel) { + return std::find(rRel.begin(), rRel.end(), aIDRel) != rRel.end(); }); + if (pRel != std::cend(aSeq)) + { + auto nInd = static_cast<sal_Int32>(std::distance(std::cbegin(aSeq), pRel)); + comphelper::removeElementAt(aSeq, nInd); + + m_pImpl->m_aNewRelInfo = aSeq; + m_pImpl->m_xNewRelInfoStream.clear(); + m_pImpl->m_nRelInfoStatus = RELINFO_CHANGED; + + // TODO/LATER: in future the unification of the ID could be checked + return; + } + + throw container::NoSuchElementException(); +} + +void SAL_CALL OWriteStream::insertRelationships( const uno::Sequence< uno::Sequence< beans::StringPair > >& aEntries, sal_Bool bReplace ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException(); + + OUString aIDTag( "Id" ); + const uno::Sequence< uno::Sequence< beans::StringPair > > aSeq = getAllRelationships(); + std::vector< uno::Sequence<beans::StringPair> > aResultVec; + aResultVec.reserve(aSeq.getLength() + aEntries.getLength()); + + std::copy_if(aSeq.begin(), aSeq.end(), std::back_inserter(aResultVec), + [&aIDTag, &aEntries, bReplace](const uno::Sequence<beans::StringPair>& rTargetRel) { + auto pTargetPair = lcl_findPairByName(rTargetRel, aIDTag); + if (pTargetPair == rTargetRel.end()) + return false; + + bool bIsSourceSame = std::any_of(aEntries.begin(), aEntries.end(), + [&pTargetPair](const uno::Sequence<beans::StringPair>& rSourceEntry) { + return std::find(rSourceEntry.begin(), rSourceEntry.end(), *pTargetPair) != rSourceEntry.end(); }); + + if ( bIsSourceSame && !bReplace ) + throw container::ElementExistException(); + + // if no such element in the provided sequence + return !bIsSourceSame; + }); + + std::transform(aEntries.begin(), aEntries.end(), std::back_inserter(aResultVec), + [&aIDTag](const uno::Sequence<beans::StringPair>& rEntry) -> uno::Sequence<beans::StringPair> { + auto pPair = lcl_findPairByName(rEntry, aIDTag); + if (pPair == rEntry.end()) + throw io::IOException(); // TODO: illegal relation ( no ID ) + + auto aResult = comphelper::sequenceToContainer<std::vector<beans::StringPair>>(rEntry); + auto nIDInd = std::distance(rEntry.begin(), pPair); + std::rotate(aResult.begin(), std::next(aResult.begin(), nIDInd), std::next(aResult.begin(), nIDInd + 1)); + + return comphelper::containerToSequence(aResult); + }); + + m_pImpl->m_aNewRelInfo = comphelper::containerToSequence(aResultVec); + m_pImpl->m_xNewRelInfoStream.clear(); + m_pImpl->m_nRelInfoStatus = RELINFO_CHANGED; +} + +void SAL_CALL OWriteStream::clearRelationships() +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException(); + + m_pImpl->m_aNewRelInfo.realloc( 0 ); + m_pImpl->m_xNewRelInfoStream.clear(); + m_pImpl->m_nRelInfoStatus = RELINFO_CHANGED; +} + +uno::Reference< beans::XPropertySetInfo > SAL_CALL OWriteStream::getPropertySetInfo() +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + //TODO: + return uno::Reference< beans::XPropertySetInfo >(); +} + +void SAL_CALL OWriteStream::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue ) +{ + osl::ClearableMutexGuard aGuard(m_xSharedMutex->GetMutex()); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + m_pImpl->GetStreamProperties(); + static constexpr OUString aCompressedString( u"Compressed"_ustr ); + static constexpr OUString aMediaTypeString( u"MediaType"_ustr ); + if ( m_nStorageType == embed::StorageFormats::PACKAGE && aPropertyName == aMediaTypeString ) + { + // if the "Compressed" property is not set explicitly, the MediaType can change the default value + bool bCompressedValueFromType = true; + OUString aType; + aValue >>= aType; + + if ( !m_pImpl->m_bCompressedSetExplicit ) + { + if ( aType == "image/jpeg" || aType == "image/png" || aType == "image/gif" ) + bCompressedValueFromType = false; + } + + for ( auto& rProp : asNonConstRange(m_pImpl->m_aProps) ) + { + if ( aPropertyName == rProp.Name ) + rProp.Value = aValue; + else if ( !m_pImpl->m_bCompressedSetExplicit && aCompressedString == rProp.Name ) + rProp.Value <<= bCompressedValueFromType; + } + } + else if ( aPropertyName == aCompressedString ) + { + // if the "Compressed" property is not set explicitly, the MediaType can change the default value + m_pImpl->m_bCompressedSetExplicit = true; + for ( auto& rProp : asNonConstRange(m_pImpl->m_aProps) ) + { + if ( aPropertyName == rProp.Name ) + rProp.Value = aValue; + } + } + else if ( m_nStorageType == embed::StorageFormats::PACKAGE + && aPropertyName == "UseCommonStoragePasswordEncryption" ) + { + bool bUseCommonEncryption = false; + if ( !(aValue >>= bUseCommonEncryption) ) + throw lang::IllegalArgumentException(); //TODO + + if ( m_bInitOnDemand && m_pImpl->m_bHasInsertedStreamOptimization ) + { + // the data stream is provided to the packagestream directly + m_pImpl->m_bUseCommonEncryption = bUseCommonEncryption; + } + else if ( bUseCommonEncryption ) + { + if ( !m_pImpl->m_bUseCommonEncryption ) + { + m_pImpl->SetDecrypted(); + m_pImpl->m_bUseCommonEncryption = true; + } + } + else + m_pImpl->m_bUseCommonEncryption = false; + } + else if ( m_nStorageType == embed::StorageFormats::OFOPXML && aPropertyName == aMediaTypeString ) + { + for ( auto& rProp : asNonConstRange(m_pImpl->m_aProps) ) + { + if ( aPropertyName == rProp.Name ) + rProp.Value = aValue; + } + } + else if ( m_nStorageType == embed::StorageFormats::OFOPXML && aPropertyName == "RelationsInfoStream" ) + { + uno::Reference< io::XInputStream > xInRelStream; + if ( !( aValue >>= xInRelStream ) || !xInRelStream.is() ) + throw lang::IllegalArgumentException(); // TODO + + uno::Reference< io::XSeekable > xSeek( xInRelStream, uno::UNO_QUERY ); + if ( !xSeek.is() ) + { + // currently this is an internal property that is used for optimization + // and the stream must support XSeekable interface + // TODO/LATER: in future it can be changed if property is used from outside + throw lang::IllegalArgumentException(); // TODO + } + + m_pImpl->m_xNewRelInfoStream = xInRelStream; + m_pImpl->m_aNewRelInfo = uno::Sequence< uno::Sequence< beans::StringPair > >(); + m_pImpl->m_nRelInfoStatus = RELINFO_CHANGED_STREAM; + } + else if ( m_nStorageType == embed::StorageFormats::OFOPXML && aPropertyName == "RelationsInfo" ) + { + if ( !(aValue >>= m_pImpl->m_aNewRelInfo) ) + throw lang::IllegalArgumentException(); // TODO + } + else if ( aPropertyName == "Size" ) + throw beans::PropertyVetoException(); // TODO + else if ( m_nStorageType == embed::StorageFormats::PACKAGE + && ( aPropertyName == "IsEncrypted" || aPropertyName == "Encrypted" ) ) + throw beans::PropertyVetoException(); // TODO + else if ( aPropertyName == "RelId" ) + { + aValue >>= m_pImpl->m_nRelId; + } + else + throw beans::UnknownPropertyException(aPropertyName); // TODO + + m_pImpl->m_bHasDataToFlush = true; + ModifyParentUnlockMutex_Impl( aGuard ); +} + +uno::Any SAL_CALL OWriteStream::getPropertyValue( const OUString& aProp ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( aProp == "RelId" ) + { + return uno::Any( m_pImpl->GetNewRelId() ); + } + + OUString aPropertyName; + if ( aProp == "IsEncrypted" ) + aPropertyName = "Encrypted"; + else + aPropertyName = aProp; + + if ( ( ( m_nStorageType == embed::StorageFormats::PACKAGE || m_nStorageType == embed::StorageFormats::OFOPXML ) + && aPropertyName == "MediaType" ) + || ( m_nStorageType == embed::StorageFormats::PACKAGE && aPropertyName == "Encrypted" ) + || aPropertyName == "Compressed" ) + { + m_pImpl->GetStreamProperties(); + + auto pProp = std::find_if(std::cbegin(m_pImpl->m_aProps), std::cend(m_pImpl->m_aProps), + [&aPropertyName](const css::beans::PropertyValue& rProp){ return aPropertyName == rProp.Name; }); + if (pProp != std::cend(m_pImpl->m_aProps)) + return pProp->Value; + } + else if ( m_nStorageType == embed::StorageFormats::PACKAGE + && aPropertyName == "UseCommonStoragePasswordEncryption" ) + return uno::Any( m_pImpl->m_bUseCommonEncryption ); + else if ( aPropertyName == "Size" ) + { + bool bThrow = false; + try + { + CheckInitOnDemand(); + } + catch (const io::IOException&) + { + bThrow = true; + } + if (bThrow || !m_xSeekable.is()) + throw uno::RuntimeException(); + + return uno::Any( m_xSeekable->getLength() ); + } + + throw beans::UnknownPropertyException(aPropertyName); // TODO +} + +void SAL_CALL OWriteStream::addPropertyChangeListener( + const OUString& /*aPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/ ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + //TODO: +} + +void SAL_CALL OWriteStream::removePropertyChangeListener( + const OUString& /*aPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*aListener*/ ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + //TODO: +} + +void SAL_CALL OWriteStream::addVetoableChangeListener( + const OUString& /*PropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*aListener*/ ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + //TODO: +} + +void SAL_CALL OWriteStream::removeVetoableChangeListener( + const OUString& /*PropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*aListener*/ ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + //TODO: +} + +// XTransactedObject + +void OWriteStream::BroadcastTransaction( sal_Int8 nMessage ) +/* + 1 - preCommit + 2 - committed + 3 - preRevert + 4 - reverted +*/ +{ + // no need to lock mutex here for the checking of m_pImpl, and m_pData is alive until the object is destructed + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + lang::EventObject aSource( getXWeak() ); + + comphelper::OInterfaceContainerHelper2* pContainer = + m_aListenersContainer.getContainer( + cppu::UnoType<embed::XTransactionListener>::get()); + if ( !pContainer ) + return; + + comphelper::OInterfaceIteratorHelper2 pIterator( *pContainer ); + while ( pIterator.hasMoreElements( ) ) + { + OSL_ENSURE( nMessage >= 1 && nMessage <= 4, "Wrong internal notification code is used!" ); + + switch( nMessage ) + { + case STOR_MESS_PRECOMMIT: + static_cast<embed::XTransactionListener*>( pIterator.next( ) )->preCommit( aSource ); + break; + case STOR_MESS_COMMITTED: + static_cast<embed::XTransactionListener*>( pIterator.next( ) )->commited( aSource ); + break; + case STOR_MESS_PREREVERT: + static_cast<embed::XTransactionListener*>( pIterator.next( ) )->preRevert( aSource ); + break; + case STOR_MESS_REVERTED: + static_cast< embed::XTransactionListener*>( pIterator.next( ) )->reverted( aSource ); + break; + } + } +} +void SAL_CALL OWriteStream::commit() +{ + SAL_INFO( "package.xstor", "package (mv76033) OWriteStream::commit" ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( !m_bTransacted ) + throw uno::RuntimeException(); + + try { + BroadcastTransaction( STOR_MESS_PRECOMMIT ); + + osl::ClearableMutexGuard aGuard(m_xSharedMutex->GetMutex()); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + m_pImpl->Commit(); + + // when the storage is committed the parent is modified + ModifyParentUnlockMutex_Impl( aGuard ); + } + catch( const io::IOException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const embed::StorageWrappedTargetException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + throw embed::StorageWrappedTargetException( "Problems on commit!", + getXWeak(), + aCaught ); + } + + BroadcastTransaction( STOR_MESS_COMMITTED ); +} + +void SAL_CALL OWriteStream::revert() +{ + SAL_INFO( "package.xstor", "package (mv76033) OWriteStream::revert" ); + + // the method removes all the changes done after last commit + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( !m_bTransacted ) + throw uno::RuntimeException(); + + BroadcastTransaction( STOR_MESS_PREREVERT ); + + { + osl::MutexGuard aGuard(m_xSharedMutex->GetMutex()); + + if (!m_pImpl) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + try { + m_pImpl->Revert(); + } + catch (const io::IOException&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch (const embed::StorageWrappedTargetException&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch (const uno::RuntimeException&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch (const uno::Exception&) + { + uno::Any aCaught(::cppu::getCaughtException()); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + throw embed::StorageWrappedTargetException("Problems on revert!", + getXWeak(), + aCaught); + } + } + + BroadcastTransaction( STOR_MESS_REVERTED ); +} + +// XTransactionBroadcaster + +void SAL_CALL OWriteStream::addTransactionListener( const uno::Reference< embed::XTransactionListener >& aListener ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( !m_bTransacted ) + throw uno::RuntimeException(); + + m_aListenersContainer.addInterface( cppu::UnoType<embed::XTransactionListener>::get(), + aListener ); +} + +void SAL_CALL OWriteStream::removeTransactionListener( const uno::Reference< embed::XTransactionListener >& aListener ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", "Disposed!"); + throw lang::DisposedException(); + } + + if ( !m_bTransacted ) + throw uno::RuntimeException(); + + m_aListenersContainer.removeInterface( cppu::UnoType<embed::XTransactionListener>::get(), + aListener ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/xstor/owriteablestream.hxx b/package/source/xstor/owriteablestream.hxx new file mode 100644 index 0000000000..e04b50c993 --- /dev/null +++ b/package/source/xstor/owriteablestream.hxx @@ -0,0 +1,358 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_PACKAGE_SOURCE_XSTOR_OWRITEABLESTREAM_HXX +#define INCLUDED_PACKAGE_SOURCE_XSTOR_OWRITEABLESTREAM_HXX + +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/io/XStream.hpp> +#include <com/sun/star/io/XSeekable.hpp> +#include <com/sun/star/io/XTruncate.hpp> +#include <com/sun/star/packages/XDataSinkEncrSupport.hpp> +#include <com/sun/star/lang/XEventListener.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/embed/XEncryptionProtectedSource2.hpp> +#include <com/sun/star/embed/XStorage.hpp> +#include <com/sun/star/embed/XRelationshipAccess.hpp> +#include <com/sun/star/embed/XExtendedStorageStream.hpp> +#include <com/sun/star/embed/XTransactedObject.hpp> +#include <com/sun/star/embed/XTransactionBroadcaster.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/beans/StringPair.hpp> + +#include <cppuhelper/weak.hxx> +#include <cppuhelper/typeprovider.hxx> +#include <comphelper/multicontainer2.hxx> + +#include <comphelper/bytereader.hxx> +#include <comphelper/refcountedmutex.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <unotools/tempfile.hxx> + +#include <vector> +#include <memory> +#include <string_view> + +#include "ocompinstream.hxx" + +namespace com::sun::star::uno { + class XComponentContext; +} + +namespace package { + // all data in aHash1 is contained in aHash2 + bool PackageEncryptionDataLessOrEqual( const ::comphelper::SequenceAsHashMap& aHash1, const ::comphelper::SequenceAsHashMap& aHash2 ); +} + +struct OStorage_Impl; +class OWriteStream; + +struct OWriteStream_Impl +{ + rtl::Reference<comphelper::RefCountedMutex> m_xMutex; + + friend struct OStorage_Impl; + friend class OWriteStream; + friend class OInputCompStream; + + OWriteStream* m_pAntiImpl; + std::optional<utl::TempFileFast> m_oTempFile; + + css::uno::Reference< css::io::XStream > m_xCacheStream; + css::uno::Reference< css::io::XSeekable > m_xCacheSeek; + + std::vector< OInputCompStream* > m_aInputStreamsVector; + + bool m_bHasDataToFlush; // only modified elements will be sent to the original content + bool m_bFlushed; // sending the streams is coordinated by the root storage of the package + + css::uno::Reference< css::packages::XDataSinkEncrSupport > m_xPackageStream; + + css::uno::Reference< css::uno::XComponentContext > m_xContext; + + OStorage_Impl* m_pParent; + + css::uno::Sequence< css::beans::PropertyValue > m_aProps; + + bool m_bForceEncrypted; + + bool m_bUseCommonEncryption; + bool m_bHasCachedEncryptionData; + ::comphelper::SequenceAsHashMap m_aEncryptionData; + + bool m_bCompressedSetExplicit; + + css::uno::Reference< css::lang::XSingleServiceFactory > m_xPackage; + + bool m_bHasInsertedStreamOptimization; + + sal_Int32 m_nStorageType; + + // Relations info related data, stored in *.rels file in OFOPXML format + css::uno::Reference< css::io::XInputStream > m_xOrigRelInfoStream; + css::uno::Sequence< css::uno::Sequence< css::beans::StringPair > > m_aOrigRelInfo; + bool m_bOrigRelInfoBroken; + + css::uno::Sequence< css::uno::Sequence< css::beans::StringPair > > m_aNewRelInfo; + css::uno::Reference< css::io::XInputStream > m_xNewRelInfoStream; + sal_Int16 m_nRelInfoStatus; + sal_Int32 m_nRelId; + +private: + void GetFilledTempFileIfNo( const css::uno::Reference< css::io::XInputStream >& xStream ); + void FillTempGetFileName(); + css::uno::Reference< css::io::XStream > GetTempFileAsStream(); + css::uno::Reference< css::io::XInputStream > GetTempFileAsInputStream(); + + css::uno::Reference< css::io::XStream > GetStream_Impl( sal_Int32 nStreamMode, + bool bHierarchyAccess ); + + /// @throws css::packages::NoEncryptionException + ::comphelper::SequenceAsHashMap GetCommonRootEncryptionData(); + + css::uno::Sequence< css::beans::PropertyValue > ReadPackageStreamProperties(); + css::uno::Sequence< css::beans::PropertyValue > InsertOwnProps( + const css::uno::Sequence< css::beans::PropertyValue >& aProps, + bool bUseCommonEncryption ); + +public: + OWriteStream_Impl( + OStorage_Impl* pParent, + const css::uno::Reference< css::packages::XDataSinkEncrSupport >& xPackageStream, + const css::uno::Reference< css::lang::XSingleServiceFactory >& xPackage, + css::uno::Reference< css::uno::XComponentContext > xContext, + bool bForceEncrypted, + sal_Int32 nStorageType, + bool bDefaultCompress, + css::uno::Reference< css::io::XInputStream > xRelInfoStream = + css::uno::Reference< css::io::XInputStream >() ); + + ~OWriteStream_Impl(); + + void CleanCacheStream(); + + bool UsesCommonEncryption_Impl() const { return m_bUseCommonEncryption; } + bool HasTempFile_Impl() const { return m_oTempFile.has_value(); } + bool IsTransacted(); + + bool HasWriteOwner_Impl() const { return ( m_pAntiImpl != nullptr ); } + + void InsertIntoPackageFolder( + const OUString& aName, + const css::uno::Reference< css::container::XNameContainer >& xParentPackageFolder ); + + void SetToBeCommited() { m_bFlushed = true; } + + bool HasCachedEncryptionData() const { return m_bHasCachedEncryptionData; } + ::comphelper::SequenceAsHashMap& GetCachedEncryptionData() { return m_aEncryptionData; } + + bool IsModified() const { return m_bHasDataToFlush || m_bFlushed; } + + bool IsEncrypted(); + void SetDecrypted(); + void SetEncrypted( const ::comphelper::SequenceAsHashMap& aEncryptionData ); + + void DisposeWrappers(); + + void InsertStreamDirectly( + const css::uno::Reference< css::io::XInputStream >& xInStream, + const css::uno::Sequence< css::beans::PropertyValue >& aProps ); + + void Commit(); + void Revert(); + + css::uno::Sequence< css::beans::PropertyValue > const & GetStreamProperties(); + + css::uno::Sequence< css::uno::Sequence< css::beans::StringPair > > GetAllRelationshipsIfAny(); + + void CopyInternallyTo_Impl( const css::uno::Reference< css::io::XStream >& xDestStream, + const ::comphelper::SequenceAsHashMap& aEncryptionData ); + void CopyInternallyTo_Impl( const css::uno::Reference< css::io::XStream >& xDestStream ); + + css::uno::Reference< css::io::XStream > GetStream( + sal_Int32 nStreamMode, + const ::comphelper::SequenceAsHashMap& aEncryptionData, + bool bHierarchyAccess ); + + css::uno::Reference< css::io::XStream > GetStream( + sal_Int32 nStreamMode, + bool bHierarchyAccess ); + + css::uno::Reference< css::io::XInputStream > GetRawInStream(); + css::uno::Reference< css::io::XInputStream > GetPlainRawInStream(); + + void InputStreamDisposed( OInputCompStream* pStream ); + + void CreateReadonlyCopyBasedOnData( + const css::uno::Reference< css::io::XInputStream >& xDataToCopy, + const css::uno::Sequence< css::beans::PropertyValue >& aProps, + css::uno::Reference< css::io::XStream >& xTargetStream ); + + void GetCopyOfLastCommit( css::uno::Reference< css::io::XStream >& xTargetStream ); + void GetCopyOfLastCommit( + css::uno::Reference< css::io::XStream >& xTargetStream, + const ::comphelper::SequenceAsHashMap& aEncryptionData ); + + void CommitStreamRelInfo( + const css::uno::Reference< css::embed::XStorage >& xRelStorage, + std::u16string_view aOrigStreamName, + std::u16string_view aNewStreamName ); + + void ReadRelInfoIfNecessary(); + + sal_Int32 GetNewRelId() { return m_nRelId ++; } +}; + +class OWriteStream : public css::lang::XTypeProvider + , public css::io::XInputStream + , public css::io::XOutputStream + , public css::embed::XExtendedStorageStream + , public css::io::XSeekable + , public css::io::XTruncate + , public css::embed::XEncryptionProtectedSource2 + , public css::embed::XRelationshipAccess + , public css::embed::XTransactedObject + , public css::embed::XTransactionBroadcaster + , public css::beans::XPropertySet + , public ::cppu::OWeakObject + , public comphelper::ByteWriter +{ + friend struct OWriteStream_Impl; + +protected: + css::uno::Reference < css::io::XInputStream > m_xInStream; + css::uno::Reference < css::io::XOutputStream > m_xOutStream; + css::uno::Reference < css::io::XSeekable > m_xSeekable; + + OWriteStream_Impl* m_pImpl; + rtl::Reference<comphelper::RefCountedMutex> m_xSharedMutex; + ::std::optional< ::cppu::OTypeCollection> m_oTypeCollection; + comphelper::OMultiTypeInterfaceContainerHelper2 m_aListenersContainer; // list of listeners + sal_Int32 m_nStorageType; + + bool m_bInStreamDisconnected; + bool m_bInitOnDemand; + sal_Int64 m_nInitPosition; + + bool m_bTransacted; + + OWriteStream( OWriteStream_Impl& rImpl, bool bTransacted ); + OWriteStream( OWriteStream_Impl& rImpl, css::uno::Reference< css::io::XStream > const & xStream, bool bTransacted ); + + void CloseOutput_Impl(); + + void CopyToStreamInternally_Impl( const css::uno::Reference< css::io::XStream >& xStream ); + + void ModifyParentUnlockMutex_Impl(osl::ClearableMutexGuard& aGuard); + + void BroadcastTransaction( sal_Int8 nMessage ); + +public: + + virtual ~OWriteStream() override; + + void CheckInitOnDemand(); + void DeInit(); + + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type& rType ) override; + virtual void SAL_CALL acquire() noexcept override; + virtual void SAL_CALL release() noexcept override; + + // XTypeProvider + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes() override; + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId() override; + + // XInputStream + virtual sal_Int32 SAL_CALL readBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) override; + virtual sal_Int32 SAL_CALL readSomeBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) override; + virtual void SAL_CALL skipBytes( sal_Int32 nBytesToSkip ) override; + virtual sal_Int32 SAL_CALL available( ) override; + virtual void SAL_CALL closeInput( ) override; + + // XOutputStream + virtual void SAL_CALL writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) override; + virtual void SAL_CALL flush( ) override; + virtual void SAL_CALL closeOutput( ) override; + + //XSeekable + virtual void SAL_CALL seek( sal_Int64 location ) override; + virtual sal_Int64 SAL_CALL getPosition() override; + virtual sal_Int64 SAL_CALL getLength() override; + + //XStream + virtual css::uno::Reference< css::io::XInputStream > SAL_CALL getInputStream( ) override; + virtual css::uno::Reference< css::io::XOutputStream > SAL_CALL getOutputStream( ) override; + + // XTruncate + virtual void SAL_CALL truncate() override; + + //XComponent + virtual void SAL_CALL dispose() override; + virtual void SAL_CALL addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) override; + virtual void SAL_CALL removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) override; + + //XEncryptionProtectedSource + virtual void SAL_CALL setEncryptionPassword( const OUString& aPass ) override; + virtual void SAL_CALL removeEncryption() override; + + //XEncryptionProtectedSource2 + virtual void SAL_CALL setEncryptionData( const css::uno::Sequence< css::beans::NamedValue >& aEncryptionData ) override; + virtual sal_Bool SAL_CALL hasEncryptionData() override; + + //XRelationshipAccess + virtual sal_Bool SAL_CALL hasByID( const OUString& sID ) override; + virtual OUString SAL_CALL getTargetByID( const OUString& sID ) override; + virtual OUString SAL_CALL getTypeByID( const OUString& sID ) override; + virtual css::uno::Sequence< css::beans::StringPair > SAL_CALL getRelationshipByID( const OUString& sID ) override; + virtual css::uno::Sequence< css::uno::Sequence< css::beans::StringPair > > SAL_CALL getRelationshipsByType( const OUString& sType ) override; + virtual css::uno::Sequence< css::uno::Sequence< css::beans::StringPair > > SAL_CALL getAllRelationships( ) override; + virtual void SAL_CALL insertRelationshipByID( const OUString& sID, const css::uno::Sequence< css::beans::StringPair >& aEntry, sal_Bool bReplace ) override; + virtual void SAL_CALL removeRelationshipByID( const OUString& sID ) override; + virtual void SAL_CALL insertRelationships( const css::uno::Sequence< css::uno::Sequence< css::beans::StringPair > >& aEntries, sal_Bool bReplace ) override; + virtual void SAL_CALL clearRelationships( ) override; + + //XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo() override; + virtual void SAL_CALL setPropertyValue( const OUString& aPropertyName, const css::uno::Any& aValue ) override; + virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& PropertyName ) override; + virtual void SAL_CALL addPropertyChangeListener( const OUString& aPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener ) override; + virtual void SAL_CALL removePropertyChangeListener( const OUString& aPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener >& aListener ) override; + virtual void SAL_CALL addVetoableChangeListener( const OUString& PropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override; + virtual void SAL_CALL removeVetoableChangeListener( const OUString& PropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override; + + // XTransactedObject + virtual void SAL_CALL commit() override; + virtual void SAL_CALL revert() override; + + // XTransactionBroadcaster + virtual void SAL_CALL addTransactionListener( + const css::uno::Reference< css::embed::XTransactionListener >& aListener ) override; + virtual void SAL_CALL removeTransactionListener( + const css::uno::Reference< css::embed::XTransactionListener >& aListener ) override; + + // comphelper::ByteWriter + virtual void writeBytes(const sal_Int8* aData, sal_Int32 nBytesToWrite) override; + +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/xstor/selfterminatefilestream.cxx b/package/source/xstor/selfterminatefilestream.cxx new file mode 100644 index 0000000000..f3fe794397 --- /dev/null +++ b/package/source/xstor/selfterminatefilestream.cxx @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/ucb/SimpleFileAccess.hpp> + +#include "selfterminatefilestream.hxx" +#include <comphelper/processfactory.hxx> +#include <unotools/streamwrap.hxx> + +using namespace ::com::sun::star; + +OSelfTerminateFileStream::OSelfTerminateFileStream( const uno::Reference< uno::XComponentContext >& xContext, utl::TempFileFast aTempFile ) +: m_oTempFile( std::move(aTempFile) ) +{ + uno::Reference< uno::XComponentContext > xOwnContext = xContext; + if ( !xOwnContext.is() ) + xOwnContext.set( ::comphelper::getProcessComponentContext(), uno::UNO_SET_THROW ); + + m_xStreamWrapper = new utl::OSeekableInputStreamWrapper( m_oTempFile->GetStream(StreamMode::READWRITE), /*bOwner*/false ); +} + +OSelfTerminateFileStream::~OSelfTerminateFileStream() +{ + CloseStreamDeleteFile(); +} + +void OSelfTerminateFileStream::CloseStreamDeleteFile() +{ + try + { + m_xStreamWrapper->closeInput(); + } + catch( uno::Exception& ) + {} + + m_oTempFile.reset(); +} + +sal_Int32 SAL_CALL OSelfTerminateFileStream::readBytes( uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) +{ + return m_xStreamWrapper->readBytes( aData, nBytesToRead ); +} + +sal_Int32 SAL_CALL OSelfTerminateFileStream::readSomeBytes( uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) +{ + return m_xStreamWrapper->readSomeBytes( aData, nMaxBytesToRead ); +} + +void SAL_CALL OSelfTerminateFileStream::skipBytes( sal_Int32 nBytesToSkip ) +{ + return m_xStreamWrapper->skipBytes( nBytesToSkip ); +} + +sal_Int32 SAL_CALL OSelfTerminateFileStream::available( ) +{ + return m_xStreamWrapper->available(); +} + +void SAL_CALL OSelfTerminateFileStream::closeInput( ) +{ + CloseStreamDeleteFile(); +} + +void SAL_CALL OSelfTerminateFileStream::seek( sal_Int64 location ) +{ + m_xStreamWrapper->seek( location ); +} + +sal_Int64 SAL_CALL OSelfTerminateFileStream::getPosition() +{ + return m_xStreamWrapper->getPosition(); +} + +sal_Int64 SAL_CALL OSelfTerminateFileStream::getLength() +{ + return m_xStreamWrapper->getLength(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/xstor/selfterminatefilestream.hxx b/package/source/xstor/selfterminatefilestream.hxx new file mode 100644 index 0000000000..f8cedcb64c --- /dev/null +++ b/package/source/xstor/selfterminatefilestream.hxx @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_PACKAGE_SOURCE_XSTOR_SELFTERMINATEFILESTREAM_HXX +#define INCLUDED_PACKAGE_SOURCE_XSTOR_SELFTERMINATEFILESTREAM_HXX + +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XSeekable.hpp> +#include <com/sun/star/ucb/XSimpleFileAccess3.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <cppuhelper/implbase.hxx> +#include <unotools/streamwrap.hxx> +#include <unotools/tempfile.hxx> +#include <rtl/ref.hxx> +#include <optional> + +struct OWriteStream_Impl; + +class OSelfTerminateFileStream final : public cppu::WeakImplHelper< css::io::XInputStream, + css::io::XSeekable > +{ + std::optional<utl::TempFileFast> m_oTempFile; + rtl::Reference< utl::OSeekableInputStreamWrapper > m_xStreamWrapper; + +public: + OSelfTerminateFileStream( const css::uno::Reference< css::uno::XComponentContext >& xContext, utl::TempFileFast aTempFile ); + + virtual ~OSelfTerminateFileStream() override; + + void CloseStreamDeleteFile(); + + // XInputStream + virtual sal_Int32 SAL_CALL readBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) override; + virtual sal_Int32 SAL_CALL readSomeBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) override; + virtual void SAL_CALL skipBytes( sal_Int32 nBytesToSkip ) override; + virtual sal_Int32 SAL_CALL available() override; + virtual void SAL_CALL closeInput() override; + + //XSeekable + virtual void SAL_CALL seek( sal_Int64 location ) override; + virtual sal_Int64 SAL_CALL getPosition() override; + virtual sal_Int64 SAL_CALL getLength() override; + +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/xstor/switchpersistencestream.cxx b/package/source/xstor/switchpersistencestream.cxx new file mode 100644 index 0000000000..ff61187266 --- /dev/null +++ b/package/source/xstor/switchpersistencestream.cxx @@ -0,0 +1,407 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <osl/diagnose.h> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/io/NotConnectedException.hpp> +#include <com/sun/star/io/TempFile.hpp> +#include <comphelper/storagehelper.hxx> +#include <unotools/tempfile.hxx> +#include <utility> +#include "switchpersistencestream.hxx" + +using namespace ::com::sun::star; + +struct SPStreamData_Impl +{ + bool m_bInStreamBased; + + // the streams below are not visible from outside so there is no need to remember position + + // original stream related members + uno::Reference< io::XTruncate > m_xOrigTruncate; + uno::Reference< io::XSeekable > m_xOrigSeekable; + uno::Reference< io::XInputStream > m_xOrigInStream; + uno::Reference< io::XOutputStream > m_xOrigOutStream; + + bool m_bInOpen; + bool m_bOutOpen; + + SPStreamData_Impl( + bool bInStreamBased, + uno::Reference< io::XTruncate > xOrigTruncate, + uno::Reference< io::XSeekable > xOrigSeekable, + uno::Reference< io::XInputStream > xOrigInStream, + uno::Reference< io::XOutputStream > xOrigOutStream, + bool bInOpen, + bool bOutOpen ) + : m_bInStreamBased( bInStreamBased ) + , m_xOrigTruncate(std::move( xOrigTruncate )) + , m_xOrigSeekable(std::move( xOrigSeekable )) + , m_xOrigInStream(std::move( xOrigInStream )) + , m_xOrigOutStream(std::move( xOrigOutStream )) + , m_bInOpen( bInOpen ) + , m_bOutOpen( bOutOpen ) + { + } +}; + +SwitchablePersistenceStream::SwitchablePersistenceStream( + const uno::Reference< io::XStream >& xStream ) +{ + SwitchPersistenceTo( xStream ); +} + +SwitchablePersistenceStream::SwitchablePersistenceStream( + const uno::Reference< io::XInputStream >& xInputStream ) +{ + SwitchPersistenceTo( xInputStream ); +} + +SwitchablePersistenceStream::~SwitchablePersistenceStream() +{ + CloseAll_Impl(); +} + +void SwitchablePersistenceStream::SwitchPersistenceTo( const uno::Reference< io::XStream >& xStream ) +{ + uno::Reference< io::XTruncate > xNewTruncate( xStream, uno::UNO_QUERY_THROW ); + uno::Reference< io::XSeekable > xNewSeekable( xStream, uno::UNO_QUERY_THROW ); + uno::Reference< io::XInputStream > xNewInStream = xStream->getInputStream(); + uno::Reference< io::XOutputStream > xNewOutStream = xStream->getOutputStream(); + if ( !xNewInStream.is() || !xNewOutStream.is() ) + throw uno::RuntimeException(); + + sal_Int64 nPos = 0; + bool bInOpen = false; + bool bOutOpen = false; + + if ( m_pStreamData && m_pStreamData->m_xOrigSeekable.is() ) + { + // check that the length is the same + if ( m_pStreamData->m_xOrigSeekable->getLength() != xNewSeekable->getLength() ) + throw uno::RuntimeException(); + + // get the current position + nPos = m_pStreamData->m_xOrigSeekable->getPosition(); + bInOpen = m_pStreamData->m_bInOpen; + bOutOpen = m_pStreamData->m_bOutOpen; + } + + xNewSeekable->seek( nPos ); + + CloseAll_Impl(); + + m_pStreamData.reset( new SPStreamData_Impl( false, + xNewTruncate, xNewSeekable, xNewInStream, xNewOutStream, + bInOpen, bOutOpen ) ); +} + +void SwitchablePersistenceStream::SwitchPersistenceTo( const uno::Reference< io::XInputStream >& xInputStream ) +{ + uno::Reference< io::XTruncate > xNewTruncate; + uno::Reference< io::XSeekable > xNewSeekable( xInputStream, uno::UNO_QUERY_THROW ); + uno::Reference< io::XOutputStream > xNewOutStream; + if ( !xInputStream.is() ) + throw uno::RuntimeException(); + + sal_Int64 nPos = 0; + bool bInOpen = false; + bool bOutOpen = false; + + if ( m_pStreamData && m_pStreamData->m_xOrigSeekable.is() ) + { + // check that the length is the same + if ( m_pStreamData->m_xOrigSeekable->getLength() != xNewSeekable->getLength() ) + throw uno::RuntimeException(); + + // get the current position + nPos = m_pStreamData->m_xOrigSeekable->getPosition(); + bInOpen = m_pStreamData->m_bInOpen; + bOutOpen = m_pStreamData->m_bOutOpen; + } + + xNewSeekable->seek( nPos ); + + CloseAll_Impl(); + + m_pStreamData.reset( new SPStreamData_Impl( true, + xNewTruncate, xNewSeekable, xInputStream, xNewOutStream, + bInOpen, bOutOpen ) ); + +} + +void SwitchablePersistenceStream::CopyAndSwitchPersistenceTo( const uno::Reference< io::XStream >& xStream ) +{ + uno::Reference< io::XStream > xTargetStream = xStream; + uno::Reference< io::XSeekable > xTargetSeek; + + if ( !xTargetStream.is() ) + { + xTargetStream.set( new utl::TempFileFastService ); + xTargetSeek.set( xTargetStream, uno::UNO_QUERY_THROW ); + } + else + { + // the provided stream must be empty + xTargetSeek.set( xTargetStream, uno::UNO_QUERY_THROW ); + if ( xTargetSeek->getLength() ) + throw io::IOException("provided stream not empty"); + } + + uno::Reference< io::XTruncate > xTargetTruncate( xTargetStream, uno::UNO_QUERY_THROW ); + uno::Reference< io::XInputStream > xTargetInStream = xTargetStream->getInputStream(); + uno::Reference< io::XOutputStream > xTargetOutStream = xTargetStream->getOutputStream(); + if ( !xTargetInStream.is() || !xTargetOutStream.is() ) + throw uno::RuntimeException(); + + if ( !m_pStreamData->m_xOrigInStream.is() || !m_pStreamData->m_xOrigSeekable.is() ) + throw uno::RuntimeException(); + + sal_Int64 nPos = m_pStreamData->m_xOrigSeekable->getPosition(); + m_pStreamData->m_xOrigSeekable->seek( 0 ); + ::comphelper::OStorageHelper::CopyInputToOutput( m_pStreamData->m_xOrigInStream, xTargetOutStream ); + xTargetOutStream->flush(); + xTargetSeek->seek( nPos ); + + bool bInOpen = m_pStreamData->m_bInOpen; + bool bOutOpen = m_pStreamData->m_bOutOpen; + + CloseAll_Impl(); + + m_pStreamData.reset( new SPStreamData_Impl( false, + xTargetTruncate, xTargetSeek, xTargetInStream, xTargetOutStream, + bInOpen, bOutOpen ) ); +} + +void SwitchablePersistenceStream::CloseAll_Impl() +{ + m_pStreamData.reset(); +} + +// css::io::XStream +uno::Reference< io::XInputStream > SAL_CALL SwitchablePersistenceStream::getInputStream( ) +{ + std::scoped_lock aGuard( m_aMutex ); + + if ( m_pStreamData ) + m_pStreamData->m_bInOpen = true; + return static_cast< io::XInputStream* >( this ); +} + +uno::Reference< io::XOutputStream > SAL_CALL SwitchablePersistenceStream::getOutputStream( ) +{ + std::scoped_lock aGuard( m_aMutex ); + + if ( m_pStreamData ) + m_pStreamData->m_bOutOpen = true; + return static_cast< io::XOutputStream* >( this ); +} + +// css::io::XInputStream +::sal_Int32 SAL_CALL SwitchablePersistenceStream::readBytes( uno::Sequence< ::sal_Int8 >& aData, ::sal_Int32 nBytesToRead ) +{ + std::scoped_lock aGuard( m_aMutex ); + + if ( !m_pStreamData ) + throw io::NotConnectedException(); + + // the original stream data should be provided + if ( !m_pStreamData->m_xOrigInStream.is() ) + throw uno::RuntimeException(); + + return m_pStreamData->m_xOrigInStream->readBytes( aData, nBytesToRead ); +} + +::sal_Int32 SAL_CALL SwitchablePersistenceStream::readSomeBytes( uno::Sequence< ::sal_Int8 >& aData, ::sal_Int32 nMaxBytesToRead ) +{ + std::scoped_lock aGuard( m_aMutex ); + + if ( !m_pStreamData ) + throw io::NotConnectedException(); + + // the original stream data should be provided + if ( !m_pStreamData->m_xOrigInStream.is() ) + throw uno::RuntimeException(); + + return m_pStreamData->m_xOrigInStream->readBytes( aData, nMaxBytesToRead ); +} + +void SAL_CALL SwitchablePersistenceStream::skipBytes( ::sal_Int32 nBytesToSkip ) +{ + std::scoped_lock aGuard( m_aMutex ); + + if ( !m_pStreamData ) + throw io::NotConnectedException(); + + // the original stream data should be provided + if ( !m_pStreamData->m_xOrigInStream.is() ) + throw uno::RuntimeException(); + + m_pStreamData->m_xOrigInStream->skipBytes( nBytesToSkip ); +} + +::sal_Int32 SAL_CALL SwitchablePersistenceStream::available( ) +{ + std::scoped_lock aGuard( m_aMutex ); + + if ( !m_pStreamData ) + throw io::NotConnectedException(); + + // the original stream data should be provided + if ( !m_pStreamData->m_xOrigInStream.is() ) + throw uno::RuntimeException(); + + return m_pStreamData->m_xOrigInStream->available(); +} + +void SAL_CALL SwitchablePersistenceStream::closeInput() +{ + std::scoped_lock aGuard( m_aMutex ); + + if ( !m_pStreamData ) + throw io::NotConnectedException(); + + m_pStreamData->m_bInOpen = false; + if ( !m_pStreamData->m_bOutOpen ) + CloseAll_Impl(); +} + +// css::io::XOutputStream +void SAL_CALL SwitchablePersistenceStream::writeBytes( const uno::Sequence< ::sal_Int8 >& aData ) +{ + std::scoped_lock aGuard( m_aMutex ); + + if ( !m_pStreamData ) + throw io::NotConnectedException(); + + if ( m_pStreamData->m_bInStreamBased ) + throw io::IOException(); + + // the original stream data should be provided + if ( !m_pStreamData->m_xOrigOutStream.is() ) + throw uno::RuntimeException(); + + m_pStreamData->m_xOrigOutStream->writeBytes( aData ); +} + +void SAL_CALL SwitchablePersistenceStream::flush( ) +{ + std::scoped_lock aGuard( m_aMutex ); + + if ( !m_pStreamData || m_pStreamData->m_bInStreamBased ) + { + OSL_FAIL( "flush() is not acceptable!" ); + return; + // in future throw exception, for now some code might call flush() on closed stream + // since file ucp implementation allows it + // throw io::NotConnectedException(); + } + + // the original stream data should be provided + if ( !m_pStreamData->m_xOrigOutStream.is() ) + throw uno::RuntimeException(); + + m_pStreamData->m_xOrigOutStream->flush(); +} + +void SAL_CALL SwitchablePersistenceStream::closeOutput( ) +{ + std::scoped_lock aGuard( m_aMutex ); + + if ( !m_pStreamData ) + throw io::NotConnectedException(); + + m_pStreamData->m_bOutOpen = false; + if ( !m_pStreamData->m_bInOpen ) + CloseAll_Impl(); +} + +// css::io::XTruncate +void SAL_CALL SwitchablePersistenceStream::truncate( ) +{ + std::scoped_lock aGuard( m_aMutex ); + + if ( !m_pStreamData ) + throw io::NotConnectedException(); + + if ( m_pStreamData->m_bInStreamBased ) + throw io::IOException(); + + // the original stream data should be provided + if ( !m_pStreamData->m_xOrigTruncate.is() ) + throw uno::RuntimeException(); + + m_pStreamData->m_xOrigTruncate->truncate(); +} + +// css::io::XSeekable +void SAL_CALL SwitchablePersistenceStream::seek( ::sal_Int64 location ) +{ + std::scoped_lock aGuard( m_aMutex ); + + if ( !m_pStreamData ) + throw io::NotConnectedException(); + + // the original stream data should be provided + if ( !m_pStreamData->m_xOrigSeekable.is() ) + throw uno::RuntimeException(); + + m_pStreamData->m_xOrigSeekable->seek( location ); +} + +::sal_Int64 SAL_CALL SwitchablePersistenceStream::getPosition( ) +{ + std::scoped_lock aGuard( m_aMutex ); + + if ( !m_pStreamData ) + throw io::NotConnectedException(); + + // the original stream data should be provided + if ( !m_pStreamData->m_xOrigSeekable.is() ) + throw uno::RuntimeException(); + + return m_pStreamData->m_xOrigSeekable->getPosition(); +} + +::sal_Int64 SAL_CALL SwitchablePersistenceStream::getLength( ) +{ + std::scoped_lock aGuard( m_aMutex ); + + if ( !m_pStreamData ) + throw io::NotConnectedException(); + + // the original stream data should be provided + if ( !m_pStreamData->m_xOrigSeekable.is() ) + throw uno::RuntimeException(); + + return m_pStreamData->m_xOrigSeekable->getLength(); +} + +void SAL_CALL SwitchablePersistenceStream::waitForCompletion() +{ + if ( !m_pStreamData ) + throw io::NotConnectedException(); + + uno::Reference< io::XAsyncOutputMonitor > asyncOutputMonitor( m_pStreamData->m_xOrigOutStream, uno::UNO_QUERY ); + if ( asyncOutputMonitor.is() ) + asyncOutputMonitor->waitForCompletion(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/xstor/switchpersistencestream.hxx b/package/source/xstor/switchpersistencestream.hxx new file mode 100644 index 0000000000..64d4e37fd0 --- /dev/null +++ b/package/source/xstor/switchpersistencestream.hxx @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_PACKAGE_SOURCE_XSTOR_SWITCHPERSISTENCESTREAM_HXX +#define INCLUDED_PACKAGE_SOURCE_XSTOR_SWITCHPERSISTENCESTREAM_HXX + +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/io/XSeekable.hpp> +#include <com/sun/star/io/XTruncate.hpp> +#include <com/sun/star/io/XStream.hpp> +#include <com/sun/star/io/XAsyncOutputMonitor.hpp> +#include <mutex> +#include <cppuhelper/implbase.hxx> + +// SwitchablePersistenceStream + +// Allows to switch the stream persistence on the fly. The target +// stream ( if not filled by the implementation ) MUST have the same +// size as the original one! + +struct SPStreamData_Impl; +class SwitchablePersistenceStream + : public ::cppu::WeakImplHelper < + css::io::XStream, + css::io::XInputStream, + css::io::XOutputStream, + css::io::XTruncate, + css::io::XSeekable, + css::io::XAsyncOutputMonitor > +{ + std::mutex m_aMutex; + + std::unique_ptr<SPStreamData_Impl> m_pStreamData; + + void CloseAll_Impl(); + +public: + + SwitchablePersistenceStream( + const css::uno::Reference< css::io::XStream >& xStream ); + + SwitchablePersistenceStream( + const css::uno::Reference< css::io::XInputStream >& xInStream ); + + virtual ~SwitchablePersistenceStream() override; + + void SwitchPersistenceTo( const css::uno::Reference< css::io::XStream >& xStream ); + + void SwitchPersistenceTo( const css::uno::Reference< css::io::XInputStream >& xInputStream ); + + void CopyAndSwitchPersistenceTo( const css::uno::Reference< css::io::XStream >& xStream ); + +// css::io::XStream + virtual css::uno::Reference< css::io::XInputStream > SAL_CALL getInputStream( ) override; + virtual css::uno::Reference< css::io::XOutputStream > SAL_CALL getOutputStream( ) override; + +// css::io::XInputStream + virtual ::sal_Int32 SAL_CALL readBytes( css::uno::Sequence< ::sal_Int8 >& aData, ::sal_Int32 nBytesToRead ) override; + virtual ::sal_Int32 SAL_CALL readSomeBytes( css::uno::Sequence< ::sal_Int8 >& aData, ::sal_Int32 nMaxBytesToRead ) override; + virtual void SAL_CALL skipBytes( ::sal_Int32 nBytesToSkip ) override; + virtual ::sal_Int32 SAL_CALL available( ) override; + virtual void SAL_CALL closeInput( ) override; + +// css::io::XOutputStream + virtual void SAL_CALL writeBytes( const css::uno::Sequence< ::sal_Int8 >& aData ) override; + virtual void SAL_CALL flush( ) override; + virtual void SAL_CALL closeOutput( ) override; + +// css::io::XTruncate + virtual void SAL_CALL truncate( ) override; + +// css::io::XSeekable + virtual void SAL_CALL seek( ::sal_Int64 location ) override; + virtual ::sal_Int64 SAL_CALL getPosition( ) override; + virtual ::sal_Int64 SAL_CALL getLength( ) override; + +// css::io::XAsyncOutputMonitor + virtual void SAL_CALL waitForCompletion( ) override; + +}; + +#endif // INCLUDED_PACKAGE_SOURCE_XSTOR_SWITCHPERSISTENCESTREAM_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/xstor/xfactory.cxx b/package/source/xstor/xfactory.cxx new file mode 100644 index 0000000000..d611ddfabe --- /dev/null +++ b/package/source/xstor/xfactory.cxx @@ -0,0 +1,292 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <com/sun/star/ucb/SimpleFileAccess.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/embed/StorageFormats.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/io/TempFile.hpp> +#include <com/sun/star/io/XSeekable.hpp> + +#include <comphelper/propertyvalue.hxx> +#include <comphelper/storagehelper.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/weak.hxx> +#include <osl/diagnose.h> +#include <unotools/tempfile.hxx> + +#include "xfactory.hxx" +#include "xstorage.hxx" + +using namespace ::com::sun::star; + +#if OSL_DEBUG_LEVEL > 0 +#define THROW_WHERE SAL_WHERE +#else +#define THROW_WHERE "" +#endif + +static bool CheckPackageSignature_Impl( const uno::Reference< io::XInputStream >& xInputStream, + const uno::Reference< io::XSeekable >& xSeekable ) +{ + if ( !xInputStream.is() || !xSeekable.is() ) + throw uno::RuntimeException(); + + if ( xSeekable->getLength() ) + { + uno::Sequence< sal_Int8 > aData( 4 ); + xSeekable->seek( 0 ); + sal_Int32 nRead = xInputStream->readBytes( aData, 4 ); + xSeekable->seek( 0 ); + + // TODO/LATER: should the disk spanned files be supported? + // 0x50, 0x4b, 0x07, 0x08 + return ( nRead == 4 && aData[0] == 0x50 && aData[1] == 0x4b && aData[2] == 0x03 && aData[3] == 0x04 ); + } + else + return true; // allow to create a storage based on empty stream +} + + + +uno::Reference< uno::XInterface > SAL_CALL OStorageFactory::createInstance() +{ + // TODO: reimplement TempStream service to support XStream interface + uno::Reference < io::XStream > xTempStream(new utl::TempFileFastService); + + return cppu::getXWeak(new OStorage(xTempStream, embed::ElementModes::READWRITE, + uno::Sequence<beans::PropertyValue>(), m_xContext, + embed::StorageFormats::PACKAGE)); +} + +uno::Reference< uno::XInterface > SAL_CALL OStorageFactory::createInstanceWithArguments( + const uno::Sequence< uno::Any >& aArguments ) +{ + // The request for storage can be done with up to three arguments + + // The first argument specifies a source for the storage + // it can be URL, XStream, XInputStream. + // The second value is a mode the storage should be open in. + // And the third value is a media descriptor. + + sal_Int32 nArgNum = aArguments.getLength(); + OSL_ENSURE( nArgNum < 4, "Wrong parameter number" ); + + if ( !nArgNum ) + return createInstance(); + + // first try to retrieve storage open mode if any + // by default the storage will be open in readonly mode + sal_Int32 nStorageMode = embed::ElementModes::READ; + if ( nArgNum >= 2 ) + { + if( !( aArguments[1] >>= nStorageMode ) ) + { + OSL_FAIL( "Wrong second argument!" ); + throw lang::IllegalArgumentException(); // TODO: + } + // it's always possible to read written storage in this implementation + nStorageMode |= embed::ElementModes::READ; + } + + if ( ( nStorageMode & embed::ElementModes::TRUNCATE ) == embed::ElementModes::TRUNCATE + && ( nStorageMode & embed::ElementModes::WRITE ) != embed::ElementModes::WRITE ) + throw lang::IllegalArgumentException(); // TODO: + + // retrieve storage source stream + OUString aURL; + uno::Reference< io::XStream > xStream; + uno::Reference< io::XInputStream > xInputStream; + + if ( aArguments[0] >>= aURL ) + { + if ( aURL.isEmpty() ) + { + OSL_FAIL( "Empty URL is provided!" ); + throw lang::IllegalArgumentException(); // TODO: + } + + if ( aURL.startsWithIgnoreAsciiCase("vnd.sun.star.pkg:") ) + { + OSL_FAIL( "Packages URL's are not valid for storages!" ); // ??? + throw lang::IllegalArgumentException(); // TODO: + } + + uno::Reference < ucb::XSimpleFileAccess3 > xTempAccess( + ucb::SimpleFileAccess::create( + m_xContext ) ); + + if ( nStorageMode & embed::ElementModes::WRITE ) + xStream = xTempAccess->openFileReadWrite( aURL ); + else + xInputStream = xTempAccess->openFileRead( aURL ); + } + else if ( !( aArguments[0] >>= xStream ) && !( aArguments[0] >>= xInputStream ) ) + { + OSL_FAIL( "Wrong first argument!" ); + throw uno::Exception("wrong first arg", nullptr); // TODO: Illegal argument + } + + // retrieve mediadescriptor and set storage properties + uno::Sequence< beans::PropertyValue > aDescr; + uno::Sequence< beans::PropertyValue > aPropsToSet; + + sal_Int32 nStorageType = embed::StorageFormats::PACKAGE; + + if ( nArgNum >= 3 ) + { + if( aArguments[2] >>= aDescr ) + { + if ( !aURL.isEmpty() ) + { + aPropsToSet = { comphelper::makePropertyValue("URL", aURL) }; + } + + sal_Int32 nNumArgs = 1; + for ( const auto& rProp : std::as_const(aDescr) ) + { + if ( rProp.Name == "InteractionHandler" + || rProp.Name == "Password" + || rProp.Name == "RepairPackage" + || rProp.Name == "StatusIndicator" ) + { + aPropsToSet.realloc( ++nNumArgs ); + auto pPropsToSet = aPropsToSet.getArray(); + pPropsToSet[nNumArgs-1].Name = rProp.Name; + pPropsToSet[nNumArgs-1].Value = rProp.Value; + } + else if ( rProp.Name == "StorageFormat" ) + { + OUString aFormatName; + sal_Int32 nFormatID = 0; + if ( rProp.Value >>= aFormatName ) + { + if ( aFormatName == PACKAGE_STORAGE_FORMAT_STRING ) + nStorageType = embed::StorageFormats::PACKAGE; + else if ( aFormatName == ZIP_STORAGE_FORMAT_STRING ) + nStorageType = embed::StorageFormats::ZIP; + else if ( aFormatName == OFOPXML_STORAGE_FORMAT_STRING ) + nStorageType = embed::StorageFormats::OFOPXML; + else + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); + } + else if ( rProp.Value >>= nFormatID ) + { + if ( nFormatID != embed::StorageFormats::PACKAGE + && nFormatID != embed::StorageFormats::ZIP + && nFormatID != embed::StorageFormats::OFOPXML ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); + + nStorageType = nFormatID; + } + else + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); + } + else if (rProp.Name == "NoFileSync") + { + // Forward NoFileSync to the storage. + aPropsToSet.realloc(++nNumArgs); + auto pPropsToSet = aPropsToSet.getArray(); + pPropsToSet[nNumArgs - 1].Name = rProp.Name; + pPropsToSet[nNumArgs - 1].Value = rProp.Value; + } + else + OSL_FAIL( "Unacceptable property, will be ignored!" ); + } + } + else + { + OSL_FAIL( "Wrong third argument!" ); + throw uno::Exception("wrong 3rd arg", nullptr); // TODO: Illegal argument + } + + } + + // create storage based on source + if ( xInputStream.is() ) + { + // if xInputStream is set the storage should be open from it + if ( nStorageMode & embed::ElementModes::WRITE ) + throw uno::Exception("storagemode==write", nullptr); // TODO: access denied + + uno::Reference< io::XSeekable > xSeekable( xInputStream, uno::UNO_QUERY ); + if ( !xSeekable.is() ) + { + // TODO: wrap stream to let it be seekable + OSL_FAIL( "Nonseekable streams are not supported for now!" ); + } + + if ( !CheckPackageSignature_Impl( xInputStream, xSeekable ) ) + throw io::IOException("package signature check failed, probably not a package file", nullptr); // TODO: this is not a package file + + return cppu::getXWeak( + new OStorage(xInputStream, nStorageMode, aPropsToSet, m_xContext, nStorageType)); + } + else if ( xStream.is() ) + { + if ( ( ( nStorageMode & embed::ElementModes::WRITE ) && !xStream->getOutputStream().is() ) + || !xStream->getInputStream().is() ) + throw uno::Exception("access denied", nullptr); // TODO: access denied + + uno::Reference< io::XSeekable > xSeekable( xStream, uno::UNO_QUERY ); + if ( !xSeekable.is() ) + { + // TODO: wrap stream to let it be seekable + OSL_FAIL( "Nonseekable streams are not supported for now!" ); + } + + if ( !CheckPackageSignature_Impl( xStream->getInputStream(), xSeekable ) ) + throw io::IOException(); // TODO: this is not a package file + + return cppu::getXWeak( + new OStorage(xStream, nStorageMode, aPropsToSet, m_xContext, nStorageType)); + } + + throw uno::Exception("no input stream or regular stream", nullptr); // general error during creation +} + +OUString SAL_CALL OStorageFactory::getImplementationName() +{ + return "com.sun.star.comp.embed.StorageFactory"; +} + +sal_Bool SAL_CALL OStorageFactory::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +uno::Sequence< OUString > SAL_CALL OStorageFactory::getSupportedServiceNames() +{ + return { "com.sun.star.embed.StorageFactory", + "com.sun.star.comp.embed.StorageFactory" }; +} + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +package_OStorageFactory_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new OStorageFactory(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/xstor/xfactory.hxx b/package/source/xstor/xfactory.hxx new file mode 100644 index 0000000000..3669118492 --- /dev/null +++ b/package/source/xstor/xfactory.hxx @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_PACKAGE_SOURCE_XSTOR_XFACTORY_HXX +#define INCLUDED_PACKAGE_SOURCE_XSTOR_XFACTORY_HXX + +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <cppuhelper/implbase.hxx> +#include <osl/diagnose.h> + +class OStorageFactory : public ::cppu::WeakImplHelper< css::lang::XSingleServiceFactory, + css::lang::XServiceInfo > +{ + css::uno::Reference< css::uno::XComponentContext > m_xContext; + +public: + explicit OStorageFactory( const css::uno::Reference< css::uno::XComponentContext >& xContext ) + : m_xContext( xContext ) + { + OSL_ENSURE( xContext.is(), "No service manager is provided!" ); + } + + // XSingleServiceFactory + virtual css::uno::Reference< css::uno::XInterface > SAL_CALL createInstance() override; + virtual css::uno::Reference< css::uno::XInterface > SAL_CALL createInstanceWithArguments( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/xstor/xstor.component b/package/source/xstor/xstor.component new file mode 100644 index 0000000000..ff6de82b65 --- /dev/null +++ b/package/source/xstor/xstor.component @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.embed.StorageFactory" + constructor="package_OStorageFactory_get_implementation" single-instance="true"> + <service name="com.sun.star.comp.embed.StorageFactory"/> + <service name="com.sun.star.embed.StorageFactory"/> + </implementation> +</component> diff --git a/package/source/xstor/xstorage.cxx b/package/source/xstor/xstorage.cxx new file mode 100644 index 0000000000..d19d1cac7f --- /dev/null +++ b/package/source/xstor/xstorage.cxx @@ -0,0 +1,5493 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <memory> +#include <sal/config.h> +#include <sal/log.hxx> + +#include <cassert> +#include <string_view> + +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/embed/InvalidStorageException.hpp> +#include <com/sun/star/embed/UseBackupException.hpp> +#include <com/sun/star/embed/StorageFormats.hpp> +#include <com/sun/star/embed/StorageWrappedTargetException.hpp> +#include <com/sun/star/packages/NoEncryptionException.hpp> +#include <com/sun/star/packages/NoRawFormatException.hpp> +#include <com/sun/star/packages/WrongPasswordException.hpp> +#include <com/sun/star/io/TempFile.hpp> +#include <com/sun/star/ucb/SimpleFileAccess.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> +#include <com/sun/star/container/XEnumerationAccess.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/util/XChangesBatch.hpp> + +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/beans/NamedValue.hpp> + +#include <PackageConstants.hxx> + +#include <comphelper/sequence.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <cppuhelper/exc_hlp.hxx> + +#include <comphelper/servicehelper.hxx> +#include <comphelper/storagehelper.hxx> +#include <comphelper/ofopxmlhelper.hxx> +#include <utility> +#include <comphelper/diagnose_ex.hxx> + +#include "xstorage.hxx" +#include "owriteablestream.hxx" +#include "switchpersistencestream.hxx" + +using namespace ::com::sun::star; + +#if OSL_DEBUG_LEVEL > 0 +#define THROW_WHERE SAL_WHERE +#else +#define THROW_WHERE "" +#endif + +// static +void OStorage_Impl::completeStorageStreamCopy_Impl( + const uno::Reference< io::XStream >& xSource, + const uno::Reference< io::XStream >& xDest, + sal_Int32 nStorageType, + const uno::Sequence< uno::Sequence< beans::StringPair > >& aRelInfo ) +{ + uno::Reference< beans::XPropertySet > xSourceProps( xSource, uno::UNO_QUERY_THROW ); + uno::Reference< beans::XPropertySet > xDestProps( xDest, uno::UNO_QUERY_THROW ); + + uno::Reference< io::XOutputStream > xDestOutStream = xDest->getOutputStream(); + if ( !xDestOutStream.is() ) + throw io::IOException( THROW_WHERE ); + + uno::Reference< io::XInputStream > xSourceInStream = xSource->getInputStream(); + if ( !xSourceInStream.is() ) + throw io::IOException( THROW_WHERE ); + + // TODO: headers of encrypted streams should be copied also + ::comphelper::OStorageHelper::CopyInputToOutput( xSourceInStream, xDestOutStream ); + + uno::Sequence<OUString> aPropNames { "Compressed", "MediaType", + "UseCommonStoragePasswordEncryption" }; + + if ( nStorageType == embed::StorageFormats::OFOPXML ) + { + // TODO/LATER: in future it might make sense to provide the stream if there is one + uno::Reference< embed::XRelationshipAccess > xRelAccess( xDest, uno::UNO_QUERY_THROW ); + xRelAccess->clearRelationships(); + xRelAccess->insertRelationships( aRelInfo, false ); + + aPropNames.realloc( 2 ); + } + else if ( nStorageType != embed::StorageFormats::PACKAGE ) + { + aPropNames.realloc( 1 ); + } + + for ( const auto& rPropName : std::as_const(aPropNames) ) + xDestProps->setPropertyValue( rPropName, xSourceProps->getPropertyValue( rPropName ) ); +} + +static uno::Reference< io::XInputStream > GetSeekableTempCopy( const uno::Reference< io::XInputStream >& xInStream ) +{ + rtl::Reference < utl::TempFileFastService > xTempFile = new utl::TempFileFastService; + uno::Reference < io::XOutputStream > xTempOut = xTempFile->getOutputStream(); + uno::Reference < io::XInputStream > xTempIn = xTempFile->getInputStream(); + + if ( !xTempOut.is() || !xTempIn.is() ) + throw io::IOException( THROW_WHERE ); + + ::comphelper::OStorageHelper::CopyInputToOutput( xInStream, xTempOut ); + xTempOut->closeOutput(); + + return xTempIn; +} + +SotElement_Impl::SotElement_Impl(OUString aName, bool bStor, bool bNew) + : m_aOriginalName(std::move(aName)) + , m_bIsRemoved(false) + , m_bIsInserted(bNew) + , m_bIsStorage(bStor) +{ +} + +// most of properties are holt by the storage but are not used +OStorage_Impl::OStorage_Impl( uno::Reference< io::XInputStream > const & xInputStream, + sal_Int32 nMode, + const uno::Sequence< beans::PropertyValue >& xProperties, + uno::Reference< uno::XComponentContext > const & xContext, + sal_Int32 nStorageType ) +: m_xMutex( new comphelper::RefCountedMutex ) +, m_pAntiImpl( nullptr ) +, m_nStorageMode( nMode & ~embed::ElementModes::SEEKABLE ) +, m_bIsModified( ( nMode & ( embed::ElementModes::WRITE | embed::ElementModes::TRUNCATE ) ) == ( embed::ElementModes::WRITE | embed::ElementModes::TRUNCATE ) ) +, m_bBroadcastModified( false ) +, m_bCommited( false ) +, m_bIsRoot( true ) +, m_bListCreated( false ) +, m_nModifiedListenerCount( 0 ) +, m_xContext( xContext ) +, m_xProperties( xProperties ) +, m_bHasCommonEncryptionData( false ) +, m_pParent( nullptr ) +, m_bControlMediaType( false ) +, m_bMTFallbackUsed( false ) +, m_bControlVersion( false ) +, m_nStorageType( nStorageType ) +, m_pRelStorElement( nullptr ) +, m_nRelInfoStatus( RELINFO_NO_INIT ) +{ + // all the checks done below by assertion statements must be done by factory + SAL_WARN_IF( !xInputStream.is(), "package.xstor", "No input stream is provided!" ); + assert(xContext.is()); + + m_pSwitchStream = new SwitchablePersistenceStream(xInputStream); + m_xInputStream = m_pSwitchStream->getInputStream(); + + if ( m_nStorageMode & embed::ElementModes::WRITE ) + { + // check that the stream allows to write + SAL_WARN( "package.xstor", "No stream for writing is provided!" ); + } +} + +// most of properties are holt by the storage but are not used +OStorage_Impl::OStorage_Impl( uno::Reference< io::XStream > const & xStream, + sal_Int32 nMode, + const uno::Sequence< beans::PropertyValue >& xProperties, + uno::Reference< uno::XComponentContext > const & xContext, + sal_Int32 nStorageType ) +: m_xMutex( new comphelper::RefCountedMutex ) +, m_pAntiImpl( nullptr ) +, m_nStorageMode( nMode & ~embed::ElementModes::SEEKABLE ) +, m_bIsModified( ( nMode & ( embed::ElementModes::WRITE | embed::ElementModes::TRUNCATE ) ) == ( embed::ElementModes::WRITE | embed::ElementModes::TRUNCATE ) ) +, m_bBroadcastModified( false ) +, m_bCommited( false ) +, m_bIsRoot( true ) +, m_bListCreated( false ) +, m_nModifiedListenerCount( 0 ) +, m_xContext( xContext ) +, m_xProperties( xProperties ) +, m_bHasCommonEncryptionData( false ) +, m_pParent( nullptr ) +, m_bControlMediaType( false ) +, m_bMTFallbackUsed( false ) +, m_bControlVersion( false ) +, m_nStorageType( nStorageType ) +, m_pRelStorElement( nullptr ) +, m_nRelInfoStatus( RELINFO_NO_INIT ) +{ + // all the checks done below by assertion statements must be done by factory + SAL_WARN_IF( !xStream.is(), "package.xstor", "No stream is provided!" ); + assert(xContext.is()); + + if ( m_nStorageMode & embed::ElementModes::WRITE ) + { + m_pSwitchStream = new SwitchablePersistenceStream(xStream); + m_xStream = static_cast< io::XStream* >( m_pSwitchStream.get() ); + } + else + { + m_pSwitchStream = new SwitchablePersistenceStream(xStream->getInputStream()); + m_xInputStream = m_pSwitchStream->getInputStream(); + } +} + +OStorage_Impl::OStorage_Impl( OStorage_Impl* pParent, + sal_Int32 nMode, + uno::Reference< container::XNameContainer > const & xPackageFolder, + uno::Reference< lang::XSingleServiceFactory > xPackage, + uno::Reference< uno::XComponentContext > const & xContext, + sal_Int32 nStorageType ) +: m_xMutex( new comphelper::RefCountedMutex ) +, m_pAntiImpl( nullptr ) +, m_nStorageMode( nMode & ~embed::ElementModes::SEEKABLE ) +, m_bIsModified( ( nMode & ( embed::ElementModes::WRITE | embed::ElementModes::TRUNCATE ) ) == ( embed::ElementModes::WRITE | embed::ElementModes::TRUNCATE ) ) +, m_bBroadcastModified( false ) +, m_bCommited( false ) +, m_bIsRoot( false ) +, m_bListCreated( false ) +, m_nModifiedListenerCount( 0 ) +, m_xPackageFolder( xPackageFolder ) +, m_xPackage(std::move( xPackage )) +, m_xContext( xContext ) +, m_bHasCommonEncryptionData( false ) +, m_pParent( pParent ) // can be empty in case of temporary readonly substorages and relation storage +, m_bControlMediaType( false ) +, m_bMTFallbackUsed( false ) +, m_bControlVersion( false ) +, m_nStorageType( nStorageType ) +, m_pRelStorElement( nullptr ) +, m_nRelInfoStatus( RELINFO_NO_INIT ) +{ + SAL_WARN_IF( !xPackageFolder.is(), "package.xstor", "No package folder!" ); + assert(xContext.is()); +} + +OStorage_Impl::~OStorage_Impl() +{ + { + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + if ( m_pAntiImpl ) // root storage wrapper must set this member to NULL before destruction of object + { + SAL_WARN_IF( m_bIsRoot, "package.xstor", "The root storage wrapper must be disposed already" ); + + try { + m_pAntiImpl->InternalDispose( false ); + } + catch ( const uno::Exception& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Quiet exception"); + } + m_pAntiImpl = nullptr; + } + else if ( !m_aReadOnlyWrapVector.empty() ) + { + for ( auto& rStorage : m_aReadOnlyWrapVector ) + { + uno::Reference< embed::XStorage > xTmp = rStorage.m_xWeakRef; + if ( xTmp.is() ) + try { + rStorage.m_pPointer->InternalDispose( false ); + } catch( const uno::Exception& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Quiet exception"); + } + } + + m_aReadOnlyWrapVector.clear(); + } + + m_pParent = nullptr; + } + + for (const auto & pair : m_aChildrenMap) + for (auto pElement : pair.second) + delete pElement; + m_aChildrenMap.clear(); + + std::for_each(m_aDeletedVector.begin(), m_aDeletedVector.end(), std::default_delete<SotElement_Impl>()); + m_aDeletedVector.clear(); + + if ( m_nStorageType == embed::StorageFormats::OFOPXML && m_pRelStorElement ) + { + delete m_pRelStorElement; + m_pRelStorElement = nullptr; + } + + m_xPackageFolder.clear(); + m_xPackage.clear(); + + OUString aPropertyName = "URL"; + for ( const auto& rProp : std::as_const(m_xProperties) ) + { + if ( rProp.Name == aPropertyName ) + { + // the storage is URL based so all the streams are opened by factory and should be closed + try + { + if ( m_xInputStream.is() ) + { + m_xInputStream->closeInput(); + m_xInputStream.clear(); + } + + if ( m_xStream.is() ) + { + uno::Reference< io::XInputStream > xInStr = m_xStream->getInputStream(); + if ( xInStr.is() ) + xInStr->closeInput(); + + uno::Reference< io::XOutputStream > xOutStr = m_xStream->getOutputStream(); + if ( xOutStr.is() ) + xOutStr->closeOutput(); + + m_xStream.clear(); + } + } + catch (const uno::Exception&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Quiet exception"); + } + } + } +} + +void OStorage_Impl::SetReadOnlyWrap( OStorage& aStorage ) +{ + // Weak reference is used inside the holder so the refcount must not be zero at this point + OSL_ENSURE( aStorage.GetRefCount_Impl(), "There must be a reference alive to use this method!" ); + m_aReadOnlyWrapVector.emplace_back( &aStorage ); +} + +void OStorage_Impl::RemoveReadOnlyWrap( const OStorage& aStorage ) +{ + for ( StorageHoldersType::iterator pStorageIter = m_aReadOnlyWrapVector.begin(); + pStorageIter != m_aReadOnlyWrapVector.end();) + { + uno::Reference< embed::XStorage > xTmp = pStorageIter->m_xWeakRef; + if ( !xTmp.is() || pStorageIter->m_pPointer == &aStorage ) + { + try { + pStorageIter->m_pPointer->InternalDispose( false ); + } catch( const uno::Exception& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Quiet exception"); + } + + pStorageIter = m_aReadOnlyWrapVector.erase(pStorageIter); + } + else + ++pStorageIter; + } +} + +void OStorage_Impl::OpenOwnPackage() +{ + SAL_WARN_IF( !m_bIsRoot, "package.xstor", "Opening of the package has no sense!" ); + + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( !m_xPackageFolder.is() ) + { + if ( !m_xPackage.is() ) + { + uno::Sequence< uno::Any > aArguments( 2 ); + auto pArguments = aArguments.getArray(); + if ( m_nStorageMode & embed::ElementModes::WRITE ) + pArguments[ 0 ] <<= m_xStream; + else + { + SAL_WARN_IF( !m_xInputStream.is(), "package.xstor", "Input stream must be set for readonly access!" ); + pArguments[ 0 ] <<= m_xInputStream; + // TODO: if input stream is not seekable or XSeekable interface is supported + // on XStream object a wrapper must be used + } + + // do not allow elements to remove themself from the old container in case of insertion to another container + pArguments[ 1 ] <<= beans::NamedValue( "AllowRemoveOnInsert", + uno::Any( false ) ); + + sal_Int32 nArgNum = 2; + for ( const auto& rProp : std::as_const(m_xProperties) ) + { + if ( rProp.Name == "RepairPackage" + || rProp.Name == "ProgressHandler" + || rProp.Name == "NoFileSync" ) + { + // Forward these to the package. + beans::NamedValue aNamedValue( rProp.Name, rProp.Value ); + aArguments.realloc( ++nArgNum ); + pArguments = aArguments.getArray(); + pArguments[nArgNum-1] <<= aNamedValue; + if (rProp.Name == "RepairPackage") + rProp.Value >>= m_bRepairPackage; + } + else if ( rProp.Name == "Password" ) + { + // TODO: implement password setting for documents + // the password entry must be removed after setting + } + } + + if ( m_nStorageType == embed::StorageFormats::ZIP ) + { + // let the package support only plain zip format + beans::NamedValue aNamedValue; + aNamedValue.Name = "StorageFormat"; + aNamedValue.Value <<= OUString( "ZipFormat" ); + aArguments.realloc( ++nArgNum ); + pArguments = aArguments.getArray(); + pArguments[nArgNum-1] <<= aNamedValue; + } + else if ( m_nStorageType == embed::StorageFormats::OFOPXML ) + { + // let the package support OFOPXML media type handling + beans::NamedValue aNamedValue; + aNamedValue.Name = "StorageFormat"; + aNamedValue.Value <<= OUString( "OFOPXMLFormat" ); + aArguments.realloc( ++nArgNum ); + pArguments = aArguments.getArray(); + pArguments[nArgNum-1] <<= aNamedValue; + } + + m_xPackage.set( m_xContext->getServiceManager()->createInstanceWithArgumentsAndContext( + "com.sun.star.packages.comp.ZipPackage", aArguments, m_xContext), + uno::UNO_QUERY ); + } + + uno::Reference< container::XHierarchicalNameAccess > xHNameAccess( m_xPackage, uno::UNO_QUERY ); + SAL_WARN_IF( !xHNameAccess.is(), "package.xstor", "The package could not be created!" ); + + if ( xHNameAccess.is() ) + { + uno::Any aFolder = xHNameAccess->getByHierarchicalName("/"); + aFolder >>= m_xPackageFolder; + } + } + + SAL_WARN_IF( !m_xPackageFolder.is(), "package.xstor", "The package root folder can not be opened!" ); + if ( !m_xPackageFolder.is() ) + throw embed::InvalidStorageException( THROW_WHERE ); +} + +bool OStorage_Impl::HasChildren() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + ReadContents(); + return !m_aChildrenMap.empty(); +} + +void OStorage_Impl::GetStorageProperties() +{ + if ( m_nStorageType != embed::StorageFormats::PACKAGE ) + return; + + uno::Reference< beans::XPropertySet > xProps( m_xPackageFolder, uno::UNO_QUERY_THROW ); + + if ( !m_bControlMediaType ) + { + uno::Reference< beans::XPropertySet > xPackageProps( m_xPackage, uno::UNO_QUERY_THROW ); + xPackageProps->getPropertyValue( MEDIATYPE_FALLBACK_USED_PROPERTY ) >>= m_bMTFallbackUsed; + + static constexpr OUStringLiteral sMediaType(u"MediaType"); + xProps->getPropertyValue( sMediaType ) >>= m_aMediaType; + m_bControlMediaType = true; + } + + if ( !m_bControlVersion ) + { + xProps->getPropertyValue( "Version" ) >>= m_aVersion; + m_bControlVersion = true; + } + + // the properties of OFOPXML will be handled directly +} + +void OStorage_Impl::ReadRelInfoIfNecessary() +{ + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + return; + + if ( m_nRelInfoStatus == RELINFO_NO_INIT ) + { + // Init from original stream + uno::Reference< io::XInputStream > xRelInfoStream + = GetRelInfoStreamForName( std::u16string_view() ); + try + { + if ( xRelInfoStream.is() ) + m_aRelInfo = ::comphelper::OFOPXMLHelper::ReadRelationsInfoSequence( + xRelInfoStream, + u"_rels/.rels", + m_xContext ); + m_nRelInfoStatus = RELINFO_READ; + } + catch (css::uno::Exception &) + { + TOOLS_INFO_EXCEPTION("package.xstor", ""); + } + } + else if ( m_nRelInfoStatus == RELINFO_CHANGED_STREAM ) + { + // Init from the new stream + try + { + if ( m_xNewRelInfoStream.is() ) + m_aRelInfo = ::comphelper::OFOPXMLHelper::ReadRelationsInfoSequence( + m_xNewRelInfoStream, + u"_rels/.rels", + m_xContext ); + + m_nRelInfoStatus = RELINFO_CHANGED_STREAM_READ; + } + catch( const uno::Exception& ) + { + m_nRelInfoStatus = RELINFO_CHANGED_BROKEN; + } + } +} + +void OStorage_Impl::ReadContents() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( m_bListCreated ) + return; + + if ( m_bIsRoot ) + OpenOwnPackage(); + + uno::Reference< container::XEnumerationAccess > xEnumAccess( m_xPackageFolder, uno::UNO_QUERY_THROW ); + uno::Reference< container::XEnumeration > xEnum = xEnumAccess->createEnumeration(); + if ( !xEnum.is() ) + throw uno::RuntimeException( THROW_WHERE ); + + m_bListCreated = true; + + while( xEnum->hasMoreElements() ) + { + try { + uno::Reference< container::XNamed > xNamed; + xEnum->nextElement() >>= xNamed; + + if ( !xNamed.is() ) + { + SAL_WARN( "package.xstor", "XNamed is not supported!" ); + throw uno::RuntimeException( THROW_WHERE ); + } + + OUString aName = xNamed->getName(); + SAL_WARN_IF( aName.isEmpty(), "package.xstor", "Empty name!" ); + + uno::Reference< container::XNameContainer > xNameContainer( xNamed, uno::UNO_QUERY ); + + std::unique_ptr<SotElement_Impl> xNewElement(new SotElement_Impl(aName, xNameContainer.is(), false)); + if ( m_nStorageType == embed::StorageFormats::OFOPXML && aName == "_rels" ) + { + if (!xNewElement->m_bIsStorage) + throw io::IOException( THROW_WHERE ); // TODO: Unexpected format + + m_pRelStorElement = xNewElement.release(); + CreateRelStorage(); + } + else + { + if ( ( m_nStorageMode & embed::ElementModes::TRUNCATE ) == embed::ElementModes::TRUNCATE ) + { + // if a storage is truncated all of it elements are marked as deleted + xNewElement->m_bIsRemoved = true; + } + + m_aChildrenMap[aName].push_back(xNewElement.release()); + } + } + catch( const container::NoSuchElementException& ) + { + TOOLS_WARN_EXCEPTION( "package.xstor", "hasMoreElements() implementation has problems!"); + break; + } + } + if ( ( m_nStorageMode & embed::ElementModes::TRUNCATE ) == embed::ElementModes::TRUNCATE ) + { + // if a storage is truncated the relations information should be cleaned + m_xNewRelInfoStream.clear(); + m_aRelInfo = uno::Sequence< uno::Sequence< beans::StringPair > >(); + m_nRelInfoStatus = RELINFO_CHANGED; + } + + // cache changeable folder properties + GetStorageProperties(); +} + +void OStorage_Impl::CopyToStorage( const uno::Reference< embed::XStorage >& xDest, bool bDirect ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + uno::Reference< beans::XPropertySet > xPropSet( xDest, uno::UNO_QUERY ); + if ( !xPropSet.is() ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); + + sal_Int32 nDestMode = embed::ElementModes::READ; + xPropSet->getPropertyValue( "OpenMode" ) >>= nDestMode; + + if ( !( nDestMode & embed::ElementModes::WRITE ) ) + throw io::IOException( THROW_WHERE ); // TODO: access_denied + + ReadContents(); + + if ( !m_xPackageFolder.is() ) + throw embed::InvalidStorageException( THROW_WHERE ); + + for ( const auto& pair : m_aChildrenMap ) + for (auto pElement : pair.second) + { + if ( !pElement->m_bIsRemoved ) + CopyStorageElement( pElement, xDest, /*aName*/pair.first, bDirect ); + } + + // move storage properties to the destination one ( means changeable properties ) + if ( m_nStorageType == embed::StorageFormats::PACKAGE ) + { + xPropSet->setPropertyValue( "MediaType", uno::Any( m_aMediaType ) ); + xPropSet->setPropertyValue( "Version", uno::Any( m_aVersion ) ); + } + + if ( m_nStorageType == embed::StorageFormats::PACKAGE ) + { + // if this is a root storage, the common key from current one should be moved there + bool bIsRoot = false; + if ( ( xPropSet->getPropertyValue( "IsRoot" ) >>= bIsRoot ) && bIsRoot ) + { + try + { + uno::Reference< embed::XEncryptionProtectedStorage > xEncr( xDest, uno::UNO_QUERY ); + if ( xEncr.is() ) + { + xEncr->setEncryptionData( GetCommonRootEncryptionData().getAsConstNamedValueList() ); + + uno::Sequence< beans::NamedValue > aAlgorithms; + uno::Reference< beans::XPropertySet > xPackPropSet( m_xPackage, uno::UNO_QUERY_THROW ); + xPackPropSet->getPropertyValue( ENCRYPTION_ALGORITHMS_PROPERTY ) + >>= aAlgorithms; + xEncr->setEncryptionAlgorithms( aAlgorithms ); + } + } + catch( const packages::NoEncryptionException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "No Encryption"); + } + } + } + else if ( m_nStorageType == embed::StorageFormats::OFOPXML ) + { + + // TODO/LATER: currently the optimization is not active + // uno::Reference< io::XInputStream > xRelInfoStream = GetRelInfoStreamForName( OUString() ); // own stream + // if ( xRelInfoStream.is() ) + // { + // // Relations info stream is a writeonly property, introduced only to optimize copying + // // Should be used carefully since no check for stream consistency is done, and the stream must not stay locked + + // OUString aRelInfoString = "RelationsInfoStream"; + // xPropSet->setPropertyValue( aRelInfoString, uno::makeAny( GetSeekableTempCopy( xRelInfoStream, m_xFactory ) ) ); + // } + + uno::Reference< embed::XRelationshipAccess > xRels( xDest, uno::UNO_QUERY ); + if ( !xRels.is() ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); + + xRels->insertRelationships( GetAllRelationshipsIfAny(), false ); + } + + // if possible the destination storage should be committed after successful copying + uno::Reference< embed::XTransactedObject > xObjToCommit( xDest, uno::UNO_QUERY ); + if ( xObjToCommit.is() ) + xObjToCommit->commit(); +} + +void OStorage_Impl::CopyStorageElement( SotElement_Impl* pElement, + const uno::Reference< embed::XStorage >& xDest, + const OUString& aName, + bool bDirect ) +{ + SAL_WARN_IF( !xDest.is(), "package.xstor", "No destination storage!" ); + SAL_WARN_IF( aName.isEmpty(), "package.xstor", "Empty element name!" ); + + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + uno::Reference< container::XNameAccess > xDestAccess( xDest, uno::UNO_QUERY_THROW ); + if ( xDestAccess->hasByName( aName ) + && !( pElement->m_bIsStorage && xDest->isStorageElement( aName ) ) ) + xDest->removeElement( aName ); + + if ( pElement->m_bIsStorage ) + { + uno::Reference< embed::XStorage > xSubDest = + xDest->openStorageElement( aName, + embed::ElementModes::WRITE ); + + SAL_WARN_IF( !xSubDest.is(), "package.xstor", "No destination substorage!" ); + + if (!pElement->m_xStorage) + { + OpenSubStorage( pElement, embed::ElementModes::READ ); + if (!pElement->m_xStorage) + throw io::IOException( THROW_WHERE ); + } + + pElement->m_xStorage->CopyToStorage(xSubDest, bDirect); + } + else + { + if (!pElement->m_xStream) + { + OpenSubStream( pElement ); + if (!pElement->m_xStream) + throw io::IOException( THROW_WHERE ); + } + + if (!pElement->m_xStream->IsEncrypted()) + { + if ( bDirect ) + { + // fill in the properties for the stream + uno::Sequence< beans::PropertyValue > aStrProps(0); + const uno::Sequence< beans::PropertyValue > aSrcPkgProps = pElement->m_xStream->GetStreamProperties(); + sal_Int32 nNum = 0; + for ( const auto& rSrcPkgProp : aSrcPkgProps ) + { + if ( rSrcPkgProp.Name == "MediaType" || rSrcPkgProp.Name == "Compressed" ) + { + aStrProps.realloc( ++nNum ); + auto pStrProps = aStrProps.getArray(); + pStrProps[nNum-1].Name = rSrcPkgProp.Name; + pStrProps[nNum-1].Value = rSrcPkgProp.Value; + } + } + + if ( m_nStorageType == embed::StorageFormats::PACKAGE ) + { + aStrProps.realloc( ++nNum ); + auto pStrProps = aStrProps.getArray(); + pStrProps[nNum-1].Name = "UseCommonStoragePasswordEncryption"; + pStrProps[nNum-1].Value <<= pElement->m_xStream->UsesCommonEncryption_Impl(); + } + else if ( m_nStorageType == embed::StorageFormats::OFOPXML ) + { + // TODO/LATER: currently the optimization is not active + // uno::Reference< io::XInputStream > xInStream = GetRelInfoStreamForName( OUString() ); // own rels stream + // if ( xInStream.is() ) + // { + // aStrProps.realloc( ++nNum ); + // aStrProps[nNum-1].Name = "RelationsInfoStream"; + // aStrProps[nNum-1].Value <<= GetSeekableTempCopy( xInStream, m_xFactory ); + // } + + uno::Reference< embed::XRelationshipAccess > xRels( xDest, uno::UNO_QUERY ); + if ( !xRels.is() ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 0 ); + + xRels->insertRelationships( GetAllRelationshipsIfAny(), false ); + } + + uno::Reference< embed::XOptimizedStorage > xOptDest( xDest, uno::UNO_QUERY_THROW ); + uno::Reference < io::XInputStream > xInputToInsert; + + if (pElement->m_xStream->HasTempFile_Impl() || !pElement->m_xStream->m_xPackageStream.is()) + { + SAL_WARN_IF(!pElement->m_xStream->m_xPackageStream.is(), "package.xstor", "No package stream!"); + + // if the stream is modified - the temporary file must be used for insertion + xInputToInsert = pElement->m_xStream->GetTempFileAsInputStream(); + } + else + { + // for now get just nonseekable access to the stream + // TODO/LATER: the raw stream can be used + + xInputToInsert = pElement->m_xStream->m_xPackageStream->getDataStream(); + } + + if ( !xInputToInsert.is() ) + throw io::IOException( THROW_WHERE ); + + xOptDest->insertStreamElementDirect( aName, xInputToInsert, aStrProps ); + } + else + { + uno::Reference< io::XStream > xSubStr = + xDest->openStreamElement( aName, + embed::ElementModes::READWRITE | embed::ElementModes::TRUNCATE ); + SAL_WARN_IF( !xSubStr.is(), "package.xstor", "No destination substream!" ); + + pElement->m_xStream->CopyInternallyTo_Impl(xSubStr); + } + } + else if ( m_nStorageType != embed::StorageFormats::PACKAGE ) + { + SAL_WARN( "package.xstor", "Encryption is only supported in package storage!" ); + throw io::IOException( THROW_WHERE ); + } + else if ( pElement->m_xStream->HasCachedEncryptionData() + && ( pElement->m_xStream->IsModified() || pElement->m_xStream->HasWriteOwner_Impl() ) ) + { + ::comphelper::SequenceAsHashMap aCommonEncryptionData; + bool bHasCommonEncryptionData = false; + try + { + aCommonEncryptionData = GetCommonRootEncryptionData(); + bHasCommonEncryptionData = true; + } + catch( const packages::NoEncryptionException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "No Encryption"); + } + + if (bHasCommonEncryptionData && ::package::PackageEncryptionDataLessOrEqual(pElement->m_xStream->GetCachedEncryptionData(), aCommonEncryptionData)) + { + // If the stream can be opened with the common storage password + // it must be stored with the common storage password as well + uno::Reference< io::XStream > xDestStream = + xDest->openStreamElement( aName, + embed::ElementModes::READWRITE | embed::ElementModes::TRUNCATE ); + + pElement->m_xStream->CopyInternallyTo_Impl( xDestStream ); + + uno::Reference< beans::XPropertySet > xProps( xDestStream, uno::UNO_QUERY_THROW ); + xProps->setPropertyValue( + "UseCommonStoragePasswordEncryption", + uno::Any( true ) ); + } + else + { + // the stream is already opened for writing or was changed + uno::Reference< embed::XStorage2 > xDest2( xDest, uno::UNO_QUERY_THROW ); + uno::Reference< io::XStream > xSubStr = + xDest2->openEncryptedStream( aName, + embed::ElementModes::READWRITE | embed::ElementModes::TRUNCATE, + pElement->m_xStream->GetCachedEncryptionData().getAsConstNamedValueList() ); + SAL_WARN_IF( !xSubStr.is(), "package.xstor", "No destination substream!" ); + + pElement->m_xStream->CopyInternallyTo_Impl(xSubStr, pElement->m_xStream->GetCachedEncryptionData()); + } + } + else + { + // the stream is not opened at all, so it can be just opened for reading + try + { + // If the stream can be opened with the common storage password + // it must be stored with the common storage password as well + + uno::Reference< io::XStream > xOwnStream = pElement->m_xStream->GetStream(embed::ElementModes::READ, + false); + uno::Reference< io::XStream > xDestStream = + xDest->openStreamElement( aName, + embed::ElementModes::READWRITE | embed::ElementModes::TRUNCATE ); + SAL_WARN_IF( !xDestStream.is(), "package.xstor", "No destination substream!" ); + completeStorageStreamCopy_Impl( xOwnStream, xDestStream, m_nStorageType, GetAllRelationshipsIfAny() ); + + uno::Reference< beans::XPropertySet > xProps( xDestStream, uno::UNO_QUERY_THROW ); + xProps->setPropertyValue( + "UseCommonStoragePasswordEncryption", + uno::Any( true ) ); + } + catch( const packages::WrongPasswordException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Handled exception"); + + // If the common storage password does not allow to open the stream + // it could be copied in raw way, the problem is that the StartKey should be the same + // in the ODF1.2 package, so an invalid package could be produced if the stream + // is copied from ODF1.1 package, where it is allowed to have different StartKeys + uno::Reference< embed::XStorageRawAccess > xRawDest( xDest, uno::UNO_QUERY_THROW ); + uno::Reference< io::XInputStream > xRawInStream = pElement->m_xStream->GetRawInStream(); + xRawDest->insertRawEncrStreamElement( aName, xRawInStream ); + } + } + } +} + +uno::Sequence< uno::Sequence< beans::StringPair > > OStorage_Impl::GetAllRelationshipsIfAny() +{ + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + return uno::Sequence< uno::Sequence< beans::StringPair > >(); + + ReadRelInfoIfNecessary(); + + if ( m_nRelInfoStatus != RELINFO_READ + && m_nRelInfoStatus != RELINFO_CHANGED_STREAM_READ + && m_nRelInfoStatus != RELINFO_CHANGED ) + throw io::IOException( THROW_WHERE "Wrong relinfo stream!" ); + // m_nRelInfoStatus == RELINFO_CHANGED_BROKEN || m_nRelInfoStatus == RELINFO_BROKEN + return m_aRelInfo; +} + +void OStorage_Impl::CopyLastCommitTo( const uno::Reference< embed::XStorage >& xNewStor ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + SAL_WARN_IF( !m_xPackageFolder.is(), "package.xstor", "A committed storage is incomplete!" ); + if ( !m_xPackageFolder.is() ) + throw uno::RuntimeException( THROW_WHERE ); + + OStorage_Impl aTempRepresent( nullptr, + embed::ElementModes::READ, + m_xPackageFolder, + m_xPackage, + m_xContext, + m_nStorageType); + + // TODO/LATER: could use direct copying + aTempRepresent.CopyToStorage( xNewStor, false ); +} + +void OStorage_Impl::InsertIntoPackageFolder( const OUString& aName, + const uno::Reference< container::XNameContainer >& xParentPackageFolder ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + SAL_WARN_IF( !m_xPackageFolder.is(), "package.xstor", "An inserted storage is incomplete!" ); + uno::Reference< uno::XInterface > xTmp( m_xPackageFolder, uno::UNO_QUERY_THROW ); + xParentPackageFolder->insertByName( aName, uno::Any( xTmp ) ); + + m_bCommited = false; +} + +void OStorage_Impl::Commit() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( !m_bIsModified ) + return; + + // in case of a new empty storage it is possible that the contents are still not read + // ( the storage of course has no contents, but the initialization is postponed till the first use, + // thus if a new storage was created and committed immediately it must be initialized here ) + ReadContents(); + + // if storage is committed it should have a valid Package representation + SAL_WARN_IF( !m_xPackageFolder.is(), "package.xstor", "The package representation should exist!" ); + if ( !m_xPackageFolder.is() ) + throw embed::InvalidStorageException( THROW_WHERE ); + + OSL_ENSURE( m_nStorageMode & embed::ElementModes::WRITE, + "Commit of readonly storage, should be detected before!" ); + + uno::Reference< container::XNameContainer > xNewPackageFolder; + + // here the storage will switch to the temporary package folder + // if the storage was already committed and the parent was not committed after that + // the switch should not be done since the package folder in use is a temporary one; + // it can be detected by m_bCommited flag ( root storage doesn't need temporary representation ) + if ( !m_bCommited && !m_bIsRoot ) + { + uno::Sequence< uno::Any > aSeq{ uno::Any(true) }; + xNewPackageFolder.set( m_xPackage->createInstanceWithArguments( aSeq ), + uno::UNO_QUERY ); + } + else + xNewPackageFolder = m_xPackageFolder; + + // remove replaced removed elements + for ( auto& pDeleted : m_aDeletedVector ) + { + + if ( m_nStorageType == embed::StorageFormats::OFOPXML && !pDeleted->m_bIsStorage ) + RemoveStreamRelInfo( pDeleted->m_aOriginalName ); + + // the removed elements are not in new temporary storage + if ( m_bCommited || m_bIsRoot ) + xNewPackageFolder->removeByName( pDeleted->m_aOriginalName ); + delete pDeleted; + pDeleted = nullptr; + } + m_aDeletedVector.clear(); + + // remove removed elements + for (auto mapIt = m_aChildrenMap.begin(); mapIt != m_aChildrenMap.end(); ) + { + for (auto it = mapIt->second.begin(); it != mapIt->second.end(); ) + { + // renamed and inserted elements must be really inserted to package later + // since they can conflict with removed elements + auto & pElement = *it; + if ( pElement->m_bIsRemoved ) + { + if ( m_nStorageType == embed::StorageFormats::OFOPXML && !pElement->m_bIsStorage ) + RemoveStreamRelInfo( pElement->m_aOriginalName ); + + // the removed elements are not in new temporary storage + if ( m_bCommited || m_bIsRoot ) + xNewPackageFolder->removeByName( pElement->m_aOriginalName ); + + delete pElement; + it = mapIt->second.erase(it); + } + else + ++it; + } + if (mapIt->second.empty()) + mapIt = m_aChildrenMap.erase(mapIt); + else + ++mapIt; + } + + + // there should be no more deleted elements + for ( const auto& pair : m_aChildrenMap ) + for (auto pElement : pair.second) + { + // if it is a 'duplicate commit' inserted elements must be really inserted to package later + // since they can conflict with renamed elements + if ( !pElement->m_bIsInserted ) + { + // for now stream is opened in direct mode that means that in case + // storage is committed all the streams from it are committed in current state. + // following two steps are separated to allow easily implement transacted mode + // for streams if we need it in future. + // Only hierarchical access uses transacted streams currently + if ( !pElement->m_bIsStorage && pElement->m_xStream + && !pElement->m_xStream->IsTransacted() ) + pElement->m_xStream->Commit(); + + // if the storage was not open, there is no need to commit it ??? + // the storage should be checked that it is committed + if (pElement->m_bIsStorage && pElement->m_xStorage && pElement->m_xStorage->m_bCommited) + { + // it's temporary PackageFolder should be inserted instead of current one + // also the new copy of PackageFolder should be used by the children storages + + // the renamed elements are not in new temporary storage + if ( m_bCommited || m_bIsRoot ) + xNewPackageFolder->removeByName( pElement->m_aOriginalName ); + + pElement->m_xStorage->InsertIntoPackageFolder(/*aName*/pair.first, xNewPackageFolder); + } + else if (!pElement->m_bIsStorage && pElement->m_xStream && pElement->m_xStream->m_bFlushed) + { + if ( m_nStorageType == embed::StorageFormats::OFOPXML ) + CommitStreamRelInfo( /*aName*/pair.first, pElement ); + + // the renamed elements are not in new temporary storage + if ( m_bCommited || m_bIsRoot ) + xNewPackageFolder->removeByName( pElement->m_aOriginalName ); + + pElement->m_xStream->InsertIntoPackageFolder(/*aName*/pair.first, xNewPackageFolder); + } + else if ( !m_bCommited && !m_bIsRoot ) + { + // the element must be just copied to the new temporary package folder + // the connection with the original package should not be lost just because + // the element is still referred by the folder in the original hierarchy + uno::Any aPackageElement = m_xPackageFolder->getByName( pElement->m_aOriginalName ); + xNewPackageFolder->insertByName( /*aName*/pair.first, aPackageElement ); + } + else if ( pair.first != pElement->m_aOriginalName ) + { + // this is the case when xNewPackageFolder refers to m_xPackageFolder + // in case the name was changed and it is not a changed storage - rename the element + uno::Any aPackageElement = xNewPackageFolder->getByName( pElement->m_aOriginalName ); + xNewPackageFolder->removeByName( pElement->m_aOriginalName ); + xNewPackageFolder->insertByName( /*aName*/pair.first, aPackageElement ); + + if ( m_nStorageType == embed::StorageFormats::OFOPXML && !pElement->m_bIsStorage ) + { + if (!pElement->m_xStream) + { + OpenSubStream( pElement ); + if (!pElement->m_xStream) + throw uno::RuntimeException( THROW_WHERE ); + } + + CommitStreamRelInfo( /*aName*/pair.first, pElement ); + } + } + + pElement->m_aOriginalName = pair.first; + } + } + + for ( const auto& pair : m_aChildrenMap ) + for (auto pElement : pair.second) + { + // now inserted elements can be inserted to the package + if ( pElement->m_bIsInserted ) + { + pElement->m_aOriginalName = pair.first; + + if ( pElement->m_bIsStorage ) + { + OSL_ENSURE(pElement->m_xStorage, "An inserted storage is incomplete!"); + if (!pElement->m_xStorage) + throw uno::RuntimeException( THROW_WHERE ); + + if (pElement->m_xStorage->m_bCommited) + { + pElement->m_xStorage->InsertIntoPackageFolder(/*aName*/pair.first, xNewPackageFolder); + + pElement->m_bIsInserted = false; + } + } + else + { + OSL_ENSURE(pElement->m_xStream, "An inserted stream is incomplete!"); + if (!pElement->m_xStream) + throw uno::RuntimeException( THROW_WHERE ); + + if (!pElement->m_xStream->IsTransacted()) + pElement->m_xStream->Commit(); + + if (pElement->m_xStream->m_bFlushed) + { + if ( m_nStorageType == embed::StorageFormats::OFOPXML ) + CommitStreamRelInfo( /*aName*/pair.first, pElement ); + + pElement->m_xStream->InsertIntoPackageFolder( /*aName*/pair.first, xNewPackageFolder ); + + pElement->m_bIsInserted = false; + } + } + } + } + + if ( m_nStorageType == embed::StorageFormats::PACKAGE ) + { + // move properties to the destination package folder + uno::Reference< beans::XPropertySet > xProps( xNewPackageFolder, uno::UNO_QUERY_THROW ); + xProps->setPropertyValue( "MediaType", uno::Any( m_aMediaType ) ); + xProps->setPropertyValue( "Version", uno::Any( m_aVersion ) ); + } + + if ( m_nStorageType == embed::StorageFormats::OFOPXML ) + CommitRelInfo( xNewPackageFolder ); // store own relations and commit complete relations storage + + if ( m_bIsRoot ) + { + uno::Reference< util::XChangesBatch > xChangesBatch( m_xPackage, uno::UNO_QUERY_THROW ); + try + { + xChangesBatch->commitChanges(); + } + catch( const lang::WrappedTargetException& r ) + { + css::uno::Any ex( cppu::getCaughtException() ); + // the wrapped UseBackupException means that the target medium can be corrupted + embed::UseBackupException aException; + if ( r.TargetException >>= aException ) + { + m_xStream.clear(); + m_xInputStream.clear(); + throw aException; + } + + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(ex)); + throw; + } + } + else if ( !m_bCommited ) + { + m_xPackageFolder = xNewPackageFolder; + m_bCommited = true; + } + + // after commit the mediatype treated as the correct one + m_bMTFallbackUsed = false; +} + +void OStorage_Impl::Revert() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( !( m_nStorageMode & embed::ElementModes::WRITE ) ) + return; // nothing to do + + // all the children must be removed + // they will be created later on demand + + // rebuild the map - cannot do it in-place, because we're changing some of the key values + std::unordered_map<OUString, std::vector<SotElement_Impl*>> oldMap; + std::swap(oldMap, m_aChildrenMap); + + for (const auto & rPair : oldMap) + for (auto pElement : rPair.second) + { + if ( pElement->m_bIsInserted ) + delete pElement; + else + { + ClearElement( pElement ); + + pElement->m_bIsRemoved = false; + + m_aChildrenMap[pElement->m_aOriginalName].push_back(pElement); + } + } + + // return replaced removed elements + for ( auto& pDeleted : m_aDeletedVector ) + { + m_aChildrenMap[pDeleted->m_aOriginalName].push_back(pDeleted); + + ClearElement( pDeleted ); + + pDeleted->m_bIsRemoved = false; + } + m_aDeletedVector.clear(); + + m_bControlMediaType = false; + m_bControlVersion = false; + + GetStorageProperties(); + + if ( m_nStorageType == embed::StorageFormats::OFOPXML ) + { + // currently the relations storage is changed only on commit + m_xNewRelInfoStream.clear(); + m_aRelInfo = uno::Sequence< uno::Sequence< beans::StringPair > >(); + m_nRelInfoStatus = RELINFO_NO_INIT; + } +} + +::comphelper::SequenceAsHashMap OStorage_Impl::GetCommonRootEncryptionData() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ) ; + + if ( m_nStorageType != embed::StorageFormats::PACKAGE ) + throw packages::NoEncryptionException( THROW_WHERE ); + + if ( m_bIsRoot ) + { + if ( !m_bHasCommonEncryptionData ) + throw packages::NoEncryptionException( THROW_WHERE ); + + return m_aCommonEncryptionData; + } + else + { + if ( !m_pParent ) + throw packages::NoEncryptionException( THROW_WHERE ); + + return m_pParent->GetCommonRootEncryptionData(); + } +} + +SotElement_Impl* OStorage_Impl::FindElement( const OUString& rName ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + SAL_WARN_IF( rName.isEmpty(), "package.xstor", "Name is empty!" ); + + ReadContents(); + + auto mapIt = m_aChildrenMap.find(rName); + if (mapIt == m_aChildrenMap.end() && m_bRepairPackage) + mapIt = std::find_if(m_aChildrenMap.begin(), m_aChildrenMap.end(), + [&rName](const auto& pair) + { return rName.equalsIgnoreAsciiCase(pair.first); }); + if (mapIt == m_aChildrenMap.end()) + return nullptr; + for (auto pElement : mapIt->second) + if (!pElement->m_bIsRemoved) + return pElement; + + return nullptr; +} + +SotElement_Impl* OStorage_Impl::InsertStream( const OUString& aName, bool bEncr ) +{ + SAL_WARN_IF( !m_xPackage.is(), "package.xstor", "Not possible to refer to package as to factory!" ); + if ( !m_xPackage.is() ) + throw embed::InvalidStorageException( THROW_WHERE); + + uno::Sequence< uno::Any > aSeq{ uno::Any(false) }; + uno::Reference< uno::XInterface > xNewElement( m_xPackage->createInstanceWithArguments( aSeq ) ); + + SAL_WARN_IF( !xNewElement.is(), "package.xstor", "Not possible to create a new stream!" ); + if ( !xNewElement.is() ) + throw io::IOException( THROW_WHERE ); + + uno::Reference< packages::XDataSinkEncrSupport > xPackageSubStream( xNewElement, uno::UNO_QUERY_THROW ); + + OSL_ENSURE( m_nStorageType == embed::StorageFormats::PACKAGE || !bEncr, "Only package storage supports encryption!" ); + if ( m_nStorageType != embed::StorageFormats::PACKAGE && bEncr ) + throw packages::NoEncryptionException( THROW_WHERE ); + + // the mode is not needed for storage stream internal implementation + SotElement_Impl* pNewElement = InsertElement( aName, false ); + pNewElement->m_xStream.reset(new OWriteStream_Impl(this, xPackageSubStream, m_xPackage, m_xContext, bEncr, m_nStorageType, true)); + + m_aChildrenMap[aName].push_back( pNewElement ); + m_bIsModified = true; + m_bBroadcastModified = true; + + return pNewElement; +} + +void OStorage_Impl::InsertRawStream( const OUString& aName, const uno::Reference< io::XInputStream >& xInStream ) +{ + // insert of raw stream means insert and commit + SAL_WARN_IF( !m_xPackage.is(), "package.xstor", "Not possible to refer to package as to factory!" ); + if ( !m_xPackage.is() ) + throw embed::InvalidStorageException( THROW_WHERE ); + + if ( m_nStorageType != embed::StorageFormats::PACKAGE ) + throw packages::NoEncryptionException( THROW_WHERE ); + + uno::Reference< io::XSeekable > xSeek( xInStream, uno::UNO_QUERY ); + uno::Reference< io::XInputStream > xInStrToInsert = xSeek.is() ? xInStream : + GetSeekableTempCopy( xInStream ); + + uno::Sequence< uno::Any > aSeq{ uno::Any(false) }; + uno::Reference< uno::XInterface > xNewElement( m_xPackage->createInstanceWithArguments( aSeq ) ); + + SAL_WARN_IF( !xNewElement.is(), "package.xstor", "Not possible to create a new stream!" ); + if ( !xNewElement.is() ) + throw io::IOException( THROW_WHERE ); + + uno::Reference< packages::XDataSinkEncrSupport > xPackageSubStream( xNewElement, uno::UNO_QUERY_THROW ); + xPackageSubStream->setRawStream( xInStrToInsert ); + + // the mode is not needed for storage stream internal implementation + SotElement_Impl* pNewElement = InsertElement( aName, false ); + pNewElement->m_xStream.reset(new OWriteStream_Impl(this, xPackageSubStream, m_xPackage, m_xContext, true, m_nStorageType, false)); + // the stream is inserted and must be treated as a committed one + pNewElement->m_xStream->SetToBeCommited(); + + m_aChildrenMap[aName].push_back( pNewElement ); + m_bIsModified = true; + m_bBroadcastModified = true; +} + +std::unique_ptr<OStorage_Impl> OStorage_Impl::CreateNewStorageImpl( sal_Int32 nStorageMode ) +{ + SAL_WARN_IF( !m_xPackage.is(), "package.xstor", "Not possible to refer to package as to factory!" ); + if ( !m_xPackage.is() ) + throw embed::InvalidStorageException( THROW_WHERE ); + + uno::Sequence< uno::Any > aSeq{ uno::Any(true) }; + uno::Reference< uno::XInterface > xNewElement( m_xPackage->createInstanceWithArguments( aSeq ) ); + + SAL_WARN_IF( !xNewElement.is(), "package.xstor", "Not possible to create a new storage!" ); + if ( !xNewElement.is() ) + throw io::IOException( THROW_WHERE ); + + uno::Reference< container::XNameContainer > xPackageSubFolder( xNewElement, uno::UNO_QUERY_THROW ); + std::unique_ptr<OStorage_Impl> pResult( + new OStorage_Impl( this, nStorageMode, xPackageSubFolder, m_xPackage, m_xContext, m_nStorageType )); + pResult->m_bIsModified = true; + + return pResult; +} + +SotElement_Impl* OStorage_Impl::InsertStorage( const OUString& aName, sal_Int32 nStorageMode ) +{ + SotElement_Impl* pNewElement = InsertElement( aName, true ); + + pNewElement->m_xStorage = CreateNewStorageImpl(nStorageMode); + + m_aChildrenMap[aName].push_back( pNewElement ); + + return pNewElement; +} + +SotElement_Impl* OStorage_Impl::InsertElement( const OUString& aName, bool bIsStorage ) +{ + assert( FindElement(aName) == nullptr && "Should not try to insert existing element"); + + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + SotElement_Impl* pDeletedElm = nullptr; + + auto it = m_aChildrenMap.find(aName); + if (it != m_aChildrenMap.end()) + for (auto pElement : it->second) + { + SAL_WARN_IF( !pElement->m_bIsRemoved, "package.xstor", "Try to insert an element instead of existing one!" ); + if ( pElement->m_bIsRemoved ) + { + SAL_WARN_IF( pElement->m_bIsInserted, "package.xstor", "Inserted elements must be deleted immediately!" ); + pDeletedElm = pElement; + } + } + + if ( pDeletedElm ) + { + if ( pDeletedElm->m_bIsStorage ) + OpenSubStorage( pDeletedElm, embed::ElementModes::READWRITE ); + else + OpenSubStream( pDeletedElm ); + + auto & rVec = m_aChildrenMap[aName]; + std::erase(rVec, pDeletedElm); + if (rVec.empty()) + m_aChildrenMap.erase(aName); + m_aDeletedVector.push_back( pDeletedElm ); + } + + // create new element + return new SotElement_Impl( aName, bIsStorage, true ); +} + +void OStorage_Impl::OpenSubStorage( SotElement_Impl* pElement, sal_Int32 nStorageMode ) +{ + SAL_WARN_IF( !pElement, "package.xstor", "pElement is not set!" ); + SAL_WARN_IF( !pElement->m_bIsStorage, "package.xstor", "Storage flag is not set!" ); + + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if (!pElement->m_xStorage) + { + SAL_WARN_IF( pElement->m_bIsInserted, "package.xstor", "Inserted element must be created already!" ); + + uno::Reference< uno::XInterface > xTmp; + m_xPackageFolder->getByName( pElement->m_aOriginalName ) >>= xTmp; + if ( !xTmp.is() ) + throw container::NoSuchElementException( THROW_WHERE ); + + uno::Reference< container::XNameContainer > xPackageSubFolder( xTmp, uno::UNO_QUERY_THROW ); + pElement->m_xStorage.reset(new OStorage_Impl(this, nStorageMode, xPackageSubFolder, m_xPackage, m_xContext, m_nStorageType)); + } +} + +void OStorage_Impl::OpenSubStream( SotElement_Impl* pElement ) +{ + SAL_WARN_IF( !pElement, "package.xstor", "pElement is not set!" ); + SAL_WARN_IF( pElement->m_bIsStorage, "package.xstor", "Storage flag is set!" ); + + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if (pElement->m_xStream) + return; + + SAL_WARN_IF( pElement->m_bIsInserted, "package.xstor", "Inserted element must be created already!" ); + + uno::Reference< uno::XInterface > xTmp; + m_xPackageFolder->getByName( pElement->m_aOriginalName ) >>= xTmp; + if ( !xTmp.is() ) + throw container::NoSuchElementException( THROW_WHERE ); + + uno::Reference< packages::XDataSinkEncrSupport > xPackageSubStream( xTmp, uno::UNO_QUERY_THROW ); + + // the stream can never be inserted here, because inserted stream element holds the stream till commit or destruction + pElement->m_xStream.reset(new OWriteStream_Impl(this, xPackageSubStream, m_xPackage, m_xContext, false, m_nStorageType, false, GetRelInfoStreamForName(pElement->m_aOriginalName))); +} + +uno::Sequence< OUString > OStorage_Impl::GetElementNames() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + ReadContents(); + + sal_Int32 nCnt = 0; + for ( const auto& pair : m_aChildrenMap ) + for (auto pElement : pair.second) + { + if ( !pElement->m_bIsRemoved ) + nCnt++; + } + + uno::Sequence<OUString> aElementNames(nCnt); + OUString* pArray = aElementNames.getArray(); + for ( const auto& pair : m_aChildrenMap ) + for (auto pElement : pair.second) + { + if ( !pElement->m_bIsRemoved ) + *pArray++ = pair.first; + } + + return aElementNames; +} + +void OStorage_Impl::RemoveElement( OUString const & rName, SotElement_Impl* pElement ) +{ + assert(pElement); + + if ( (pElement->m_xStorage && ( pElement->m_xStorage->m_pAntiImpl || !pElement->m_xStorage->m_aReadOnlyWrapVector.empty() )) + || (pElement->m_xStream && ( pElement->m_xStream->m_pAntiImpl || !pElement->m_xStream->m_aInputStreamsVector.empty() )) ) + throw io::IOException( THROW_WHERE ); // TODO: Access denied + + auto mapIt = m_aChildrenMap.find(rName); + for (auto it = mapIt->second.begin(); it != mapIt->second.end(); ++it) + if (pElement == *it) + { + if ( pElement->m_bIsInserted ) + { + delete pElement; + std::erase(mapIt->second, pElement); + if (mapIt->second.empty()) + m_aChildrenMap.erase(mapIt); + } + else + { + pElement->m_bIsRemoved = true; + ClearElement( pElement ); + } + return; + } + assert(false && "not found"); + + // TODO/OFOPXML: the rel stream should be removed as well +} + +void OStorage_Impl::ClearElement( SotElement_Impl* pElement ) +{ + pElement->m_xStorage.reset(); + pElement->m_xStream.reset(); +} + +void OStorage_Impl::CloneStreamElement( const OUString& aStreamName, + bool bEncryptionDataProvided, + const ::comphelper::SequenceAsHashMap& aEncryptionData, + uno::Reference< io::XStream >& xTargetStream ) +{ + SotElement_Impl *pElement = FindElement( aStreamName ); + if ( !pElement ) + { + // element does not exist, throw exception + throw io::IOException( THROW_WHERE ); // TODO: access_denied + } + else if ( pElement->m_bIsStorage ) + throw io::IOException( THROW_WHERE ); + + if (!pElement->m_xStream) + OpenSubStream( pElement ); + + if (!pElement->m_xStream || !pElement->m_xStream->m_xPackageStream.is()) + throw io::IOException( THROW_WHERE ); // TODO: general_error + + // the existence of m_pAntiImpl of the child is not interesting, + // the copy will be created internally + + // usual copying is not applicable here, only last flushed version of the + // child stream should be used for copying. Probably the children m_xPackageStream + // can be used as a base of a new stream, that would be copied to result + // storage. The only problem is that some package streams can be accessed from outside + // at the same time (now solved by wrappers that remember own position). + + if (bEncryptionDataProvided) + pElement->m_xStream->GetCopyOfLastCommit(xTargetStream, aEncryptionData); + else + pElement->m_xStream->GetCopyOfLastCommit(xTargetStream); +} + +void OStorage_Impl::RemoveStreamRelInfo( std::u16string_view aOriginalName ) +{ + // this method should be used only in OStorage_Impl::Commit() method + // the aOriginalName can be empty, in this case the storage relation info should be removed + + if ( m_nStorageType == embed::StorageFormats::OFOPXML && m_xRelStorage.is() ) + { + OUString aRelStreamName = OUString::Concat(aOriginalName) + ".rels"; + + if ( m_xRelStorage->hasByName( aRelStreamName ) ) + m_xRelStorage->removeElement( aRelStreamName ); + } +} + +void OStorage_Impl::CreateRelStorage() +{ + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + return; + + if ( m_xRelStorage.is() ) + return; + + if ( !m_pRelStorElement ) + { + m_pRelStorElement = new SotElement_Impl( "_rels", true, true ); + m_pRelStorElement->m_xStorage = CreateNewStorageImpl(embed::ElementModes::WRITE); + if (m_pRelStorElement->m_xStorage) + m_pRelStorElement->m_xStorage->m_pParent = nullptr; // the relation storage is completely controlled by parent + } + + if (!m_pRelStorElement->m_xStorage) + OpenSubStorage( m_pRelStorElement, embed::ElementModes::WRITE ); + + if (!m_pRelStorElement->m_xStorage) + throw uno::RuntimeException( THROW_WHERE ); + + m_xRelStorage = new OStorage(m_pRelStorElement->m_xStorage.get(), false); +} + +void OStorage_Impl::CommitStreamRelInfo( std::u16string_view rName, SotElement_Impl const * pStreamElement ) +{ + // this method should be used only in OStorage_Impl::Commit() method + + // the stream element must be provided + if ( !pStreamElement ) + throw uno::RuntimeException( THROW_WHERE ); + + if (m_nStorageType == embed::StorageFormats::OFOPXML && pStreamElement->m_xStream) + { + SAL_WARN_IF( rName.empty(), "package.xstor", "The name must not be empty!" ); + + if ( !m_xRelStorage.is() ) + { + // Create new rels storage, this is commit scenario so it must be possible + CreateRelStorage(); + } + + pStreamElement->m_xStream->CommitStreamRelInfo(m_xRelStorage, pStreamElement->m_aOriginalName, rName); + } +} + +uno::Reference< io::XInputStream > OStorage_Impl::GetRelInfoStreamForName( + std::u16string_view aName ) +{ + if ( m_nStorageType == embed::StorageFormats::OFOPXML ) + { + ReadContents(); + if ( m_xRelStorage.is() ) + { + OUString aRelStreamName = OUString::Concat(aName) + ".rels"; + if ( m_xRelStorage->hasByName( aRelStreamName ) ) + { + uno::Reference< io::XStream > xStream = m_xRelStorage->openStreamElement( aRelStreamName, embed::ElementModes::READ ); + if ( xStream.is() ) + return xStream->getInputStream(); + } + } + } + + return uno::Reference< io::XInputStream >(); +} + +void OStorage_Impl::CommitRelInfo( const uno::Reference< container::XNameContainer >& xNewPackageFolder ) +{ + // this method should be used only in OStorage_Impl::Commit() method + OUString aRelsStorName("_rels"); + + if ( !xNewPackageFolder.is() ) + throw uno::RuntimeException( THROW_WHERE ); + + if ( m_nStorageType != embed::StorageFormats::OFOPXML ) + return; + + if ( m_nRelInfoStatus == RELINFO_BROKEN || m_nRelInfoStatus == RELINFO_CHANGED_BROKEN ) + throw io::IOException( THROW_WHERE ); + + if (m_nRelInfoStatus == RELINFO_CHANGED) + { + if (m_aRelInfo.hasElements()) + { + CreateRelStorage(); + + uno::Reference<io::XStream> xRelsStream = m_xRelStorage->openStreamElement( + ".rels", embed::ElementModes::TRUNCATE | embed::ElementModes::READWRITE); + + uno::Reference<io::XOutputStream> xOutStream = xRelsStream->getOutputStream(); + if (!xOutStream.is()) + throw uno::RuntimeException(THROW_WHERE); + + ::comphelper::OFOPXMLHelper::WriteRelationsInfoSequence(xOutStream, m_aRelInfo, + m_xContext); + + // set the mediatype + uno::Reference<beans::XPropertySet> xPropSet(xRelsStream, uno::UNO_QUERY_THROW); + xPropSet->setPropertyValue( + "MediaType", uno::Any(OUString( + "application/vnd.openxmlformats-package.relationships+xml"))); + + m_nRelInfoStatus = RELINFO_READ; + } + else if (m_xRelStorage.is()) + RemoveStreamRelInfo(std::u16string_view()); // remove own rel info + } + else if (m_nRelInfoStatus == RELINFO_CHANGED_STREAM_READ + || m_nRelInfoStatus == RELINFO_CHANGED_STREAM) + { + CreateRelStorage(); + + uno::Reference<io::XStream> xRelsStream = m_xRelStorage->openStreamElement( + ".rels", embed::ElementModes::TRUNCATE | embed::ElementModes::READWRITE); + + uno::Reference<io::XOutputStream> xOutputStream = xRelsStream->getOutputStream(); + if (!xOutputStream.is()) + throw uno::RuntimeException(THROW_WHERE); + + uno::Reference<io::XSeekable> xSeek(m_xNewRelInfoStream, uno::UNO_QUERY_THROW); + xSeek->seek(0); + ::comphelper::OStorageHelper::CopyInputToOutput(m_xNewRelInfoStream, xOutputStream); + + // set the mediatype + uno::Reference<beans::XPropertySet> xPropSet(xRelsStream, uno::UNO_QUERY_THROW); + xPropSet->setPropertyValue( + "MediaType", + uno::Any(OUString("application/vnd.openxmlformats-package.relationships+xml"))); + + m_xNewRelInfoStream.clear(); + if (m_nRelInfoStatus == RELINFO_CHANGED_STREAM) + { + m_aRelInfo = uno::Sequence<uno::Sequence<beans::StringPair>>(); + m_nRelInfoStatus = RELINFO_NO_INIT; + } + else + m_nRelInfoStatus = RELINFO_READ; + } + + if ( !m_xRelStorage.is() ) + return; + + if ( m_xRelStorage->hasElements() ) + { + uno::Reference< embed::XTransactedObject > xTrans( m_xRelStorage, uno::UNO_QUERY_THROW ); + xTrans->commit(); + } + + if ( xNewPackageFolder.is() && xNewPackageFolder->hasByName( aRelsStorName ) ) + xNewPackageFolder->removeByName( aRelsStorName ); + + if ( !m_xRelStorage->hasElements() ) + { + // the empty relations storage should not be created + delete m_pRelStorElement; + m_pRelStorElement = nullptr; + m_xRelStorage.clear(); + } + else if ( m_pRelStorElement && m_pRelStorElement->m_xStorage && xNewPackageFolder.is() ) + m_pRelStorElement->m_xStorage->InsertIntoPackageFolder( aRelsStorName, xNewPackageFolder ); +} + +// OStorage implementation + +OStorage::OStorage( uno::Reference< io::XInputStream > const & xInputStream, + sal_Int32 nMode, + const uno::Sequence< beans::PropertyValue >& xProperties, + uno::Reference< uno::XComponentContext > const & xContext, + sal_Int32 nStorageType ) +: m_pImpl( new OStorage_Impl( xInputStream, nMode, xProperties, xContext, nStorageType ) ) +, m_xSharedMutex( m_pImpl->m_xMutex ) +, m_aListenersContainer( m_pImpl->m_xMutex->GetMutex() ) +, m_bReadOnlyWrap( false ) +{ + m_pImpl->m_pAntiImpl = this; +} + +OStorage::OStorage( uno::Reference< io::XStream > const & xStream, + sal_Int32 nMode, + const uno::Sequence< beans::PropertyValue >& xProperties, + uno::Reference< uno::XComponentContext > const & xContext, + sal_Int32 nStorageType ) +: m_pImpl( new OStorage_Impl( xStream, nMode, xProperties, xContext, nStorageType ) ) +, m_xSharedMutex( m_pImpl->m_xMutex ) +, m_aListenersContainer( m_pImpl->m_xMutex->GetMutex() ) +, m_bReadOnlyWrap( false ) +{ + m_pImpl->m_pAntiImpl = this; +} + +OStorage::OStorage( OStorage_Impl* pImpl, bool bReadOnlyWrap ) +: m_pImpl( pImpl ) +, m_xSharedMutex( m_pImpl->m_xMutex ) +, m_aListenersContainer( m_pImpl->m_xMutex->GetMutex() ) +, m_bReadOnlyWrap( bReadOnlyWrap ) +{ + // this call can be done only from OStorage_Impl implementation to create child storage + assert( m_pImpl && m_pImpl->m_xMutex.is() && "The provided pointer & mutex MUST NOT be empty!" ); + + OSL_ENSURE( ( m_pImpl->m_nStorageMode & embed::ElementModes::WRITE ) == embed::ElementModes::WRITE || + m_bReadOnlyWrap, + "The wrapper can not allow writing in case implementation does not!" ); + + if ( !bReadOnlyWrap ) + m_pImpl->m_pAntiImpl = this; +} + +OStorage::~OStorage() +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + if ( m_pImpl ) + { + osl_atomic_increment(&m_refCount); // to call dispose + try { + dispose(); + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Handled exception"); + } + } +} + +void OStorage::InternalDispose( bool bNotifyImpl ) +{ + if ( !m_pImpl ) + return; + + // the source object is also a kind of locker for the current object + // since the listeners could dispose the object while being notified + lang::EventObject aSource( getXWeak() ); + m_aListenersContainer.disposeAndClear( aSource ); + + if ( !m_pImpl ) + return; + + m_pImpl->m_nModifiedListenerCount = 0; + + if ( m_bReadOnlyWrap ) + { + OSL_ENSURE( m_aOpenSubComponentsVector.empty() || m_pSubElDispListener, + "If any subelements are open the listener must exist!" ); + + if (m_pSubElDispListener) + { + m_pSubElDispListener->OwnerIsDisposed(); + + // iterate through m_pData->m_aOpenSubComponentsVector + // deregister m_pData->m_pSubElDispListener and dispose all of them + if ( !m_aOpenSubComponentsVector.empty() ) + { + for ( const auto& pComp : m_aOpenSubComponentsVector ) + { + uno::Reference< lang::XComponent > xTmp = pComp; + if ( xTmp.is() ) + { + xTmp->removeEventListener( uno::Reference< lang::XEventListener >( + static_cast< lang::XEventListener* >( m_pSubElDispListener.get()))); + + try { + xTmp->dispose(); + } catch( const uno::Exception& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Quiet exception"); + } + } + } + + m_aOpenSubComponentsVector.clear(); + } + } + + if ( bNotifyImpl ) + m_pImpl->RemoveReadOnlyWrap( *this ); + } + else + { + m_pImpl->m_pAntiImpl = nullptr; + + if ( bNotifyImpl ) + { + if ( m_pImpl->m_bIsRoot ) + delete m_pImpl; + else + { + // the non-committed changes for the storage must be removed + m_pImpl->Revert(); + } + } + } + + m_pImpl = nullptr; +} + +void OStorage::ChildIsDisposed( const uno::Reference< uno::XInterface >& xChild ) +{ + // this method can only be called by child disposing listener + + // this method must not contain any locking + // the locking is done in the listener + + auto& rVec = m_aOpenSubComponentsVector; + std::erase_if(rVec, + [&xChild](const uno::Reference<lang::XComponent>& xTmp) { + return !xTmp.is() || xTmp == xChild; + }); +} + +void OStorage::BroadcastModifiedIfNecessary() +{ + // no need to lock mutex here for the checking of m_pImpl, and m_pData is alive until the object is destructed + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( !m_pImpl->m_bBroadcastModified ) + return; + + m_pImpl->m_bBroadcastModified = false; + + SAL_WARN_IF( m_bReadOnlyWrap, "package.xstor", "The storage can not be modified at all!" ); + + lang::EventObject aSource( getXWeak() ); + + comphelper::OInterfaceContainerHelper2* pContainer = + m_aListenersContainer.getContainer( + cppu::UnoType<util::XModifyListener>::get()); + if ( pContainer ) + { + comphelper::OInterfaceIteratorHelper2 pIterator( *pContainer ); + while ( pIterator.hasMoreElements( ) ) + { + static_cast<util::XModifyListener*>( pIterator.next( ) )->modified( aSource ); + } + } +} + +void OStorage::BroadcastTransaction( sal_Int8 nMessage ) +/* + 1 - preCommit + 2 - committed + 3 - preRevert + 4 - reverted +*/ +{ + // no need to lock mutex here for the checking of m_pImpl, and m_pData is alive until the object is destructed + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + SAL_WARN_IF( m_bReadOnlyWrap, "package.xstor", "The storage can not be modified at all!" ); + + lang::EventObject aSource( getXWeak() ); + + comphelper::OInterfaceContainerHelper2* pContainer = + m_aListenersContainer.getContainer( + cppu::UnoType<embed::XTransactionListener>::get()); + if ( !pContainer ) + return; + + comphelper::OInterfaceIteratorHelper2 pIterator( *pContainer ); + while ( pIterator.hasMoreElements( ) ) + { + OSL_ENSURE( nMessage >= 1 && nMessage <= 4, "Wrong internal notification code is used!" ); + + switch( nMessage ) + { + case STOR_MESS_PRECOMMIT: + static_cast<embed::XTransactionListener*>( pIterator.next( ) )->preCommit( aSource ); + break; + case STOR_MESS_COMMITTED: + static_cast<embed::XTransactionListener*>( pIterator.next( ) )->commited( aSource ); + break; + case STOR_MESS_PREREVERT: + static_cast<embed::XTransactionListener*>( pIterator.next( ) )->preRevert( aSource ); + break; + case STOR_MESS_REVERTED: + static_cast<embed::XTransactionListener*>( pIterator.next( ) )->reverted( aSource ); + break; + } + } +} + +SotElement_Impl* OStorage::OpenStreamElement_Impl( const OUString& aStreamName, sal_Int32 nOpenMode, bool bEncr ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + OSL_ENSURE( !m_bReadOnlyWrap || ( nOpenMode & embed::ElementModes::WRITE ) != embed::ElementModes::WRITE, + "An element can not be opened for writing in readonly storage!" ); + + SotElement_Impl *pElement = m_pImpl->FindElement( aStreamName ); + if ( !pElement ) + { + // element does not exist, check if creation is allowed + if ( !( m_pImpl->m_nStorageMode & embed::ElementModes::WRITE ) + || (( nOpenMode & embed::ElementModes::WRITE ) != embed::ElementModes::WRITE ) + || ( nOpenMode & embed::ElementModes::NOCREATE ) == embed::ElementModes::NOCREATE ) + { + throw io::IOException("Element does not exist and cannot be " + "created: \"" + aStreamName + "\""); // TODO: access_denied + } + + // create a new StreamElement and insert it into the list + pElement = m_pImpl->InsertStream( aStreamName, bEncr ); + } + else if ( pElement->m_bIsStorage ) + { + throw io::IOException( THROW_WHERE ); + } + + SAL_WARN_IF( !pElement, "package.xstor", "In case element can not be created an exception must be thrown!" ); + + if (!pElement->m_xStream) + m_pImpl->OpenSubStream( pElement ); + + if (!pElement->m_xStream) + throw io::IOException( THROW_WHERE ); + + return pElement; +} + +void OStorage::MakeLinkToSubComponent_Impl( const uno::Reference< lang::XComponent >& xComponent ) +{ + if ( !xComponent.is() ) + throw uno::RuntimeException( THROW_WHERE ); + + if (!m_pSubElDispListener) + { + m_pSubElDispListener = new OChildDispListener_Impl( *this ); + } + + xComponent->addEventListener( m_pSubElDispListener ); + + m_aOpenSubComponentsVector.emplace_back(xComponent ); +} + +// XInterface + +uno::Any SAL_CALL OStorage::queryInterface( const uno::Type& rType ) +{ + // common interfaces + uno::Any aReturn = ::cppu::queryInterface + ( rType + , static_cast<lang::XTypeProvider*> ( this ) + , static_cast<embed::XStorage*> ( this ) + , static_cast<embed::XStorage2*> ( this ) + , static_cast<embed::XTransactedObject*> ( this ) + , static_cast<embed::XTransactionBroadcaster*> ( this ) + , static_cast<util::XModifiable*> ( this ) + , static_cast<container::XNameAccess*> ( this ) + , static_cast<container::XElementAccess*> ( this ) + , static_cast<lang::XComponent*> ( this ) + , static_cast<beans::XPropertySet*> ( this ) + , static_cast<embed::XOptimizedStorage*> ( this ) ); + + if ( aReturn.hasValue() ) + return aReturn ; + + aReturn = ::cppu::queryInterface + ( rType + , static_cast<embed::XHierarchicalStorageAccess*> ( this ) + , static_cast<embed::XHierarchicalStorageAccess2*> ( this ) ); + + if ( aReturn.hasValue() ) + return aReturn ; + + if ( m_pImpl->m_nStorageType == embed::StorageFormats::PACKAGE ) + { + if ( m_pImpl->m_bIsRoot ) + { + aReturn = ::cppu::queryInterface + ( rType + , static_cast<embed::XStorageRawAccess*> ( this ) + , static_cast<embed::XEncryptionProtectedSource*> ( this ) + , static_cast<embed::XEncryptionProtectedSource2*> ( this ) + , static_cast<embed::XEncryptionProtectedStorage*> ( this ) ); + } + else + { + aReturn = ::cppu::queryInterface + ( rType + , static_cast<embed::XStorageRawAccess*> ( this ) ); + } + } + else if ( m_pImpl->m_nStorageType == embed::StorageFormats::OFOPXML ) + { + aReturn = ::cppu::queryInterface + ( rType + , static_cast<embed::XRelationshipAccess*> ( this ) ); + } + + if ( aReturn.hasValue() ) + return aReturn ; + + return OWeakObject::queryInterface( rType ); +} + +void SAL_CALL OStorage::acquire() noexcept +{ + OWeakObject::acquire(); +} + +void SAL_CALL OStorage::release() noexcept +{ + OWeakObject::release(); +} + +// XTypeProvider +uno::Sequence< uno::Type > SAL_CALL OStorage::getTypes() +{ + if (! m_oTypeCollection) + { + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if (! m_oTypeCollection) + { + if ( m_pImpl->m_nStorageType == embed::StorageFormats::PACKAGE ) + { + if ( m_pImpl->m_bIsRoot ) + { + m_oTypeCollection.emplace( + cppu::UnoType<lang::XTypeProvider>::get() + , cppu::UnoType<embed::XStorage>::get() + , cppu::UnoType<embed::XStorage2>::get() + , cppu::UnoType<embed::XStorageRawAccess>::get() + , cppu::UnoType<embed::XTransactedObject>::get() + , cppu::UnoType<embed::XTransactionBroadcaster>::get() + , cppu::UnoType<util::XModifiable>::get() + , cppu::UnoType<embed::XEncryptionProtectedStorage>::get() + , cppu::UnoType<embed::XEncryptionProtectedSource2>::get() + , cppu::UnoType<embed::XEncryptionProtectedSource>::get() + , cppu::UnoType<beans::XPropertySet>::get()); + } + else + { + m_oTypeCollection.emplace( + cppu::UnoType<lang::XTypeProvider>::get() + , cppu::UnoType<embed::XStorage>::get() + , cppu::UnoType<embed::XStorage2>::get() + , cppu::UnoType<embed::XStorageRawAccess>::get() + , cppu::UnoType<embed::XTransactedObject>::get() + , cppu::UnoType<embed::XTransactionBroadcaster>::get() + , cppu::UnoType<util::XModifiable>::get() + , cppu::UnoType<beans::XPropertySet>::get()); + } + } + else if ( m_pImpl->m_nStorageType == embed::StorageFormats::OFOPXML ) + { + m_oTypeCollection.emplace( + cppu::UnoType<lang::XTypeProvider>::get() + , cppu::UnoType<embed::XStorage>::get() + , cppu::UnoType<embed::XTransactedObject>::get() + , cppu::UnoType<embed::XTransactionBroadcaster>::get() + , cppu::UnoType<util::XModifiable>::get() + , cppu::UnoType<embed::XRelationshipAccess>::get() + , cppu::UnoType<beans::XPropertySet>::get()); + } + else + { + m_oTypeCollection.emplace( + cppu::UnoType<lang::XTypeProvider>::get() + , cppu::UnoType<embed::XStorage>::get() + , cppu::UnoType<embed::XTransactedObject>::get() + , cppu::UnoType<embed::XTransactionBroadcaster>::get() + , cppu::UnoType<util::XModifiable>::get() + , cppu::UnoType<beans::XPropertySet>::get()); + } + } + } + + return m_oTypeCollection->getTypes() ; +} + +uno::Sequence< sal_Int8 > SAL_CALL OStorage::getImplementationId() +{ + static const comphelper::UnoIdInit lcl_ImplId; + return lcl_ImplId.getSeq(); +} + +// XStorage +void SAL_CALL OStorage::copyToStorage( const uno::Reference< embed::XStorage >& xDest ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( !xDest.is() || xDest == getXWeak() ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); + + try { + m_pImpl->CopyToStorage( xDest, false ); + } + catch( const embed::InvalidStorageException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const lang::IllegalArgumentException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const embed::StorageWrappedTargetException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const io::IOException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw embed::StorageWrappedTargetException( THROW_WHERE "Can't copy storage!", + uno::Reference< io::XInputStream >(), + aCaught ); + } +} + +uno::Reference< io::XStream > SAL_CALL OStorage::openStreamElement( + const OUString& aStreamName, sal_Int32 nOpenMode ) +{ + osl::ClearableMutexGuard aGuard(m_xSharedMutex->GetMutex()); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( aStreamName.isEmpty() || !::comphelper::OStorageHelper::IsValidZipEntryFileName( aStreamName, false ) ) + throw lang::IllegalArgumentException( THROW_WHERE "Unexpected entry name syntax.", uno::Reference< uno::XInterface >(), 1 ); + + if ( m_pImpl->m_nStorageType == embed::StorageFormats::OFOPXML && aStreamName == "_rels" ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); // unacceptable element name + + if ( ( nOpenMode & embed::ElementModes::WRITE ) && m_bReadOnlyWrap ) + throw io::IOException( THROW_WHERE ); // TODO: access denied + + uno::Reference< io::XStream > xResult; + try + { + SotElement_Impl *pElement = OpenStreamElement_Impl( aStreamName, nOpenMode, false ); + OSL_ENSURE(pElement && pElement->m_xStream, "In case element can not be created an exception must be thrown!"); + + xResult = pElement->m_xStream->GetStream(nOpenMode, false); + SAL_WARN_IF( !xResult.is(), "package.xstor", "The method must throw exception instead of removing empty result!" ); + + if ( m_bReadOnlyWrap ) + { + // before the storage disposes the stream it must deregister itself as listener + uno::Reference< lang::XComponent > xStreamComponent( xResult, uno::UNO_QUERY_THROW ); + MakeLinkToSubComponent_Impl( xStreamComponent ); + } + } + catch( const embed::InvalidStorageException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const lang::IllegalArgumentException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const packages::WrongPasswordException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const embed::StorageWrappedTargetException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const io::IOException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw embed::StorageWrappedTargetException(THROW_WHERE "Can't open stream element!", + uno::Reference< io::XInputStream >(), + aCaught ); + } + + aGuard.clear(); + + BroadcastModifiedIfNecessary(); + + return xResult; +} + +uno::Reference< io::XStream > SAL_CALL OStorage::openEncryptedStreamElement( + const OUString& aStreamName, sal_Int32 nOpenMode, const OUString& aPass ) +{ + return openEncryptedStream( aStreamName, nOpenMode, ::comphelper::OStorageHelper::CreatePackageEncryptionData( aPass ) ); +} + +uno::Reference< embed::XStorage > SAL_CALL OStorage::openStorageElement( + const OUString& aStorName, sal_Int32 nStorageMode ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( aStorName.isEmpty() || !::comphelper::OStorageHelper::IsValidZipEntryFileName( aStorName, false ) ) + throw lang::IllegalArgumentException( THROW_WHERE "Unexpected entry name syntax.", uno::Reference< uno::XInterface >(), 1 ); + + if ( m_pImpl->m_nStorageType == embed::StorageFormats::OFOPXML && aStorName == "_rels" ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); // unacceptable storage name + + if ( ( nStorageMode & embed::ElementModes::WRITE ) && m_bReadOnlyWrap ) + throw io::IOException( THROW_WHERE ); // TODO: access denied + + if ( ( nStorageMode & embed::ElementModes::TRUNCATE ) + && !( nStorageMode & embed::ElementModes::WRITE ) ) + throw io::IOException( THROW_WHERE ); // TODO: access denied + + // it's always possible to read written storage in this implementation + nStorageMode |= embed::ElementModes::READ; + + uno::Reference< embed::XStorage > xResult; + try + { + SotElement_Impl *pElement = m_pImpl->FindElement( aStorName ); + if ( !pElement ) + { + // element does not exist, check if creation is allowed + if ( !( m_pImpl->m_nStorageMode & embed::ElementModes::WRITE ) + || (( nStorageMode & embed::ElementModes::WRITE ) != embed::ElementModes::WRITE ) + || ( nStorageMode & embed::ElementModes::NOCREATE ) == embed::ElementModes::NOCREATE ) + throw io::IOException( THROW_WHERE ); // TODO: access_denied + + // create a new StorageElement and insert it into the list + pElement = m_pImpl->InsertStorage( aStorName, nStorageMode ); + } + else if ( !pElement->m_bIsStorage ) + { + throw io::IOException( THROW_WHERE ); + } + else if (pElement->m_xStorage) + { + // storage has already been opened; it may be opened another time, if it the mode allows to do so + if (pElement->m_xStorage->m_pAntiImpl) + { + throw io::IOException( THROW_WHERE ); // TODO: access_denied + } + else if ( !pElement->m_xStorage->m_aReadOnlyWrapVector.empty() + && ( nStorageMode & embed::ElementModes::WRITE ) ) + { + throw io::IOException( THROW_WHERE ); // TODO: access_denied + } + else + { + // in case parent storage allows writing the readonly mode of the child storage is + // virtual, that means that it is just enough to change the flag to let it be writable + // and since there is no AntiImpl nobody should be notified about it + pElement->m_xStorage->m_nStorageMode = nStorageMode | embed::ElementModes::READ; + + if ( nStorageMode & embed::ElementModes::TRUNCATE ) + { + for (const auto & rPair : pElement->m_xStorage->m_aChildrenMap) + for (auto pElementToDel : rPair.second) + m_pImpl->RemoveElement( /*aName*/rPair.first, pElementToDel ); + } + } + } + + if (!pElement->m_xStorage) + m_pImpl->OpenSubStorage(pElement, nStorageMode); + + if (!pElement->m_xStorage) + throw io::IOException( THROW_WHERE ); // TODO: general_error + + bool bReadOnlyWrap = ( ( nStorageMode & embed::ElementModes::WRITE ) != embed::ElementModes::WRITE ); + rtl::Reference<OStorage> pResultStorage = new OStorage(pElement->m_xStorage.get(), bReadOnlyWrap); + xResult = pResultStorage; + + if ( bReadOnlyWrap ) + { + // Before this call is done the object must be refcounted already + pElement->m_xStorage->SetReadOnlyWrap(*pResultStorage); + + // before the storage disposes the stream it must deregister itself as listener + uno::Reference< lang::XComponent > xStorageComponent( xResult, uno::UNO_QUERY_THROW ); + MakeLinkToSubComponent_Impl( xStorageComponent ); + } + } + catch( const embed::InvalidStorageException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const lang::IllegalArgumentException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const embed::StorageWrappedTargetException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const io::IOException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw embed::StorageWrappedTargetException( THROW_WHERE "Can't open storage!", + uno::Reference< io::XInputStream >(), + aCaught ); + } + + return xResult; +} + +uno::Reference< io::XStream > SAL_CALL OStorage::cloneStreamElement( const OUString& aStreamName ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( aStreamName.isEmpty() || !::comphelper::OStorageHelper::IsValidZipEntryFileName( aStreamName, false ) ) + throw lang::IllegalArgumentException( THROW_WHERE "Unexpected entry name syntax.", uno::Reference< uno::XInterface >(), 1 ); + + if ( m_pImpl->m_nStorageType == embed::StorageFormats::OFOPXML && aStreamName == "_rels" ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); // unacceptable storage name + + try + { + uno::Reference< io::XStream > xResult; + m_pImpl->CloneStreamElement( aStreamName, false, ::comphelper::SequenceAsHashMap(), xResult ); + if ( !xResult.is() ) + throw uno::RuntimeException( THROW_WHERE ); + return xResult; + } + catch( const embed::InvalidStorageException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const lang::IllegalArgumentException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const packages::WrongPasswordException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const io::IOException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const embed::StorageWrappedTargetException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw embed::StorageWrappedTargetException( THROW_WHERE "Can't clone stream!", + uno::Reference< io::XInputStream >(), + aCaught ); + } +} + +uno::Reference< io::XStream > SAL_CALL OStorage::cloneEncryptedStreamElement( + const OUString& aStreamName, + const OUString& aPass ) +{ + return cloneEncryptedStream( aStreamName, ::comphelper::OStorageHelper::CreatePackageEncryptionData( aPass ) ); +} + +void SAL_CALL OStorage::copyLastCommitTo( + const uno::Reference< embed::XStorage >& xTargetStorage ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + try + { + m_pImpl->CopyLastCommitTo( xTargetStorage ); + } + catch( const embed::InvalidStorageException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const lang::IllegalArgumentException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const embed::StorageWrappedTargetException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const io::IOException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw embed::StorageWrappedTargetException( THROW_WHERE "Can't copy last commit version!", + uno::Reference< io::XInputStream >(), + aCaught ); + } + +} + +void SAL_CALL OStorage::copyStorageElementLastCommitTo( + const OUString& aStorName, + const uno::Reference< embed::XStorage >& xTargetStorage ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( aStorName.isEmpty() || !::comphelper::OStorageHelper::IsValidZipEntryFileName( aStorName, false ) ) + throw lang::IllegalArgumentException( THROW_WHERE "Unexpected entry name syntax.", uno::Reference< uno::XInterface >(), 1 ); + + if ( m_pImpl->m_nStorageType == embed::StorageFormats::OFOPXML && aStorName == "_rels" ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); // unacceptable storage name + + try + { + SotElement_Impl *pElement = m_pImpl->FindElement( aStorName ); + if ( !pElement ) + { + // element does not exist, throw exception + throw io::IOException( THROW_WHERE ); // TODO: access_denied + } + else if ( !pElement->m_bIsStorage ) + { + throw io::IOException( THROW_WHERE ); + } + + if (!pElement->m_xStorage) + m_pImpl->OpenSubStorage( pElement, embed::ElementModes::READ ); + + if (!pElement->m_xStorage) + throw io::IOException( THROW_WHERE ); // TODO: general_error + + // the existence of m_pAntiImpl of the child is not interesting, + // the copy will be created internally + + pElement->m_xStorage->CopyLastCommitTo(xTargetStorage); + } + catch( const embed::InvalidStorageException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const lang::IllegalArgumentException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const io::IOException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const embed::StorageWrappedTargetException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw embed::StorageWrappedTargetException( THROW_WHERE "Can't copy last commit element version!", + uno::Reference< io::XInputStream >(), + aCaught ); + } +} + +sal_Bool SAL_CALL OStorage::isStreamElement( const OUString& aElementName ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( aElementName.isEmpty() || !::comphelper::OStorageHelper::IsValidZipEntryFileName( aElementName, false ) ) + throw lang::IllegalArgumentException( THROW_WHERE "Unexpected entry name syntax.", uno::Reference< uno::XInterface >(), 1 ); + + if ( m_pImpl->m_nStorageType == embed::StorageFormats::OFOPXML && aElementName == "_rels" ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); // unacceptable name + + SotElement_Impl* pElement = nullptr; + + try + { + pElement = m_pImpl->FindElement( aElementName ); + } + catch( const embed::InvalidStorageException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const lang::IllegalArgumentException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const container::NoSuchElementException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw lang::WrappedTargetRuntimeException( THROW_WHERE "Can't detect whether it is a stream!", + uno::Reference< io::XInputStream >(), + aCaught ); + } + + if ( !pElement ) + throw container::NoSuchElementException( THROW_WHERE ); //??? + + return !pElement->m_bIsStorage; +} + +sal_Bool SAL_CALL OStorage::isStorageElement( const OUString& aElementName ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( aElementName.isEmpty() || !::comphelper::OStorageHelper::IsValidZipEntryFileName( aElementName, false ) ) + throw lang::IllegalArgumentException( THROW_WHERE "Unexpected entry name syntax.", uno::Reference< uno::XInterface >(), 1 ); + + if ( m_pImpl->m_nStorageType == embed::StorageFormats::OFOPXML && aElementName == "_rels" ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); + + SotElement_Impl* pElement = nullptr; + + try + { + pElement = m_pImpl->FindElement( aElementName ); + } + catch( const embed::InvalidStorageException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const lang::IllegalArgumentException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const container::NoSuchElementException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw lang::WrappedTargetRuntimeException( THROW_WHERE "can't detect whether it is a storage", + uno::Reference< io::XInputStream >(), + aCaught ); + } + + if ( !pElement ) + throw container::NoSuchElementException( THROW_WHERE ); //??? + + return pElement->m_bIsStorage; +} + +void SAL_CALL OStorage::removeElement( const OUString& aElementName ) +{ + { + osl::MutexGuard aGuard(m_xSharedMutex->GetMutex()); + + if (!m_pImpl) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException(THROW_WHERE); + } + + if (aElementName.isEmpty() + || !::comphelper::OStorageHelper::IsValidZipEntryFileName(aElementName, false)) + throw lang::IllegalArgumentException(THROW_WHERE "Unexpected entry name syntax.", + uno::Reference<uno::XInterface>(), 1); + + if (m_pImpl->m_nStorageType == embed::StorageFormats::OFOPXML && aElementName == "_rels") + throw lang::IllegalArgumentException(THROW_WHERE, uno::Reference<uno::XInterface>(), + 1); // TODO: unacceptable name + + if (!(m_pImpl->m_nStorageMode & embed::ElementModes::WRITE)) + throw io::IOException(THROW_WHERE); // TODO: access denied + + try + { + auto pElement = m_pImpl->FindElement(aElementName); + if ( !pElement ) + throw container::NoSuchElementException(THROW_WHERE); //??? + + m_pImpl->RemoveElement(aElementName, pElement); + + m_pImpl->m_bIsModified = true; + m_pImpl->m_bBroadcastModified = true; + } + catch (const embed::InvalidStorageException&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch (const lang::IllegalArgumentException&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch (const container::NoSuchElementException&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch (const io::IOException&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch (const embed::StorageWrappedTargetException&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch (const uno::RuntimeException&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch (const uno::Exception&) + { + uno::Any aCaught(::cppu::getCaughtException()); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw embed::StorageWrappedTargetException(THROW_WHERE "Can't remove element!", + uno::Reference<io::XInputStream>(), aCaught); + } + } + + BroadcastModifiedIfNecessary(); +} + +void SAL_CALL OStorage::renameElement( const OUString& aElementName, const OUString& aNewName ) +{ + { + osl::MutexGuard aGuard(m_xSharedMutex->GetMutex()); + + if (!m_pImpl) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException(THROW_WHERE); + } + + if (aElementName.isEmpty() + || !::comphelper::OStorageHelper::IsValidZipEntryFileName(aElementName, false) + || aNewName.isEmpty() + || !::comphelper::OStorageHelper::IsValidZipEntryFileName(aNewName, false)) + throw lang::IllegalArgumentException(THROW_WHERE "Unexpected entry name syntax.", + uno::Reference<uno::XInterface>(), 1); + + if (m_pImpl->m_nStorageType == embed::StorageFormats::OFOPXML + && (aElementName == "_rels" || aNewName == "_rels")) + throw lang::IllegalArgumentException(THROW_WHERE, uno::Reference<uno::XInterface>(), + 0); // TODO: unacceptable element name + + if (!(m_pImpl->m_nStorageMode & embed::ElementModes::WRITE)) + throw io::IOException(THROW_WHERE); // TODO: access denied + + try + { + SotElement_Impl* pRefElement = m_pImpl->FindElement(aNewName); + if (pRefElement) + throw container::ElementExistException(THROW_WHERE); //??? + + auto pElement = m_pImpl->FindElement( aElementName ); + if ( !pElement ) + throw container::NoSuchElementException(THROW_WHERE); //??? + + auto mapIt = m_pImpl->m_aChildrenMap.find(aElementName); + auto rVec = mapIt->second; + for (auto it = rVec.begin(); it != rVec.end(); ++it) + if (pElement == *it) + { + std::erase(rVec, pElement); + if (rVec.empty()) + m_pImpl->m_aChildrenMap.erase(mapIt); + break; + } + m_pImpl->m_aChildrenMap[aNewName].push_back(pElement); + m_pImpl->m_bIsModified = true; + m_pImpl->m_bBroadcastModified = true; + } + catch (const embed::InvalidStorageException&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch (const lang::IllegalArgumentException&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch (const container::NoSuchElementException&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch (const container::ElementExistException&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch (const io::IOException&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch (const embed::StorageWrappedTargetException&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch (const uno::RuntimeException&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch (const uno::Exception&) + { + uno::Any aCaught(::cppu::getCaughtException()); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw embed::StorageWrappedTargetException(THROW_WHERE "Can't rename element!", + uno::Reference<io::XInputStream>(), aCaught); + } + } + + BroadcastModifiedIfNecessary(); +} + +void SAL_CALL OStorage::copyElementTo( const OUString& aElementName, + const uno::Reference< embed::XStorage >& xDest, + const OUString& aNewName ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( aElementName.isEmpty() || !::comphelper::OStorageHelper::IsValidZipEntryFileName( aElementName, false ) + || aNewName.isEmpty() || !::comphelper::OStorageHelper::IsValidZipEntryFileName( aNewName, false ) ) + throw lang::IllegalArgumentException( THROW_WHERE "Unexpected entry name syntax.", uno::Reference< uno::XInterface >(), 1 ); + + if ( !xDest.is() ) + // || xDest == getXWeak() ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 2 ); + + if ( m_pImpl->m_nStorageType == embed::StorageFormats::OFOPXML && ( aElementName == "_rels" || aNewName == "_rels" ) ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 0 ); // unacceptable element name + + try + { + SotElement_Impl* pElement = m_pImpl->FindElement( aElementName ); + if ( !pElement ) + throw container::NoSuchElementException( THROW_WHERE ); + + uno::Reference< XNameAccess > xNameAccess( xDest, uno::UNO_QUERY_THROW ); + if ( xNameAccess->hasByName( aNewName ) ) + throw container::ElementExistException( THROW_WHERE ); + + m_pImpl->CopyStorageElement( pElement, xDest, aNewName, false ); + } + catch( const embed::InvalidStorageException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const lang::IllegalArgumentException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const container::NoSuchElementException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const container::ElementExistException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const embed::StorageWrappedTargetException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const io::IOException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw embed::StorageWrappedTargetException( THROW_WHERE "Can't copy element!", + uno::Reference< io::XInputStream >(), + aCaught ); + } +} + +void SAL_CALL OStorage::moveElementTo( const OUString& aElementName, + const uno::Reference< embed::XStorage >& xDest, + const OUString& aNewName ) +{ + { + osl::MutexGuard aGuard(m_xSharedMutex->GetMutex()); + + if (!m_pImpl) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException(THROW_WHERE); + } + + if (aElementName.isEmpty() + || !::comphelper::OStorageHelper::IsValidZipEntryFileName(aElementName, false) + || aNewName.isEmpty() + || !::comphelper::OStorageHelper::IsValidZipEntryFileName(aNewName, false)) + throw lang::IllegalArgumentException(THROW_WHERE "Unexpected entry name syntax.", + uno::Reference<uno::XInterface>(), 1); + + if (!xDest.is() || xDest == getXWeak()) + throw lang::IllegalArgumentException(THROW_WHERE, uno::Reference<uno::XInterface>(), 2); + + if (m_pImpl->m_nStorageType == embed::StorageFormats::OFOPXML + && (aElementName == "_rels" || aNewName == "_rels")) + throw lang::IllegalArgumentException(THROW_WHERE, uno::Reference<uno::XInterface>(), + 0); // unacceptable element name + + if (!(m_pImpl->m_nStorageMode & embed::ElementModes::WRITE)) + throw io::IOException(THROW_WHERE); // TODO: access denied + + try + { + auto pElement = m_pImpl->FindElement( aElementName ); + if ( !pElement ) + throw container::NoSuchElementException(THROW_WHERE); //??? + + uno::Reference<XNameAccess> xNameAccess(xDest, uno::UNO_QUERY_THROW); + if (xNameAccess->hasByName(aNewName)) + throw container::ElementExistException(THROW_WHERE); + + m_pImpl->CopyStorageElement(pElement, xDest, aNewName, false); + + m_pImpl->RemoveElement(aElementName, pElement); + + m_pImpl->m_bIsModified = true; + m_pImpl->m_bBroadcastModified = true; + } + catch (const embed::InvalidStorageException&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch (const lang::IllegalArgumentException&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch (const container::NoSuchElementException&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch (const container::ElementExistException&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch (const embed::StorageWrappedTargetException&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch (const io::IOException&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch (const uno::RuntimeException&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch (const uno::Exception&) + { + uno::Any aCaught(::cppu::getCaughtException()); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw embed::StorageWrappedTargetException(THROW_WHERE "Can't move element!", + uno::Reference<io::XInputStream>(), aCaught); + } + } + + BroadcastModifiedIfNecessary(); +} + +// XStorage2 +uno::Reference< io::XStream > SAL_CALL OStorage::openEncryptedStream( + const OUString& aStreamName, sal_Int32 nOpenMode, const uno::Sequence< beans::NamedValue >& aEncryptionData ) +{ + osl::ClearableMutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( ( nOpenMode & embed::ElementModes::WRITE ) && m_bReadOnlyWrap ) + throw io::IOException( THROW_WHERE ); // TODO: access denied + + if ( !aEncryptionData.hasElements() ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 3 ); + + uno::Reference< io::XStream > xResult; + try + { + SotElement_Impl *pElement = OpenStreamElement_Impl( aStreamName, nOpenMode, true ); + OSL_ENSURE(pElement && pElement->m_xStream, "In case element can not be created an exception must be thrown!"); + + xResult = pElement->m_xStream->GetStream(nOpenMode, aEncryptionData, false); + SAL_WARN_IF( !xResult.is(), "package.xstor", "The method must throw exception instead of removing empty result!" ); + + if ( m_bReadOnlyWrap ) + { + // before the storage disposes the stream it must deregister itself as listener + uno::Reference< lang::XComponent > xStreamComponent( xResult, uno::UNO_QUERY_THROW ); + MakeLinkToSubComponent_Impl( xStreamComponent ); + } + } + catch( const embed::InvalidStorageException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const lang::IllegalArgumentException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const packages::NoEncryptionException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const packages::WrongPasswordException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const embed::StorageWrappedTargetException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const io::IOException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw embed::StorageWrappedTargetException( THROW_WHERE "Can't open encrypted stream!", + uno::Reference< io::XInputStream >(), + aCaught ); + } + + aGuard.clear(); + + BroadcastModifiedIfNecessary(); + + return xResult; +} + +uno::Reference< io::XStream > SAL_CALL OStorage::cloneEncryptedStream( + const OUString& aStreamName, + const uno::Sequence< beans::NamedValue >& aEncryptionData ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( !aEncryptionData.hasElements() ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 2 ); + + try + { + uno::Reference< io::XStream > xResult; + m_pImpl->CloneStreamElement( aStreamName, true, aEncryptionData, xResult ); + if ( !xResult.is() ) + throw uno::RuntimeException( THROW_WHERE ); + return xResult; + } + catch( const embed::InvalidStorageException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const lang::IllegalArgumentException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const packages::NoEncryptionException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const packages::WrongPasswordException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const io::IOException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const embed::StorageWrappedTargetException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw embed::StorageWrappedTargetException( THROW_WHERE "Can't clone encrypted stream!", + uno::Reference< io::XInputStream >(), + aCaught ); + } +} + +// XStorageRawAccess +uno::Reference< io::XInputStream > SAL_CALL OStorage::getPlainRawStreamElement( + const OUString& sStreamName ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( m_pImpl->m_nStorageType == embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException( THROW_WHERE ); // the interface is not supported and must not be accessible + + if ( sStreamName.isEmpty() || !::comphelper::OStorageHelper::IsValidZipEntryFileName( sStreamName, false ) ) + throw lang::IllegalArgumentException( THROW_WHERE "Unexpected entry name syntax.", uno::Reference< uno::XInterface >(), 1 ); + + uno::Reference < io::XInputStream > xTempIn; + try + { + SotElement_Impl* pElement = m_pImpl->FindElement( sStreamName ); + if ( !pElement ) + throw container::NoSuchElementException( THROW_WHERE ); + + if (!pElement->m_xStream) + { + m_pImpl->OpenSubStream( pElement ); + if (!pElement->m_xStream) + throw io::IOException( THROW_WHERE ); + } + + uno::Reference<io::XInputStream> xRawInStream = pElement->m_xStream->GetPlainRawInStream(); + if ( !xRawInStream.is() ) + throw io::IOException( THROW_WHERE ); + + rtl::Reference < utl::TempFileFastService > xTempFile = new utl::TempFileFastService; + uno::Reference < io::XOutputStream > xTempOut = xTempFile->getOutputStream(); + xTempIn = xTempFile->getInputStream(); + + if ( !xTempOut.is() || !xTempIn.is() ) + throw io::IOException( THROW_WHERE ); + + // Copy temporary file to a new one + ::comphelper::OStorageHelper::CopyInputToOutput( xRawInStream, xTempOut ); + xTempOut->closeOutput(); + xTempFile->seek( 0 ); + } + catch( const embed::InvalidStorageException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const lang::IllegalArgumentException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const container::NoSuchElementException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const embed::StorageWrappedTargetException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const io::IOException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw embed::StorageWrappedTargetException( THROW_WHERE "Can't get plain raw stream!", + uno::Reference< io::XInputStream >(), + aCaught ); + } + + return xTempIn; +} + +uno::Reference< io::XInputStream > SAL_CALL OStorage::getRawEncrStreamElement( + const OUString& sStreamName ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( m_pImpl->m_nStorageType != embed::StorageFormats::PACKAGE ) + throw packages::NoEncryptionException( THROW_WHERE ); + + if ( sStreamName.isEmpty() || !::comphelper::OStorageHelper::IsValidZipEntryFileName( sStreamName, false ) ) + throw lang::IllegalArgumentException( THROW_WHERE "Unexpected entry name syntax.", uno::Reference< uno::XInterface >(), 1 ); + + uno::Reference < io::XInputStream > xTempIn; + try + { + SotElement_Impl* pElement = m_pImpl->FindElement( sStreamName ); + if ( !pElement ) + throw container::NoSuchElementException( THROW_WHERE ); + + if (!pElement->m_xStream) + { + m_pImpl->OpenSubStream( pElement ); + if (!pElement->m_xStream) + throw io::IOException( THROW_WHERE ); + } + + if (!pElement->m_xStream->IsEncrypted()) + throw packages::NoEncryptionException( THROW_WHERE ); + + uno::Reference< io::XInputStream > xRawInStream = pElement->m_xStream->GetRawInStream(); + if ( !xRawInStream.is() ) + throw io::IOException( THROW_WHERE ); + + rtl::Reference < utl::TempFileFastService > xTempFile = new utl::TempFileFastService; + uno::Reference < io::XOutputStream > xTempOut = xTempFile; + xTempIn = xTempFile; + + if ( !xTempFile ) + throw io::IOException( THROW_WHERE ); + + // Copy temporary file to a new one + ::comphelper::OStorageHelper::CopyInputToOutput( xRawInStream, xTempOut ); + xTempFile->closeOutput(); + xTempFile->seek( 0 ); + + } + catch( const embed::InvalidStorageException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const lang::IllegalArgumentException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const packages::NoEncryptionException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const container::NoSuchElementException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const embed::StorageWrappedTargetException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const io::IOException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw embed::StorageWrappedTargetException( THROW_WHERE "Can't get raw stream!", + uno::Reference< io::XInputStream >(), + aCaught ); + } + + return xTempIn; +} + +void SAL_CALL OStorage::insertRawEncrStreamElement( const OUString& aStreamName, + const uno::Reference< io::XInputStream >& xInStream ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( m_pImpl->m_nStorageType != embed::StorageFormats::PACKAGE ) + throw embed::InvalidStorageException( THROW_WHERE ); + + if ( aStreamName.isEmpty() || !::comphelper::OStorageHelper::IsValidZipEntryFileName( aStreamName, false ) ) + throw lang::IllegalArgumentException( THROW_WHERE "Unexpected entry name syntax.", uno::Reference< uno::XInterface >(), 1 ); + + if ( !xInStream.is() ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 2 ); + + if ( !( m_pImpl->m_nStorageMode & embed::ElementModes::WRITE ) ) + throw io::IOException( THROW_WHERE ); // TODO: access denied + + try + { + SotElement_Impl* pElement = m_pImpl->FindElement( aStreamName ); + if ( pElement ) + throw container::ElementExistException( THROW_WHERE ); + + m_pImpl->InsertRawStream( aStreamName, xInStream ); + } + catch( const embed::InvalidStorageException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const lang::IllegalArgumentException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const packages::NoRawFormatException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const container::ElementExistException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const embed::StorageWrappedTargetException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const io::IOException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw embed::StorageWrappedTargetException( THROW_WHERE "Can't insert raw stream!", + uno::Reference< io::XInputStream >(), + aCaught ); + } +} + +// XTransactedObject +void SAL_CALL OStorage::commit() +{ + uno::Reference< util::XModifiable > xParentModif; + + try { + BroadcastTransaction( STOR_MESS_PRECOMMIT ); + + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( m_bReadOnlyWrap ) + throw io::IOException( THROW_WHERE ); // TODO: access_denied + + m_pImpl->Commit(); // the root storage initiates the storing to source + + // when the storage is committed the parent is modified + if ( m_pImpl->m_pParent && m_pImpl->m_pParent->m_pAntiImpl ) + xParentModif = static_cast<util::XModifiable*>(m_pImpl->m_pParent->m_pAntiImpl); + } + catch( const io::IOException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const embed::StorageWrappedTargetException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw embed::StorageWrappedTargetException( THROW_WHERE "Problems on commit!", + getXWeak(), + aCaught ); + } + + setModified( false ); + if ( xParentModif.is() ) + xParentModif->setModified( true ); + + BroadcastTransaction( STOR_MESS_COMMITTED ); +} + +void SAL_CALL OStorage::revert() +{ + // the method removes all the changes done after last commit + + BroadcastTransaction( STOR_MESS_PREREVERT ); + + { + osl::MutexGuard aGuard(m_xSharedMutex->GetMutex()); + + if (!m_pImpl) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException(THROW_WHERE); + } + + for (const auto & rPair : m_pImpl->m_aChildrenMap) + for (auto pElement : rPair.second) + { + bool bThrow = (pElement->m_xStorage + && (pElement->m_xStorage->m_pAntiImpl + || !pElement->m_xStorage->m_aReadOnlyWrapVector.empty())) + || (pElement->m_xStream + && (pElement->m_xStream->m_pAntiImpl + || !pElement->m_xStream->m_aInputStreamsVector.empty())); + if (bThrow) + throw io::IOException(THROW_WHERE); // TODO: access denied + } + + if (m_bReadOnlyWrap || !m_pImpl->m_bListCreated) + return; // nothing to do + + try + { + m_pImpl->Revert(); + m_pImpl->m_bIsModified = false; + m_pImpl->m_bBroadcastModified = true; + } + catch (const io::IOException&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch (const embed::StorageWrappedTargetException&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch (const uno::RuntimeException&) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch (const uno::Exception&) + { + uno::Any aCaught(::cppu::getCaughtException()); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw embed::StorageWrappedTargetException(THROW_WHERE "Problems on revert!", + getXWeak(), + aCaught); + } + } + + setModified( false ); + BroadcastTransaction( STOR_MESS_REVERTED ); +} + +// XTransactionBroadcaster +void SAL_CALL OStorage::addTransactionListener( const uno::Reference< embed::XTransactionListener >& aListener ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + m_aListenersContainer.addInterface( cppu::UnoType<embed::XTransactionListener>::get(), + aListener ); +} + +void SAL_CALL OStorage::removeTransactionListener( const uno::Reference< embed::XTransactionListener >& aListener ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + m_aListenersContainer.removeInterface( cppu::UnoType<embed::XTransactionListener>::get(), + aListener ); +} + +// XModifiable +// TODO: if there will be no demand on this interface it will be removed from implementation, +// I do not want to remove it now since it is still possible that it will be inserted +// to the service back. + +sal_Bool SAL_CALL OStorage::isModified() +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + return m_pImpl->m_bIsModified; +} + +void SAL_CALL OStorage::setModified( sal_Bool bModified ) +{ + { + osl::MutexGuard aGuard(m_xSharedMutex->GetMutex()); + + if (!m_pImpl) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException(THROW_WHERE); + } + + if (m_bReadOnlyWrap) + throw beans::PropertyVetoException(THROW_WHERE); // TODO: access denied + + if (m_pImpl->m_bIsModified != bool(bModified)) + m_pImpl->m_bIsModified = bModified; + } + + if ( bModified ) + { + m_pImpl->m_bBroadcastModified = true; + BroadcastModifiedIfNecessary(); + } +} + +void SAL_CALL OStorage::addModifyListener( + const uno::Reference< util::XModifyListener >& aListener ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + osl_atomic_increment( &m_pImpl->m_nModifiedListenerCount ); + m_aListenersContainer.addInterface( + cppu::UnoType<util::XModifyListener>::get(), aListener ); +} + +void SAL_CALL OStorage::removeModifyListener( + const uno::Reference< util::XModifyListener >& aListener ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + osl_atomic_decrement( &m_pImpl->m_nModifiedListenerCount ); + m_aListenersContainer.removeInterface( + cppu::UnoType<util::XModifyListener>::get(), aListener ); +} + +// XNameAccess + +uno::Any SAL_CALL OStorage::getByName( const OUString& aName ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( aName.isEmpty() || !::comphelper::OStorageHelper::IsValidZipEntryFileName( aName, false ) ) + throw lang::IllegalArgumentException( THROW_WHERE "Unexpected entry name syntax.", uno::Reference< uno::XInterface >(), 1 ); + + if ( m_pImpl->m_nStorageType == embed::StorageFormats::OFOPXML && aName == "_rels" ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); // unacceptable element name + + uno::Any aResult; + try + { + SotElement_Impl* pElement = m_pImpl->FindElement( aName ); + if ( !pElement ) + throw container::NoSuchElementException( THROW_WHERE ); + + if ( pElement->m_bIsStorage ) + aResult <<= openStorageElement( aName, embed::ElementModes::READ ); + else + aResult <<= openStreamElement( aName, embed::ElementModes::READ ); + } + catch( const container::NoSuchElementException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const lang::WrappedTargetException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw lang::WrappedTargetException( THROW_WHERE "Can not open storage!", + getXWeak(), + aCaught ); + } + + return aResult; +} + +uno::Sequence< OUString > SAL_CALL OStorage::getElementNames() +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + try + { + return m_pImpl->GetElementNames(); + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch ( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw lang::WrappedTargetRuntimeException( THROW_WHERE "Can not open storage!", + getXWeak(), + aCaught ); + } +} + +sal_Bool SAL_CALL OStorage::hasByName( const OUString& aName ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( aName.isEmpty() ) + return false; + + if ( m_pImpl->m_nStorageType == embed::StorageFormats::OFOPXML && aName == "_rels" ) + return false; + + SotElement_Impl* pElement = nullptr; + try + { + pElement = m_pImpl->FindElement( aName ); + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch ( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw lang::WrappedTargetRuntimeException( THROW_WHERE "Can not open storage!", + getXWeak(), + aCaught ); + } + + return ( pElement != nullptr ); +} + +uno::Type SAL_CALL OStorage::getElementType() +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + // it is a multitype container + return uno::Type(); +} + +sal_Bool SAL_CALL OStorage::hasElements() +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + try + { + return m_pImpl->HasChildren(); + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw lang::WrappedTargetRuntimeException( THROW_WHERE "Can not open storage!", + getXWeak(), + aCaught ); + } +} + +// XComponent +void SAL_CALL OStorage::dispose() +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + try + { + InternalDispose( true ); + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw lang::WrappedTargetRuntimeException( THROW_WHERE "Can not open storage!", + getXWeak(), + aCaught ); + } +} + +void SAL_CALL OStorage::addEventListener( + const uno::Reference< lang::XEventListener >& xListener ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + m_aListenersContainer.addInterface( + cppu::UnoType<lang::XEventListener>::get(), xListener ); +} + +void SAL_CALL OStorage::removeEventListener( + const uno::Reference< lang::XEventListener >& xListener ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + m_aListenersContainer.removeInterface( + cppu::UnoType<lang::XEventListener>::get(), xListener ); +} + +// XEncryptionProtectedSource + +void SAL_CALL OStorage::setEncryptionPassword( const OUString& aPass ) +{ + setEncryptionData( ::comphelper::OStorageHelper::CreatePackageEncryptionData( aPass ) ); +} + +void SAL_CALL OStorage::removeEncryption() +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( m_pImpl->m_nStorageType != embed::StorageFormats::PACKAGE ) + throw uno::RuntimeException( THROW_WHERE ); // the interface must be visible only for package storage + + SAL_WARN_IF( !m_pImpl->m_bIsRoot, "package.xstor", "removeEncryption() method is not available for nonroot storages!" ); + if ( !m_pImpl->m_bIsRoot ) + return; + + try { + m_pImpl->ReadContents(); + } + catch ( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch ( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw lang::WrappedTargetRuntimeException( THROW_WHERE "Can not open package!", + getXWeak(), + aCaught ); + } + + // TODO: check if the password is valid + // update all streams that was encrypted with old password + + uno::Reference< beans::XPropertySet > xPackPropSet( m_pImpl->m_xPackage, uno::UNO_QUERY_THROW ); + try + { + xPackPropSet->setPropertyValue( STORAGE_ENCRYPTION_KEYS_PROPERTY, + uno::Any( uno::Sequence< beans::NamedValue >() ) ); + + m_pImpl->m_bHasCommonEncryptionData = false; + m_pImpl->m_aCommonEncryptionData.clear(); + } + catch( const uno::RuntimeException& ) + { + TOOLS_WARN_EXCEPTION( "package.xstor", "The call must not fail, it is pretty simple!" ); + throw; + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "package.xstor", "The call must not fail, it is pretty simple!" ); + throw io::IOException( THROW_WHERE ); + } +} + +// XEncryptionProtectedSource2 + +void SAL_CALL OStorage::setEncryptionData( const uno::Sequence< beans::NamedValue >& aEncryptionData ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( m_pImpl->m_nStorageType != embed::StorageFormats::PACKAGE ) + throw uno::RuntimeException( THROW_WHERE ); // the interface must be visible only for package storage + + if ( !aEncryptionData.hasElements() ) + throw uno::RuntimeException( THROW_WHERE "Unexpected empty encryption data!" ); + + SAL_WARN_IF( !m_pImpl->m_bIsRoot, "package.xstor", "setEncryptionData() method is not available for nonroot storages!" ); + if ( !m_pImpl->m_bIsRoot ) + return; + + try { + m_pImpl->ReadContents(); + } + catch ( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch ( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw lang::WrappedTargetRuntimeException( THROW_WHERE "Can not open package!", + getXWeak(), + aCaught ); + } + + uno::Reference< beans::XPropertySet > xPackPropSet( m_pImpl->m_xPackage, uno::UNO_QUERY_THROW ); + try + { + ::comphelper::SequenceAsHashMap aEncryptionMap( aEncryptionData ); + xPackPropSet->setPropertyValue( STORAGE_ENCRYPTION_KEYS_PROPERTY, + uno::Any( aEncryptionMap.getAsConstNamedValueList() ) ); + + m_pImpl->m_bHasCommonEncryptionData = true; + m_pImpl->m_aCommonEncryptionData = aEncryptionMap; + } + catch( const uno::Exception& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:" ); + + throw io::IOException( THROW_WHERE ); + } +} + +sal_Bool SAL_CALL OStorage::hasEncryptionData() +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + return m_pImpl && m_pImpl->m_bHasCommonEncryptionData; +} + +// XEncryptionProtectedStorage + +void SAL_CALL OStorage::setEncryptionAlgorithms( const uno::Sequence< beans::NamedValue >& aAlgorithms ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( m_pImpl->m_nStorageType != embed::StorageFormats::PACKAGE ) + throw uno::RuntimeException( THROW_WHERE ); // the interface must be visible only for package storage + + if ( !aAlgorithms.hasElements() ) + throw uno::RuntimeException( THROW_WHERE "Unexpected empty encryption algorithms list!" ); + + SAL_WARN_IF( !m_pImpl->m_bIsRoot, "package.xstor", "setEncryptionAlgorithms() method is not available for nonroot storages!" ); + if ( !m_pImpl->m_bIsRoot ) + return; + + try { + m_pImpl->ReadContents(); + } + catch ( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch ( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw lang::WrappedTargetRuntimeException( THROW_WHERE "Can not open package!", + getXWeak(), + aCaught ); + } + + uno::Reference< beans::XPropertySet > xPackPropSet( m_pImpl->m_xPackage, uno::UNO_QUERY_THROW ); + try + { + xPackPropSet->setPropertyValue( ENCRYPTION_ALGORITHMS_PROPERTY, + uno::Any( aAlgorithms ) ); + } + catch ( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw lang::WrappedTargetRuntimeException( THROW_WHERE "Can not open package!", + getXWeak(), + aCaught ); + } +} + +void SAL_CALL OStorage::setGpgProperties( const uno::Sequence< uno::Sequence< beans::NamedValue > >& aProps ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( m_pImpl->m_nStorageType != embed::StorageFormats::PACKAGE ) + throw uno::RuntimeException( THROW_WHERE ); // the interface must be visible only for package storage + + if ( !aProps.hasElements() ) + throw uno::RuntimeException( THROW_WHERE "Unexpected empty encryption algorithms list!" ); + + SAL_WARN_IF( !m_pImpl->m_bIsRoot, "package.xstor", "setGpgProperties() method is not available for nonroot storages!" ); + if ( !m_pImpl->m_bIsRoot ) + return; + + try { + m_pImpl->ReadContents(); + } + catch ( const uno::RuntimeException& aRuntimeException ) + { + SAL_INFO("package.xstor", "Rethrow: " << aRuntimeException.Message); + throw; + } + catch ( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw lang::WrappedTargetRuntimeException( THROW_WHERE "Can not open package!", + getXWeak(), + aCaught ); + } + + uno::Reference< beans::XPropertySet > xPackPropSet( m_pImpl->m_xPackage, uno::UNO_QUERY_THROW ); + try + { + xPackPropSet->setPropertyValue( ENCRYPTION_GPG_PROPERTIES, + uno::Any( aProps ) ); + } + catch ( const uno::RuntimeException& aRuntimeException ) + { + SAL_INFO("package.xstor", "Rethrow: " << aRuntimeException.Message); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw lang::WrappedTargetRuntimeException( THROW_WHERE "Can not open package!", + getXWeak(), + aCaught ); + } +} + +uno::Sequence< beans::NamedValue > SAL_CALL OStorage::getEncryptionAlgorithms() +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( m_pImpl->m_nStorageType != embed::StorageFormats::PACKAGE ) + throw uno::RuntimeException( THROW_WHERE ); // the interface must be visible only for package storage + + uno::Sequence< beans::NamedValue > aResult; + SAL_WARN_IF( !m_pImpl->m_bIsRoot, "package.xstor", "getEncryptionAlgorithms() method is not available for nonroot storages!" ); + if ( m_pImpl->m_bIsRoot ) + { + try { + m_pImpl->ReadContents(); + } + catch ( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch ( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw lang::WrappedTargetRuntimeException( THROW_WHERE "Can not open package!", + getXWeak(), + aCaught ); + } + + uno::Reference< beans::XPropertySet > xPackPropSet( m_pImpl->m_xPackage, uno::UNO_QUERY_THROW ); + try + { + xPackPropSet->getPropertyValue( ENCRYPTION_ALGORITHMS_PROPERTY ) >>= aResult; + } + catch ( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw lang::WrappedTargetRuntimeException( THROW_WHERE "Can not open package!", + getXWeak(), + aCaught ); + } + } + + return aResult; +} + +// XPropertySet + +uno::Reference< beans::XPropertySetInfo > SAL_CALL OStorage::getPropertySetInfo() +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + //TODO: + return uno::Reference< beans::XPropertySetInfo >(); +} + +void SAL_CALL OStorage::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + //TODO: think about interaction handler + + // WORKAROUND: + // The old document might have no version in the manifest.xml, so we have to allow to set the version + // even for readonly storages, so that the version from content.xml can be used. + if ( m_bReadOnlyWrap && aPropertyName != "Version" ) + throw uno::RuntimeException( THROW_WHERE ); // TODO: Access denied + + if ( m_pImpl->m_nStorageType == embed::StorageFormats::ZIP ) + throw beans::UnknownPropertyException( aPropertyName ); + else if ( m_pImpl->m_nStorageType == embed::StorageFormats::PACKAGE ) + { + if ( aPropertyName == "MediaType" ) + { + aValue >>= m_pImpl->m_aMediaType; + m_pImpl->m_bControlMediaType = true; + + m_pImpl->m_bBroadcastModified = true; + m_pImpl->m_bIsModified = true; + } + else if ( aPropertyName == "Version" ) + { + aValue >>= m_pImpl->m_aVersion; + m_pImpl->m_bControlVersion = true; + + // this property can be set even for readonly storage + if ( !m_bReadOnlyWrap ) + { + m_pImpl->m_bBroadcastModified = true; + m_pImpl->m_bIsModified = true; + } + } + else if ( ( m_pImpl->m_bIsRoot && ( aPropertyName == HAS_ENCRYPTED_ENTRIES_PROPERTY + || aPropertyName == HAS_NONENCRYPTED_ENTRIES_PROPERTY + || aPropertyName == IS_INCONSISTENT_PROPERTY + || aPropertyName == "URL" + || aPropertyName == "RepairPackage" + || aPropertyName == ENCRYPTION_GPG_PROPERTIES) ) + || aPropertyName == "IsRoot" + || aPropertyName == MEDIATYPE_FALLBACK_USED_PROPERTY ) + throw beans::PropertyVetoException( THROW_WHERE ); + else + throw beans::UnknownPropertyException( aPropertyName ); + } + else if ( m_pImpl->m_nStorageType == embed::StorageFormats::OFOPXML ) + { + if ( aPropertyName == "RelationsInfoStream" ) + { + uno::Reference< io::XInputStream > xInRelStream; + if ( !( aValue >>= xInRelStream ) || !xInRelStream.is() ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 0 ); + + uno::Reference< io::XSeekable > xSeek( xInRelStream, uno::UNO_QUERY ); + if ( !xSeek.is() ) + { + // currently this is an internal property that is used for optimization + // and the stream must support XSeekable interface + // TODO/LATER: in future it can be changed if property is used from outside + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 0 ); + } + + m_pImpl->m_xNewRelInfoStream = xInRelStream; + m_pImpl->m_aRelInfo = uno::Sequence< uno::Sequence< beans::StringPair > >(); + m_pImpl->m_nRelInfoStatus = RELINFO_CHANGED_STREAM; + m_pImpl->m_bBroadcastModified = true; + m_pImpl->m_bIsModified = true; + } + else if ( aPropertyName == "RelationsInfo" ) + { + if ( !(aValue >>= m_pImpl->m_aRelInfo) ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 0 ); + + m_pImpl->m_xNewRelInfoStream.clear(); + m_pImpl->m_nRelInfoStatus = RELINFO_CHANGED; + m_pImpl->m_bBroadcastModified = true; + m_pImpl->m_bIsModified = true; + } + else if ( ( m_pImpl->m_bIsRoot && ( aPropertyName == "URL" || aPropertyName == "RepairPackage") ) + || aPropertyName == "IsRoot" ) + throw beans::PropertyVetoException( THROW_WHERE ); + else + throw beans::UnknownPropertyException( aPropertyName ); + } + else + throw beans::UnknownPropertyException( aPropertyName ); + + BroadcastModifiedIfNecessary(); +} + +uno::Any SAL_CALL OStorage::getPropertyValue( const OUString& aPropertyName ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( m_pImpl->m_nStorageType == embed::StorageFormats::PACKAGE + && ( aPropertyName == "MediaType" || aPropertyName == MEDIATYPE_FALLBACK_USED_PROPERTY || aPropertyName == "Version" ) ) + { + try + { + m_pImpl->ReadContents(); + } + catch ( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch ( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw lang::WrappedTargetException( + "Can't read contents!", + getXWeak(), + aCaught ); + } + + if ( aPropertyName == "MediaType" ) + return uno::Any( m_pImpl->m_aMediaType ); + else if ( aPropertyName == "Version" ) + return uno::Any( m_pImpl->m_aVersion ); + else + return uno::Any( m_pImpl->m_bMTFallbackUsed ); + } + else if ( aPropertyName == "IsRoot" ) + { + return uno::Any( m_pImpl->m_bIsRoot ); + } + else if ( aPropertyName == "OpenMode" ) + { + return uno::Any( m_pImpl->m_nStorageMode ); + } + else if ( m_pImpl->m_bIsRoot ) + { + if ( aPropertyName == "URL" + || aPropertyName == "RepairPackage" ) + { + auto pProp = std::find_if(std::cbegin(m_pImpl->m_xProperties), std::cend(m_pImpl->m_xProperties), + [&aPropertyName](const css::beans::PropertyValue& rProp) { return rProp.Name == aPropertyName; }); + if (pProp != std::cend(m_pImpl->m_xProperties)) + return pProp->Value; + + if ( aPropertyName == "URL" ) + return uno::Any( OUString() ); + + return uno::Any( false ); // RepairPackage + } + else if ( m_pImpl->m_nStorageType == embed::StorageFormats::PACKAGE + && ( aPropertyName == HAS_ENCRYPTED_ENTRIES_PROPERTY + || aPropertyName == HAS_NONENCRYPTED_ENTRIES_PROPERTY + || aPropertyName == ENCRYPTION_GPG_PROPERTIES + || aPropertyName == IS_INCONSISTENT_PROPERTY ) ) + { + try { + m_pImpl->ReadContents(); + uno::Reference< beans::XPropertySet > xPackPropSet( m_pImpl->m_xPackage, uno::UNO_QUERY_THROW ); + return xPackPropSet->getPropertyValue( aPropertyName ); + } + catch ( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch ( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw lang::WrappedTargetException( THROW_WHERE "Can not open package!", + getXWeak(), + aCaught ); + } + } + } + + throw beans::UnknownPropertyException(aPropertyName); +} + +void SAL_CALL OStorage::addPropertyChangeListener( + const OUString& /*aPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/ ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + //TODO: +} + +void SAL_CALL OStorage::removePropertyChangeListener( + const OUString& /*aPropertyName*/, + const uno::Reference< beans::XPropertyChangeListener >& /*aListener*/ ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + //TODO: +} + +void SAL_CALL OStorage::addVetoableChangeListener( + const OUString& /*PropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*aListener*/ ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + //TODO: +} + +void SAL_CALL OStorage::removeVetoableChangeListener( + const OUString& /*PropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*aListener*/ ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + //TODO: +} + +// XRelationshipAccess + +// TODO/LATER: the storage and stream implementations of this interface are very similar, they could use a helper class + +sal_Bool SAL_CALL OStorage::hasByID( const OUString& sID ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( m_pImpl->m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException( THROW_WHERE ); + + try + { + getRelationshipByID( sID ); + return true; + } + catch( const container::NoSuchElementException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + } + + return false; +} + +namespace +{ + +const beans::StringPair* lcl_findPairByName(const uno::Sequence<beans::StringPair>& rSeq, const OUString& rName) +{ + return std::find_if(rSeq.begin(), rSeq.end(), [&rName](const beans::StringPair& rPair) { return rPair.First == rName; }); +} + +} + +OUString SAL_CALL OStorage::getTargetByID( const OUString& sID ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( m_pImpl->m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException( THROW_WHERE ); + + const uno::Sequence< beans::StringPair > aSeq = getRelationshipByID( sID ); + auto pRel = lcl_findPairByName(aSeq, "Target"); + if (pRel != aSeq.end()) + return pRel->Second; + + return OUString(); +} + +OUString SAL_CALL OStorage::getTypeByID( const OUString& sID ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( m_pImpl->m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException( THROW_WHERE ); + + const uno::Sequence< beans::StringPair > aSeq = getRelationshipByID( sID ); + auto pRel = lcl_findPairByName(aSeq, "Type"); + if (pRel != aSeq.end()) + return pRel->Second; + + return OUString(); +} + +uno::Sequence< beans::StringPair > SAL_CALL OStorage::getRelationshipByID( const OUString& sID ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( m_pImpl->m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException( THROW_WHERE ); + + // TODO/LATER: in future the unification of the ID could be checked + const uno::Sequence< uno::Sequence< beans::StringPair > > aSeq = getAllRelationships(); + const beans::StringPair aIDRel("Id", sID); + + auto pRel = std::find_if(aSeq.begin(), aSeq.end(), + [&aIDRel](const uno::Sequence<beans::StringPair>& rRel) { + return std::find(rRel.begin(), rRel.end(), aIDRel) != rRel.end(); }); + if (pRel != aSeq.end()) + return *pRel; + + throw container::NoSuchElementException( THROW_WHERE ); +} + +uno::Sequence< uno::Sequence< beans::StringPair > > SAL_CALL OStorage::getRelationshipsByType( const OUString& sType ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( m_pImpl->m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException( THROW_WHERE ); + + // TODO/LATER: in future the unification of the ID could be checked + const uno::Sequence< uno::Sequence< beans::StringPair > > aSeq = getAllRelationships(); + std::vector< uno::Sequence< beans::StringPair > > aResult; + aResult.reserve(aSeq.getLength()); + + std::copy_if(aSeq.begin(), aSeq.end(), std::back_inserter(aResult), + [&sType](const uno::Sequence<beans::StringPair>& rRel) { + auto pRel = lcl_findPairByName(rRel, "Type"); + return pRel != rRel.end() + // the type is usually a URL, so the check should be case insensitive + && pRel->Second.equalsIgnoreAsciiCase( sType ); + }); + + return comphelper::containerToSequence(aResult); +} + +uno::Sequence< uno::Sequence< beans::StringPair > > SAL_CALL OStorage::getAllRelationships() +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( m_pImpl->m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException( THROW_WHERE ); + + uno::Sequence< uno::Sequence< beans::StringPair > > aRet; + try + { + aRet = m_pImpl->GetAllRelationshipsIfAny(); + } + catch (const io::IOException&) + { + throw; + } + catch (const uno::RuntimeException&) + { + throw; + } + catch (const uno::Exception &) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + throw lang::WrappedTargetRuntimeException(THROW_WHERE "Can't getAllRelationships!", + uno::Reference< uno::XInterface >(), + aCaught); + } + + return aRet; +} + +void SAL_CALL OStorage::insertRelationshipByID( const OUString& sID, const uno::Sequence< beans::StringPair >& aEntry, sal_Bool bReplace ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( m_pImpl->m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException( THROW_WHERE ); + + const beans::StringPair aIDRel("Id", sID); + + uno::Sequence<beans::StringPair>* pResult = nullptr; + + // TODO/LATER: in future the unification of the ID could be checked + uno::Sequence< uno::Sequence< beans::StringPair > > aSeq = getAllRelationships(); + for ( sal_Int32 nInd = 0; nInd < aSeq.getLength(); nInd++ ) + { + const auto& rRel = aSeq[nInd]; + if (std::find(rRel.begin(), rRel.end(), aIDRel) != rRel.end()) + pResult = &aSeq.getArray()[nInd]; + } + + if ( pResult && !bReplace ) + throw container::ElementExistException( THROW_WHERE ); + + if ( !pResult ) + { + const sal_Int32 nIDInd = aSeq.getLength(); + aSeq.realloc( nIDInd + 1 ); + pResult = &aSeq.getArray()[nIDInd]; + } + + std::vector<beans::StringPair> aResult; + aResult.reserve(aEntry.getLength() + 1); + + aResult.push_back(aIDRel); + std::copy_if(aEntry.begin(), aEntry.end(), std::back_inserter(aResult), + [](const beans::StringPair& rPair) { return rPair.First != "Id"; }); + + *pResult = comphelper::containerToSequence(aResult); + + m_pImpl->m_aRelInfo = aSeq; + m_pImpl->m_xNewRelInfoStream.clear(); + m_pImpl->m_nRelInfoStatus = RELINFO_CHANGED; +} + +void SAL_CALL OStorage::removeRelationshipByID( const OUString& sID ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( m_pImpl->m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException( THROW_WHERE ); + + uno::Sequence< uno::Sequence< beans::StringPair > > aSeq = getAllRelationships(); + const beans::StringPair aIDRel("Id", sID); + auto pRel = std::find_if(std::cbegin(aSeq), std::cend(aSeq), + [&aIDRel](const uno::Sequence< beans::StringPair >& rRel) { + return std::find(rRel.begin(), rRel.end(), aIDRel) != rRel.end(); }); + if (pRel != std::cend(aSeq)) + { + auto nInd = static_cast<sal_Int32>(std::distance(std::cbegin(aSeq), pRel)); + comphelper::removeElementAt(aSeq, nInd); + + m_pImpl->m_aRelInfo = aSeq; + m_pImpl->m_xNewRelInfoStream.clear(); + m_pImpl->m_nRelInfoStatus = RELINFO_CHANGED; + + // TODO/LATER: in future the unification of the ID could be checked + return; + } + + throw container::NoSuchElementException( THROW_WHERE ); +} + +void SAL_CALL OStorage::insertRelationships( const uno::Sequence< uno::Sequence< beans::StringPair > >& aEntries, sal_Bool bReplace ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( m_pImpl->m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException( THROW_WHERE ); + + OUString aIDTag( "Id" ); + const uno::Sequence< uno::Sequence< beans::StringPair > > aSeq = getAllRelationships(); + std::vector< uno::Sequence<beans::StringPair> > aResultVec; + aResultVec.reserve(aSeq.getLength() + aEntries.getLength()); + + std::copy_if(aSeq.begin(), aSeq.end(), std::back_inserter(aResultVec), + [&aIDTag, &aEntries, bReplace](const uno::Sequence<beans::StringPair>& rTargetRel) { + auto pTargetPair = lcl_findPairByName(rTargetRel, aIDTag); + if (pTargetPair == rTargetRel.end()) + return false; + + bool bIsSourceSame = std::any_of(aEntries.begin(), aEntries.end(), + [&pTargetPair](const uno::Sequence<beans::StringPair>& rSourceEntry) { + return std::find(rSourceEntry.begin(), rSourceEntry.end(), *pTargetPair) != rSourceEntry.end(); }); + + if ( bIsSourceSame && !bReplace ) + throw container::ElementExistException( THROW_WHERE ); + + // if no such element in the provided sequence + return !bIsSourceSame; + }); + + std::transform(aEntries.begin(), aEntries.end(), std::back_inserter(aResultVec), + [&aIDTag](const uno::Sequence<beans::StringPair>& rEntry) -> uno::Sequence<beans::StringPair> { + auto pPair = lcl_findPairByName(rEntry, aIDTag); + if (pPair == rEntry.end()) + throw io::IOException( THROW_WHERE ); // TODO: illegal relation ( no ID ) + + auto aResult = comphelper::sequenceToContainer<std::vector<beans::StringPair>>(rEntry); + auto nIDInd = std::distance(rEntry.begin(), pPair); + std::rotate(aResult.begin(), std::next(aResult.begin(), nIDInd), std::next(aResult.begin(), nIDInd + 1)); + + return comphelper::containerToSequence(aResult); + }); + + m_pImpl->m_aRelInfo = comphelper::containerToSequence(aResultVec); + m_pImpl->m_xNewRelInfoStream.clear(); + m_pImpl->m_nRelInfoStatus = RELINFO_CHANGED; +} + +void SAL_CALL OStorage::clearRelationships() +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( m_pImpl->m_nStorageType != embed::StorageFormats::OFOPXML ) + throw uno::RuntimeException( THROW_WHERE ); + + m_pImpl->m_aRelInfo.realloc( 0 ); + m_pImpl->m_xNewRelInfoStream.clear(); + m_pImpl->m_nRelInfoStatus = RELINFO_CHANGED; +} + +// XOptimizedStorage +void SAL_CALL OStorage::insertRawNonEncrStreamElementDirect( + const OUString& /*sStreamName*/, + const uno::Reference< io::XInputStream >& /*xInStream*/ ) +{ + // not implemented currently because there is still no demand + // might need to be implemented if direct copying of compressed streams is used + throw io::IOException( THROW_WHERE ); +} + +void SAL_CALL OStorage::insertStreamElementDirect( + const OUString& aStreamName, + const uno::Reference< io::XInputStream >& xInStream, + const uno::Sequence< beans::PropertyValue >& aProps ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( aStreamName.isEmpty() || !::comphelper::OStorageHelper::IsValidZipEntryFileName( aStreamName, false ) ) + throw lang::IllegalArgumentException( THROW_WHERE "Unexpected entry name syntax.", uno::Reference< uno::XInterface >(), 1 ); + + if ( m_pImpl->m_nStorageType == embed::StorageFormats::OFOPXML && aStreamName == "_rels" ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); // unacceptable storage name + + if ( m_bReadOnlyWrap ) + throw io::IOException( THROW_WHERE ); // TODO: access denied + + try + { + SotElement_Impl* pElement = m_pImpl->FindElement( aStreamName ); + + if ( pElement ) + throw container::ElementExistException( THROW_WHERE ); + + pElement = OpenStreamElement_Impl( aStreamName, embed::ElementModes::READWRITE, false ); + OSL_ENSURE(pElement && pElement->m_xStream, "In case element can not be created an exception must be thrown!"); + + pElement->m_xStream->InsertStreamDirectly(xInStream, aProps); + } + catch( const embed::InvalidStorageException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const lang::IllegalArgumentException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const container::ElementExistException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const embed::StorageWrappedTargetException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const io::IOException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw embed::StorageWrappedTargetException( THROW_WHERE "Can't insert stream directly!", + uno::Reference< io::XInputStream >(), + aCaught ); + } +} + +void SAL_CALL OStorage::copyElementDirectlyTo( + const OUString& aElementName, + const uno::Reference< embed::XOptimizedStorage >& xDest, + const OUString& aNewName ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( aElementName.isEmpty() || !::comphelper::OStorageHelper::IsValidZipEntryFileName( aElementName, false ) + || aNewName.isEmpty() || !::comphelper::OStorageHelper::IsValidZipEntryFileName( aNewName, false ) ) + throw lang::IllegalArgumentException( THROW_WHERE "Unexpected entry name syntax.", uno::Reference< uno::XInterface >(), 1 ); + + if ( !xDest.is() || xDest == getXWeak() ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 2 ); + + if ( m_pImpl->m_nStorageType == embed::StorageFormats::OFOPXML && ( aElementName == "_rels" || aNewName == "_rels" ) ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 0 ); // unacceptable name + + try + { + SotElement_Impl* pElement = m_pImpl->FindElement( aElementName ); + if ( !pElement ) + throw container::NoSuchElementException( THROW_WHERE ); + + uno::Reference< XNameAccess > xNameAccess( xDest, uno::UNO_QUERY_THROW ); + if ( xNameAccess->hasByName( aNewName ) ) + throw container::ElementExistException( THROW_WHERE ); + + // let the element be copied directly + uno::Reference< embed::XStorage > xStorDest( xDest, uno::UNO_QUERY_THROW ); + m_pImpl->CopyStorageElement( pElement, xStorDest, aNewName, true ); + } + catch( const embed::InvalidStorageException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const lang::IllegalArgumentException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const container::NoSuchElementException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const container::ElementExistException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const embed::StorageWrappedTargetException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const io::IOException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw embed::StorageWrappedTargetException( THROW_WHERE "Can't copy element directly!", + uno::Reference< io::XInputStream >(), + aCaught ); + } +} + +void SAL_CALL OStorage::writeAndAttachToStream( const uno::Reference< io::XStream >& xStream ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( !m_pImpl->m_bIsRoot ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 0 ); + + if ( !m_pImpl->m_pSwitchStream ) + throw uno::RuntimeException( THROW_WHERE ); + + try + { + m_pImpl->m_pSwitchStream->CopyAndSwitchPersistenceTo( xStream ); + } + catch( const embed::InvalidStorageException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const lang::IllegalArgumentException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const embed::StorageWrappedTargetException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const io::IOException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:" ); + throw; + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw embed::StorageWrappedTargetException( THROW_WHERE "Can't write and attach to stream!", + uno::Reference< io::XInputStream >(), + aCaught ); + } + +} + +void SAL_CALL OStorage::attachToURL( const OUString& sURL, + sal_Bool bReadOnly ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( !m_pImpl->m_bIsRoot ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 0 ); + + if ( !m_pImpl->m_pSwitchStream ) + throw uno::RuntimeException( THROW_WHERE ); + + uno::Reference < ucb::XSimpleFileAccess3 > xAccess( + ucb::SimpleFileAccess::create( m_pImpl->m_xContext ) ); + + try + { + if ( bReadOnly ) + { + uno::Reference< io::XInputStream > xInputStream = xAccess->openFileRead( sURL ); + m_pImpl->m_pSwitchStream->SwitchPersistenceTo( xInputStream ); + } + else + { + uno::Reference< io::XStream > xStream = xAccess->openFileReadWrite( sURL ); + m_pImpl->m_pSwitchStream->SwitchPersistenceTo( xStream ); + } + } + catch( const embed::InvalidStorageException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const lang::IllegalArgumentException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const embed::StorageWrappedTargetException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const io::IOException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw embed::StorageWrappedTargetException( THROW_WHERE "Can't attach to URL!", + uno::Reference< io::XInputStream >(), + aCaught ); + } +} + +uno::Any SAL_CALL OStorage::getElementPropertyValue( const OUString& aElementName, const OUString& aPropertyName ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( aElementName.isEmpty() || !::comphelper::OStorageHelper::IsValidZipEntryFileName( aElementName, false ) ) + throw lang::IllegalArgumentException( THROW_WHERE "Unexpected entry name syntax.", uno::Reference< uno::XInterface >(), 1 ); + + if ( m_pImpl->m_nStorageType == embed::StorageFormats::OFOPXML && aElementName == "_rels" ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); // TODO: unacceptable name + + try + { + SotElement_Impl *pElement = m_pImpl->FindElement( aElementName ); + if ( !pElement ) + throw container::NoSuchElementException( THROW_WHERE ); + + // TODO/LATER: Currently it is only implemented for MediaType property of substorages, might be changed in future + if ( !pElement->m_bIsStorage || m_pImpl->m_nStorageType != embed::StorageFormats::PACKAGE || aPropertyName != "MediaType" ) + throw beans::PropertyVetoException( THROW_WHERE ); + + if (!pElement->m_xStorage) + m_pImpl->OpenSubStorage( pElement, embed::ElementModes::READ ); + + if (!pElement->m_xStorage) + throw io::IOException( THROW_WHERE ); // TODO: general_error + + pElement->m_xStorage->ReadContents(); + return uno::Any(pElement->m_xStorage->m_aMediaType); + } + catch( const embed::InvalidStorageException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const lang::IllegalArgumentException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const container::NoSuchElementException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const beans::UnknownPropertyException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const beans::PropertyVetoException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const embed::StorageWrappedTargetException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const io::IOException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw embed::StorageWrappedTargetException( THROW_WHERE "Can't get element property!", + uno::Reference< io::XInputStream >(), + aCaught ); + } +} + +void SAL_CALL OStorage::copyStreamElementData( const OUString& aStreamName, const uno::Reference< io::XStream >& xTargetStream ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( aStreamName.isEmpty() || !::comphelper::OStorageHelper::IsValidZipEntryFileName( aStreamName, false ) ) + throw lang::IllegalArgumentException( THROW_WHERE "Unexpected entry name syntax.", uno::Reference< uno::XInterface >(), 1 ); + + if ( m_pImpl->m_nStorageType == embed::StorageFormats::OFOPXML && aStreamName == "_rels" ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); // unacceptable name + + if ( !xTargetStream.is() ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 2 ); + + try + { + uno::Reference< io::XStream > xNonconstRef = xTargetStream; + m_pImpl->CloneStreamElement( aStreamName, false, ::comphelper::SequenceAsHashMap(), xNonconstRef ); + + SAL_WARN_IF( xNonconstRef != xTargetStream, "package.xstor", "The provided stream reference seems not be filled in correctly!" ); + if ( xNonconstRef != xTargetStream ) + throw uno::RuntimeException( THROW_WHERE ); // if the stream reference is set it must not be changed! + } + catch( const embed::InvalidStorageException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const lang::IllegalArgumentException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const packages::WrongPasswordException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const io::IOException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const embed::StorageWrappedTargetException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const uno::RuntimeException& ) + { + TOOLS_INFO_EXCEPTION("package.xstor", "Rethrow:"); + throw; + } + catch( const uno::Exception& ) + { + uno::Any aCaught( ::cppu::getCaughtException() ); + SAL_INFO("package.xstor", "Rethrow: " << exceptionToString(aCaught)); + + throw embed::StorageWrappedTargetException( THROW_WHERE "Can't copy stream data!", + uno::Reference< io::XInputStream >(), + aCaught ); + } + +} + +// XHierarchicalStorageAccess +uno::Reference< embed::XExtendedStorageStream > SAL_CALL OStorage::openStreamElementByHierarchicalName( const OUString& aStreamPath, ::sal_Int32 nOpenMode ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( aStreamPath.isEmpty() || !::comphelper::OStorageHelper::IsValidZipEntryFileName( aStreamPath, true ) ) + throw lang::IllegalArgumentException( THROW_WHERE "Unexpected entry name syntax.", uno::Reference< uno::XInterface >(), 1 ); + + if ( !( m_pImpl->m_nStorageMode & embed::ElementModes::WRITE ) + && ( nOpenMode & embed::ElementModes::WRITE ) ) + throw io::IOException( THROW_WHERE ); // Access denied + + std::vector<OUString> aListPath = OHierarchyHolder_Impl::GetListPathFromString( aStreamPath ); + OSL_ENSURE( aListPath.size(), "The result list must not be empty!" ); + + uno::Reference< embed::XExtendedStorageStream > xResult; + if ( aListPath.size() == 1 ) + { + try + { + // that must be a direct request for a stream + // the transacted version of the stream should be opened + + SotElement_Impl *pElement = OpenStreamElement_Impl( aStreamPath, nOpenMode, false ); + assert(pElement && pElement->m_xStream && "In case element can not be created an exception must be thrown!"); + + xResult.set(pElement->m_xStream->GetStream(nOpenMode, true), + uno::UNO_QUERY_THROW); + } + catch ( const container::NoSuchElementException & ) + { + throw io::IOException( THROW_WHERE ); // file not found + } + } + else + { + // there are still storages in between + if ( !m_rHierarchyHolder.is() ) + m_rHierarchyHolder = new OHierarchyHolder_Impl( + uno::Reference< embed::XStorage >( static_cast< embed::XStorage* >( this ) ) ); + + xResult = m_rHierarchyHolder->GetStreamHierarchically( + ( m_pImpl->m_nStorageMode & embed::ElementModes::READWRITE ), + aListPath, + nOpenMode ); + } + + if ( !xResult.is() ) + throw uno::RuntimeException( THROW_WHERE ); + + return xResult; +} + +uno::Reference< embed::XExtendedStorageStream > SAL_CALL OStorage::openEncryptedStreamElementByHierarchicalName( const OUString& aStreamPath, ::sal_Int32 nOpenMode, const OUString& sPassword ) +{ + return openEncryptedStreamByHierarchicalName( aStreamPath, nOpenMode, ::comphelper::OStorageHelper::CreatePackageEncryptionData( sPassword ) ); +} + +void SAL_CALL OStorage::removeStreamElementByHierarchicalName( const OUString& aStreamPath ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( aStreamPath.isEmpty() || !::comphelper::OStorageHelper::IsValidZipEntryFileName( aStreamPath, true ) ) + throw lang::IllegalArgumentException( THROW_WHERE "Unexpected entry name syntax.", uno::Reference< uno::XInterface >(), 1 ); + + if ( !( m_pImpl->m_nStorageMode & embed::ElementModes::WRITE ) ) + throw io::IOException( THROW_WHERE ); // Access denied + + std::vector<OUString> aListPath = OHierarchyHolder_Impl::GetListPathFromString( aStreamPath ); + OSL_ENSURE( aListPath.size(), "The result list must not be empty!" ); + + if ( !m_rHierarchyHolder.is() ) + m_rHierarchyHolder = new OHierarchyHolder_Impl( + uno::Reference< embed::XStorage >( static_cast< embed::XStorage* >( this ) ) ); + + m_rHierarchyHolder->RemoveStreamHierarchically( aListPath ); +} + +// XHierarchicalStorageAccess2 +uno::Reference< embed::XExtendedStorageStream > SAL_CALL OStorage::openEncryptedStreamByHierarchicalName( const OUString& aStreamPath, ::sal_Int32 nOpenMode, const uno::Sequence< beans::NamedValue >& aEncryptionData ) +{ + ::osl::MutexGuard aGuard( m_xSharedMutex->GetMutex() ); + + if ( !m_pImpl ) + { + SAL_INFO("package.xstor", THROW_WHERE "Disposed!"); + throw lang::DisposedException( THROW_WHERE ); + } + + if ( m_pImpl->m_nStorageType != embed::StorageFormats::PACKAGE ) + throw packages::NoEncryptionException( THROW_WHERE ); + + if ( aStreamPath.isEmpty() || !::comphelper::OStorageHelper::IsValidZipEntryFileName( aStreamPath, true ) ) + throw lang::IllegalArgumentException( THROW_WHERE "Unexpected entry name syntax.", uno::Reference< uno::XInterface >(), 1 ); + + if ( !aEncryptionData.hasElements() ) + throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 3 ); + + if ( !( m_pImpl->m_nStorageMode & embed::ElementModes::WRITE ) + && ( nOpenMode & embed::ElementModes::WRITE ) ) + throw io::IOException( THROW_WHERE ); // Access denied + + std::vector<OUString> aListPath = OHierarchyHolder_Impl::GetListPathFromString( aStreamPath ); + OSL_ENSURE( aListPath.size(), "The result list must not be empty!" ); + + uno::Reference< embed::XExtendedStorageStream > xResult; + if ( aListPath.size() == 1 ) + { + // that must be a direct request for a stream + // the transacted version of the stream should be opened + + SotElement_Impl *pElement = OpenStreamElement_Impl( aStreamPath, nOpenMode, true ); + OSL_ENSURE(pElement && pElement->m_xStream, "In case element can not be created an exception must be thrown!"); + + xResult.set(pElement->m_xStream->GetStream(nOpenMode, aEncryptionData, true), + uno::UNO_QUERY_THROW); + } + else + { + // there are still storages in between + if ( !m_rHierarchyHolder.is() ) + m_rHierarchyHolder = new OHierarchyHolder_Impl( + uno::Reference< embed::XStorage >( static_cast< embed::XStorage* >( this ) ) ); + + xResult = m_rHierarchyHolder->GetStreamHierarchically( + ( m_pImpl->m_nStorageMode & embed::ElementModes::READWRITE ), + aListPath, + nOpenMode, + aEncryptionData ); + } + + if ( !xResult.is() ) + throw uno::RuntimeException( THROW_WHERE ); + + return xResult; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/xstor/xstorage.hxx b/package/source/xstor/xstorage.hxx new file mode 100644 index 0000000000..54fe49a9d9 --- /dev/null +++ b/package/source/xstor/xstorage.hxx @@ -0,0 +1,540 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_PACKAGE_SOURCE_XSTOR_XSTORAGE_HXX +#define INCLUDED_PACKAGE_SOURCE_XSTOR_XSTORAGE_HXX + +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/embed/XStorage2.hpp> +#include <com/sun/star/embed/XOptimizedStorage.hpp> +#include <com/sun/star/embed/XHierarchicalStorageAccess2.hpp> +#include <com/sun/star/embed/XStorageRawAccess.hpp> +#include <com/sun/star/embed/XTransactedObject.hpp> +#include <com/sun/star/embed/XTransactionBroadcaster.hpp> +#include <com/sun/star/embed/XEncryptionProtectedStorage.hpp> +#include <com/sun/star/embed/XRelationshipAccess.hpp> +#include <com/sun/star/util/XModifiable.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/StringPair.hpp> +#include <com/sun/star/io/XStream.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/lang/XTypeProvider.hpp> +#include <com/sun/star/lang/XComponent.hpp> + +#include <cppuhelper/typeprovider.hxx> +#include <cppuhelper/weak.hxx> +#include <cppuhelper/weakref.hxx> +#include <comphelper/multicontainer2.hxx> +#include <comphelper/refcountedmutex.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <o3tl/deleter.hxx> +#include <rtl/ref.hxx> + +#include "ohierarchyholder.hxx" +#include "disposelistener.hxx" + +#include <vector> +#include <memory> +#include <optional> +#include <string_view> + +namespace com::sun::star::uno { + class XComponentContext; +} + +#define RELINFO_NO_INIT 1 +#define RELINFO_READ 2 +#define RELINFO_CHANGED 3 +#define RELINFO_CHANGED_STREAM 4 +#define RELINFO_CHANGED_STREAM_READ 5 +#define RELINFO_BROKEN 6 +#define RELINFO_CHANGED_BROKEN 7 + +#define STOR_MESS_PRECOMMIT 1 +#define STOR_MESS_COMMITTED 2 +#define STOR_MESS_PREREVERT 3 +#define STOR_MESS_REVERTED 4 + +// a common implementation for an entry + +struct OStorage_Impl; +struct OWriteStream_Impl; + +struct SotElement_Impl +{ + OUString m_aOriginalName; + bool m_bIsRemoved; + bool m_bIsInserted; + bool m_bIsStorage; + + std::unique_ptr<OStorage_Impl> m_xStorage; + std::unique_ptr<OWriteStream_Impl, o3tl::default_delete<OWriteStream_Impl>> m_xStream; + +public: + SotElement_Impl(OUString aName, bool bStor, bool bNew); +}; + +// Main storage implementation + +class OStorage; + +struct StorageHolder_Impl +{ + OStorage* m_pPointer; + css::uno::WeakReference< css::embed::XStorage > m_xWeakRef; + + explicit inline StorageHolder_Impl( OStorage* pStorage ); +}; + +class SwitchablePersistenceStream; +struct OStorage_Impl +{ + typedef std::vector<StorageHolder_Impl> StorageHoldersType; + + rtl::Reference<comphelper::RefCountedMutex> m_xMutex; + + OStorage* m_pAntiImpl; // only valid if external references exists + StorageHoldersType m_aReadOnlyWrapVector; // only valid if readonly external reference exists + + sal_Int32 m_nStorageMode; // open mode ( read/write/trunc/nocreate ) + bool m_bIsModified; // only modified elements will be sent to the original content + bool m_bBroadcastModified; // will be set if notification is required + + bool m_bCommited; // sending the streams is coordinated by the root storage of the package + + bool m_bIsRoot; // marks this storage as root storages that manages all commits and reverts + bool m_bListCreated; + bool m_bRepairPackage = false; + + /// Count of registered modification listeners + oslInterlockedCount m_nModifiedListenerCount; + bool HasModifiedListener() const + { + return m_nModifiedListenerCount > 0 && m_pAntiImpl != nullptr; + } + + std::unordered_map<OUString, std::vector<SotElement_Impl*>> m_aChildrenMap; + std::vector< SotElement_Impl* > m_aDeletedVector; + + css::uno::Reference< css::container::XNameContainer > m_xPackageFolder; + + css::uno::Reference< css::lang::XSingleServiceFactory > m_xPackage; + css::uno::Reference< css::uno::XComponentContext > m_xContext; + + // valid only for root storage + css::uno::Reference< css::io::XInputStream > m_xInputStream; // ??? may be stored in properties + css::uno::Reference< css::io::XStream > m_xStream; // ??? may be stored in properties + css::uno::Sequence< css::beans::PropertyValue > m_xProperties; + bool m_bHasCommonEncryptionData; + ::comphelper::SequenceAsHashMap m_aCommonEncryptionData; + + // must be empty in case of root storage + OStorage_Impl* m_pParent; + + bool m_bControlMediaType; + OUString m_aMediaType; + bool m_bMTFallbackUsed; + + bool m_bControlVersion; + OUString m_aVersion; + + rtl::Reference<SwitchablePersistenceStream> m_pSwitchStream; + + sal_Int32 m_nStorageType; // the mode in which the storage is used + + // the _rels substorage that is handled in a special way in embed::StorageFormats::OFOPXML + SotElement_Impl* m_pRelStorElement; + css::uno::Reference< css::embed::XStorage > m_xRelStorage; + css::uno::Sequence< css::uno::Sequence< css::beans::StringPair > > m_aRelInfo; + css::uno::Reference< css::io::XInputStream > m_xNewRelInfoStream; + sal_Int16 m_nRelInfoStatus; + + // Constructors + OStorage_Impl( css::uno::Reference< css::io::XInputStream > const & xInputStream, + sal_Int32 nMode, + const css::uno::Sequence< css::beans::PropertyValue >& xProperties, + css::uno::Reference< css::uno::XComponentContext > const & xContext, + sal_Int32 nStorageType ); + + OStorage_Impl( css::uno::Reference< css::io::XStream > const & xStream, + sal_Int32 nMode, + const css::uno::Sequence< css::beans::PropertyValue >& xProperties, + css::uno::Reference< css::uno::XComponentContext > const & xContext, + sal_Int32 nStorageType ); + + // constructor for a substorage + OStorage_Impl( OStorage_Impl* pParent, + sal_Int32 nMode, + css::uno::Reference< css::container::XNameContainer > const & xPackageFolder, + css::uno::Reference< css::lang::XSingleServiceFactory > xPackage, + css::uno::Reference< css::uno::XComponentContext > const & xContext, + sal_Int32 nStorageType ); + + ~OStorage_Impl(); + + void SetReadOnlyWrap( OStorage& aStorage ); + void RemoveReadOnlyWrap( const OStorage& aStorage ); + + void OpenOwnPackage(); + void ReadContents(); + void ReadRelInfoIfNecessary(); + + bool HasChildren(); + void GetStorageProperties(); + + css::uno::Sequence< css::uno::Sequence< css::beans::StringPair > > GetAllRelationshipsIfAny(); + void CopyLastCommitTo( const css::uno::Reference< css::embed::XStorage >& xNewStor ); + + void InsertIntoPackageFolder( + const OUString& aName, + const css::uno::Reference< css::container::XNameContainer >& xParentPackageFolder ); + + void Commit(); + void Revert(); + + /// @throws css::packages::NoEncryptionException + ::comphelper::SequenceAsHashMap GetCommonRootEncryptionData(); + + void CopyToStorage( const css::uno::Reference< css::embed::XStorage >& xDest, + bool bDirect ); + void CopyStorageElement( SotElement_Impl* pElement, + const css::uno::Reference< css::embed::XStorage >& xDest, + const OUString& aName, + bool bDirect ); + + SotElement_Impl* FindElement( const OUString& rName ); + + SotElement_Impl* InsertStream( const OUString& aName, bool bEncr ); + void InsertRawStream( const OUString& aName, const css::uno::Reference< css::io::XInputStream >& xInStream ); + + std::unique_ptr<OStorage_Impl> CreateNewStorageImpl( sal_Int32 nStorageMode ); + SotElement_Impl* InsertStorage( const OUString& aName, sal_Int32 nStorageMode ); + SotElement_Impl* InsertElement( const OUString& aName, bool bIsStorage ); + + void OpenSubStorage( SotElement_Impl* pElement, sal_Int32 nStorageMode ); + void OpenSubStream( SotElement_Impl* pElement ); + + css::uno::Sequence< OUString > GetElementNames(); + + void RemoveElement( OUString const & rName, SotElement_Impl* pElement ); + static void ClearElement( SotElement_Impl* pElement ); + + /// @throws css::embed::InvalidStorageException + /// @throws css::lang::IllegalArgumentException + /// @throws css::packages::WrongPasswordException + /// @throws css::packages::NoEncryptionException + /// @throws css::container::NoSuchElementException + /// @throws css::io::IOException + /// @throws css::embed::StorageWrappedTargetException + /// @throws css::uno::RuntimeException + void CloneStreamElement( + const OUString& aStreamName, + bool bPassProvided, + const ::comphelper::SequenceAsHashMap& aEncryptionData, + css::uno::Reference< css::io::XStream >& xTargetStream ); + + void RemoveStreamRelInfo( std::u16string_view aOriginalName ); + void CreateRelStorage(); + void CommitStreamRelInfo( std::u16string_view rName, SotElement_Impl const * pStreamElement ); + css::uno::Reference< css::io::XInputStream > GetRelInfoStreamForName( + std::u16string_view aName ); + void CommitRelInfo( const css::uno::Reference< css::container::XNameContainer >& xNewPackageFolder ); + + static void completeStorageStreamCopy_Impl( + const css::uno::Reference< css::io::XStream >& xSource, + const css::uno::Reference< css::io::XStream >& xDest, + sal_Int32 nStorageType, + const css::uno::Sequence< css::uno::Sequence< css::beans::StringPair > >& aRelInfo ); + +}; + +class OStorage final : public css::lang::XTypeProvider + , public css::embed::XStorage2 + , public css::embed::XStorageRawAccess + , public css::embed::XTransactedObject + , public css::embed::XTransactionBroadcaster + , public css::util::XModifiable + , public css::embed::XEncryptionProtectedStorage + , public css::beans::XPropertySet + , public css::embed::XOptimizedStorage + , public css::embed::XRelationshipAccess + , public css::embed::XHierarchicalStorageAccess2 + , public ::cppu::OWeakObject +{ + OStorage_Impl* m_pImpl; + rtl::Reference<comphelper::RefCountedMutex> m_xSharedMutex; + comphelper::OMultiTypeInterfaceContainerHelper2 m_aListenersContainer; // list of listeners + ::std::optional< ::cppu::OTypeCollection> m_oTypeCollection; + bool m_bReadOnlyWrap; + ::rtl::Reference<OChildDispListener_Impl> m_pSubElDispListener; + ::std::vector< css::uno::WeakReference< css::lang::XComponent > > m_aOpenSubComponentsVector; + ::rtl::Reference< OHierarchyHolder_Impl > m_rHierarchyHolder; + + SotElement_Impl* OpenStreamElement_Impl( const OUString& aStreamName, sal_Int32 nOpenMode, bool bEncr ); + + void BroadcastModifiedIfNecessary(); + + void BroadcastTransaction( sal_Int8 nMessage ); + + void MakeLinkToSubComponent_Impl( + const css::uno::Reference< css::lang::XComponent >& xComponent ); + +public: + + OStorage( css::uno::Reference< css::io::XInputStream > const & xInputStream, + sal_Int32 nMode, + const css::uno::Sequence< css::beans::PropertyValue >& xProperties, + css::uno::Reference< css::uno::XComponentContext > const & xContext, + sal_Int32 nStorageType ); + + OStorage( css::uno::Reference< css::io::XStream > const & xStream, + sal_Int32 nMode, + const css::uno::Sequence< css::beans::PropertyValue >& xProperties, + css::uno::Reference< css::uno::XComponentContext > const & xContext, + sal_Int32 nStorageType ); + + OStorage( OStorage_Impl* pImpl, bool bReadOnlyWrap ); + + virtual ~OStorage() override; + + void InternalDispose( bool bNotifyImpl ); + + void ChildIsDisposed( const css::uno::Reference< css::uno::XInterface >& xChild ); + + sal_Int32 GetRefCount_Impl() const { return m_refCount; } + + // XInterface + + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type& rType ) override; + + virtual void SAL_CALL acquire() noexcept override; + + virtual void SAL_CALL release() noexcept override; + + // XTypeProvider + + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes() override; + + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId() override; + + // XStorage + + virtual void SAL_CALL copyToStorage( const css::uno::Reference< css::embed::XStorage >& xDest ) override; + + virtual css::uno::Reference< css::io::XStream > SAL_CALL openStreamElement( + const OUString& aStreamName, sal_Int32 nOpenMode ) override; + + virtual css::uno::Reference< css::io::XStream > SAL_CALL openEncryptedStreamElement( + const OUString& aStreamName, sal_Int32 nOpenMode, const OUString& aPass ) override; + + virtual css::uno::Reference< css::embed::XStorage > SAL_CALL openStorageElement( + const OUString& aStorName, sal_Int32 nStorageMode ) override; + + virtual css::uno::Reference< css::io::XStream > SAL_CALL cloneStreamElement( + const OUString& aStreamName ) override; + + virtual css::uno::Reference< css::io::XStream > SAL_CALL cloneEncryptedStreamElement( + const OUString& aStreamName, const OUString& aPass ) override; + + virtual void SAL_CALL copyLastCommitTo( + const css::uno::Reference< css::embed::XStorage >& xTargetStorage ) override; + + virtual void SAL_CALL copyStorageElementLastCommitTo( + const OUString& aStorName, + const css::uno::Reference< css::embed::XStorage >& xTargetStorage ) override; + + virtual sal_Bool SAL_CALL isStreamElement( const OUString& aElementName ) override; + + virtual sal_Bool SAL_CALL isStorageElement( const OUString& aElementName ) override; + + virtual void SAL_CALL removeElement( const OUString& aElementName ) override; + + virtual void SAL_CALL renameElement( const OUString& rEleName, const OUString& rNewName ) override; + + virtual void SAL_CALL copyElementTo( const OUString& aElementName, + const css::uno::Reference< css::embed::XStorage >& xDest, + const OUString& aNewName ) override; + + virtual void SAL_CALL moveElementTo( const OUString& aElementName, + const css::uno::Reference< css::embed::XStorage >& xDest, + const OUString& rNewName ) override; + + // XStorage2 + + virtual css::uno::Reference< css::io::XStream > SAL_CALL openEncryptedStream( const OUString& sStreamName, ::sal_Int32 nOpenMode, const css::uno::Sequence< css::beans::NamedValue >& aEncryptionData ) override; + + virtual css::uno::Reference< css::io::XStream > SAL_CALL cloneEncryptedStream( const OUString& sStreamName, const css::uno::Sequence< css::beans::NamedValue >& aEncryptionData ) override; + + // XStorageRawAccess + + virtual css::uno::Reference< css::io::XInputStream > SAL_CALL getPlainRawStreamElement( + const OUString& sStreamName ) override; + + virtual css::uno::Reference< css::io::XInputStream > SAL_CALL getRawEncrStreamElement( + const OUString& sStreamName ) override; + + virtual void SAL_CALL insertRawEncrStreamElement( const OUString& aStreamName, + const css::uno::Reference< css::io::XInputStream >& xInStream ) override; + + // XTransactedObject + virtual void SAL_CALL commit() override; + + virtual void SAL_CALL revert() override; + + // XTransactionBroadcaster + virtual void SAL_CALL addTransactionListener( + const css::uno::Reference< css::embed::XTransactionListener >& aListener ) override; + + virtual void SAL_CALL removeTransactionListener( + const css::uno::Reference< css::embed::XTransactionListener >& aListener ) override; + + // XModifiable + + virtual sal_Bool SAL_CALL isModified() override; + + virtual void SAL_CALL setModified( sal_Bool bModified ) override; + + virtual void SAL_CALL addModifyListener( + const css::uno::Reference< css::util::XModifyListener >& aListener ) override; + + virtual void SAL_CALL removeModifyListener( + const css::uno::Reference< css::util::XModifyListener >& aListener ) override; + + // XNameAccess + + virtual css::uno::Any SAL_CALL getByName( const OUString& aName ) override; + + virtual css::uno::Sequence< OUString > SAL_CALL getElementNames() override; + + virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override; + + virtual css::uno::Type SAL_CALL getElementType() override; + + virtual sal_Bool SAL_CALL hasElements() override; + + // XComponent + + virtual void SAL_CALL dispose() override; + + virtual void SAL_CALL addEventListener( + const css::uno::Reference< css::lang::XEventListener >& xListener ) override; + + virtual void SAL_CALL removeEventListener( + const css::uno::Reference< css::lang::XEventListener >& xListener ) override; + + // XEncryptionProtectedSource + + virtual void SAL_CALL setEncryptionPassword( const OUString& aPass ) override; + + virtual void SAL_CALL removeEncryption() override; + + // XEncryptionProtectedSource2 + + virtual void SAL_CALL setEncryptionData( + const css::uno::Sequence< css::beans::NamedValue >& aEncryptionData ) override; + + virtual sal_Bool SAL_CALL hasEncryptionData() override; + + // XEncryptionProtectedStorage + + virtual void SAL_CALL setEncryptionAlgorithms( const css::uno::Sequence< css::beans::NamedValue >& aAlgorithms ) override; + virtual void SAL_CALL setGpgProperties( const css::uno::Sequence< css::uno::Sequence< css::beans::NamedValue > >& aCryptProps ) override; + + virtual css::uno::Sequence< css::beans::NamedValue > SAL_CALL getEncryptionAlgorithms() override; + + // XPropertySet + + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo() override; + + virtual void SAL_CALL setPropertyValue( const OUString& aPropertyName, const css::uno::Any& aValue ) override; + + virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& PropertyName ) override; + + virtual void SAL_CALL addPropertyChangeListener( + const OUString& aPropertyName, + const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener ) override; + + virtual void SAL_CALL removePropertyChangeListener( + const OUString& aPropertyName, + const css::uno::Reference< css::beans::XPropertyChangeListener >& aListener ) override; + + virtual void SAL_CALL addVetoableChangeListener( + const OUString& PropertyName, + const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override; + + virtual void SAL_CALL removeVetoableChangeListener( const OUString& PropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override; + + // XOptimizedStorage + virtual void SAL_CALL insertRawNonEncrStreamElementDirect( const OUString& sStreamName, const css::uno::Reference< css::io::XInputStream >& xInStream ) override; + + virtual void SAL_CALL insertStreamElementDirect( const OUString& sStreamName, const css::uno::Reference< css::io::XInputStream >& xInStream, const css::uno::Sequence< css::beans::PropertyValue >& aProps ) override; + + virtual void SAL_CALL copyElementDirectlyTo( const OUString& sSourceName, const css::uno::Reference< css::embed::XOptimizedStorage >& xTargetStorage, const OUString& sTargetName ) override; + + virtual void SAL_CALL writeAndAttachToStream( const css::uno::Reference< css::io::XStream >& xStream ) override; + + virtual void SAL_CALL attachToURL( const OUString& sURL, sal_Bool bReadOnly ) override; + + virtual css::uno::Any SAL_CALL getElementPropertyValue( const OUString& sElementName, const OUString& sPropertyName ) override; + + virtual void SAL_CALL copyStreamElementData( const OUString& sStreamName, const css::uno::Reference< css::io::XStream >& xTargetStream ) override; + + // XRelationshipAccess + virtual sal_Bool SAL_CALL hasByID( const OUString& sID ) override; + + virtual OUString SAL_CALL getTargetByID( const OUString& sID ) override; + + virtual OUString SAL_CALL getTypeByID( const OUString& sID ) override; + + virtual css::uno::Sequence< css::beans::StringPair > SAL_CALL getRelationshipByID( const OUString& sID ) override; + + virtual css::uno::Sequence< css::uno::Sequence< css::beans::StringPair > > SAL_CALL getRelationshipsByType( const OUString& sType ) override; + + virtual css::uno::Sequence< css::uno::Sequence< css::beans::StringPair > > SAL_CALL getAllRelationships( ) override; + + virtual void SAL_CALL insertRelationshipByID( const OUString& sID, const css::uno::Sequence< css::beans::StringPair >& aEntry, sal_Bool bReplace ) override; + + virtual void SAL_CALL removeRelationshipByID( const OUString& sID ) override; + + virtual void SAL_CALL insertRelationships( const css::uno::Sequence< css::uno::Sequence< css::beans::StringPair > >& aEntries, sal_Bool bReplace ) override; + + virtual void SAL_CALL clearRelationships( ) override; + + // XHierarchicalStorageAccess + virtual css::uno::Reference< css::embed::XExtendedStorageStream > SAL_CALL openStreamElementByHierarchicalName( const OUString& sStreamPath, ::sal_Int32 nOpenMode ) override; + + virtual css::uno::Reference< css::embed::XExtendedStorageStream > SAL_CALL openEncryptedStreamElementByHierarchicalName( const OUString& sStreamName, ::sal_Int32 nOpenMode, const OUString& sPassword ) override; + + virtual void SAL_CALL removeStreamElementByHierarchicalName( const OUString& sElementPath ) override; + + // XHierarchicalStorageAccess2 + virtual css::uno::Reference< css::embed::XExtendedStorageStream > SAL_CALL openEncryptedStreamByHierarchicalName( const OUString& sStreamName, ::sal_Int32 nOpenMode, const css::uno::Sequence< css::beans::NamedValue >& aEncryptionData ) override; +}; + +StorageHolder_Impl::StorageHolder_Impl( OStorage* pStorage ) +: m_pPointer( pStorage ) +, m_xWeakRef( css::uno::Reference< css::embed::XStorage >( pStorage ) ) +{ +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/ByteChucker.cxx b/package/source/zipapi/ByteChucker.cxx new file mode 100644 index 0000000000..fe1f6a8685 --- /dev/null +++ b/package/source/zipapi/ByteChucker.cxx @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <ByteChucker.hxx> +#include <com/sun/star/io/XSeekable.hpp> +#include <com/sun/star/io/XOutputStream.hpp> + +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::uno; + +ByteChucker::ByteChucker(Reference<XOutputStream> const & xOstream) +: xStream(xOstream) +, xSeek (xOstream, UNO_QUERY ) +, a2Sequence ( 2 ) +, a4Sequence ( 4 ) +, a8Sequence ( 8 ) +, p2Sequence ( a2Sequence.getArray() ) +, p4Sequence ( a4Sequence.getArray() ) +, p8Sequence ( a8Sequence.getArray() ) +{ +} + +ByteChucker::~ByteChucker() +{ +} + +void ByteChucker::WriteBytes( const Sequence< sal_Int8 >& aData ) +{ + xStream->writeBytes(aData); +} + +sal_Int64 ByteChucker::GetPosition( ) +{ + return xSeek->getPosition(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/ByteGrabber.cxx b/package/source/zipapi/ByteGrabber.cxx new file mode 100644 index 0000000000..5a491de650 --- /dev/null +++ b/package/source/zipapi/ByteGrabber.cxx @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <ByteGrabber.hxx> +#include <sal/log.hxx> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/io/XSeekable.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> + +using namespace ::com::sun::star; + +#if OSL_DEBUG_LEVEL > 0 +#define THROW_WHERE SAL_WHERE +#else +#define THROW_WHERE "" +#endif + +/** ByteGrabber implements the >> operators on an XOutputStream. This is + * potentially quite slow and may need to be optimised + */ + +ByteGrabber::ByteGrabber(uno::Reference < io::XInputStream > const & xIstream) +: xStream(xIstream) +, xSeek (xIstream, uno::UNO_QUERY ) +, aSequence ( 4 ) +{ + pSequence = aSequence.getArray(); +} + +ByteGrabber::~ByteGrabber() +{ +} + +void ByteGrabber::setInputStream (const uno::Reference < io::XInputStream >& xNewStream) +{ + std::scoped_lock aGuard( m_aMutex ); + xStream = xNewStream; + xSeek.set(xNewStream, uno::UNO_QUERY); +} + +// XInputStream chained +sal_Int32 ByteGrabber::readBytes( uno::Sequence< sal_Int8 >& aData, + sal_Int32 nBytesToRead ) +{ + std::scoped_lock aGuard( m_aMutex ); + return xStream->readBytes(aData, nBytesToRead ); +} + +// XSeekable chained... +void ByteGrabber::seek( sal_Int64 location ) +{ + std::scoped_lock aGuard( m_aMutex ); + if (!xSeek.is() ) + throw io::IOException(THROW_WHERE ); + + xSeek->seek( location ); +} + +sal_Int64 ByteGrabber::getPosition( ) +{ + std::scoped_lock aGuard( m_aMutex ); + if (!xSeek.is() ) + throw io::IOException(THROW_WHERE ); + + return xSeek->getPosition(); +} + +sal_Int64 ByteGrabber::getLength( ) +{ + std::scoped_lock aGuard( m_aMutex ); + if (!xSeek.is() ) + throw io::IOException(THROW_WHERE ); + + return xSeek->getLength(); +} + +sal_uInt16 ByteGrabber::ReadUInt16() +{ + std::scoped_lock aGuard( m_aMutex ); + + if (xStream->readBytes(aSequence, 2) != 2) + return 0; + + pSequence = aSequence.getConstArray(); + return static_cast <sal_uInt16> + ( (pSequence[0] & 0xFF) + | (pSequence[1] & 0xFF) << 8); +} + +sal_uInt32 ByteGrabber::ReadUInt32() +{ + std::scoped_lock aGuard( m_aMutex ); + + if (xStream->readBytes(aSequence, 4) != 4) + return 0; + + pSequence = aSequence.getConstArray(); + return static_cast < sal_uInt32 > + ( (pSequence[0] & 0xFF) + | ( pSequence[1] & 0xFF ) << 8 + | ( pSequence[2] & 0xFF ) << 16 + | ( pSequence[3] & 0xFF ) << 24 ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/CRC32.cxx b/package/source/zipapi/CRC32.cxx new file mode 100644 index 0000000000..bd06822486 --- /dev/null +++ b/package/source/zipapi/CRC32.cxx @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <CRC32.hxx> +#include <PackageConstants.hxx> +#include <rtl/crc.h> +#include <com/sun/star/io/XInputStream.hpp> + +using namespace com::sun::star::uno; +using namespace com::sun::star::io; + +/** A class to compute the CRC32 value of a data stream + */ + +CRC32::CRC32() +: nCRC(0) +{ +} +void CRC32::reset() +{ + nCRC=0; +} +sal_Int32 CRC32::getValue() const +{ + return nCRC; +} +/** Update CRC32 with specified sequence of bytes + */ +void CRC32::updateSegment(const Sequence< sal_Int8 > &b, sal_Int32 len) +{ + nCRC = rtl_crc32(nCRC, b.getConstArray(), len ); +} +/** Update CRC32 with specified sequence of bytes + */ +void CRC32::update(const Sequence< sal_Int8 > &b) +{ + nCRC = rtl_crc32(nCRC, b.getConstArray(),b.getLength()); +} + +sal_Int64 CRC32::updateStream( Reference < XInputStream > const & xStream ) +{ + sal_Int32 nLength; + sal_Int64 nTotal = 0; + Sequence < sal_Int8 > aSeq ( n_ConstBufferSize ); + do + { + nLength = xStream->readBytes ( aSeq, n_ConstBufferSize ); + updateSegment ( aSeq, nLength ); + nTotal += nLength; + } + while ( nLength == n_ConstBufferSize ); + + return nTotal; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/Deflater.cxx b/package/source/zipapi/Deflater.cxx new file mode 100644 index 0000000000..9439e3f56b --- /dev/null +++ b/package/source/zipapi/Deflater.cxx @@ -0,0 +1,162 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <package/Deflater.hxx> +#include <zlib.h> +#include <com/sun/star/packages/zip/ZipConstants.hpp> +#include <osl/diagnose.h> +#include <string.h> + +using namespace com::sun::star::packages::zip::ZipConstants; +using namespace com::sun::star; +using namespace ZipUtils; + +/** Provides general purpose compression using the ZLIB compression + * library. + */ + +Deflater::~Deflater() +{ + end(); +} +void Deflater::init (sal_Int32 nLevelArg, bool bNowrap) +{ + pStream.reset(new z_stream); + /* Memset it to 0...sets zalloc/zfree/opaque to NULL */ + memset (pStream.get(), 0, sizeof(*pStream)); + + switch (deflateInit2(pStream.get(), nLevelArg, Z_DEFLATED, bNowrap? -MAX_WBITS : MAX_WBITS, + DEF_MEM_LEVEL, DEFAULT_STRATEGY)) + { + case Z_OK: + break; + case Z_MEM_ERROR: + pStream.reset(); + break; + case Z_STREAM_ERROR: + pStream.reset(); + break; + default: + break; + } +} + +Deflater::Deflater(sal_Int32 nSetLevel, bool bNowrap) +: bFinish(false) +, bFinished(false) +, nOffset(0) +, nLength(0) +, nTotalOut64(0) +, nTotalIn64(0) +{ + init(nSetLevel, bNowrap); +} + +sal_Int32 Deflater::doDeflateBytes (uno::Sequence < sal_Int8 > &rBuffer, sal_Int32 nNewOffset, sal_Int32 nNewLength) +{ + sal_Int32 nResult; + pStream->next_in = reinterpret_cast<const unsigned char*>( sInBuffer.getConstArray() + nOffset ); + pStream->next_out = reinterpret_cast<unsigned char*>(rBuffer.getArray())+nNewOffset; + pStream->avail_in = nLength; + pStream->avail_out = nNewLength; + auto nLastTotalIn = pStream->total_in; + auto nLastTotalOut = pStream->total_out; + +#if !defined Z_PREFIX + nResult = deflate(pStream.get(), bFinish ? Z_FINISH : Z_NO_FLUSH); +#else + nResult = z_deflate(pStream.get(), bFinish ? Z_FINISH : Z_NO_FLUSH); +#endif + // total_in / total_out may stored only in 32bit, and can overflow during deflate + // 1 deflate call, uncompress only a few data, so only 1 overflow can happen at once. + if (pStream->total_in < nLastTotalIn) + { + nTotalIn64 += 0x100000000; + } + if (pStream->total_out < nLastTotalOut) + { + nTotalOut64 += 0x100000000; + } + switch (nResult) + { + case Z_STREAM_END: + bFinished = true; + [[fallthrough]]; + case Z_OK: + nOffset += nLength - pStream->avail_in; + nLength = pStream->avail_in; + return nNewLength - pStream->avail_out; + default: + return 0; + } +} + +void Deflater::setInputSegment( const uno::Sequence< sal_Int8 >& rBuffer ) +{ + sInBuffer = rBuffer; + nOffset = 0; + nLength = rBuffer.getLength(); +} + +bool Deflater::needsInput() const +{ + return nLength <=0; +} +void Deflater::finish( ) +{ + bFinish = true; +} +sal_Int32 Deflater::doDeflateSegment( uno::Sequence< sal_Int8 >& rBuffer, sal_Int32 nNewLength ) +{ + OSL_ASSERT( !(nNewLength < 0 || nNewLength > rBuffer.getLength())); + return doDeflateBytes(rBuffer, /*nNewOffset*/0, nNewLength); +} +sal_Int64 Deflater::getTotalIn() const +{ + return pStream->total_in + nTotalIn64; +} +sal_Int64 Deflater::getTotalOut() const +{ + return pStream->total_out + nTotalOut64; +} +void Deflater::reset( ) +{ +#if !defined Z_PREFIX + deflateReset(pStream.get()); +#else + z_deflateReset(pStream.get()); +#endif + bFinish = false; + bFinished = false; + nOffset = nLength = 0; +} +void Deflater::end( ) +{ + if (pStream) + { +#if !defined Z_PREFIX + deflateEnd(pStream.get()); +#else + z_deflateEnd(pStream.get()); +#endif + pStream.reset(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/Inflater.cxx b/package/source/zipapi/Inflater.cxx new file mode 100644 index 0000000000..d03fed8c09 --- /dev/null +++ b/package/source/zipapi/Inflater.cxx @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <package/Inflater.hxx> +#include <zlib.h> +#include <string.h> + +using namespace com::sun::star::uno; +using namespace ZipUtils; + +/** Provides general purpose decompression using the ZLIB library */ + +Inflater::Inflater(bool bNoWrap) +: bFinished(false), + bNeedDict(false), + nOffset(0), + nLength(0), + nLastInflateError(0) +{ + pStream.reset(new z_stream); + /* memset to 0 to set zalloc/opaque etc */ + memset (pStream.get(), 0, sizeof(*pStream)); + sal_Int32 nRes; + nRes = inflateInit2(pStream.get(), bNoWrap ? -MAX_WBITS : MAX_WBITS); + switch (nRes) + { + case Z_OK: + break; + case Z_MEM_ERROR: + pStream.reset(); + break; + case Z_STREAM_ERROR: + pStream.reset(); + break; + default: + break; + } +} + +Inflater::~Inflater() +{ + end(); +} + +void Inflater::setInput( const Sequence< sal_Int8 >& rBuffer ) +{ + sInBuffer = rBuffer; + nOffset = 0; + nLength = rBuffer.getLength(); +} + + +sal_Int32 Inflater::doInflateSegment( Sequence< sal_Int8 >& rBuffer, sal_Int32 nNewOffset, sal_Int32 nNewLength ) +{ + if (nNewOffset < 0 || nNewLength < 0 || nNewOffset + nNewLength > rBuffer.getLength()) + { + // do error handling + } + return doInflateBytes(rBuffer, nNewOffset, nNewLength); +} + +void Inflater::end( ) +{ + if (pStream) + { +#if !defined Z_PREFIX + inflateEnd(pStream.get()); +#else + z_inflateEnd(pStream.get()); +#endif + pStream.reset(); + } +} + +sal_Int32 Inflater::doInflateBytes (Sequence < sal_Int8 > &rBuffer, sal_Int32 nNewOffset, sal_Int32 nNewLength) +{ + if ( !pStream ) + { + nLastInflateError = Z_STREAM_ERROR; + return 0; + } + + nLastInflateError = 0; + + pStream->next_in = reinterpret_cast<const unsigned char*>( sInBuffer.getConstArray() + nOffset ); + pStream->avail_in = nLength; + pStream->next_out = reinterpret_cast < unsigned char* > ( rBuffer.getArray() + nNewOffset ); + pStream->avail_out = nNewLength; + +#if !defined Z_PREFIX + sal_Int32 nResult = ::inflate(pStream.get(), Z_PARTIAL_FLUSH); +#else + sal_Int32 nResult = ::z_inflate(pStream.get(), Z_PARTIAL_FLUSH); +#endif + + switch (nResult) + { + case Z_STREAM_END: + bFinished = true; + [[fallthrough]]; + case Z_OK: + nOffset += nLength - pStream->avail_in; + nLength = pStream->avail_in; + return nNewLength - pStream->avail_out; + + case Z_NEED_DICT: + bNeedDict = true; + nOffset += nLength - pStream->avail_in; + nLength = pStream->avail_in; + return 0; + + default: + // it is no error, if there is no input or no output + if ( nLength && nNewLength ) + nLastInflateError = nResult; + } + + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/MemoryByteGrabber.hxx b/package/source/zipapi/MemoryByteGrabber.hxx new file mode 100644 index 0000000000..d474be40cd --- /dev/null +++ b/package/source/zipapi/MemoryByteGrabber.hxx @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_PACKAGE_SOURCE_ZIPAPI_MEMORYBYTEGRABBER_HXX +#define INCLUDED_PACKAGE_SOURCE_ZIPAPI_MEMORYBYTEGRABBER_HXX + +#include <com/sun/star/uno/Sequence.h> + +class MemoryByteGrabber final +{ + const sal_Int8 *mpBuffer; + sal_Int32 mnCurrent, mnEnd; +public: + MemoryByteGrabber ( const css::uno::Sequence < sal_Int8 > & rBuffer ) + : mpBuffer ( rBuffer.getConstArray() ) + , mnCurrent ( 0 ) + , mnEnd ( rBuffer.getLength() ) + { + } + MemoryByteGrabber(css::uno::Sequence<sal_Int8> &&) = delete; + + const sal_Int8 * getCurrentPos () const { return mpBuffer + mnCurrent; } + + sal_Int32 remainingSize() const { return mnEnd - mnCurrent; } + + // XInputStream chained + + /// @throws css::io::NotConnectedException + /// @throws css::io::BufferSizeExceededException + /// @throws css::io::IOException + /// @throws css::uno::RuntimeException + void skipBytes( sal_Int32 nBytesToSkip ) + { + mnCurrent += nBytesToSkip; + } + + // XSeekable chained... + sal_Int16 ReadInt16() + { + if (mnCurrent + 2 > mnEnd ) + return 0; + sal_Int16 nInt16 = mpBuffer[mnCurrent++] & 0xFF; + nInt16 |= ( mpBuffer[mnCurrent++] & 0xFF ) << 8; + return nInt16; + } + + sal_Int16 ReadUInt16() + { + if (mnCurrent + 2 > mnEnd ) + return 0; + sal_uInt16 nInt16 = mpBuffer[mnCurrent++] & 0xFF; + nInt16 |= ( mpBuffer[mnCurrent++] & 0xFF ) << 8; + return nInt16; + } + + sal_Int32 ReadInt32() + { + if (mnCurrent + 4 > mnEnd ) + return 0; + + sal_Int32 nInt32 = mpBuffer[mnCurrent++] & 0xFF; + nInt32 |= ( mpBuffer[mnCurrent++] & 0xFF ) << 8; + nInt32 |= ( mpBuffer[mnCurrent++] & 0xFF ) << 16; + nInt32 |= ( mpBuffer[mnCurrent++] & 0xFF ) << 24; + return nInt32; + } + + sal_uInt32 ReadUInt32() + { + if (mnCurrent + 4 > mnEnd ) + return 0; + + sal_uInt32 nInt32 = mpBuffer [mnCurrent++] & 0xFF; + nInt32 |= ( mpBuffer [mnCurrent++] & 0xFF ) << 8; + nInt32 |= ( mpBuffer [mnCurrent++] & 0xFF ) << 16; + nInt32 |= ( mpBuffer [mnCurrent++] & 0xFF ) << 24; + return nInt32; + } + + sal_uInt64 ReadUInt64() + { + if (mnCurrent + 8 > mnEnd) + return 0; + + sal_uInt64 nInt64 = mpBuffer[mnCurrent++] & 0xFF; + nInt64 |= static_cast<sal_Int64>(mpBuffer[mnCurrent++] & 0xFF) << 8; + nInt64 |= static_cast<sal_Int64>(mpBuffer[mnCurrent++] & 0xFF) << 16; + nInt64 |= static_cast<sal_Int64>(mpBuffer[mnCurrent++] & 0xFF) << 24; + nInt64 |= static_cast<sal_Int64>(mpBuffer[mnCurrent++] & 0xFF) << 32; + nInt64 |= static_cast<sal_Int64>(mpBuffer[mnCurrent++] & 0xFF) << 40; + nInt64 |= static_cast<sal_Int64>(mpBuffer[mnCurrent++] & 0xFF) << 48; + nInt64 |= static_cast<sal_Int64>(mpBuffer[mnCurrent++] & 0xFF) << 56; + return nInt64; + } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/ThreadedDeflater.cxx b/package/source/zipapi/ThreadedDeflater.cxx new file mode 100644 index 0000000000..f574105ad4 --- /dev/null +++ b/package/source/zipapi/ThreadedDeflater.cxx @@ -0,0 +1,220 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <ThreadedDeflater.hxx> +#include <zlib.h> +#include <com/sun/star/packages/zip/ZipConstants.hpp> +#include <sal/log.hxx> + +using namespace com::sun::star::packages::zip::ZipConstants; +using namespace com::sun::star; + +namespace ZipUtils +{ +const sal_Int64 MaxBlockSize = 128 * 1024; + +// Parallel ZLIB compression using threads. The class internally splits the data into +// blocks and spawns ThreadPool tasks to process them independently. This is achieved +// in a similar way how pigz works, see comments from Mark Adler at +// https://stackoverflow.com/questions/30294766/how-to-use-multiple-threads-for-zlib-compression +// and +// https://stackoverflow.com/questions/30794053/how-to-use-multiple-threads-for-zlib-compression-same-input-source + +// Everything here should be either read-only, or writing to distinct data, or atomic. + +class ThreadedDeflater::Task : public comphelper::ThreadTask +{ + z_stream stream; + ThreadedDeflater* deflater; + int sequence; + int blockSize; + bool firstTask : 1; + bool lastTask : 1; + +public: + Task(ThreadedDeflater* deflater_, int sequence_, int blockSize_, bool firstTask_, + bool lastTask_) + : comphelper::ThreadTask(deflater_->threadTaskTag) + , stream() + , deflater(deflater_) + , sequence(sequence_) + , blockSize(blockSize_) + , firstTask(firstTask_) + , lastTask(lastTask_) + { + } + +private: + virtual void doWork() override; +}; + +ThreadedDeflater::ThreadedDeflater(sal_Int32 nSetLevel) + : threadTaskTag(comphelper::ThreadPool::createThreadTaskTag()) + , totalIn(0) + , totalOut(0) + , zlibLevel(nSetLevel) +{ +} + +ThreadedDeflater::~ThreadedDeflater() COVERITY_NOEXCEPT_FALSE { clear(); } + +void ThreadedDeflater::deflateWrite( + const css::uno::Reference<css::io::XInputStream>& xInStream, + std::function<void(const css::uno::Sequence<sal_Int8>&, sal_Int32)> aProcessInputFunc, + std::function<void(const css::uno::Sequence<sal_Int8>&, sal_Int32)> aProcessOutputFunc) +{ + sal_Int64 nThreadCount = comphelper::ThreadPool::getSharedOptimalPool().getWorkerCount(); + sal_Int64 batchSize = MaxBlockSize * nThreadCount; + inBuffer.realloc(batchSize); + prevDataBlock.realloc(MaxBlockSize); + outBuffers.resize(nThreadCount); + maProcessOutputFunc = aProcessOutputFunc; + bool firstTask = true; + + while (xInStream->available() > 0) + { + sal_Int64 inputBytes = xInStream->readBytes(inBuffer, batchSize); + aProcessInputFunc(inBuffer, inputBytes); + totalIn += inputBytes; + int sequence = 0; + bool lastBatch = xInStream->available() <= 0; + sal_Int64 bytesPending = inputBytes; + while (bytesPending > 0) + { + sal_Int64 taskSize = std::min(MaxBlockSize, bytesPending); + bytesPending -= taskSize; + bool lastTask = lastBatch && !bytesPending; + comphelper::ThreadPool::getSharedOptimalPool().pushTask( + std::make_unique<Task>(this, sequence++, taskSize, firstTask, lastTask)); + + if (firstTask) + firstTask = false; + } + + assert(bytesPending == 0); + + comphelper::ThreadPool::getSharedOptimalPool().waitUntilDone(threadTaskTag); + + if (!lastBatch) + { + assert(inputBytes == batchSize); + std::copy_n(std::cbegin(inBuffer) + (batchSize - MaxBlockSize), MaxBlockSize, + prevDataBlock.getArray()); + } + + processDeflatedBuffers(); + } +} + +void ThreadedDeflater::processDeflatedBuffers() +{ + sal_Int64 batchOutputSize = 0; + for (const auto& buffer : outBuffers) + batchOutputSize += buffer.size(); + + css::uno::Sequence<sal_Int8> outBuffer(batchOutputSize); + + auto pos = outBuffer.getArray(); + for (auto& buffer : outBuffers) + { + pos = std::copy(buffer.begin(), buffer.end(), pos); + buffer.clear(); + } + + maProcessOutputFunc(outBuffer, batchOutputSize); + totalOut += batchOutputSize; +} + +void ThreadedDeflater::clear() +{ + inBuffer = uno::Sequence<sal_Int8>(); + outBuffers.clear(); +} + +#if defined Z_PREFIX +#define deflateInit2 z_deflateInit2 +#define deflateBound z_deflateBound +#define deflateSetDictionary z_deflateSetDictionary +#define deflate z_deflate +#define deflateEnd z_deflateEnd +#endif + +void ThreadedDeflater::Task::doWork() +{ + stream.zalloc = nullptr; + stream.zfree = nullptr; + stream.opaque = nullptr; + // -MAX_WBITS means 32k window size and raw stream + if (deflateInit2(&stream, deflater->zlibLevel, Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, + Z_DEFAULT_STRATEGY) + != Z_OK) + { + SAL_WARN("package.threadeddeflate", "deflateInit2() failed"); + abort(); + } + // Find out size for our output buffer to be large enough for deflate() needing to be called just once. + sal_Int64 outputMaxSize = deflateBound(&stream, blockSize); + // add extra size for Z_SYNC_FLUSH + outputMaxSize += 20; + deflater->outBuffers[sequence].resize(outputMaxSize); + sal_Int64 myInBufferStart = sequence * MaxBlockSize; + // zlib doesn't handle const properly + unsigned char* inBufferPtr = reinterpret_cast<unsigned char*>( + const_cast<signed char*>(deflater->inBuffer.getConstArray())); + if (!firstTask) + { + // the window size is 32k, so set last 32k of previous data as the dictionary + assert(MAX_WBITS == 15); + assert(MaxBlockSize >= 32768); + if (sequence > 0) + { + deflateSetDictionary(&stream, inBufferPtr + myInBufferStart - 32768, 32768); + } + else + { + unsigned char* prevBufferPtr = reinterpret_cast<unsigned char*>( + const_cast<signed char*>(deflater->prevDataBlock.getConstArray())); + deflateSetDictionary(&stream, prevBufferPtr + MaxBlockSize - 32768, 32768); + } + } + stream.next_in = inBufferPtr + myInBufferStart; + stream.avail_in = blockSize; + stream.next_out = reinterpret_cast<unsigned char*>(deflater->outBuffers[sequence].data()); + stream.avail_out = outputMaxSize; + + // The trick is in using Z_SYNC_FLUSH instead of Z_NO_FLUSH. It will align the data at a byte boundary, + // and since we use a raw stream, the data blocks then can be simply concatenated. + int res = deflate(&stream, lastTask ? Z_FINISH : Z_SYNC_FLUSH); + assert(stream.avail_in == 0); // Check that everything has been deflated. + if (lastTask ? res == Z_STREAM_END : res == Z_OK) + { // ok + sal_Int64 outSize = outputMaxSize - stream.avail_out; + deflater->outBuffers[sequence].resize(outSize); + } + else + { + SAL_WARN("package.threadeddeflate", "deflate() failed"); + abort(); + } + deflateEnd(&stream); +} + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/XBufferedThreadedStream.cxx b/package/source/zipapi/XBufferedThreadedStream.cxx new file mode 100644 index 0000000000..d3bf995d90 --- /dev/null +++ b/package/source/zipapi/XBufferedThreadedStream.cxx @@ -0,0 +1,189 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "XBufferedThreadedStream.hxx" + +using namespace css::uno; + +namespace { + +class UnzippingThread: public salhelper::Thread +{ + XBufferedThreadedStream &mxStream; +public: + explicit UnzippingThread(XBufferedThreadedStream &xStream): Thread("Unzipping"), mxStream(xStream) {} +private: + virtual void execute() override + { + try + { + mxStream.produce(); + } + catch (...) + { + mxStream.saveException(std::current_exception()); + } + + mxStream.setTerminateThread(); + } +}; + +} + +XBufferedThreadedStream::XBufferedThreadedStream( + const Reference<XInputStream>& xSrcStream, + sal_Int64 nStreamSize) +: mxSrcStream( xSrcStream ) +, mnPos(0) +, mnStreamSize( nStreamSize ) +, mnOffset( 0 ) +, mxUnzippingThread( new UnzippingThread(*this) ) +, mbTerminateThread( false ) +{ + mxUnzippingThread->launch(); +} + +XBufferedThreadedStream::~XBufferedThreadedStream() +{ + setTerminateThread(); + mxUnzippingThread->join(); +} + +/** + * Reads from UnbufferedStream in a separate thread and stores the buffer blocks + * in maPendingBuffers queue for further use. + */ +void XBufferedThreadedStream::produce() +{ + Buffer pProducedBuffer; + sal_Int64 nTotalBytesRead(0); + std::unique_lock<std::mutex> aGuard( maBufferProtector ); + do + { + if( !maUsedBuffers.empty() ) + { + pProducedBuffer = maUsedBuffers.front(); + maUsedBuffers.pop(); + } + + aGuard.unlock(); + nTotalBytesRead += mxSrcStream->readBytes( pProducedBuffer, nBufferSize ); + + aGuard.lock(); + maPendingBuffers.push( pProducedBuffer ); + maBufferConsumeResume.notify_one(); + + if (!mbTerminateThread) + maBufferProduceResume.wait( aGuard, [&]{return canProduce(); } ); + + } while( !mbTerminateThread && nTotalBytesRead < mnStreamSize ); +} + +/** + * Fetches next available block from maPendingBuffers for use in Reading thread. + */ +const Buffer& XBufferedThreadedStream::getNextBlock() +{ + std::unique_lock<std::mutex> aGuard( maBufferProtector ); + const sal_Int32 nBufSize = maInUseBuffer.getLength(); + if( nBufSize <= 0 || mnOffset >= nBufSize ) + { + if( mnOffset >= nBufSize ) + maUsedBuffers.push( maInUseBuffer ); + + maBufferConsumeResume.wait( aGuard, [&]{return canConsume(); } ); + + if( maPendingBuffers.empty() ) + { + maInUseBuffer = Buffer(); + if (maSavedException) + std::rethrow_exception(maSavedException); + } + else + { + maInUseBuffer = maPendingBuffers.front(); + maPendingBuffers.pop(); + mnOffset = 0; + + if( maPendingBuffers.size() <= nBufferLowWater ) + maBufferProduceResume.notify_one(); + } + } + + return maInUseBuffer; +} + +void XBufferedThreadedStream::setTerminateThread() +{ + std::scoped_lock<std::mutex> aGuard( maBufferProtector ); + mbTerminateThread = true; + maBufferProduceResume.notify_one(); + maBufferConsumeResume.notify_one(); +} + +sal_Int32 SAL_CALL XBufferedThreadedStream::readBytes( Sequence< sal_Int8 >& rData, sal_Int32 nBytesToRead ) +{ + if( !hasBytes() ) + return 0; + + const sal_Int32 nAvailableSize = static_cast< sal_Int32 > ( std::min< sal_Int64 >( nBytesToRead, remainingSize() ) ); + rData.realloc( nAvailableSize ); + auto pData = rData.getArray(); + sal_Int32 i = 0, nPendingBytes = nAvailableSize; + + while( nPendingBytes ) + { + const Buffer &pBuffer = getNextBlock(); + if( !pBuffer.hasElements() ) + { + rData.realloc( nAvailableSize - nPendingBytes ); + return nAvailableSize - nPendingBytes; + } + const sal_Int32 limit = std::min<sal_Int32>( nPendingBytes, pBuffer.getLength() - mnOffset ); + + memcpy( &pData[i], &pBuffer[mnOffset], limit ); + + nPendingBytes -= limit; + mnOffset += limit; + mnPos += limit; + i += limit; + } + + return nAvailableSize; +} + +sal_Int32 SAL_CALL XBufferedThreadedStream::readSomeBytes( Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) +{ + return readBytes( aData, nMaxBytesToRead ); +} +void SAL_CALL XBufferedThreadedStream::skipBytes( sal_Int32 nBytesToSkip ) +{ + if( nBytesToSkip ) + { + Sequence < sal_Int8 > aSequence( nBytesToSkip ); + readBytes( aSequence, nBytesToSkip ); + } +} + +sal_Int32 SAL_CALL XBufferedThreadedStream::available() +{ + if( !hasBytes() ) + return 0; + + return static_cast< sal_Int32 > ( std::min< sal_Int64 >( SAL_MAX_INT32, remainingSize() ) ); +} + +void SAL_CALL XBufferedThreadedStream::closeInput() +{ + setTerminateThread(); + mxUnzippingThread->join(); + mxSrcStream->closeInput(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/XBufferedThreadedStream.hxx b/package/source/zipapi/XBufferedThreadedStream.hxx new file mode 100644 index 0000000000..beb1cd33c7 --- /dev/null +++ b/package/source/zipapi/XBufferedThreadedStream.hxx @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef INCLUDED_PACKAGE_SOURCE_ZIPAPI_XBUFFEREDTHREADEDSTREAM_HXX +#define INCLUDED_PACKAGE_SOURCE_ZIPAPI_XBUFFEREDTHREADEDSTREAM_HXX + +#include <com/sun/star/io/XInputStream.hpp> + +#include <cppuhelper/implbase.hxx> +#include <rtl/ref.hxx> +#include <salhelper/thread.hxx> + +#include <queue> +#include <mutex> +#include <condition_variable> +#include <exception> + +typedef css::uno::Sequence< sal_Int8 > Buffer; + +class XBufferedThreadedStream : public cppu::WeakImplHelper< css::io::XInputStream > +{ +private: + const css::uno::Reference<XInputStream> mxSrcStream; + sal_Int64 mnPos; /// position in stream + sal_Int64 mnStreamSize; /// available size of stream + + Buffer maInUseBuffer; /// Buffer block in use + int mnOffset; /// position in maInUseBuffer + std::queue < Buffer > maPendingBuffers; /// Buffers that are available for use + std::queue < Buffer > maUsedBuffers; + + rtl::Reference< salhelper::Thread > mxUnzippingThread; + std::mutex maBufferProtector; /// mutex protecting Buffer queues. + std::condition_variable maBufferConsumeResume; + std::condition_variable maBufferProduceResume; + bool mbTerminateThread; /// indicates the failure of one of the threads + + std::exception_ptr maSavedException; /// exception caught during unzipping is saved to be thrown during reading + + static const size_t nBufferLowWater = 2; + static const size_t nBufferHighWater = 4; + static const size_t nBufferSize = 32 * 1024; + + const Buffer& getNextBlock(); + sal_Int64 remainingSize() const { return mnStreamSize - mnPos; } + bool hasBytes() const { return mnPos < mnStreamSize; } + + bool canProduce() const + { + return( mbTerminateThread || maPendingBuffers.size() < nBufferHighWater ); + } + + bool canConsume() const + { + return( mbTerminateThread || !maPendingBuffers.empty() ); + } + +public: + XBufferedThreadedStream( + const css::uno::Reference<XInputStream>& xSrcStream, + sal_Int64 nStreamSize /* cf. sal_Int32 available(); */ ); + + virtual ~XBufferedThreadedStream() override; + + void produce(); + void setTerminateThread(); + void saveException(const std::exception_ptr& exception) { maSavedException = exception; } + + // XInputStream + virtual sal_Int32 SAL_CALL readBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) override; + virtual sal_Int32 SAL_CALL readSomeBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) override; + virtual void SAL_CALL skipBytes( sal_Int32 nBytesToSkip ) override; + virtual sal_Int32 SAL_CALL available( ) override; + virtual void SAL_CALL closeInput( ) override; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/XUnbufferedStream.cxx b/package/source/zipapi/XUnbufferedStream.cxx new file mode 100644 index 0000000000..8b628b14dd --- /dev/null +++ b/package/source/zipapi/XUnbufferedStream.cxx @@ -0,0 +1,331 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/packages/zip/ZipConstants.hpp> +#include <com/sun/star/packages/zip/ZipIOException.hpp> +#include <com/sun/star/xml/crypto/CipherID.hpp> + +#include "XUnbufferedStream.hxx" +#include <EncryptionData.hxx> +#include <ZipFile.hxx> +#include <EncryptedDataHeader.hxx> +#include <algorithm> +#include <string.h> + +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <osl/mutex.hxx> +#include <utility> +#include <comphelper/diagnose_ex.hxx> + +using namespace ::com::sun::star; +using namespace com::sun::star::packages::zip::ZipConstants; +using namespace com::sun::star::io; +using namespace com::sun::star::uno; +using com::sun::star::packages::zip::ZipIOException; + +XUnbufferedStream::XUnbufferedStream( + const uno::Reference< uno::XComponentContext >& xContext, + rtl::Reference< comphelper::RefCountedMutex > aMutexHolder, + ZipEntry const & rEntry, + Reference < XInputStream > const & xNewZipStream, + const ::rtl::Reference< EncryptionData >& rData, + sal_Int8 nStreamMode, + bool bIsEncrypted, + const OUString& aMediaType, + bool bRecoveryMode ) +: maMutexHolder(std::move( aMutexHolder )) +, mxZipStream ( xNewZipStream ) +, mxZipSeek ( xNewZipStream, UNO_QUERY ) +, maEntry ( rEntry ) +, mnBlockSize( 1 ) +, maInflater ( true ) +, mbRawStream ( nStreamMode == UNBUFF_STREAM_RAW || nStreamMode == UNBUFF_STREAM_WRAPPEDRAW ) +, mbWrappedRaw ( nStreamMode == UNBUFF_STREAM_WRAPPEDRAW ) +, mnHeaderToRead ( 0 ) +, mnZipCurrent ( 0 ) +, mnZipEnd ( 0 ) +, mnZipSize ( 0 ) +, mnMyCurrent ( 0 ) +, mbCheckCRC(!bRecoveryMode) +{ + mnZipCurrent = maEntry.nOffset; + sal_Int64 nSize; + if ( mbRawStream ) + { + mnZipSize = maEntry.nMethod == DEFLATED ? maEntry.nCompressedSize : maEntry.nSize; + nSize = mnZipSize; + } + else + { + mnZipSize = maEntry.nSize; + nSize = maEntry.nMethod == DEFLATED ? maEntry.nCompressedSize : maEntry.nSize; + } + + if (mnZipSize < 0) + throw ZipIOException("The stream seems to be broken!"); + + if (o3tl::checked_add(maEntry.nOffset, nSize, mnZipEnd)) + throw ZipIOException("Integer-overflow"); + + bool bHaveEncryptData = rData.is() && rData->m_aInitVector.hasElements() && + ((rData->m_aSalt.hasElements() && (rData->m_oPBKDFIterationCount || rData->m_oArgon2Args)) + || + rData->m_aKey.hasElements()); + bool bMustDecrypt = nStreamMode == UNBUFF_STREAM_DATA && bHaveEncryptData && bIsEncrypted; + + if ( bMustDecrypt ) + { + m_xCipherContext = ZipFile::StaticGetCipher( xContext, rData, false ); + // this is only relevant when padding is used + mnBlockSize = ( rData->m_nEncAlg == xml::crypto::CipherID::AES_CBC_W3C_PADDING ? 16 : 1 ); + } + + if ( !(bHaveEncryptData && mbWrappedRaw && bIsEncrypted) ) + return; + + // if we have the data needed to decrypt it, but didn't want it decrypted (or + // we couldn't decrypt it due to wrong password), then we prepend this + // data to the stream + + // Make a buffer big enough to hold both the header and the data itself + maHeader.realloc ( n_ConstHeaderSize + + rData->m_aInitVector.getLength() + + rData->m_aSalt.getLength() + + rData->m_aDigest.getLength() + + aMediaType.getLength() * sizeof( sal_Unicode ) ); + sal_Int8 * pHeader = maHeader.getArray(); + ZipFile::StaticFillHeader( rData, rEntry.nSize, aMediaType, pHeader ); + mnHeaderToRead = static_cast < sal_Int16 > ( maHeader.getLength() ); + mnZipSize += mnHeaderToRead; +} + +// allows to read package raw stream +XUnbufferedStream::XUnbufferedStream( + rtl::Reference< comphelper::RefCountedMutex > aMutexHolder, + const Reference < XInputStream >& xRawStream, + const ::rtl::Reference< EncryptionData >& rData ) +: maMutexHolder(std::move( aMutexHolder )) +, mxZipStream ( xRawStream ) +, mxZipSeek ( xRawStream, UNO_QUERY ) +, mnBlockSize( 1 ) +, maInflater ( true ) +, mbRawStream ( false ) +, mbWrappedRaw ( false ) +, mnHeaderToRead ( 0 ) +, mnZipCurrent ( 0 ) +, mnZipEnd ( 0 ) +, mnZipSize ( 0 ) +, mnMyCurrent ( 0 ) +, mbCheckCRC( false ) +{ + // for this scenario maEntry is not set !!! + OSL_ENSURE( mxZipSeek.is(), "The stream must be seekable!" ); + + // skip raw header, it must be already parsed to rData + mnZipCurrent = n_ConstHeaderSize + rData->m_aInitVector.getLength() + + rData->m_aSalt.getLength() + rData->m_aDigest.getLength(); + + try { + if ( mxZipSeek.is() ) + mnZipSize = mxZipSeek->getLength(); + } catch( const Exception& ) + { + // in case of problem the size will stay set to 0 + TOOLS_WARN_EXCEPTION("package", "ignoring"); + } + + mnZipEnd = mnZipCurrent + mnZipSize; + + // the raw data will not be decrypted, no need for the cipher + // m_xCipherContext = ZipFile::StaticGetCipher( xContext, rData, false ); +} + +XUnbufferedStream::~XUnbufferedStream() +{ +} + +sal_Int32 SAL_CALL XUnbufferedStream::readBytes( Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) +{ + ::osl::MutexGuard aGuard( maMutexHolder->GetMutex() ); + + sal_Int32 nRequestedBytes = nBytesToRead; + OSL_ENSURE( !mnHeaderToRead || mbWrappedRaw, "Only encrypted raw stream can be provided with header!" ); + if ( mnMyCurrent + nRequestedBytes > mnZipSize + maHeader.getLength() ) + nRequestedBytes = static_cast < sal_Int32 > ( mnZipSize + maHeader.getLength() - mnMyCurrent ); + + sal_Int32 nTotal = 0; + aData.realloc ( nRequestedBytes ); + if ( nRequestedBytes ) + { + sal_Int32 nRead = 0; + sal_Int32 nLastRead = 0; + if ( mbRawStream ) + { + sal_Int64 nDiff = mnZipEnd - mnZipCurrent; + + if ( mbWrappedRaw && mnHeaderToRead ) + { + sal_Int16 nHeadRead = static_cast< sal_Int16 >(( nRequestedBytes > mnHeaderToRead ? + mnHeaderToRead : nRequestedBytes )); + memcpy ( aData.getArray(), maHeader.getConstArray() + maHeader.getLength() - mnHeaderToRead, nHeadRead ); + mnHeaderToRead = mnHeaderToRead - nHeadRead; + + if ( nHeadRead < nRequestedBytes ) + { + sal_Int32 nToRead = nRequestedBytes - nHeadRead; + nToRead = ( nDiff < nToRead ) ? sal::static_int_cast< sal_Int32 >( nDiff ) : nToRead; + + Sequence< sal_Int8 > aPureData( nToRead ); + mxZipSeek->seek ( mnZipCurrent ); + nRead = mxZipStream->readBytes ( aPureData, nToRead ); + mnZipCurrent += nRead; + + aPureData.realloc( nRead ); + if ( mbCheckCRC ) + maCRC.update( aPureData ); + + aData.realloc( nHeadRead + nRead ); + + const sal_Int8* pPureBuffer = aPureData.getConstArray(); + sal_Int8* pBuffer = aData.getArray(); + for ( sal_Int32 nInd = 0; nInd < nRead; nInd++ ) + pBuffer[ nHeadRead + nInd ] = pPureBuffer[ nInd ]; + } + + nRead += nHeadRead; + } + else + { + mxZipSeek->seek ( mnZipCurrent ); + + nRead = mxZipStream->readBytes ( + aData, + std::min<sal_Int64>(nDiff, nRequestedBytes) ); + + mnZipCurrent += nRead; + + aData.realloc( nRead ); + if ( mbWrappedRaw && mbCheckCRC ) + maCRC.update( aData ); + } + } + else + { + for (;;) + { + nLastRead = maInflater.doInflateSegment( aData, nRead, aData.getLength() - nRead ); + if ( 0 != nLastRead && ( nRead + nLastRead == nRequestedBytes || mnZipCurrent >= mnZipEnd ) ) + break; + nRead += nLastRead; + if ( nRead > nRequestedBytes ) + throw RuntimeException( + "Should not be possible to read more than requested!" ); + + if ( maInflater.finished() || maInflater.getLastInflateError() ) + throw ZipIOException("The stream seems to be broken!" ); + + if ( maInflater.needsDictionary() ) + throw ZipIOException("Dictionaries are not supported!" ); + + sal_Int32 nDiff = static_cast< sal_Int32 >( mnZipEnd - mnZipCurrent ); + if ( nDiff <= 0 ) + { + throw ZipIOException("The stream seems to be broken!" ); + } + + mxZipSeek->seek ( mnZipCurrent ); + + sal_Int32 nToRead = std::max( nRequestedBytes, static_cast< sal_Int32 >( 8192 ) ); + if ( mnBlockSize > 1 ) + nToRead = nToRead + mnBlockSize - nToRead % mnBlockSize; + nToRead = std::min( nDiff, nToRead ); + + sal_Int32 nZipRead = mxZipStream->readBytes( maCompBuffer, nToRead ); + if ( nZipRead < nToRead ) + throw ZipIOException("No expected data!" ); + + mnZipCurrent += nZipRead; + // maCompBuffer now has the data, check if we need to decrypt + // before passing to the Inflater + if ( m_xCipherContext.is() ) + { + if ( mbCheckCRC ) + maCRC.update( maCompBuffer ); + + maCompBuffer = m_xCipherContext->convertWithCipherContext( maCompBuffer ); + if ( mnZipCurrent == mnZipEnd ) + { + // this should throw if AEAD is in use and the tag fails to validate + uno::Sequence< sal_Int8 > aSuffix = m_xCipherContext->finalizeCipherContextAndDispose(); + if ( aSuffix.hasElements() ) + { + sal_Int32 nOldLen = maCompBuffer.getLength(); + maCompBuffer.realloc( nOldLen + aSuffix.getLength() ); + memcpy( maCompBuffer.getArray() + nOldLen, aSuffix.getConstArray(), aSuffix.getLength() ); + } + } + } + maInflater.setInput ( maCompBuffer ); + + } + } + + mnMyCurrent += nRead + nLastRead; + nTotal = nRead + nLastRead; + if ( nTotal < nRequestedBytes) + aData.realloc ( nTotal ); + + if ( mbCheckCRC && ( !mbRawStream || mbWrappedRaw ) ) + { + if ( !m_xCipherContext.is() && !mbWrappedRaw ) + maCRC.update( aData ); + + if ( mnZipSize + maHeader.getLength() == mnMyCurrent && maCRC.getValue() != maEntry.nCrc ) + throw ZipIOException("The stream seems to be broken!" ); + } + } + + return nTotal; +} + +sal_Int32 SAL_CALL XUnbufferedStream::readSomeBytes( Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) +{ + return readBytes ( aData, nMaxBytesToRead ); +} +void SAL_CALL XUnbufferedStream::skipBytes( sal_Int32 nBytesToSkip ) +{ + if ( nBytesToSkip ) + { + Sequence < sal_Int8 > aSequence ( nBytesToSkip ); + readBytes ( aSequence, nBytesToSkip ); + } +} + +sal_Int32 SAL_CALL XUnbufferedStream::available( ) +{ + //available size must include the prepended header in case of wrapped raw stream + return static_cast< sal_Int32 > ( std::min< sal_Int64 >( SAL_MAX_INT32, (mnZipSize + mnHeaderToRead - mnMyCurrent) ) ); +} + +void SAL_CALL XUnbufferedStream::closeInput( ) +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/XUnbufferedStream.hxx b/package/source/zipapi/XUnbufferedStream.hxx new file mode 100644 index 0000000000..af57706386 --- /dev/null +++ b/package/source/zipapi/XUnbufferedStream.hxx @@ -0,0 +1,93 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_PACKAGE_SOURCE_ZIPAPI_XUNBUFFEREDSTREAM_HXX +#define INCLUDED_PACKAGE_SOURCE_ZIPAPI_XUNBUFFEREDSTREAM_HXX + +#include <com/sun/star/io/XSeekable.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/xml/crypto/XCipherContext.hpp> + +#include <comphelper/refcountedmutex.hxx> +#include <cppuhelper/implbase.hxx> +#include <rtl/ref.hxx> +#include <package/Inflater.hxx> +#include <ZipEntry.hxx> +#include <CRC32.hxx> + +namespace com::sun::star::uno { + class XComponentContext; +} + +#define UNBUFF_STREAM_DATA 0 +#define UNBUFF_STREAM_RAW 1 +#define UNBUFF_STREAM_WRAPPEDRAW 2 + +class EncryptionData; +class XUnbufferedStream final : public cppu::WeakImplHelper +< + css::io::XInputStream +> +{ + rtl::Reference<comphelper::RefCountedMutex> maMutexHolder; + + css::uno::Reference < css::io::XInputStream > mxZipStream; + css::uno::Reference < css::io::XSeekable > mxZipSeek; + css::uno::Sequence < sal_Int8 > maCompBuffer, maHeader; + ZipEntry maEntry; + sal_Int32 mnBlockSize; + css::uno::Reference< css::xml::crypto::XCipherContext > m_xCipherContext; + ZipUtils::Inflater maInflater; + bool mbRawStream, mbWrappedRaw; + sal_Int16 mnHeaderToRead; + sal_Int64 mnZipCurrent, mnZipEnd, mnZipSize, mnMyCurrent; + CRC32 maCRC; + bool mbCheckCRC; + +public: + XUnbufferedStream( + const css::uno::Reference< css::uno::XComponentContext >& xContext, + rtl::Reference<comphelper::RefCountedMutex> aMutexHolder, + ZipEntry const & rEntry, + css::uno::Reference < css::io::XInputStream > const & xNewZipStream, + const ::rtl::Reference< EncryptionData >& rData, + sal_Int8 nStreamMode, + bool bIsEncrypted, + const OUString& aMediaType, + bool bRecoveryMode ); + + // allows to read package raw stream + XUnbufferedStream( + rtl::Reference<comphelper::RefCountedMutex> aMutexHolder, + const css::uno::Reference < css::io::XInputStream >& xRawStream, + const ::rtl::Reference< EncryptionData >& rData ); + + sal_Int64 getSize() const { return mnZipSize; } + + virtual ~XUnbufferedStream() override; + + // XInputStream + virtual sal_Int32 SAL_CALL readBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) override; + virtual sal_Int32 SAL_CALL readSomeBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) override; + virtual void SAL_CALL skipBytes( sal_Int32 nBytesToSkip ) override; + virtual sal_Int32 SAL_CALL available( ) override; + virtual void SAL_CALL closeInput( ) override; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/ZipEnumeration.cxx b/package/source/zipapi/ZipEnumeration.cxx new file mode 100644 index 0000000000..0c036882f0 --- /dev/null +++ b/package/source/zipapi/ZipEnumeration.cxx @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <ZipEnumeration.hxx> + +/** Provides an Enumeration over the contents of a Zip file */ + +ZipEnumeration::ZipEnumeration(EntryHash& rNewEntryHash) + : rEntryHash(rNewEntryHash) + , aIterator(rEntryHash.begin()) +{ +} +bool ZipEnumeration::hasMoreElements() { return (aIterator != rEntryHash.end()); } + +const ZipEntry* ZipEnumeration::nextElement() +{ + if (aIterator != rEntryHash.end()) + return &((*aIterator++).second); + else + return nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/ZipFile.cxx b/package/source/zipapi/ZipFile.cxx new file mode 100644 index 0000000000..474b73ff53 --- /dev/null +++ b/package/source/zipapi/ZipFile.cxx @@ -0,0 +1,1474 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/io/BufferSizeExceededException.hpp> +#include <com/sun/star/io/NotConnectedException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/packages/NoEncryptionException.hpp> +#include <com/sun/star/packages/WrongPasswordException.hpp> +#include <com/sun/star/packages/zip/ZipConstants.hpp> +#include <com/sun/star/packages/zip/ZipException.hpp> +#include <com/sun/star/packages/zip/ZipIOException.hpp> +#include <com/sun/star/xml/crypto/XCipherContext.hpp> +#include <com/sun/star/xml/crypto/XDigestContext.hpp> +#include <com/sun/star/xml/crypto/CipherID.hpp> +#include <com/sun/star/xml/crypto/DigestID.hpp> +#include <com/sun/star/xml/crypto/NSSInitializer.hpp> + +#include <comphelper/bytereader.hxx> +#include <comphelper/storagehelper.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/threadpool.hxx> +#include <rtl/digest.h> +#include <sal/log.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> + +#include <algorithm> +#include <iterator> +#include <utility> +#include <vector> + +#include <argon2.h> + +#include "blowfishcontext.hxx" +#include "sha1context.hxx" +#include <ZipFile.hxx> +#include <ZipEnumeration.hxx> +#include "XUnbufferedStream.hxx" +#include "XBufferedThreadedStream.hxx" +#include <PackageConstants.hxx> +#include <EncryptedDataHeader.hxx> +#include <EncryptionData.hxx> +#include "MemoryByteGrabber.hxx" + +#include <CRC32.hxx> + +using namespace com::sun::star; +using namespace com::sun::star::io; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::packages; +using namespace com::sun::star::packages::zip; +using namespace com::sun::star::packages::zip::ZipConstants; + +using ZipUtils::Inflater; + +#if OSL_DEBUG_LEVEL > 0 +#define THROW_WHERE SAL_WHERE +#else +#define THROW_WHERE "" +#endif + +/** This class is used to read entries from a zip file + */ +ZipFile::ZipFile( rtl::Reference<comphelper::RefCountedMutex> aMutexHolder, + uno::Reference < XInputStream > const &xInput, + uno::Reference < XComponentContext > xContext, + bool bInitialise ) +: m_aMutexHolder(std::move( aMutexHolder )) +, aGrabber( xInput ) +, aInflater( true ) +, xStream(xInput) +, m_xContext (std::move( xContext )) +, bRecoveryMode( false ) +{ + if (bInitialise && readCEN() == -1 ) + { + aEntries.clear(); + throw ZipException( "stream data looks to be broken" ); + } +} + +ZipFile::ZipFile( rtl::Reference< comphelper::RefCountedMutex > aMutexHolder, + uno::Reference < XInputStream > const &xInput, + uno::Reference < XComponentContext > xContext, + bool bInitialise, bool bForceRecovery) +: m_aMutexHolder(std::move( aMutexHolder )) +, aGrabber( xInput ) +, aInflater( true ) +, xStream(xInput) +, m_xContext (std::move( xContext )) +, bRecoveryMode( bForceRecovery ) +{ + if (bInitialise) + { + if ( bForceRecovery ) + { + recover(); + } + else if ( readCEN() == -1 ) + { + aEntries.clear(); + throw ZipException("stream data looks to be broken" ); + } + } +} + +ZipFile::~ZipFile() +{ + aEntries.clear(); +} + +void ZipFile::setInputStream ( const uno::Reference < XInputStream >& xNewStream ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + xStream = xNewStream; + aGrabber.setInputStream ( xStream ); +} + +uno::Reference< xml::crypto::XDigestContext > ZipFile::StaticGetDigestContextForChecksum( const uno::Reference< uno::XComponentContext >& xArgContext, const ::rtl::Reference< EncryptionData >& xEncryptionData ) +{ + assert(xEncryptionData->m_oCheckAlg); // callers checked it already + + uno::Reference< xml::crypto::XDigestContext > xDigestContext; + if (*xEncryptionData->m_oCheckAlg == xml::crypto::DigestID::SHA256_1K) + { + uno::Reference< uno::XComponentContext > xContext = xArgContext; + if ( !xContext.is() ) + xContext = comphelper::getProcessComponentContext(); + + uno::Reference< xml::crypto::XNSSInitializer > xDigestContextSupplier = xml::crypto::NSSInitializer::create( xContext ); + + xDigestContext.set(xDigestContextSupplier->getDigestContext( + *xEncryptionData->m_oCheckAlg, uno::Sequence<beans::NamedValue>()), + uno::UNO_SET_THROW); + } + else if (*xEncryptionData->m_oCheckAlg == xml::crypto::DigestID::SHA1_1K) + { + if (xEncryptionData->m_bTryWrongSHA1) + { + xDigestContext.set(StarOfficeSHA1DigestContext::Create(), uno::UNO_SET_THROW); + } + else + { + xDigestContext.set(CorrectSHA1DigestContext::Create(), uno::UNO_SET_THROW); + } + } + + return xDigestContext; +} + +uno::Reference< xml::crypto::XCipherContext > ZipFile::StaticGetCipher( const uno::Reference< uno::XComponentContext >& xArgContext, const ::rtl::Reference< EncryptionData >& xEncryptionData, bool bEncrypt ) +{ + uno::Reference< xml::crypto::XCipherContext > xResult; + + if (xEncryptionData->m_nDerivedKeySize < 0) + { + throw ZipIOException("Invalid derived key length!" ); + } + + uno::Sequence< sal_Int8 > aDerivedKey( xEncryptionData->m_nDerivedKeySize ); + if (!xEncryptionData->m_oPBKDFIterationCount && !xEncryptionData->m_oArgon2Args + && xEncryptionData->m_nDerivedKeySize == xEncryptionData->m_aKey.getLength()) + { + // gpg4libre: no need to derive key, m_aKey is already + // usable as symmetric session key + aDerivedKey = xEncryptionData->m_aKey; + } + else if (xEncryptionData->m_oArgon2Args) + { + // apparently multiple lanes cannot be processed in parallel (the + // implementation will clamp), but it doesn't make sense to have more + // threads than CPUs + uint32_t const threads(::comphelper::ThreadPool::getPreferredConcurrency()); + // need to use context to set a fixed version + argon2_context context = { + .out = reinterpret_cast<uint8_t *>(aDerivedKey.getArray()), + .outlen = ::sal::static_int_cast<uint32_t>(aDerivedKey.getLength()), + .pwd = reinterpret_cast<uint8_t *>(xEncryptionData->m_aKey.getArray()), + .pwdlen = ::sal::static_int_cast<uint32_t>(xEncryptionData->m_aKey.getLength()), + .salt = reinterpret_cast<uint8_t *>(xEncryptionData->m_aSalt.getArray()), + .saltlen = ::sal::static_int_cast<uint32_t>(xEncryptionData->m_aSalt.getLength()), + .secret = nullptr, .secretlen = 0, + .ad = nullptr, .adlen = 0, + .t_cost = ::sal::static_int_cast<uint32_t>(::std::get<0>(*xEncryptionData->m_oArgon2Args)), + .m_cost = ::sal::static_int_cast<uint32_t>(::std::get<1>(*xEncryptionData->m_oArgon2Args)), + .lanes = ::sal::static_int_cast<uint32_t>(::std::get<2>(*xEncryptionData->m_oArgon2Args)), + .threads = threads, + .version = ARGON2_VERSION_13, + .allocate_cbk = nullptr, .free_cbk = nullptr, + .flags = ARGON2_DEFAULT_FLAGS + }; + // libargon2 validates all the arguments so don't need to do it here + int const rc = argon2id_ctx(&context); + if (rc != ARGON2_OK) + { + SAL_WARN("package", "argon2id_ctx failed to derive key: " << argon2_error_message(rc)); + throw ZipIOException("argon2id_ctx failed to derive key"); + } + } + else if ( rtl_Digest_E_None != rtl_digest_PBKDF2( reinterpret_cast< sal_uInt8* >( aDerivedKey.getArray() ), + aDerivedKey.getLength(), + reinterpret_cast< const sal_uInt8 * > (xEncryptionData->m_aKey.getConstArray() ), + xEncryptionData->m_aKey.getLength(), + reinterpret_cast< const sal_uInt8 * > ( xEncryptionData->m_aSalt.getConstArray() ), + xEncryptionData->m_aSalt.getLength(), + *xEncryptionData->m_oPBKDFIterationCount) ) + { + throw ZipIOException("Can not create derived key!" ); + } + + if (xEncryptionData->m_nEncAlg == xml::crypto::CipherID::AES_CBC_W3C_PADDING + || xEncryptionData->m_nEncAlg == xml::crypto::CipherID::AES_GCM_W3C) + { + uno::Reference< uno::XComponentContext > xContext = xArgContext; + if ( !xContext.is() ) + xContext = comphelper::getProcessComponentContext(); + + uno::Reference< xml::crypto::XNSSInitializer > xCipherContextSupplier = xml::crypto::NSSInitializer::create( xContext ); + + xResult = xCipherContextSupplier->getCipherContext( xEncryptionData->m_nEncAlg, aDerivedKey, xEncryptionData->m_aInitVector, bEncrypt, uno::Sequence< beans::NamedValue >() ); + } + else if ( xEncryptionData->m_nEncAlg == xml::crypto::CipherID::BLOWFISH_CFB_8 ) + { + xResult = BlowfishCFB8CipherContext::Create( aDerivedKey, xEncryptionData->m_aInitVector, bEncrypt ); + } + else + { + throw ZipIOException("Unknown cipher algorithm is requested!" ); + } + + return xResult; +} + +void ZipFile::StaticFillHeader( const ::rtl::Reference< EncryptionData >& rData, + sal_Int64 nSize, + const OUString& aMediaType, + sal_Int8 * & pHeader ) +{ + // I think it's safe to restrict vector and salt length to 2 bytes ! + sal_Int16 nIVLength = static_cast < sal_Int16 > ( rData->m_aInitVector.getLength() ); + sal_Int16 nSaltLength = static_cast < sal_Int16 > ( rData->m_aSalt.getLength() ); + sal_Int16 nDigestLength = static_cast < sal_Int16 > ( rData->m_aDigest.getLength() ); + sal_Int16 nMediaTypeLength = static_cast < sal_Int16 > ( aMediaType.getLength() * sizeof( sal_Unicode ) ); + + // First the header + *(pHeader++) = ( n_ConstHeader >> 0 ) & 0xFF; + *(pHeader++) = ( n_ConstHeader >> 8 ) & 0xFF; + *(pHeader++) = ( n_ConstHeader >> 16 ) & 0xFF; + *(pHeader++) = ( n_ConstHeader >> 24 ) & 0xFF; + + // Then the version + *(pHeader++) = ( n_ConstCurrentVersion >> 0 ) & 0xFF; + *(pHeader++) = ( n_ConstCurrentVersion >> 8 ) & 0xFF; + + // Then the iteration Count + sal_Int32 const nIterationCount = rData->m_oPBKDFIterationCount ? *rData->m_oPBKDFIterationCount : 0; + *(pHeader++) = static_cast< sal_Int8 >(( nIterationCount >> 0 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nIterationCount >> 8 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nIterationCount >> 16 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nIterationCount >> 24 ) & 0xFF); + + sal_Int32 const nArgon2t = rData->m_oArgon2Args ? ::std::get<0>(*rData->m_oArgon2Args) : 0; + *(pHeader++) = static_cast<sal_Int8>((nArgon2t >> 0) & 0xFF); + *(pHeader++) = static_cast<sal_Int8>((nArgon2t >> 8) & 0xFF); + *(pHeader++) = static_cast<sal_Int8>((nArgon2t >> 16) & 0xFF); + *(pHeader++) = static_cast<sal_Int8>((nArgon2t >> 24) & 0xFF); + + sal_Int32 const nArgon2m = rData->m_oArgon2Args ? ::std::get<1>(*rData->m_oArgon2Args) : 0; + *(pHeader++) = static_cast<sal_Int8>((nArgon2m >> 0) & 0xFF); + *(pHeader++) = static_cast<sal_Int8>((nArgon2m >> 8) & 0xFF); + *(pHeader++) = static_cast<sal_Int8>((nArgon2m >> 16) & 0xFF); + *(pHeader++) = static_cast<sal_Int8>((nArgon2m >> 24) & 0xFF); + + sal_Int32 const nArgon2p = rData->m_oArgon2Args ? ::std::get<2>(*rData->m_oArgon2Args) : 0; + *(pHeader++) = static_cast<sal_Int8>((nArgon2p >> 0) & 0xFF); + *(pHeader++) = static_cast<sal_Int8>((nArgon2p >> 8) & 0xFF); + *(pHeader++) = static_cast<sal_Int8>((nArgon2p >> 16) & 0xFF); + *(pHeader++) = static_cast<sal_Int8>((nArgon2p >> 24) & 0xFF); + + // FIXME64: need to handle larger sizes + // Then the size: + *(pHeader++) = static_cast< sal_Int8 >(( nSize >> 0 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nSize >> 8 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nSize >> 16 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nSize >> 24 ) & 0xFF); + + // Then the encryption algorithm + sal_Int32 nEncAlgID = rData->m_nEncAlg; + *(pHeader++) = static_cast< sal_Int8 >(( nEncAlgID >> 0 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nEncAlgID >> 8 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nEncAlgID >> 16 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nEncAlgID >> 24 ) & 0xFF); + + // Then the checksum algorithm + sal_Int32 nChecksumAlgID = rData->m_oCheckAlg ? *rData->m_oCheckAlg : 0; + *(pHeader++) = static_cast< sal_Int8 >(( nChecksumAlgID >> 0 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nChecksumAlgID >> 8 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nChecksumAlgID >> 16 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nChecksumAlgID >> 24 ) & 0xFF); + + // Then the derived key size + sal_Int32 nDerivedKeySize = rData->m_nDerivedKeySize; + *(pHeader++) = static_cast< sal_Int8 >(( nDerivedKeySize >> 0 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nDerivedKeySize >> 8 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nDerivedKeySize >> 16 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nDerivedKeySize >> 24 ) & 0xFF); + + // Then the start key generation algorithm + sal_Int32 nKeyAlgID = rData->m_nStartKeyGenID; + *(pHeader++) = static_cast< sal_Int8 >(( nKeyAlgID >> 0 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nKeyAlgID >> 8 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nKeyAlgID >> 16 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nKeyAlgID >> 24 ) & 0xFF); + + // Then the salt length + *(pHeader++) = static_cast< sal_Int8 >(( nSaltLength >> 0 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nSaltLength >> 8 ) & 0xFF); + + // Then the IV length + *(pHeader++) = static_cast< sal_Int8 >(( nIVLength >> 0 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nIVLength >> 8 ) & 0xFF); + + // Then the digest length + *(pHeader++) = static_cast< sal_Int8 >(( nDigestLength >> 0 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nDigestLength >> 8 ) & 0xFF); + + // Then the mediatype length + *(pHeader++) = static_cast< sal_Int8 >(( nMediaTypeLength >> 0 ) & 0xFF); + *(pHeader++) = static_cast< sal_Int8 >(( nMediaTypeLength >> 8 ) & 0xFF); + + // Then the salt content + memcpy ( pHeader, rData->m_aSalt.getConstArray(), nSaltLength ); + pHeader += nSaltLength; + + // Then the IV content + memcpy ( pHeader, rData->m_aInitVector.getConstArray(), nIVLength ); + pHeader += nIVLength; + + // Then the digest content + memcpy ( pHeader, rData->m_aDigest.getConstArray(), nDigestLength ); + pHeader += nDigestLength; + + // Then the mediatype itself + memcpy ( pHeader, aMediaType.getStr(), nMediaTypeLength ); + pHeader += nMediaTypeLength; +} + +bool ZipFile::StaticFillData ( ::rtl::Reference< BaseEncryptionData > const & rData, + sal_Int32 &rEncAlg, + sal_Int32 &rChecksumAlg, + sal_Int32 &rDerivedKeySize, + sal_Int32 &rStartKeyGenID, + sal_Int32 &rSize, + OUString& aMediaType, + const uno::Reference< XInputStream >& rStream ) +{ + bool bOk = false; + const sal_Int32 nHeaderSize = n_ConstHeaderSize - 4; + Sequence < sal_Int8 > aBuffer ( nHeaderSize ); + if ( nHeaderSize == rStream->readBytes ( aBuffer, nHeaderSize ) ) + { + sal_Int16 nPos = 0; + sal_Int8 *pBuffer = aBuffer.getArray(); + sal_Int16 nVersion = pBuffer[nPos++] & 0xFF; + nVersion |= ( pBuffer[nPos++] & 0xFF ) << 8; + if ( nVersion == n_ConstCurrentVersion ) + { + sal_Int32 nCount = pBuffer[nPos++] & 0xFF; + nCount |= ( pBuffer[nPos++] & 0xFF ) << 8; + nCount |= ( pBuffer[nPos++] & 0xFF ) << 16; + nCount |= ( pBuffer[nPos++] & 0xFF ) << 24; + if (nCount != 0) + { + rData->m_oPBKDFIterationCount.emplace(nCount); + } + else + { + rData->m_oPBKDFIterationCount.reset(); + } + + sal_Int32 nArgon2t = pBuffer[nPos++] & 0xFF; + nArgon2t |= ( pBuffer[nPos++] & 0xFF ) << 8; + nArgon2t |= ( pBuffer[nPos++] & 0xFF ) << 16; + nArgon2t |= ( pBuffer[nPos++] & 0xFF ) << 24; + + sal_Int32 nArgon2m = pBuffer[nPos++] & 0xFF; + nArgon2m |= ( pBuffer[nPos++] & 0xFF ) << 8; + nArgon2m |= ( pBuffer[nPos++] & 0xFF ) << 16; + nArgon2m |= ( pBuffer[nPos++] & 0xFF ) << 24; + + sal_Int32 nArgon2p = pBuffer[nPos++] & 0xFF; + nArgon2p |= ( pBuffer[nPos++] & 0xFF ) << 8; + nArgon2p |= ( pBuffer[nPos++] & 0xFF ) << 16; + nArgon2p |= ( pBuffer[nPos++] & 0xFF ) << 24; + + if (nArgon2t != 0 && nArgon2m != 0 && nArgon2p != 0) + { + rData->m_oArgon2Args.emplace(nArgon2t, nArgon2m, nArgon2p); + } + else + { + rData->m_oArgon2Args.reset(); + } + + rSize = pBuffer[nPos++] & 0xFF; + rSize |= ( pBuffer[nPos++] & 0xFF ) << 8; + rSize |= ( pBuffer[nPos++] & 0xFF ) << 16; + rSize |= ( pBuffer[nPos++] & 0xFF ) << 24; + + rEncAlg = pBuffer[nPos++] & 0xFF; + rEncAlg |= ( pBuffer[nPos++] & 0xFF ) << 8; + rEncAlg |= ( pBuffer[nPos++] & 0xFF ) << 16; + rEncAlg |= ( pBuffer[nPos++] & 0xFF ) << 24; + + rChecksumAlg = pBuffer[nPos++] & 0xFF; + rChecksumAlg |= ( pBuffer[nPos++] & 0xFF ) << 8; + rChecksumAlg |= ( pBuffer[nPos++] & 0xFF ) << 16; + rChecksumAlg |= ( pBuffer[nPos++] & 0xFF ) << 24; + + rDerivedKeySize = pBuffer[nPos++] & 0xFF; + rDerivedKeySize |= ( pBuffer[nPos++] & 0xFF ) << 8; + rDerivedKeySize |= ( pBuffer[nPos++] & 0xFF ) << 16; + rDerivedKeySize |= ( pBuffer[nPos++] & 0xFF ) << 24; + + rStartKeyGenID = pBuffer[nPos++] & 0xFF; + rStartKeyGenID |= ( pBuffer[nPos++] & 0xFF ) << 8; + rStartKeyGenID |= ( pBuffer[nPos++] & 0xFF ) << 16; + rStartKeyGenID |= ( pBuffer[nPos++] & 0xFF ) << 24; + + sal_Int16 nSaltLength = pBuffer[nPos++] & 0xFF; + nSaltLength |= ( pBuffer[nPos++] & 0xFF ) << 8; + sal_Int16 nIVLength = ( pBuffer[nPos++] & 0xFF ); + nIVLength |= ( pBuffer[nPos++] & 0xFF ) << 8; + sal_Int16 nDigestLength = pBuffer[nPos++] & 0xFF; + nDigestLength |= ( pBuffer[nPos++] & 0xFF ) << 8; + + sal_Int16 nMediaTypeLength = pBuffer[nPos++] & 0xFF; + nMediaTypeLength |= ( pBuffer[nPos++] & 0xFF ) << 8; + + if ( nSaltLength == rStream->readBytes ( aBuffer, nSaltLength ) ) + { + rData->m_aSalt.realloc ( nSaltLength ); + memcpy ( rData->m_aSalt.getArray(), aBuffer.getConstArray(), nSaltLength ); + if ( nIVLength == rStream->readBytes ( aBuffer, nIVLength ) ) + { + rData->m_aInitVector.realloc ( nIVLength ); + memcpy ( rData->m_aInitVector.getArray(), aBuffer.getConstArray(), nIVLength ); + if ( nDigestLength == rStream->readBytes ( aBuffer, nDigestLength ) ) + { + rData->m_aDigest.realloc ( nDigestLength ); + memcpy ( rData->m_aDigest.getArray(), aBuffer.getConstArray(), nDigestLength ); + + if ( nMediaTypeLength == rStream->readBytes ( aBuffer, nMediaTypeLength ) ) + { + aMediaType = OUString( reinterpret_cast<sal_Unicode const *>(aBuffer.getConstArray()), + nMediaTypeLength / sizeof( sal_Unicode ) ); + bOk = true; + } + } + } + } + } + } + return bOk; +} + +#if 0 +// for debugging purposes +void CheckSequence( const uno::Sequence< sal_Int8 >& aSequence ) +{ + if ( aSequence.getLength() ) + { + sal_Int32* pPointer = *( (sal_Int32**)&aSequence ); + sal_Int32 nSize = *( pPointer + 1 ); + sal_Int32 nMemSize = *( pPointer - 2 ); + sal_Int32 nUsedMemSize = ( nSize + 4 * sizeof( sal_Int32 ) ); + OSL_ENSURE( nSize == aSequence.getLength() && nUsedMemSize + 7 - ( nUsedMemSize - 1 ) % 8 == nMemSize, "Broken Sequence!" ); + } +} +#endif + +bool ZipFile::StaticHasValidPassword( const uno::Reference< uno::XComponentContext >& rxContext, const Sequence< sal_Int8 > &aReadBuffer, const ::rtl::Reference< EncryptionData > &rData ) +{ + assert(rData->m_nEncAlg != xml::crypto::CipherID::AES_GCM_W3C); // should not be called for AEAD + + if ( !rData.is() || !rData->m_aKey.hasElements() ) + return false; + + bool bRet = false; + + uno::Reference< xml::crypto::XCipherContext > xCipher( StaticGetCipher( rxContext, rData, false ), uno::UNO_SET_THROW ); + + uno::Sequence< sal_Int8 > aDecryptBuffer; + uno::Sequence< sal_Int8 > aDecryptBuffer2; + try + { + aDecryptBuffer = xCipher->convertWithCipherContext( aReadBuffer ); + aDecryptBuffer2 = xCipher->finalizeCipherContextAndDispose(); + } + catch( uno::Exception& ) + { + // decryption with padding will throw the exception in finalizing if the buffer represent only part of the stream + // it is no problem, actually this is why we read 32 additional bytes ( two of maximal possible encryption blocks ) + } + + if ( aDecryptBuffer2.hasElements() ) + { + sal_Int32 nOldLen = aDecryptBuffer.getLength(); + aDecryptBuffer.realloc( nOldLen + aDecryptBuffer2.getLength() ); + memcpy( aDecryptBuffer.getArray() + nOldLen, aDecryptBuffer2.getConstArray(), aDecryptBuffer2.getLength() ); + } + + if ( aDecryptBuffer.getLength() > n_ConstDigestLength ) + aDecryptBuffer.realloc( n_ConstDigestLength ); + + uno::Sequence< sal_Int8 > aDigestSeq; + uno::Reference< xml::crypto::XDigestContext > xDigestContext( StaticGetDigestContextForChecksum( rxContext, rData ), uno::UNO_SET_THROW ); + + xDigestContext->updateDigest( aDecryptBuffer ); + aDigestSeq = xDigestContext->finalizeDigestAndDispose(); + + // If we don't have a digest, then we have to assume that the password is correct + if ( rData->m_aDigest.hasElements() && + ( aDigestSeq.getLength() != rData->m_aDigest.getLength() || + 0 != memcmp ( aDigestSeq.getConstArray(), + rData->m_aDigest.getConstArray(), + aDigestSeq.getLength() ) ) ) + { + // We should probably tell the user that the password they entered was wrong + } + else + bRet = true; + + return bRet; +} + +uno::Reference<io::XInputStream> ZipFile::checkValidPassword( + ZipEntry const& rEntry, ::rtl::Reference<EncryptionData> const& rData, + rtl::Reference<comphelper::RefCountedMutex> const& rMutex) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + if (rData.is() && rData->m_nEncAlg == xml::crypto::CipherID::AES_GCM_W3C) + { + try // the only way to find out: decrypt the whole stream, which will + { // check the tag + uno::Reference<io::XInputStream> const xRet = + createStreamForZipEntry(rMutex, rEntry, rData, UNBUFF_STREAM_DATA, true); + // currently XBufferedStream reads the whole stream in its ctor (to + // verify the tag) - in case this gets changed, explicitly seek here + uno::Reference<io::XSeekable> const xSeek(xRet, uno::UNO_QUERY_THROW); + xSeek->seek(xSeek->getLength()); + xSeek->seek(0); + return xRet; + } + catch (uno::Exception const&) + { + return {}; + } + } + else if (rData.is() && rData->m_aKey.hasElements()) + { + css::uno::Reference < css::io::XSeekable > xSeek(xStream, UNO_QUERY_THROW); + xSeek->seek( rEntry.nOffset ); + sal_Int64 nSize = rEntry.nMethod == DEFLATED ? rEntry.nCompressedSize : rEntry.nSize; + + // Only want to read enough to verify the digest + if ( nSize > n_ConstDigestDecrypt ) + nSize = n_ConstDigestDecrypt; + + Sequence < sal_Int8 > aReadBuffer ( nSize ); + + xStream->readBytes( aReadBuffer, nSize ); + + if (StaticHasValidPassword(m_xContext, aReadBuffer, rData)) + { + return createStreamForZipEntry( + rMutex, rEntry, rData, UNBUFF_STREAM_DATA, true); + } + } + + return {}; +} + +namespace { + +class XBufferedStream : public cppu::WeakImplHelper<css::io::XInputStream, css::io::XSeekable> +{ + std::vector<sal_Int8> maBytes; + size_t mnPos; + + size_t remainingSize() const + { + return maBytes.size() - mnPos; + } + + bool hasBytes() const + { + return mnPos < maBytes.size(); + } + +public: + XBufferedStream( const uno::Reference<XInputStream>& xSrcStream ) : mnPos(0) + { + sal_Int32 nRemaining = xSrcStream->available(); + maBytes.reserve(nRemaining); + + if (auto pByteReader = dynamic_cast< comphelper::ByteReader* >( xSrcStream.get() )) + { + maBytes.resize(nRemaining); + + sal_Int8* pData = maBytes.data(); + while (nRemaining > 0) + { + sal_Int32 nRead = pByteReader->readSomeBytes(pData, nRemaining); + nRemaining -= nRead; + pData += nRead; + } + return; + } + + const sal_Int32 nBufSize = 8192; + uno::Sequence<sal_Int8> aBuf(nBufSize); + while (nRemaining > 0) + { + const sal_Int32 nBytes = xSrcStream->readBytes(aBuf, std::min(nBufSize, nRemaining)); + if (!nBytes) + break; + maBytes.insert(maBytes.end(), aBuf.begin(), aBuf.begin() + nBytes); + nRemaining -= nBytes; + } + } + + virtual sal_Int32 SAL_CALL readBytes( uno::Sequence<sal_Int8>& rData, sal_Int32 nBytesToRead ) override + { + if (!hasBytes()) + return 0; + + sal_Int32 nReadSize = std::min<sal_Int32>(nBytesToRead, remainingSize()); + rData.realloc(nReadSize); + auto pData = rData.getArray(); + std::vector<sal_Int8>::const_iterator it = maBytes.cbegin(); + std::advance(it, mnPos); + for (sal_Int32 i = 0; i < nReadSize; ++i, ++it) + pData[i] = *it; + + mnPos += nReadSize; + + return nReadSize; + } + + virtual sal_Int32 SAL_CALL readSomeBytes( ::css::uno::Sequence<sal_Int8>& rData, sal_Int32 nMaxBytesToRead ) override + { + return readBytes(rData, nMaxBytesToRead); + } + + virtual void SAL_CALL skipBytes( sal_Int32 nBytesToSkip ) override + { + if (!hasBytes()) + return; + + mnPos += nBytesToSkip; + } + + virtual sal_Int32 SAL_CALL available() override + { + if (!hasBytes()) + return 0; + + return remainingSize(); + } + + virtual void SAL_CALL closeInput() override + { + } + // XSeekable + virtual void SAL_CALL seek( sal_Int64 location ) override + { + if ( location < 0 || o3tl::make_unsigned(location) > maBytes.size() ) + throw IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); + mnPos = location; + } + virtual sal_Int64 SAL_CALL getPosition() override + { + return mnPos; + } + virtual sal_Int64 SAL_CALL getLength() override + { + return maBytes.size(); + } +}; + +} + +uno::Reference< XInputStream > ZipFile::createStreamForZipEntry( + const rtl::Reference< comphelper::RefCountedMutex >& aMutexHolder, + ZipEntry const & rEntry, + const ::rtl::Reference< EncryptionData > &rData, + sal_Int8 nStreamMode, + bool bIsEncrypted, + const bool bUseBufferedStream, + const OUString& aMediaType ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + rtl::Reference< XUnbufferedStream > xSrcStream = new XUnbufferedStream( + m_xContext, aMutexHolder, rEntry, xStream, rData, nStreamMode, bIsEncrypted, aMediaType, bRecoveryMode); + + if (!bUseBufferedStream) + return xSrcStream; + + uno::Reference<io::XInputStream> xBufStream; +#ifndef EMSCRIPTEN + static const sal_Int32 nThreadingThreshold = 10000; + + // "encrypted-package" is the only data stream, no point in threading it + if (rEntry.sPath != "encrypted-package" && nThreadingThreshold < xSrcStream->available()) + xBufStream = new XBufferedThreadedStream(xSrcStream, xSrcStream->getSize()); + else +#endif + xBufStream = new XBufferedStream(xSrcStream); + + return xBufStream; +} + +uno::Reference< XInputStream > ZipFile::StaticGetDataFromRawStream( + const rtl::Reference<comphelper::RefCountedMutex>& rMutexHolder, + const uno::Reference<uno::XComponentContext>& rxContext, + const uno::Reference<XInputStream>& xStream, + const ::rtl::Reference<EncryptionData> &rData) +{ + if (!rData.is()) + throw ZipIOException("Encrypted stream without encryption data!" ); + + if (!rData->m_aKey.hasElements()) + throw packages::WrongPasswordException(THROW_WHERE); + + uno::Reference<XSeekable> xSeek(xStream, UNO_QUERY); + if (!xSeek.is()) + throw ZipIOException("The stream must be seekable!"); + + // if we have a digest, then this file is an encrypted one and we should + // check if we can decrypt it or not + SAL_WARN_IF(rData->m_nEncAlg != xml::crypto::CipherID::AES_GCM_W3C && !rData->m_aDigest.hasElements(), + "package", "Can't detect password correctness without digest!"); + if (rData->m_nEncAlg == xml::crypto::CipherID::AES_GCM_W3C) + { + // skip header + xSeek->seek(n_ConstHeaderSize + rData->m_aInitVector.getLength() + + rData->m_aSalt.getLength() + rData->m_aDigest.getLength()); + + try + { // XUnbufferedStream does not support XSeekable so wrap it + ::rtl::Reference<XBufferedStream> const pRet( + new XBufferedStream(new XUnbufferedStream(rMutexHolder, xStream, rData))); + // currently XBufferedStream reads the whole stream in its ctor (to + // verify the tag) - in case this gets changed, explicitly seek here + pRet->seek(pRet->getLength()); + pRet->seek(0); + return pRet; + } + catch (uno::Exception const&) + { + throw packages::WrongPasswordException(THROW_WHERE); + } + } + else if (rData->m_aDigest.hasElements()) + { + sal_Int32 nSize = sal::static_int_cast<sal_Int32>(xSeek->getLength()); + if (nSize > n_ConstDigestLength + 32) + nSize = n_ConstDigestLength + 32; + + // skip header + xSeek->seek(n_ConstHeaderSize + rData->m_aInitVector.getLength() + + rData->m_aSalt.getLength() + rData->m_aDigest.getLength()); + + // Only want to read enough to verify the digest + Sequence<sal_Int8> aReadBuffer(nSize); + + xStream->readBytes(aReadBuffer, nSize); + + if (!StaticHasValidPassword(rxContext, aReadBuffer, rData)) + throw packages::WrongPasswordException(THROW_WHERE); + } + + return new XUnbufferedStream(rMutexHolder, xStream, rData); +} + +ZipEnumeration ZipFile::entries() +{ + return aEntries; +} + +uno::Reference< XInputStream > ZipFile::getInputStream( ZipEntry& rEntry, + const ::rtl::Reference< EncryptionData > &rData, + bool bIsEncrypted, + const rtl::Reference<comphelper::RefCountedMutex>& aMutexHolder ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + if ( rEntry.nOffset <= 0 ) + readLOC( rEntry ); + + // We want to return a rawStream if we either don't have a key or if the + // key is wrong + + bool bNeedRawStream = rEntry.nMethod == STORED; + + if (bIsEncrypted && rData.is()) + { + uno::Reference<XInputStream> const xRet(checkValidPassword(rEntry, rData, aMutexHolder)); + if (xRet.is()) + { + return xRet; + } + bNeedRawStream = true; + } + + return createStreamForZipEntry ( aMutexHolder, + rEntry, + rData, + bNeedRawStream ? UNBUFF_STREAM_RAW : UNBUFF_STREAM_DATA, + bIsEncrypted ); +} + +uno::Reference< XInputStream > ZipFile::getDataStream( ZipEntry& rEntry, + const ::rtl::Reference< EncryptionData > &rData, + bool bIsEncrypted, + const rtl::Reference<comphelper::RefCountedMutex>& aMutexHolder ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + if ( rEntry.nOffset <= 0 ) + readLOC( rEntry ); + + // An exception must be thrown in case stream is encrypted and + // there is no key or the key is wrong + bool bNeedRawStream = false; + if ( bIsEncrypted ) + { + // in case no digest is provided there is no way + // to detect password correctness + if ( !rData.is() ) + throw ZipException("Encrypted stream without encryption data!" ); + + // if we have a digest, then this file is an encrypted one and we should + // check if we can decrypt it or not + SAL_WARN_IF(rData->m_nEncAlg != xml::crypto::CipherID::AES_GCM_W3C && !rData->m_aDigest.hasElements(), + "package", "Can't detect password correctness without digest!"); + uno::Reference<XInputStream> const xRet(checkValidPassword(rEntry, rData, aMutexHolder)); + if (!xRet.is()) + { + throw packages::WrongPasswordException(THROW_WHERE); + } + return xRet; + } + else + bNeedRawStream = ( rEntry.nMethod == STORED ); + + return createStreamForZipEntry ( aMutexHolder, + rEntry, + rData, + bNeedRawStream ? UNBUFF_STREAM_RAW : UNBUFF_STREAM_DATA, + bIsEncrypted ); +} + +uno::Reference< XInputStream > ZipFile::getRawData( ZipEntry& rEntry, + const ::rtl::Reference< EncryptionData >& rData, + bool bIsEncrypted, + const rtl::Reference<comphelper::RefCountedMutex>& aMutexHolder, + const bool bUseBufferedStream ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + if ( rEntry.nOffset <= 0 ) + readLOC( rEntry ); + + return createStreamForZipEntry ( aMutexHolder, rEntry, rData, UNBUFF_STREAM_RAW, bIsEncrypted, bUseBufferedStream ); +} + +uno::Reference< XInputStream > ZipFile::getWrappedRawStream( + ZipEntry& rEntry, + const ::rtl::Reference< EncryptionData >& rData, + const OUString& aMediaType, + const rtl::Reference<comphelper::RefCountedMutex>& aMutexHolder ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + if ( !rData.is() ) + throw packages::NoEncryptionException(THROW_WHERE ); + + if ( rEntry.nOffset <= 0 ) + readLOC( rEntry ); + + return createStreamForZipEntry ( aMutexHolder, rEntry, rData, UNBUFF_STREAM_WRAPPEDRAW, true, true, aMediaType ); +} + +void ZipFile::readLOC( ZipEntry &rEntry ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + sal_Int64 nPos = -rEntry.nOffset; + + aGrabber.seek(nPos); + sal_Int32 nTestSig = aGrabber.ReadInt32(); + if (nTestSig != LOCSIG) + throw ZipIOException("Invalid LOC header (bad signature)" ); + + // Ignore all (duplicated) information from the local file header. + // various programs produced "broken" zip files; even LO at some point. + // Just verify the path and calculate the data offset and otherwise + // rely on the central directory info. + + aGrabber.ReadInt16(); //version + aGrabber.ReadInt16(); //flag + aGrabber.ReadInt16(); //how + aGrabber.ReadInt32(); //time + aGrabber.ReadInt32(); //crc + aGrabber.ReadInt32(); //compressed size + aGrabber.ReadInt32(); //size + sal_Int16 nPathLen = aGrabber.ReadInt16(); + sal_Int16 nExtraLen = aGrabber.ReadInt16(); + + if (nPathLen < 0) + { + SAL_WARN("package", "bogus path len of: " << nPathLen); + nPathLen = 0; + } + + rEntry.nOffset = aGrabber.getPosition() + nPathLen + nExtraLen; + + bool bBroken = false; + + try + { + // read always in UTF8, some tools seem not to set UTF8 bit + // coverity[tainted_data] - we've checked negative lens, and up to max short is ok here + uno::Sequence<sal_Int8> aNameBuffer(nPathLen); + sal_Int32 nRead = aGrabber.readBytes(aNameBuffer, nPathLen); + if (nRead < aNameBuffer.getLength()) + aNameBuffer.realloc(nRead); + + OUString sLOCPath( reinterpret_cast<const char *>(aNameBuffer.getConstArray()), + aNameBuffer.getLength(), + RTL_TEXTENCODING_UTF8 ); + + if ( rEntry.nPathLen == -1 ) // the file was created + { + rEntry.nPathLen = nPathLen; + rEntry.sPath = sLOCPath; + } + + bBroken = rEntry.nPathLen != nPathLen + || rEntry.sPath != sLOCPath; + } + catch(...) + { + bBroken = true; + } + + if ( bBroken && !bRecoveryMode ) + throw ZipIOException("The stream seems to be broken!" ); +} + +sal_Int32 ZipFile::findEND() +{ + // this method is called in constructor only, no need for mutex + sal_Int32 nPos, nEnd; + Sequence < sal_Int8 > aBuffer; + try + { + sal_Int32 nLength = static_cast <sal_Int32 > (aGrabber.getLength()); + if (nLength < ENDHDR) + return -1; + nPos = nLength - ENDHDR - ZIP_MAXNAMELEN; + nEnd = nPos >= 0 ? nPos : 0 ; + + aGrabber.seek( nEnd ); + + auto nSize = nLength - nEnd; + if (nSize != aGrabber.readBytes(aBuffer, nSize)) + throw ZipException("Zip END signature not found!" ); + + const sal_Int8 *pBuffer = aBuffer.getConstArray(); + + nPos = nSize - ENDHDR; + while ( nPos >= 0 ) + { + if (pBuffer[nPos] == 'P' && pBuffer[nPos+1] == 'K' && pBuffer[nPos+2] == 5 && pBuffer[nPos+3] == 6 ) + return nPos + nEnd; + nPos--; + } + } + catch ( IllegalArgumentException& ) + { + throw ZipException("Zip END signature not found!" ); + } + catch ( NotConnectedException& ) + { + throw ZipException("Zip END signature not found!" ); + } + catch ( BufferSizeExceededException& ) + { + throw ZipException("Zip END signature not found!" ); + } + throw ZipException("Zip END signature not found!" ); +} + +sal_Int32 ZipFile::readCEN() +{ + // this method is called in constructor only, no need for mutex + sal_Int32 nCenPos = -1, nLocPos; + sal_uInt16 nCount; + + try + { + sal_Int32 nEndPos = findEND(); + if (nEndPos == -1) + return -1; + aGrabber.seek(nEndPos + ENDTOT); + sal_uInt16 nTotal = aGrabber.ReadUInt16(); + sal_Int32 nCenLen = aGrabber.ReadInt32(); + sal_Int32 nCenOff = aGrabber.ReadInt32(); + + if ( nTotal * CENHDR > nCenLen ) + throw ZipException("invalid END header (bad entry count)" ); + + if ( nTotal > ZIP_MAXENTRIES ) + throw ZipException("too many entries in ZIP File" ); + + if ( nCenLen < 0 || nCenLen > nEndPos ) + throw ZipException("Invalid END header (bad central directory size)" ); + + nCenPos = nEndPos - nCenLen; + + if ( nCenOff < 0 || nCenOff > nCenPos ) + throw ZipException("Invalid END header (bad central directory size)" ); + + nLocPos = nCenPos - nCenOff; + aGrabber.seek( nCenPos ); + Sequence < sal_Int8 > aCENBuffer ( nCenLen ); + sal_Int64 nRead = aGrabber.readBytes ( aCENBuffer, nCenLen ); + if ( static_cast < sal_Int64 > ( nCenLen ) != nRead ) + throw ZipException ("Error reading CEN into memory buffer!" ); + + MemoryByteGrabber aMemGrabber(aCENBuffer); + + ZipEntry aEntry; + sal_Int16 nCommentLen; + + aEntries.reserve(nTotal); + for (nCount = 0 ; nCount < nTotal; nCount++) + { + sal_Int32 nTestSig = aMemGrabber.ReadInt32(); + if ( nTestSig != CENSIG ) + throw ZipException("Invalid CEN header (bad signature)" ); + + aMemGrabber.skipBytes ( 2 ); + aEntry.nVersion = aMemGrabber.ReadInt16(); + aEntry.nFlag = aMemGrabber.ReadInt16(); + + if ( ( aEntry.nFlag & 1 ) == 1 ) + throw ZipException("Invalid CEN header (encrypted entry)" ); + + aEntry.nMethod = aMemGrabber.ReadInt16(); + + if ( aEntry.nMethod != STORED && aEntry.nMethod != DEFLATED) + throw ZipException("Invalid CEN header (bad compression method)" ); + + aEntry.nTime = aMemGrabber.ReadInt32(); + aEntry.nCrc = aMemGrabber.ReadInt32(); + + sal_uInt64 nCompressedSize = aMemGrabber.ReadUInt32(); + sal_uInt64 nSize = aMemGrabber.ReadUInt32(); + aEntry.nPathLen = aMemGrabber.ReadInt16(); + aEntry.nExtraLen = aMemGrabber.ReadInt16(); + nCommentLen = aMemGrabber.ReadInt16(); + aMemGrabber.skipBytes ( 8 ); + sal_uInt64 nOffset = aMemGrabber.ReadUInt32(); + + if ( aEntry.nPathLen < 0 ) + throw ZipException("unexpected name length" ); + + if ( nCommentLen < 0 ) + throw ZipException("unexpected comment length" ); + + if ( aEntry.nExtraLen < 0 ) + throw ZipException("unexpected extra header info length" ); + + if (aEntry.nPathLen > aMemGrabber.remainingSize()) + throw ZipException("name too long"); + + // read always in UTF8, some tools seem not to set UTF8 bit + aEntry.sPath = OUString( reinterpret_cast<char const *>(aMemGrabber.getCurrentPos()), + aEntry.nPathLen, + RTL_TEXTENCODING_UTF8 ); + + if ( !::comphelper::OStorageHelper::IsValidZipEntryFileName( aEntry.sPath, true ) ) + throw ZipException("Zip entry has an invalid name." ); + + aMemGrabber.skipBytes(aEntry.nPathLen); + + if (aEntry.nExtraLen>0) + { + readExtraFields(aMemGrabber, aEntry.nExtraLen, nSize, nCompressedSize, &nOffset); + } + aEntry.nCompressedSize = nCompressedSize; + aEntry.nSize = nSize; + aEntry.nOffset = nOffset; + + if (o3tl::checked_add<sal_Int64>(aEntry.nOffset, nLocPos, aEntry.nOffset)) + throw ZipException("Integer-overflow"); + if (o3tl::checked_multiply<sal_Int64>(aEntry.nOffset, -1, aEntry.nOffset)) + throw ZipException("Integer-overflow"); + + aMemGrabber.skipBytes(nCommentLen); + aEntries[aEntry.sPath] = aEntry; + } + + if (nCount != nTotal) + throw ZipException("Count != Total" ); + } + catch ( IllegalArgumentException & ) + { + // seek can throw this... + nCenPos = -1; // make sure we return -1 to indicate an error + } + return nCenPos; +} + +void ZipFile::readExtraFields(MemoryByteGrabber& aMemGrabber, sal_Int16 nExtraLen, + sal_uInt64& nSize, sal_uInt64& nCompressedSize, sal_uInt64* nOffset) +{ + while (nExtraLen > 0) // Extensible data fields + { + sal_Int16 nheaderID = aMemGrabber.ReadInt16(); + sal_uInt16 dataSize = aMemGrabber.ReadUInt16(); + if (nheaderID == 1) // Load Zip64 Extended Information Extra Field + { + // Datasize should be 28byte but some files have less (maybe non standard?) + nSize = aMemGrabber.ReadUInt64(); + sal_uInt16 nReadSize = 8; + if (dataSize >= 16) + { + nCompressedSize = aMemGrabber.ReadUInt64(); + nReadSize = 16; + if (dataSize >= 24 && nOffset) + { + *nOffset = aMemGrabber.ReadUInt64(); + nReadSize = 24; + // 4 byte should be "Disk Start Number" but we not need it + } + } + if (dataSize > nReadSize) + aMemGrabber.skipBytes(dataSize - nReadSize); + } + else + { + aMemGrabber.skipBytes(dataSize); + } + nExtraLen -= dataSize + 4; + } +} + +void ZipFile::recover() +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + sal_Int64 nLength; + Sequence < sal_Int8 > aBuffer; + + try + { + nLength = aGrabber.getLength(); + if (nLength < ENDHDR) + return; + + aGrabber.seek( 0 ); + + const sal_Int64 nToRead = 32000; + for( sal_Int64 nGenPos = 0; aGrabber.readBytes( aBuffer, nToRead ) && aBuffer.getLength() > 16; ) + { + const sal_Int8 *pBuffer = aBuffer.getConstArray(); + sal_Int32 nBufSize = aBuffer.getLength(); + + sal_Int64 nPos = 0; + // the buffer should contain at least one header, + // or if it is end of the file, at least the postheader with sizes and hash + while( nPos < nBufSize - 30 + || ( nBufSize < nToRead && nPos < nBufSize - 16 ) ) + + { + if ( nPos < nBufSize - 30 && pBuffer[nPos] == 'P' && pBuffer[nPos+1] == 'K' && pBuffer[nPos+2] == 3 && pBuffer[nPos+3] == 4 ) + { + //PK34: Local file header + ZipEntry aEntry; + Sequence<sal_Int8> aTmpBuffer(&(pBuffer[nPos+4]), 26); + MemoryByteGrabber aMemGrabber(aTmpBuffer); + + aEntry.nVersion = aMemGrabber.ReadInt16(); + aEntry.nFlag = aMemGrabber.ReadInt16(); + + if ( ( aEntry.nFlag & 1 ) != 1 ) + { + aEntry.nMethod = aMemGrabber.ReadInt16(); + + if ( aEntry.nMethod == STORED || aEntry.nMethod == DEFLATED ) + { + aEntry.nTime = aMemGrabber.ReadInt32(); + aEntry.nCrc = aMemGrabber.ReadInt32(); + sal_uInt64 nCompressedSize = aMemGrabber.ReadUInt32(); + sal_uInt64 nSize = aMemGrabber.ReadUInt32(); + aEntry.nPathLen = aMemGrabber.ReadInt16(); + aEntry.nExtraLen = aMemGrabber.ReadInt16(); + + sal_Int32 nDescrLength = + ( aEntry.nMethod == DEFLATED && ( aEntry.nFlag & 8 ) ) ? 16 : 0; + + sal_Int64 nBlockHeaderLength = aEntry.nPathLen + aEntry.nExtraLen + 30 + nDescrLength; + if ( aEntry.nPathLen >= 0 && aEntry.nExtraLen >= 0 + && ( nGenPos + nPos + nBlockHeaderLength ) <= nLength ) + { + // read always in UTF8, some tools seem not to set UTF8 bit + if( nPos + 30 + aEntry.nPathLen <= nBufSize ) + aEntry.sPath = OUString ( reinterpret_cast<char const *>(&pBuffer[nPos + 30]), + aEntry.nPathLen, + RTL_TEXTENCODING_UTF8 ); + else + { + Sequence < sal_Int8 > aFileName; + aGrabber.seek( nGenPos + nPos + 30 ); + aGrabber.readBytes( aFileName, aEntry.nPathLen ); + aEntry.sPath = OUString ( reinterpret_cast<const char *>(aFileName.getConstArray()), + aFileName.getLength(), + RTL_TEXTENCODING_UTF8 ); + aEntry.nPathLen = static_cast< sal_Int16 >(aFileName.getLength()); + } + + // read 64bit header + if (aEntry.nExtraLen > 0) + { + Sequence<sal_Int8> aExtraBuffer; + if (nPos + 30 + aEntry.nPathLen + aEntry.nExtraLen <= nBufSize) + { + aExtraBuffer = Sequence<sal_Int8>( + &(pBuffer[nPos + 30 + aEntry.nPathLen]), + aEntry.nExtraLen); + } + else + { + aGrabber.seek(nGenPos + nPos + 30 + aEntry.nExtraLen); + aGrabber.readBytes(aExtraBuffer, aEntry.nExtraLen); + } + MemoryByteGrabber aMemGrabberExtra(aExtraBuffer); + if (aEntry.nExtraLen > 0) + { + readExtraFields(aMemGrabberExtra, aEntry.nExtraLen, nSize, + nCompressedSize, nullptr); + } + } + + sal_Int64 nDataSize = ( aEntry.nMethod == DEFLATED ) ? nCompressedSize : nSize; + sal_Int64 nBlockLength = nDataSize + nBlockHeaderLength; + + if (( nGenPos + nPos + nBlockLength ) <= nLength ) + { + aEntry.nCompressedSize = nCompressedSize; + aEntry.nSize = nSize; + + aEntry.nOffset = nGenPos + nPos + 30 + aEntry.nPathLen + aEntry.nExtraLen; + + if ( ( aEntry.nSize || aEntry.nCompressedSize ) && !checkSizeAndCRC( aEntry ) ) + { + aEntry.nCrc = 0; + aEntry.nCompressedSize = 0; + aEntry.nSize = 0; + } + + aEntries.emplace( aEntry.sPath, aEntry ); + } + } + } + } + + nPos += 4; + } + else if (pBuffer[nPos] == 'P' && pBuffer[nPos+1] == 'K' && pBuffer[nPos+2] == 7 && pBuffer[nPos+3] == 8 ) + { + //PK78: Data descriptor + sal_Int64 nCompressedSize, nSize; + Sequence<sal_Int8> aTmpBuffer(&(pBuffer[nPos + 4]), 12 + 8 + 4); + MemoryByteGrabber aMemGrabber(aTmpBuffer); + sal_Int32 nCRC32 = aMemGrabber.ReadInt32(); + + // FIXME64: find a better way to recognize if Zip64 mode is used + // Now we check if the memory at +16 byte seems to be a signature + // if not, then probably Zip64 mode is used here, except + // if memory at +24 byte seems not to be a signature. + // Normally Data Descriptor should followed by the next Local File header + // that should start with PK34, except for the last file, then it may + // followed by Central directory that start with PK12, or + // followed by "archive decryption header" that don't have a signature. + if ((pBuffer[nPos + 16] == 'P' && pBuffer[nPos + 17] == 'K' + && pBuffer[nPos + 19] == pBuffer[nPos + 18] + 1 + && (pBuffer[nPos + 18] == 3 || pBuffer[nPos + 18] == 1)) + || !(pBuffer[nPos + 24] == 'P' && pBuffer[nPos + 25] == 'K' + && pBuffer[nPos + 27] == pBuffer[nPos + 26] + 1 + && (pBuffer[nPos + 26] == 3 || pBuffer[nPos + 26] == 1))) + { + nCompressedSize = aMemGrabber.ReadUInt32(); + nSize = aMemGrabber.ReadUInt32(); + } + else + { + nCompressedSize = aMemGrabber.ReadUInt64(); + nSize = aMemGrabber.ReadUInt64(); + } + + for( auto& rEntry : aEntries ) + { + // this is a broken package, accept this block not only for DEFLATED streams + if( rEntry.second.nFlag & 8 ) + { + sal_Int64 nStreamOffset = nGenPos + nPos - nCompressedSize; + if ( nStreamOffset == rEntry.second.nOffset && nCompressedSize > rEntry.second.nCompressedSize ) + { + // only DEFLATED blocks need to be checked + bool bAcceptBlock = ( rEntry.second.nMethod == STORED && nCompressedSize == nSize ); + + if ( !bAcceptBlock ) + { + sal_Int64 nRealSize = 0; + sal_Int32 nRealCRC = 0; + getSizeAndCRC( nStreamOffset, nCompressedSize, &nRealSize, &nRealCRC ); + bAcceptBlock = ( nRealSize == nSize && nRealCRC == nCRC32 ); + } + + if ( bAcceptBlock ) + { + rEntry.second.nCrc = nCRC32; + rEntry.second.nCompressedSize = nCompressedSize; + rEntry.second.nSize = nSize; + } + } +#if 0 +// for now ignore clearly broken streams + else if( !rEntry.second.nCompressedSize ) + { + rEntry.second.nCrc = nCRC32; + sal_Int32 nRealStreamSize = nGenPos + nPos - rEntry.second.nOffset; + rEntry.second.nCompressedSize = nRealStreamSize; + rEntry.second.nSize = nSize; + } +#endif + } + } + + nPos += 4; + } + else + nPos++; + } + + nGenPos += nPos; + aGrabber.seek( nGenPos ); + } + } + catch ( IllegalArgumentException& ) + { + throw ZipException("Zip END signature not found!" ); + } + catch ( NotConnectedException& ) + { + throw ZipException("Zip END signature not found!" ); + } + catch ( BufferSizeExceededException& ) + { + throw ZipException("Zip END signature not found!" ); + } +} + +bool ZipFile::checkSizeAndCRC( const ZipEntry& aEntry ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + sal_Int32 nCRC = 0; + sal_Int64 nSize = 0; + + if( aEntry.nMethod == STORED ) + return ( getCRC( aEntry.nOffset, aEntry.nSize ) == aEntry.nCrc ); + + if (aEntry.nCompressedSize < 0) + { + SAL_WARN("package", "bogus compressed size of: " << aEntry.nCompressedSize); + return false; + } + + getSizeAndCRC( aEntry.nOffset, aEntry.nCompressedSize, &nSize, &nCRC ); + return ( aEntry.nSize == nSize && aEntry.nCrc == nCRC ); +} + +sal_Int32 ZipFile::getCRC( sal_Int64 nOffset, sal_Int64 nSize ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + Sequence < sal_Int8 > aBuffer; + CRC32 aCRC; + sal_Int64 nBlockSize = ::std::min(nSize, static_cast< sal_Int64 >(32000)); + + aGrabber.seek( nOffset ); + for (sal_Int64 ind = 0; + aGrabber.readBytes( aBuffer, nBlockSize ) && ind * nBlockSize < nSize; + ++ind) + { + sal_Int64 nLen = ::std::min(nBlockSize, nSize - ind * nBlockSize); + aCRC.updateSegment(aBuffer, static_cast<sal_Int32>(nLen)); + } + + return aCRC.getValue(); +} + +void ZipFile::getSizeAndCRC( sal_Int64 nOffset, sal_Int64 nCompressedSize, sal_Int64 *nSize, sal_Int32 *nCRC ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + Sequence < sal_Int8 > aBuffer; + CRC32 aCRC; + sal_Int64 nRealSize = 0; + Inflater aInflaterLocal( true ); + sal_Int32 nBlockSize = static_cast< sal_Int32 > (::std::min( nCompressedSize, static_cast< sal_Int64 >( 32000 ) ) ); + + aGrabber.seek( nOffset ); + for ( sal_Int64 ind = 0; + !aInflaterLocal.finished() && aGrabber.readBytes( aBuffer, nBlockSize ) && ind * nBlockSize < nCompressedSize; + ind++ ) + { + Sequence < sal_Int8 > aData( nBlockSize ); + sal_Int32 nLastInflated = 0; + sal_Int64 nInBlock = 0; + + aInflaterLocal.setInput( aBuffer ); + do + { + nLastInflated = aInflaterLocal.doInflateSegment( aData, 0, nBlockSize ); + aCRC.updateSegment( aData, nLastInflated ); + nInBlock += nLastInflated; + } while( !aInflater.finished() && nLastInflated ); + + nRealSize += nInBlock; + } + + *nSize = nRealSize; + *nCRC = aCRC.getValue(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/ZipOutputEntry.cxx b/package/source/zipapi/ZipOutputEntry.cxx new file mode 100644 index 0000000000..9d9c1e9b6f --- /dev/null +++ b/package/source/zipapi/ZipOutputEntry.cxx @@ -0,0 +1,404 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <ZipOutputEntry.hxx> + +#include <com/sun/star/io/TempFile.hpp> +#include <com/sun/star/packages/zip/ZipConstants.hpp> +#include <com/sun/star/xml/crypto/CipherID.hpp> + +#include <osl/diagnose.h> + +#include <PackageConstants.hxx> +#include <ThreadedDeflater.hxx> +#include <ZipEntry.hxx> +#include <ZipFile.hxx> +#include <ZipPackageStream.hxx> + +#include <algorithm> +#include <utility> + +using namespace com::sun::star; +using namespace com::sun::star::io; +using namespace com::sun::star::uno; +using namespace com::sun::star::packages::zip::ZipConstants; + +/** This class is used to deflate Zip entries + */ +ZipOutputEntryBase::ZipOutputEntryBase( + css::uno::Reference< css::io::XOutputStream > xOutput, + uno::Reference< uno::XComponentContext > xContext, + ZipEntry& rEntry, + ZipPackageStream* pStream, + bool bEncrypt, + bool checkStream) +: m_xContext(std::move(xContext)) +, m_xOutStream(std::move(xOutput)) +, m_pCurrentEntry(&rEntry) +, m_nDigested(0) +, m_pCurrentStream(pStream) +, m_bEncryptCurrentEntry(bEncrypt) +{ + assert(m_pCurrentEntry->nMethod == DEFLATED && "Use ZipPackageStream::rawWrite() for STORED entries"); + (void)checkStream; + assert(!checkStream || m_xOutStream.is()); + if (m_bEncryptCurrentEntry) + { + m_xCipherContext = ZipFile::StaticGetCipher( m_xContext, pStream->GetEncryptionData(), true ); + if (pStream->GetEncryptionData()->m_oCheckAlg) + { + assert(pStream->GetEncryptionData()->m_nEncAlg != xml::crypto::CipherID::AES_GCM_W3C); + m_xDigestContext = ZipFile::StaticGetDigestContextForChecksum(m_xContext, pStream->GetEncryptionData()); + } + } +} + +void ZipOutputEntryBase::closeEntry() +{ + finishDeflater(); + + if ((m_pCurrentEntry->nFlag & 8) == 0) + { + if (m_pCurrentEntry->nSize != getDeflaterTotalIn()) + { + OSL_FAIL("Invalid entry size"); + } + if (m_pCurrentEntry->nCompressedSize != getDeflaterTotalOut()) + { + // Different compression strategies make the merit of this + // test somewhat dubious + m_pCurrentEntry->nCompressedSize = getDeflaterTotalOut(); + } + if (m_pCurrentEntry->nCrc != m_aCRC.getValue()) + { + OSL_FAIL("Invalid entry CRC-32"); + } + } + else + { + if ( !m_bEncryptCurrentEntry ) + { + m_pCurrentEntry->nSize = getDeflaterTotalIn(); + m_pCurrentEntry->nCompressedSize = getDeflaterTotalOut(); + } + m_pCurrentEntry->nCrc = m_aCRC.getValue(); + } + deflaterReset(); + m_aCRC.reset(); + + if (!m_bEncryptCurrentEntry) + return; + + m_xCipherContext.clear(); + + uno::Sequence< sal_Int8 > aDigestSeq; + if ( m_xDigestContext.is() ) + { + aDigestSeq = m_xDigestContext->finalizeDigestAndDispose(); + m_xDigestContext.clear(); + } + + if ( m_pCurrentStream ) + m_pCurrentStream->setDigest( aDigestSeq ); +} + +void ZipOutputEntryBase::processDeflated( const uno::Sequence< sal_Int8 >& deflateBuffer, sal_Int32 nLength ) +{ + if ( nLength > 0 ) + { + uno::Sequence< sal_Int8 > aTmpBuffer( deflateBuffer.getConstArray(), nLength ); + if (m_bEncryptCurrentEntry && m_xCipherContext.is()) + { + // Need to update our digest before encryption... + sal_Int32 nDiff = n_ConstDigestLength - m_nDigested; + if (m_xDigestContext.is() && nDiff) + { + sal_Int32 nEat = ::std::min( nLength, nDiff ); + uno::Sequence< sal_Int8 > aTmpSeq( aTmpBuffer.getConstArray(), nEat ); + m_xDigestContext->updateDigest( aTmpSeq ); + m_nDigested = m_nDigested + static_cast< sal_Int16 >( nEat ); + } + + // FIXME64: uno::Sequence not 64bit safe. + uno::Sequence< sal_Int8 > aEncryptionBuffer = m_xCipherContext->convertWithCipherContext( aTmpBuffer ); + + m_xOutStream->writeBytes( aEncryptionBuffer ); + + // the sizes as well as checksum for encrypted streams is calculated here + m_pCurrentEntry->nCompressedSize += aEncryptionBuffer.getLength(); + m_pCurrentEntry->nSize = m_pCurrentEntry->nCompressedSize; + m_aCRC.update( aEncryptionBuffer ); + } + else + { + m_xOutStream->writeBytes ( aTmpBuffer ); + } + } + + if (!(isDeflaterFinished() && m_bEncryptCurrentEntry && m_xCipherContext.is())) + return; + + // FIXME64: sequence not 64bit safe. + uno::Sequence< sal_Int8 > aEncryptionBuffer = m_xCipherContext->finalizeCipherContextAndDispose(); + if ( aEncryptionBuffer.hasElements() ) + { + m_xOutStream->writeBytes( aEncryptionBuffer ); + + // the sizes as well as checksum for encrypted streams are calculated here + m_pCurrentEntry->nCompressedSize += aEncryptionBuffer.getLength(); + m_pCurrentEntry->nSize = m_pCurrentEntry->nCompressedSize; + m_aCRC.update( aEncryptionBuffer ); + } +} + +void ZipOutputEntryBase::processInput( const uno::Sequence< sal_Int8 >& rBuffer ) +{ + if (!m_bEncryptCurrentEntry) + m_aCRC.updateSegment(rBuffer, rBuffer.getLength()); +} + +ZipOutputEntry::ZipOutputEntry( + const css::uno::Reference< css::io::XOutputStream >& rxOutput, + const uno::Reference< uno::XComponentContext >& rxContext, + ZipEntry& rEntry, + ZipPackageStream* pStream, + bool bEncrypt, + bool checkStream) +: ZipOutputEntryBase(rxOutput, rxContext, rEntry, pStream, bEncrypt, checkStream) +, m_aDeflateBuffer(n_ConstBufferSize) +, m_aDeflater(DEFAULT_COMPRESSION, true) +{ +} + +ZipOutputEntry::ZipOutputEntry( + const css::uno::Reference< css::io::XOutputStream >& rxOutput, + const uno::Reference< uno::XComponentContext >& rxContext, + ZipEntry& rEntry, + ZipPackageStream* pStream, + bool bEncrypt) +: ZipOutputEntry( rxOutput, rxContext, rEntry, pStream, bEncrypt, true) +{ +} + +void ZipOutputEntry::write( const Sequence< sal_Int8 >& rBuffer ) +{ + if (!m_aDeflater.finished()) + { + m_aDeflater.setInputSegment(rBuffer); + while (!m_aDeflater.needsInput()) + doDeflate(); + processInput(rBuffer); + } +} + +void ZipOutputEntry::doDeflate() +{ + sal_Int32 nLength = m_aDeflater.doDeflateSegment(m_aDeflateBuffer, m_aDeflateBuffer.getLength()); + processDeflated( m_aDeflateBuffer, nLength ); +} + +void ZipOutputEntry::finishDeflater() +{ + m_aDeflater.finish(); + while (!m_aDeflater.finished()) + doDeflate(); +} + +sal_Int64 ZipOutputEntry::getDeflaterTotalIn() const +{ + return m_aDeflater.getTotalIn(); +} + +sal_Int64 ZipOutputEntry::getDeflaterTotalOut() const +{ + return m_aDeflater.getTotalOut(); +} + +void ZipOutputEntry::deflaterReset() +{ + m_aDeflater.reset(); +} + +bool ZipOutputEntry::isDeflaterFinished() const +{ + return m_aDeflater.finished(); +} + + +ZipOutputEntryInThread::ZipOutputEntryInThread( + const uno::Reference< uno::XComponentContext >& rxContext, + ZipEntry& rEntry, + ZipPackageStream* pStream, + bool bEncrypt) +: ZipOutputEntry( uno::Reference< css::io::XOutputStream >(), rxContext, rEntry, pStream, bEncrypt, false ) +, m_bFinished(false) +{ +} + +void ZipOutputEntryInThread::createBufferFile() +{ + assert(!m_xOutStream && !m_xTempFile && + "should only be called in the threaded mode where there is no existing stream yet"); + m_xTempFile = new utl::TempFileFastService; + m_xOutStream = m_xTempFile->getOutputStream(); +} + +void ZipOutputEntryInThread::closeBufferFile() +{ + m_xOutStream->closeOutput(); + m_xOutStream.clear(); +} + +void ZipOutputEntryInThread::deleteBufferFile() +{ + assert(!m_xOutStream.is() && m_xTempFile); + m_xTempFile.clear(); +} + +uno::Reference< io::XInputStream > ZipOutputEntryInThread::getData() const +{ + return m_xTempFile->getInputStream(); +} + +class ZipOutputEntryInThread::Task : public comphelper::ThreadTask +{ + ZipOutputEntryInThread *mpEntry; + uno::Reference< io::XInputStream > mxInStream; + +public: + Task( const std::shared_ptr<comphelper::ThreadTaskTag>& pTag, ZipOutputEntryInThread *pEntry, + uno::Reference< io::XInputStream > xInStream ) + : comphelper::ThreadTask(pTag) + , mpEntry(pEntry) + , mxInStream(std::move(xInStream)) + {} + +private: + virtual void doWork() override + { + try + { + mpEntry->createBufferFile(); + mpEntry->writeStream(mxInStream); + mxInStream.clear(); + mpEntry->closeBufferFile(); + mpEntry->setFinished(); + } + catch (...) + { + mpEntry->setParallelDeflateException(std::current_exception()); + try + { + if (mpEntry->m_xOutStream.is()) + mpEntry->closeBufferFile(); + if (mpEntry->m_xTempFile) + mpEntry->deleteBufferFile(); + } + catch (uno::Exception const&) + { + } + mpEntry->setFinished(); + } + } +}; + +std::unique_ptr<comphelper::ThreadTask> ZipOutputEntryInThread::createTask( + const std::shared_ptr<comphelper::ThreadTaskTag>& pTag, + const uno::Reference< io::XInputStream >& xInStream ) +{ + return std::make_unique<Task>(pTag, this, xInStream); +} + +void ZipOutputEntry::writeStream(const uno::Reference< io::XInputStream >& xInStream) +{ + sal_Int32 nLength = 0; + uno::Sequence< sal_Int8 > aSeq(n_ConstBufferSize); + do + { + nLength = xInStream->readBytes(aSeq, n_ConstBufferSize); + if (nLength != n_ConstBufferSize) + aSeq.realloc(nLength); + + write(aSeq); + } + while (nLength == n_ConstBufferSize); + closeEntry(); +} + + +ZipOutputEntryParallel::ZipOutputEntryParallel( + const css::uno::Reference< css::io::XOutputStream >& rxOutput, + const uno::Reference< uno::XComponentContext >& rxContext, + ZipEntry& rEntry, + ZipPackageStream* pStream, + bool bEncrypt) +: ZipOutputEntryBase(rxOutput, rxContext, rEntry, pStream, bEncrypt, true) +, totalIn(0) +, totalOut(0) +, finished(false) +{ +} + +void ZipOutputEntryParallel::writeStream(const uno::Reference< io::XInputStream >& xInStream) +{ + ZipUtils::ThreadedDeflater deflater( DEFAULT_COMPRESSION ); + deflater.deflateWrite(xInStream, + [this](const uno::Sequence< sal_Int8 >& rBuffer, sal_Int32 nLen) { + if (!m_bEncryptCurrentEntry) + m_aCRC.updateSegment(rBuffer, nLen); + }, + [this](const uno::Sequence< sal_Int8 >& rBuffer, sal_Int32 nLen) { + processDeflated(rBuffer, nLen); + } + ); + finished = true; + processDeflated( uno::Sequence< sal_Int8 >(), 0 ); // finish encrypting, etc. + totalIn = deflater.getTotalIn(); + totalOut = deflater.getTotalOut(); + closeEntry(); +} + +void ZipOutputEntryParallel::finishDeflater() +{ + // ThreadedDeflater is called synchronously in one call, so nothing to do here. +} + +sal_Int64 ZipOutputEntryParallel::getDeflaterTotalIn() const +{ + return totalIn; +} + +sal_Int64 ZipOutputEntryParallel::getDeflaterTotalOut() const +{ + return totalOut; +} + +void ZipOutputEntryParallel::deflaterReset() +{ + totalIn = 0; + totalOut = 0; + finished = false; +} + +bool ZipOutputEntryParallel::isDeflaterFinished() const +{ + return finished; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/ZipOutputStream.cxx b/package/source/zipapi/ZipOutputStream.cxx new file mode 100644 index 0000000000..402a2930c0 --- /dev/null +++ b/package/source/zipapi/ZipOutputStream.cxx @@ -0,0 +1,375 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <ZipOutputStream.hxx> + +#include <com/sun/star/packages/zip/ZipConstants.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <comphelper/storagehelper.hxx> + +#include <osl/time.h> +#include <osl/thread.hxx> + +#include <PackageConstants.hxx> +#include <ZipEntry.hxx> +#include <ZipOutputEntry.hxx> +#include <ZipPackageStream.hxx> + +#include <thread> + +using namespace com::sun::star; +using namespace com::sun::star::io; +using namespace com::sun::star::uno; +using namespace com::sun::star::packages::zip::ZipConstants; + +/** This class is used to write Zip files + */ +ZipOutputStream::ZipOutputStream( const uno::Reference < io::XOutputStream > &xOStream ) +: m_xStream(xOStream) +, mpThreadTaskTag( comphelper::ThreadPool::createThreadTaskTag() ) +, m_aChucker(xOStream) +, m_pCurrentEntry(nullptr) +{ +} + +ZipOutputStream::~ZipOutputStream() +{ +} + +void ZipOutputStream::setEntry( ZipEntry *pEntry ) +{ + if (pEntry->nTime == -1) + pEntry->nTime = getCurrentDosTime(); + if (pEntry->nMethod == -1) + pEntry->nMethod = DEFLATED; + pEntry->nVersion = 20; + pEntry->nFlag = 1 << 11; + if (pEntry->nSize == -1 || pEntry->nCompressedSize == -1 || + pEntry->nCrc == -1) + { + pEntry->nSize = pEntry->nCompressedSize = 0; + pEntry->nFlag |= 8; + } +} + +void ZipOutputStream::addDeflatingThreadTask( ZipOutputEntryInThread *pEntry, std::unique_ptr<comphelper::ThreadTask> pTask ) +{ + comphelper::ThreadPool::getSharedOptimalPool().pushTask(std::move(pTask)); + m_aEntries.push_back(pEntry); +} + +void ZipOutputStream::rawWrite( const Sequence< sal_Int8 >& rBuffer ) +{ + m_aChucker.WriteBytes( rBuffer ); +} + +void ZipOutputStream::rawCloseEntry( bool bEncrypt ) +{ + assert(m_pCurrentEntry && "Forgot to call writeLOC()?"); + if ( m_pCurrentEntry->nMethod == DEFLATED && ( m_pCurrentEntry->nFlag & 8 ) ) + writeDataDescriptor(*m_pCurrentEntry); + + if (bEncrypt) + m_pCurrentEntry->nMethod = STORED; + + m_pCurrentEntry = nullptr; +} + +void ZipOutputStream::consumeScheduledThreadTaskEntry(std::unique_ptr<ZipOutputEntryInThread> pCandidate) +{ + //Any exceptions thrown in the threads were caught and stored for now + const std::exception_ptr& rCaughtException(pCandidate->getParallelDeflateException()); + if (rCaughtException) + { + m_aDeflateException = rCaughtException; // store it for later throwing + // the exception handler in DeflateThreadTask should have cleaned temp file + return; + } + + writeLOC(pCandidate->getZipEntry(), pCandidate->isEncrypt()); + + sal_Int32 nRead; + uno::Sequence< sal_Int8 > aSequence(n_ConstBufferSize); + uno::Reference< io::XInputStream > xInput = pCandidate->getData(); + do + { + nRead = xInput->readBytes(aSequence, n_ConstBufferSize); + if (nRead < n_ConstBufferSize) + aSequence.realloc(nRead); + + rawWrite(aSequence); + } + while (nRead == n_ConstBufferSize); + xInput.clear(); + + rawCloseEntry(pCandidate->isEncrypt()); + + pCandidate->getZipPackageStream()->successfullyWritten(pCandidate->getZipEntry()); + pCandidate->deleteBufferFile(); +} + +void ZipOutputStream::consumeFinishedScheduledThreadTaskEntries() +{ + std::vector< ZipOutputEntryInThread* > aNonFinishedEntries; + + for(ZipOutputEntryInThread* pEntry : m_aEntries) + { + if(pEntry->isFinished()) + { + consumeScheduledThreadTaskEntry(std::unique_ptr<ZipOutputEntryInThread>(pEntry)); + } + else + { + aNonFinishedEntries.push_back(pEntry); + } + } + + // always reset to non-consumed entries + m_aEntries = aNonFinishedEntries; +} + +void ZipOutputStream::reduceScheduledThreadTasksToGivenNumberOrLess(std::size_t nThreadTasks) +{ + while(m_aEntries.size() > nThreadTasks) + { + consumeFinishedScheduledThreadTaskEntries(); + + if(m_aEntries.size() > nThreadTasks) + { + std::this_thread::sleep_for(std::chrono::microseconds(100)); + } + } +} + +void ZipOutputStream::finish() +{ + assert(!m_aZipList.empty() && "Zip file must have at least one entry!"); + + // Wait for all thread tasks to finish & write + comphelper::ThreadPool::getSharedOptimalPool().waitUntilDone(mpThreadTaskTag); + + // consume all processed entries + while(!m_aEntries.empty()) + { + ZipOutputEntryInThread* pCandidate = m_aEntries.back(); + m_aEntries.pop_back(); + consumeScheduledThreadTaskEntry(std::unique_ptr<ZipOutputEntryInThread>(pCandidate)); + } + + sal_Int32 nOffset= static_cast < sal_Int32 > (m_aChucker.GetPosition()); + for (ZipEntry* p : m_aZipList) + { + writeCEN( *p ); + delete p; + } + writeEND( nOffset, static_cast < sal_Int32 > (m_aChucker.GetPosition()) - nOffset); + m_aZipList.clear(); + + if (m_aDeflateException) + { // throw once all thread tasks are finished and m_aEntries can be released + std::rethrow_exception(m_aDeflateException); + } +} + +const css::uno::Reference< css::io::XOutputStream >& ZipOutputStream::getStream() const +{ + return m_xStream; +} + +void ZipOutputStream::writeEND(sal_uInt32 nOffset, sal_uInt32 nLength) +{ + m_aChucker.WriteInt32( ENDSIG ); + m_aChucker.WriteInt16( 0 ); + m_aChucker.WriteInt16( 0 ); + m_aChucker.WriteInt16( m_aZipList.size() ); + m_aChucker.WriteInt16( m_aZipList.size() ); + m_aChucker.WriteUInt32( nLength ); + m_aChucker.WriteUInt32( nOffset ); + m_aChucker.WriteInt16( 0 ); +} + +static sal_uInt32 getTruncated( sal_Int64 nNum, bool *pIsTruncated ) +{ + if( nNum >= 0xffffffff ) + { + *pIsTruncated = true; + return 0xffffffff; + } + else + return static_cast< sal_uInt32 >( nNum ); +} + +void ZipOutputStream::writeCEN( const ZipEntry &rEntry ) +{ + if ( !::comphelper::OStorageHelper::IsValidZipEntryFileName( rEntry.sPath, true ) ) + throw IOException("Unexpected character is used in file name." ); + + OString sUTF8Name = OUStringToOString( rEntry.sPath, RTL_TEXTENCODING_UTF8 ); + sal_Int16 nNameLength = static_cast < sal_Int16 > ( sUTF8Name.getLength() ); + + m_aChucker.WriteInt32( CENSIG ); + m_aChucker.WriteInt16( rEntry.nVersion ); + m_aChucker.WriteInt16( rEntry.nVersion ); + m_aChucker.WriteInt16( rEntry.nFlag ); + m_aChucker.WriteInt16( rEntry.nMethod ); + bool bWrite64Header = false; + + m_aChucker.WriteUInt32( rEntry.nTime ); + m_aChucker.WriteUInt32( rEntry.nCrc ); + m_aChucker.WriteUInt32( getTruncated( rEntry.nCompressedSize, &bWrite64Header ) ); + m_aChucker.WriteUInt32( getTruncated( rEntry.nSize, &bWrite64Header ) ); + sal_uInt32 nOffset32bit = getTruncated( rEntry.nOffset, &bWrite64Header ); + m_aChucker.WriteInt16(nNameLength); + m_aChucker.WriteInt16( bWrite64Header? 32 : 0 ); //in ZIP64 case extra field is 32byte + m_aChucker.WriteInt16( 0 ); + m_aChucker.WriteInt16( 0 ); + m_aChucker.WriteInt16( 0 ); + m_aChucker.WriteInt32( 0 ); + m_aChucker.WriteUInt32( nOffset32bit ); + + Sequence < sal_Int8 > aSequence( reinterpret_cast<sal_Int8 const *>(sUTF8Name.getStr()), sUTF8Name.getLength() ); + m_aChucker.WriteBytes( aSequence ); + + if (bWrite64Header) + { + writeExtraFields( rEntry ); + } +} + +void ZipOutputStream::writeDataDescriptor(const ZipEntry& rEntry) +{ + bool bWrite64Header = false; + + m_aChucker.WriteInt32( EXTSIG ); + m_aChucker.WriteUInt32( rEntry.nCrc ); + // For ZIP64(tm) format archives, the compressed and uncompressed sizes are 8 bytes each. + // TODO: Not sure if this is the "when ZIP64(tm) format is used" + bWrite64Header = rEntry.nCompressedSize >= 0x100000000 || rEntry.nSize >= 0x100000000; + if (!bWrite64Header) + { + m_aChucker.WriteUInt32( static_cast<sal_uInt32>(rEntry.nCompressedSize) ); + m_aChucker.WriteUInt32( static_cast<sal_uInt32>(rEntry.nSize) ); + } + else + { + m_aChucker.WriteUInt64( rEntry.nCompressedSize ); + m_aChucker.WriteUInt64( rEntry.nSize ); + } +} + +void ZipOutputStream::writeExtraFields(const ZipEntry& rEntry) +{ + //Could contain more fields, now we only save Zip64 extended information + m_aChucker.WriteInt16( 1 ); //id of Zip64 extended information extra field + m_aChucker.WriteInt16( 28 ); //data size of this field = 3*8+4 byte + m_aChucker.WriteUInt64( rEntry.nSize ); + m_aChucker.WriteUInt64( rEntry.nCompressedSize ); + m_aChucker.WriteUInt64( rEntry.nOffset ); + m_aChucker.WriteInt32( 0 ); //Number of the disk on which this file starts +} + +void ZipOutputStream::writeLOC( ZipEntry *pEntry, bool bEncrypt ) +{ + assert(!m_pCurrentEntry && "Forgot to close an entry with rawCloseEntry()?"); + m_pCurrentEntry = pEntry; + m_aZipList.push_back( m_pCurrentEntry ); + const ZipEntry &rEntry = *m_pCurrentEntry; + + if ( !::comphelper::OStorageHelper::IsValidZipEntryFileName( rEntry.sPath, true ) ) + throw IOException("Unexpected character is used in file name." ); + + OString sUTF8Name = OUStringToOString( rEntry.sPath, RTL_TEXTENCODING_UTF8 ); + sal_Int16 nNameLength = static_cast < sal_Int16 > ( sUTF8Name.getLength() ); + + m_aChucker.WriteInt32( LOCSIG ); + m_aChucker.WriteInt16( rEntry.nVersion ); + + m_aChucker.WriteInt16( rEntry.nFlag ); + // If it's an encrypted entry, we pretend its stored plain text + if (bEncrypt) + m_aChucker.WriteInt16( STORED ); + else + m_aChucker.WriteInt16( rEntry.nMethod ); + + bool bWrite64Header = false; + + m_aChucker.WriteUInt32( rEntry.nTime ); + if ((rEntry.nFlag & 8) == 8 ) + { + m_aChucker.WriteInt32( 0 ); + m_aChucker.WriteInt32( 0 ); + m_aChucker.WriteInt32( 0 ); + } + else + { + m_aChucker.WriteUInt32( rEntry.nCrc ); + m_aChucker.WriteUInt32( getTruncated( rEntry.nCompressedSize, &bWrite64Header ) ); + m_aChucker.WriteUInt32( getTruncated( rEntry.nSize, &bWrite64Header ) ); + } + m_aChucker.WriteInt16( nNameLength ); + m_aChucker.WriteInt16( bWrite64Header ? 32 : 0 ); + + Sequence < sal_Int8 > aSequence( reinterpret_cast<sal_Int8 const *>(sUTF8Name.getStr()), sUTF8Name.getLength() ); + m_aChucker.WriteBytes( aSequence ); + + m_pCurrentEntry->nOffset = m_aChucker.GetPosition() - (LOCHDR + nNameLength); + + if (bWrite64Header) + { + writeExtraFields(rEntry); + } +} + +sal_uInt32 ZipOutputStream::getCurrentDosTime() +{ + oslDateTime aDateTime; + TimeValue aTimeValue; + osl_getSystemTime ( &aTimeValue ); + osl_getDateTimeFromTimeValue( &aTimeValue, &aDateTime); + + // at year 2108, there is an overflow + // -> some decision needs to be made + // how to handle the ZIP file format (just overflow?) + + // if the current system time is before 1980, + // then the time traveller will have to make a decision + // how to handle the ZIP file format before it is invented + // (just underflow?) + + assert(aDateTime.Year > 1980 && aDateTime.Year < 2108); + + sal_uInt32 nYear = static_cast <sal_uInt32> (aDateTime.Year); + + if (nYear>=1980) + nYear-=1980; + else if (nYear>=80) + { + nYear-=80; + } + sal_uInt32 nResult = static_cast < sal_uInt32>( ( ( ( aDateTime.Day) + + ( 32 * (aDateTime.Month)) + + ( 512 * nYear ) ) << 16) | + ( ( aDateTime.Seconds/2) + + ( 32 * aDateTime.Minutes) + + ( 2048 * static_cast <sal_uInt32 > (aDateTime.Hours) ) ) ); + return nResult; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/blowfishcontext.cxx b/package/source/zipapi/blowfishcontext.cxx new file mode 100644 index 0000000000..1b5ed4a148 --- /dev/null +++ b/package/source/zipapi/blowfishcontext.cxx @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <com/sun/star/lang/DisposedException.hpp> +#include <rtl/cipher.h> +#include <rtl/ref.hxx> + +#include "blowfishcontext.hxx" + +using namespace ::com::sun::star; + +// static +uno::Reference< xml::crypto::XCipherContext > BlowfishCFB8CipherContext::Create( const uno::Sequence< sal_Int8 >& aDerivedKey, const uno::Sequence< sal_Int8 >& aInitVector, bool bEncrypt ) +{ + ::rtl::Reference< BlowfishCFB8CipherContext > xResult = new BlowfishCFB8CipherContext(); + xResult->m_pCipher = rtl_cipher_create( rtl_Cipher_AlgorithmBF, rtl_Cipher_ModeStream ); + if ( !xResult->m_pCipher ) + throw uno::RuntimeException("Can not create cipher!" ); + + if ( rtl_Cipher_E_None != rtl_cipher_init( + xResult->m_pCipher, + bEncrypt ? rtl_Cipher_DirectionEncode : rtl_Cipher_DirectionDecode, + reinterpret_cast< const sal_uInt8* >( aDerivedKey.getConstArray() ), + aDerivedKey.getLength(), + reinterpret_cast< const sal_uInt8* >( aInitVector.getConstArray() ), + aInitVector.getLength() ) ) + { + throw uno::RuntimeException("Can not initialize cipher!" ); + } + + xResult->m_bEncrypt = bEncrypt; + + return xResult; +} + +BlowfishCFB8CipherContext::~BlowfishCFB8CipherContext() +{ + if ( m_pCipher ) + { + rtl_cipher_destroy ( m_pCipher ); + m_pCipher = nullptr; + } +} + +uno::Sequence< sal_Int8 > SAL_CALL BlowfishCFB8CipherContext::convertWithCipherContext( const uno::Sequence< ::sal_Int8 >& aData ) +{ + std::scoped_lock aGuard( m_aMutex ); + if ( !m_pCipher ) + throw lang::DisposedException(); + + uno::Sequence< sal_Int8 > aResult( aData.getLength() ); + rtlCipherError nError = rtl_Cipher_E_None; + + if ( m_bEncrypt ) + { + nError = rtl_cipher_encode( m_pCipher, + aData.getConstArray(), + aData.getLength(), + reinterpret_cast< sal_uInt8* >( aResult.getArray() ), + aResult.getLength() ); + } + else + { + nError = rtl_cipher_decode( m_pCipher, + aData.getConstArray(), + aData.getLength(), + reinterpret_cast< sal_uInt8* >( aResult.getArray() ), + aResult.getLength() ); + } + + if ( rtl_Cipher_E_None != nError ) + { + throw uno::RuntimeException("Can not decrypt/encrypt with cipher!" ); + } + + return aResult; +} + +uno::Sequence< ::sal_Int8 > SAL_CALL BlowfishCFB8CipherContext::finalizeCipherContextAndDispose() +{ + std::scoped_lock aGuard( m_aMutex ); + if ( !m_pCipher ) + throw lang::DisposedException(); + + rtl_cipher_destroy ( m_pCipher ); + m_pCipher = nullptr; + + return uno::Sequence< sal_Int8 >(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/blowfishcontext.hxx b/package/source/zipapi/blowfishcontext.hxx new file mode 100644 index 0000000000..c0b603c152 --- /dev/null +++ b/package/source/zipapi/blowfishcontext.hxx @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_PACKAGE_SOURCE_ZIPAPI_BLOWFISHCONTEXT_HXX +#define INCLUDED_PACKAGE_SOURCE_ZIPAPI_BLOWFISHCONTEXT_HXX + +#include <com/sun/star/xml/crypto/XCipherContext.hpp> + +#include <cppuhelper/implbase.hxx> +#include <mutex> + +class BlowfishCFB8CipherContext : public cppu::WeakImplHelper< css::xml::crypto::XCipherContext > +{ + std::mutex m_aMutex; + void* m_pCipher; + bool m_bEncrypt; + + BlowfishCFB8CipherContext() + : m_pCipher( nullptr ) + , m_bEncrypt( false ) + {} + +public: + + virtual ~BlowfishCFB8CipherContext() override; + + static css::uno::Reference< css::xml::crypto::XCipherContext > + Create( const css::uno::Sequence< sal_Int8 >& aDerivedKey, const css::uno::Sequence< sal_Int8 >& aInitVector, bool bEncrypt ); + + virtual css::uno::Sequence< ::sal_Int8 > SAL_CALL convertWithCipherContext( const css::uno::Sequence< ::sal_Int8 >& aData ) override; + virtual css::uno::Sequence< ::sal_Int8 > SAL_CALL finalizeCipherContextAndDispose( ) override; +}; + +#endif // INCLUDED_PACKAGE_SOURCE_ZIPAPI_BLOWFISHCONTEXT_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/sha1context.cxx b/package/source/zipapi/sha1context.cxx new file mode 100644 index 0000000000..4b6cbd2d33 --- /dev/null +++ b/package/source/zipapi/sha1context.cxx @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <com/sun/star/lang/DisposedException.hpp> +#include <rtl/digest.h> +#include <rtl/ref.hxx> + +#include "sha1context.hxx" + +using namespace ::com::sun::star; + +// static +uno::Reference<xml::crypto::XDigestContext> StarOfficeSHA1DigestContext::Create() +{ + ::rtl::Reference<StarOfficeSHA1DigestContext> xResult = new StarOfficeSHA1DigestContext(); + xResult->m_pDigest = rtl_digest_createSHA1(); + if ( !xResult->m_pDigest ) + throw uno::RuntimeException("Can not create cipher!" ); + + return xResult; +} + +StarOfficeSHA1DigestContext::~StarOfficeSHA1DigestContext() +{ + if ( m_pDigest ) + { + rtl_digest_destroySHA1( m_pDigest ); + m_pDigest = nullptr; + } +} + +void SAL_CALL StarOfficeSHA1DigestContext::updateDigest(const uno::Sequence<::sal_Int8>& aData) +{ + std::scoped_lock aGuard( m_aMutex ); + if ( !m_pDigest ) + throw lang::DisposedException(); + + if ( rtl_Digest_E_None != rtl_digest_updateSHA1( m_pDigest, aData.getConstArray(), aData.getLength() ) ) + { + rtl_digest_destroySHA1( m_pDigest ); + m_pDigest = nullptr; + + throw uno::RuntimeException(); + } +} + +uno::Sequence<::sal_Int8> SAL_CALL StarOfficeSHA1DigestContext::finalizeDigestAndDispose() +{ + std::scoped_lock aGuard( m_aMutex ); + if ( !m_pDigest ) + throw lang::DisposedException(); + + uno::Sequence< sal_Int8 > aResult( RTL_DIGEST_LENGTH_SHA1 ); + if ( rtl_Digest_E_None != rtl_digest_getSHA1( m_pDigest, reinterpret_cast< sal_uInt8* >( aResult.getArray() ), aResult.getLength() ) ) + { + rtl_digest_destroySHA1( m_pDigest ); + m_pDigest = nullptr; + + throw uno::RuntimeException(); + } + + rtl_digest_destroySHA1( m_pDigest ); + m_pDigest = nullptr; + + return aResult; +} + +uno::Reference<xml::crypto::XDigestContext> CorrectSHA1DigestContext::Create() +{ + return new CorrectSHA1DigestContext(); +} + +CorrectSHA1DigestContext::CorrectSHA1DigestContext() +{ +} + +CorrectSHA1DigestContext::~CorrectSHA1DigestContext() +{ +} + +void SAL_CALL CorrectSHA1DigestContext::updateDigest(const uno::Sequence<::sal_Int8>& rData) +{ + std::scoped_lock aGuard(m_Mutex); + if (m_bDisposed) + throw lang::DisposedException(); + + m_Hash.update(reinterpret_cast<unsigned char const*>(rData.getConstArray()), rData.getLength()); +} + +uno::Sequence<::sal_Int8> SAL_CALL CorrectSHA1DigestContext::finalizeDigestAndDispose() +{ + std::scoped_lock aGuard(m_Mutex); + if (m_bDisposed) + throw lang::DisposedException(); + + m_bDisposed = true; + std::vector<unsigned char> const sha1(m_Hash.finalize()); + return uno::Sequence<sal_Int8>(reinterpret_cast<sal_Int8 const*>(sha1.data()), sha1.size()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zipapi/sha1context.hxx b/package/source/zipapi/sha1context.hxx new file mode 100644 index 0000000000..6cc09da01b --- /dev/null +++ b/package/source/zipapi/sha1context.hxx @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_PACKAGE_SOURCE_ZIPAPI_SHA1CONTEXT_HXX +#define INCLUDED_PACKAGE_SOURCE_ZIPAPI_SHA1CONTEXT_HXX + +#include <com/sun/star/xml/crypto/XDigestContext.hpp> + +#include <comphelper/hash.hxx> +#include <cppuhelper/implbase.hxx> +#include <mutex> + +class StarOfficeSHA1DigestContext + : public cppu::WeakImplHelper<css::xml::crypto::XDigestContext> +{ + std::mutex m_aMutex; + void* m_pDigest; + + StarOfficeSHA1DigestContext() + : m_pDigest( nullptr ) + {} + +public: + + virtual ~StarOfficeSHA1DigestContext() override; + + static css::uno::Reference< css::xml::crypto::XDigestContext > Create(); + + virtual void SAL_CALL updateDigest( const css::uno::Sequence< ::sal_Int8 >& aData ) override; + virtual css::uno::Sequence< ::sal_Int8 > SAL_CALL finalizeDigestAndDispose() override; + +}; + +class CorrectSHA1DigestContext + : public cppu::WeakImplHelper<css::xml::crypto::XDigestContext> +{ + std::mutex m_Mutex; + ::comphelper::Hash m_Hash{::comphelper::HashType::SHA1}; + bool m_bDisposed{false}; + + CorrectSHA1DigestContext(); + +public: + + virtual ~CorrectSHA1DigestContext() override; + + static css::uno::Reference<css::xml::crypto::XDigestContext> Create(); + + virtual void SAL_CALL updateDigest(const css::uno::Sequence<::sal_Int8>& rData) override; + virtual css::uno::Sequence<::sal_Int8> SAL_CALL finalizeDigestAndDispose() override; + +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zippackage/ZipPackage.cxx b/package/source/zippackage/ZipPackage.cxx new file mode 100644 index 0000000000..02f7cf71e8 --- /dev/null +++ b/package/source/zippackage/ZipPackage.cxx @@ -0,0 +1,1961 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <ZipPackage.hxx> +#include "ZipPackageSink.hxx" +#include <ZipEnumeration.hxx> +#include <ZipPackageStream.hxx> +#include <ZipPackageFolder.hxx> +#include <ZipOutputEntry.hxx> +#include <ZipOutputStream.hxx> +#include <ZipPackageBuffer.hxx> +#include <ZipFile.hxx> +#include <PackageConstants.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/packages/zip/ZipConstants.hpp> +#include <com/sun/star/packages/zip/ZipException.hpp> +#include <com/sun/star/packages/zip/ZipIOException.hpp> +#include <com/sun/star/packages/manifest/ManifestReader.hpp> +#include <com/sun/star/packages/manifest/ManifestWriter.hpp> +#include <com/sun/star/io/TempFile.hpp> +#include <com/sun/star/io/XStream.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/io/XTruncate.hpp> +#include <com/sun/star/io/XSeekable.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <officecfg/Office/Common.hxx> +#include <comphelper/fileurl.hxx> +#include <comphelper/processfactory.hxx> +#include <ucbhelper/content.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/TransferInfo.hpp> +#include <com/sun/star/ucb/NameClash.hpp> +#include <com/sun/star/ucb/OpenCommandArgument2.hpp> +#include <com/sun/star/ucb/OpenMode.hpp> +#include <com/sun/star/ucb/SimpleFileAccess.hpp> +#include <com/sun/star/io/XActiveDataStreamer.hpp> +#include <com/sun/star/embed/UseBackupException.hpp> +#include <com/sun/star/embed/StorageFormats.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/xml/crypto/DigestID.hpp> +#include <com/sun/star/xml/crypto/KDFID.hpp> +#include <cppuhelper/implbase.hxx> +#include <rtl/uri.hxx> +#include <rtl/random.h> +#include <o3tl/string_view.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> +#include <unotools/streamwrap.hxx> +#include <unotools/tempfile.hxx> +#include <com/sun/star/io/XAsyncOutputMonitor.hpp> + +#include <string_view> + +#include <comphelper/seekableinput.hxx> +#include <comphelper/storagehelper.hxx> +#include <comphelper/ofopxmlhelper.hxx> +#include <comphelper/documentconstants.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/servicehelper.hxx> +#include <utility> + +using namespace osl; +using namespace cppu; +using namespace ucbhelper; +using namespace com::sun::star; +using namespace com::sun::star::io; +using namespace com::sun::star::uno; +using namespace com::sun::star::ucb; +using namespace com::sun::star::util; +using namespace com::sun::star::lang; +using namespace com::sun::star::task; +using namespace com::sun::star::beans; +using namespace com::sun::star::packages; +using namespace com::sun::star::container; +using namespace com::sun::star::packages::zip; +using namespace com::sun::star::packages::manifest; +using namespace com::sun::star::packages::zip::ZipConstants; + +#if OSL_DEBUG_LEVEL > 0 +#define THROW_WHERE SAL_WHERE +#else +#define THROW_WHERE "" +#endif + +namespace { + +class ActiveDataStreamer : public ::cppu::WeakImplHelper< XActiveDataStreamer > +{ + uno::Reference< XStream > mStream; +public: + + virtual uno::Reference< XStream > SAL_CALL getStream() override + { return mStream; } + + virtual void SAL_CALL setStream( const uno::Reference< XStream >& stream ) override + { mStream = stream; } +}; + +class DummyInputStream : public ::cppu::WeakImplHelper< XInputStream > +{ + virtual sal_Int32 SAL_CALL readBytes( uno::Sequence< sal_Int8 >&, sal_Int32 ) override + { return 0; } + + virtual sal_Int32 SAL_CALL readSomeBytes( uno::Sequence< sal_Int8 >&, sal_Int32 ) override + { return 0; } + + virtual void SAL_CALL skipBytes( sal_Int32 ) override + {} + + virtual sal_Int32 SAL_CALL available() override + { return 0; } + + virtual void SAL_CALL closeInput() override + {} +}; + +} + +ZipPackage::ZipPackage ( uno::Reference < XComponentContext > xContext ) +: m_aMutexHolder( new comphelper::RefCountedMutex ) +, m_nStartKeyGenerationID( xml::crypto::DigestID::SHA1 ) +, m_oChecksumDigestID( xml::crypto::DigestID::SHA1_1K ) +, m_nKeyDerivationFunctionID(xml::crypto::KDFID::PBKDF2) +, m_nCommonEncryptionID( xml::crypto::CipherID::BLOWFISH_CFB_8 ) +, m_bHasEncryptedEntries ( false ) +, m_bHasNonEncryptedEntries ( false ) +, m_bInconsistent ( false ) +, m_bForceRecovery ( false ) +, m_bMediaTypeFallbackUsed ( false ) +, m_nFormat( embed::StorageFormats::PACKAGE ) // package is the default format +, m_bAllowRemoveOnInsert( true ) +, m_eMode ( e_IMode_None ) +, m_xContext(std::move( xContext )) +{ + m_xRootFolder = new ZipPackageFolder( m_xContext, m_nFormat, m_bAllowRemoveOnInsert ); +} + +ZipPackage::~ZipPackage() +{ +} + +bool ZipPackage::isLocalFile() const +{ + return comphelper::isFileUrl(m_aURL); +} + +void ZipPackage::parseManifest() +{ + if ( m_nFormat != embed::StorageFormats::PACKAGE ) + return; + + bool bManifestParsed = false; + static constexpr OUString sMeta (u"META-INF"_ustr); + if ( m_xRootFolder->hasByName( sMeta ) ) + { + try { + static constexpr OUString sManifest (u"manifest.xml"_ustr); + Any aAny = m_xRootFolder->getByName( sMeta ); + uno::Reference< XNameContainer > xMetaInfFolder; + aAny >>= xMetaInfFolder; + if ( xMetaInfFolder.is() && xMetaInfFolder->hasByName( sManifest ) ) + { + uno::Reference < XActiveDataSink > xSink; + aAny = xMetaInfFolder->getByName( sManifest ); + aAny >>= xSink; + if ( xSink.is() ) + { + uno::Reference < XManifestReader > xReader = ManifestReader::create( m_xContext ); + + static constexpr OUStringLiteral sPropFullPath (u"FullPath"); + static constexpr OUStringLiteral sPropVersion (u"Version"); + static constexpr OUStringLiteral sPropMediaType (u"MediaType"); + static constexpr OUStringLiteral sPropInitialisationVector (u"InitialisationVector"); + static constexpr OUStringLiteral sPropSalt (u"Salt"); + static constexpr OUStringLiteral sPropIterationCount (u"IterationCount"); + static constexpr OUStringLiteral sPropSize (u"Size"); + static constexpr OUStringLiteral sPropDigest (u"Digest"); + static constexpr OUStringLiteral sPropDerivedKeySize (u"DerivedKeySize"); + static constexpr OUStringLiteral sPropDigestAlgorithm (u"DigestAlgorithm"); + static constexpr OUStringLiteral sPropEncryptionAlgorithm (u"EncryptionAlgorithm"); + static constexpr OUStringLiteral sPropStartKeyAlgorithm (u"StartKeyAlgorithm"); + static constexpr OUStringLiteral sKeyInfo (u"KeyInfo"); + + const uno::Sequence < uno::Sequence < PropertyValue > > aManifestSequence = xReader->readManifestSequence ( xSink->getInputStream() ); + const Any *pKeyInfo = nullptr; + + for ( const uno::Sequence<PropertyValue>& rSequence : aManifestSequence ) + { + OUString sPath, sMediaType, sVersion; + const Any *pSalt = nullptr, *pVector = nullptr, *pCount = nullptr, *pSize = nullptr, *pDigest = nullptr, *pDigestAlg = nullptr, *pEncryptionAlg = nullptr, *pStartKeyAlg = nullptr, *pDerivedKeySize = nullptr; + uno::Any const* pKDF = nullptr; + uno::Any const* pArgon2Args = nullptr; + for ( const PropertyValue& rValue : rSequence ) + { + if ( rValue.Name == sPropFullPath ) + rValue.Value >>= sPath; + else if ( rValue.Name == sPropVersion ) + rValue.Value >>= sVersion; + else if ( rValue.Name == sPropMediaType ) + rValue.Value >>= sMediaType; + else if ( rValue.Name == sPropSalt ) + pSalt = &( rValue.Value ); + else if ( rValue.Name == sPropInitialisationVector ) + pVector = &( rValue.Value ); + else if ( rValue.Name == sPropIterationCount ) + pCount = &( rValue.Value ); + else if ( rValue.Name == sPropSize ) + pSize = &( rValue.Value ); + else if ( rValue.Name == sPropDigest ) + pDigest = &( rValue.Value ); + else if ( rValue.Name == sPropDigestAlgorithm ) + pDigestAlg = &( rValue.Value ); + else if ( rValue.Name == sPropEncryptionAlgorithm ) + pEncryptionAlg = &( rValue.Value ); + else if ( rValue.Name == sPropStartKeyAlgorithm ) + pStartKeyAlg = &( rValue.Value ); + else if ( rValue.Name == sPropDerivedKeySize ) + pDerivedKeySize = &( rValue.Value ); + else if ( rValue.Name == sKeyInfo ) + pKeyInfo = &( rValue.Value ); + else if (rValue.Name == "KeyDerivationFunction") { + pKDF = &rValue.Value; + } else if (rValue.Name == "Argon2Args") { + pArgon2Args = &rValue.Value; + } + } + + if ( !sPath.isEmpty() && hasByHierarchicalName ( sPath ) ) + { + aAny = getByHierarchicalName( sPath ); + uno::Reference < XInterface > xTmp; + aAny >>= xTmp; + if (auto pFolder = dynamic_cast<ZipPackageFolder*>(xTmp.get())) + { + pFolder->SetMediaType ( sMediaType ); + pFolder->SetVersion ( sVersion ); + } + else if (auto pStream = dynamic_cast<ZipPackageStream*>(xTmp.get())) + { + pStream->SetMediaType ( sMediaType ); + pStream->SetFromManifest( true ); + + if (pKeyInfo + && pVector && pSize && pEncryptionAlg + && pKDF && pKDF->has<sal_Int32>() && pKDF->get<sal_Int32>() == xml::crypto::KDFID::PGP_RSA_OAEP_MGF1P + && ((pEncryptionAlg->has<sal_Int32>() + && pEncryptionAlg->get<sal_Int32>() == xml::crypto::CipherID::AES_GCM_W3C) + || (pDigest && pDigestAlg))) + { + uno::Sequence < sal_Int8 > aSequence; + sal_Int64 nSize = 0; + ::std::optional<sal_Int32> oDigestAlg; + sal_Int32 nEncryptionAlg = 0; + + pStream->SetToBeEncrypted ( true ); + + *pVector >>= aSequence; + pStream->setInitialisationVector ( aSequence ); + + *pSize >>= nSize; + pStream->setSize ( nSize ); + + if (pDigest && pDigestAlg) + { + *pDigest >>= aSequence; + pStream->setDigest(aSequence); + + assert(pDigestAlg->has<sal_Int32>()); + oDigestAlg.emplace(pDigestAlg->get<sal_Int32>()); + pStream->SetImportedChecksumAlgorithm(oDigestAlg); + } + + *pEncryptionAlg >>= nEncryptionAlg; + pStream->SetImportedEncryptionAlgorithm( nEncryptionAlg ); + + *pKeyInfo >>= m_aGpgProps; + + pStream->SetToBeCompressed ( true ); + pStream->SetToBeEncrypted ( true ); + pStream->SetIsEncrypted ( true ); + pStream->setIterationCount(::std::optional<sal_Int32>()); + pStream->setArgon2Args(::std::optional<::std::tuple<sal_Int32, sal_Int32, sal_Int32>>()); + + // clamp to default SHA256 start key magic value, + // c.f. ZipPackageStream::GetEncryptionKey() + // trying to get key value from properties + const sal_Int32 nStartKeyAlg = xml::crypto::DigestID::SHA256; + pStream->SetImportedStartKeyAlgorithm( nStartKeyAlg ); + + if (!m_bHasEncryptedEntries + && (pStream->getName() == "content.xml" + || pStream->getName() == "encrypted-package")) + { + m_bHasEncryptedEntries = true; + m_oChecksumDigestID = oDigestAlg; + m_nKeyDerivationFunctionID = xml::crypto::KDFID::PGP_RSA_OAEP_MGF1P; + m_nCommonEncryptionID = nEncryptionAlg; + m_nStartKeyGenerationID = nStartKeyAlg; + } + } + else if (pSalt + && pVector && pSize && pEncryptionAlg + && pKDF && pKDF->has<sal_Int32>() + && ((pKDF->get<sal_Int32>() == xml::crypto::KDFID::PBKDF2 && pCount) + || (pKDF->get<sal_Int32>() == xml::crypto::KDFID::Argon2id && pArgon2Args)) + && ((pEncryptionAlg->has<sal_Int32>() + && pEncryptionAlg->get<sal_Int32>() == xml::crypto::CipherID::AES_GCM_W3C) + || (pDigest && pDigestAlg))) + + { + uno::Sequence < sal_Int8 > aSequence; + sal_Int64 nSize = 0; + ::std::optional<sal_Int32> oDigestAlg; + sal_Int32 nKDF = 0; + sal_Int32 nEncryptionAlg = 0; + sal_Int32 nCount = 0; + sal_Int32 nDerivedKeySize = 16, nStartKeyAlg = xml::crypto::DigestID::SHA1; + + pStream->SetToBeEncrypted ( true ); + + *pSalt >>= aSequence; + pStream->setSalt ( aSequence ); + + *pVector >>= aSequence; + pStream->setInitialisationVector ( aSequence ); + + *pKDF >>= nKDF; + + if (pCount) + { + *pCount >>= nCount; + pStream->setIterationCount(::std::optional<sal_Int32>(nCount)); + } + + if (pArgon2Args) + { + uno::Sequence<sal_Int32> args; + *pArgon2Args >>= args; + assert(args.getLength() == 3); + ::std::optional<::std::tuple<sal_Int32, sal_Int32, sal_Int32>> oArgs; + oArgs.emplace(args[0], args[1], args[2]); + pStream->setArgon2Args(oArgs); + } + + *pSize >>= nSize; + pStream->setSize ( nSize ); + + if (pDigest && pDigestAlg) + { + *pDigest >>= aSequence; + pStream->setDigest(aSequence); + + assert(pDigestAlg->has<sal_Int32>()); + oDigestAlg.emplace(pDigestAlg->get<sal_Int32>()); + pStream->SetImportedChecksumAlgorithm(oDigestAlg); + } + + *pEncryptionAlg >>= nEncryptionAlg; + pStream->SetImportedEncryptionAlgorithm( nEncryptionAlg ); + + if ( pDerivedKeySize ) + *pDerivedKeySize >>= nDerivedKeySize; + pStream->SetImportedDerivedKeySize( nDerivedKeySize ); + + if ( pStartKeyAlg ) + *pStartKeyAlg >>= nStartKeyAlg; + pStream->SetImportedStartKeyAlgorithm( nStartKeyAlg ); + + pStream->SetToBeCompressed ( true ); + pStream->SetToBeEncrypted ( true ); + pStream->SetIsEncrypted ( true ); + if (!m_bHasEncryptedEntries + && (pStream->getName() == "content.xml" + || pStream->getName() == "encrypted-package")) + { + m_bHasEncryptedEntries = true; + m_nStartKeyGenerationID = nStartKeyAlg; + m_nKeyDerivationFunctionID = nKDF; + m_oChecksumDigestID = oDigestAlg; + m_nCommonEncryptionID = nEncryptionAlg; + } + } + else + m_bHasNonEncryptedEntries = true; + } + else + throw ZipIOException(THROW_WHERE "Wrong content"); + } + } + + bManifestParsed = true; + } + + // now hide the manifest.xml file from user + xMetaInfFolder->removeByName( sManifest ); + } + } + catch( Exception& ) + { + if ( !m_bForceRecovery ) + throw; + } + } + + if ( !bManifestParsed && !m_bForceRecovery ) + throw ZipIOException( + THROW_WHERE "Could not parse manifest.xml" ); + + static constexpr OUString sMimetype (u"mimetype"_ustr); + if ( m_xRootFolder->hasByName( sMimetype ) ) + { + // get mediatype from the "mimetype" stream + OUString aPackageMediatype; + uno::Reference < io::XActiveDataSink > xMimeSink; + m_xRootFolder->getByName( sMimetype ) >>= xMimeSink; + if ( xMimeSink.is() ) + { + uno::Reference< io::XInputStream > xMimeInStream = xMimeSink->getInputStream(); + if ( xMimeInStream.is() ) + { + // Mediatypes longer than 1024 symbols should not appear here + uno::Sequence< sal_Int8 > aData( 1024 ); + sal_Int32 nRead = xMimeInStream->readBytes( aData, 1024 ); + if ( nRead > aData.getLength() ) + nRead = aData.getLength(); + + if ( nRead ) + aPackageMediatype = OUString( reinterpret_cast<char const *>(aData.getConstArray()), nRead, RTL_TEXTENCODING_ASCII_US ); + } + } + + if (!bManifestParsed || m_xRootFolder->GetMediaType().isEmpty()) + { + // the manifest.xml could not be successfully parsed, this is an inconsistent package + if ( aPackageMediatype.startsWith("application/vnd.") ) + { + // accept only types that look similar to own mediatypes + m_xRootFolder->SetMediaType( aPackageMediatype ); + // if there is an encrypted inner package, there is no root + // document, because instead there is a package, and it is not + // an error + if (!m_xRootFolder->hasByName("encrypted-package")) + { + m_bMediaTypeFallbackUsed = true; + } + } + } + else if ( !m_bForceRecovery ) + { + // the mimetype stream should contain the same information as manifest.xml + OUString const mediaTypeXML(m_xRootFolder->hasByName("encrypted-package") + ? m_xRootFolder->doGetByName("encrypted-package").xPackageEntry->GetMediaType() + : m_xRootFolder->GetMediaType()); + if (mediaTypeXML != aPackageMediatype) + { + throw ZipIOException( + THROW_WHERE + "mimetype conflicts with manifest.xml, \"" + + mediaTypeXML + "\" vs. \"" + + aPackageMediatype + "\"" ); + } + } + + m_xRootFolder->removeByName( sMimetype ); + } + + m_bInconsistent = m_xRootFolder->LookForUnexpectedODF12Streams( + std::u16string_view(), m_xRootFolder->hasByName("encrypted-package")); + + bool bODF12AndNewer = ( m_xRootFolder->GetVersion().compareTo( ODFVER_012_TEXT ) >= 0 ); + if ( !m_bForceRecovery && bODF12AndNewer ) + { + if ( m_bInconsistent ) + { + // this is an ODF1.2 document that contains streams not referred in the manifest.xml; + // in case of ODF1.2 documents without version in manifest.xml the property IsInconsistent + // should be checked later + throw ZipIOException( + THROW_WHERE "there are streams not referred in manifest.xml" ); + } + // all the streams should be encrypted with the same StartKey in ODF1.2 + // TODO/LATER: in future the exception should be thrown + // throw ZipIOException( THROW_WHERE "More than one Start Key Generation algorithm is specified!" ); + } + + // in case it is a correct ODF1.2 document, the version must be set + // and the META-INF folder is reserved for package format + if ( bODF12AndNewer ) + m_xRootFolder->removeByName( sMeta ); +} + +void ZipPackage::parseContentType() +{ + if ( m_nFormat != embed::StorageFormats::OFOPXML ) + return; + + try { + static constexpr OUString aContentTypes(u"[Content_Types].xml"_ustr); + // the content type must exist in OFOPXML format! + if ( !m_xRootFolder->hasByName( aContentTypes ) ) + throw io::IOException(THROW_WHERE "Wrong format!" ); + + uno::Reference < io::XActiveDataSink > xSink; + uno::Any aAny = m_xRootFolder->getByName( aContentTypes ); + aAny >>= xSink; + if ( xSink.is() ) + { + uno::Reference< io::XInputStream > xInStream = xSink->getInputStream(); + if ( xInStream.is() ) + { + // here aContentTypeInfo[0] - Defaults, and aContentTypeInfo[1] - Overrides + const uno::Sequence< uno::Sequence< beans::StringPair > > aContentTypeInfo = + ::comphelper::OFOPXMLHelper::ReadContentTypeSequence( xInStream, m_xContext ); + + if ( aContentTypeInfo.getLength() != 2 ) + throw io::IOException(THROW_WHERE ); + + // set the implicit types first + for ( const auto& rPair : aContentTypeInfo[0] ) + m_xRootFolder->setChildStreamsTypeByExtension( rPair ); + + // now set the explicit types + for ( const auto& rPair : aContentTypeInfo[1] ) + { + OUString aPath; + if ( rPair.First.toChar() == '/' ) + aPath = rPair.First.copy( 1 ); + else + aPath = rPair.First; + + if ( !aPath.isEmpty() && hasByHierarchicalName( aPath ) ) + { + uno::Any aIterAny = getByHierarchicalName( aPath ); + uno::Reference < XInterface > xIterTmp; + aIterAny >>= xIterTmp; + if (auto pStream = dynamic_cast<ZipPackageStream*>(xIterTmp.get())) + { + // this is a package stream, in OFOPXML format only streams can have mediatype + pStream->SetMediaType( rPair.Second ); + } + } + } + } + } + + m_xRootFolder->removeByName( aContentTypes ); + } + catch( uno::Exception& ) + { + if ( !m_bForceRecovery ) + throw; + } +} + +void ZipPackage::getZipFileContents() +{ + ZipEnumeration aEnum = m_pZipFile->entries(); + OUString sTemp, sDirName; + sal_Int32 nOldIndex, nStreamIndex; + FolderHash::iterator aIter; + + while (aEnum.hasMoreElements()) + { + nOldIndex = 0; + ZipPackageFolder* pCurrent = m_xRootFolder.get(); + const ZipEntry & rEntry = *aEnum.nextElement(); + OUString rName = rEntry.sPath; + + if ( m_bForceRecovery ) + { + // the PKZIP Application note version 6.2 does not allows to use '\' as separator + // unfortunately it is used by some implementations, so we have to support it in recovery mode + rName = rName.replace( '\\', '/' ); + } + + nStreamIndex = rName.lastIndexOf ( '/' ); + if ( nStreamIndex != -1 ) + { + sDirName = rName.copy ( 0, nStreamIndex ); + aIter = m_aRecent.find ( sDirName ); + if ( aIter != m_aRecent.end() ) + pCurrent = ( *aIter ).second; + } + + if ( pCurrent == m_xRootFolder.get() ) + { + sal_Int32 nIndex; + while ( ( nIndex = rName.indexOf( '/', nOldIndex ) ) != -1 ) + { + sTemp = rName.copy ( nOldIndex, nIndex - nOldIndex ); + if ( nIndex == nOldIndex ) + break; + if ( !pCurrent->hasByName( sTemp ) ) + { + rtl::Reference<ZipPackageFolder> pPkgFolder = new ZipPackageFolder(m_xContext, m_nFormat, m_bAllowRemoveOnInsert); + pPkgFolder->setName( sTemp ); + pPkgFolder->doSetParent( pCurrent ); + pCurrent = pPkgFolder.get(); + } + else + { + ZipContentInfo& rInfo = pCurrent->doGetByName(sTemp); + if (!rInfo.bFolder) + throw css::packages::zip::ZipIOException("Bad Zip File, stream as folder"); + pCurrent = rInfo.pFolder; + } + nOldIndex = nIndex+1; + } + if ( nStreamIndex != -1 && !sDirName.isEmpty() ) + m_aRecent [ sDirName ] = pCurrent; + } + if ( rName.getLength() -1 != nStreamIndex ) + { + nStreamIndex++; + sTemp = rName.copy( nStreamIndex ); + + if (!pCurrent->hasByName(sTemp)) + { + rtl::Reference<ZipPackageStream> pPkgStream = new ZipPackageStream(*this, m_xContext, m_nFormat, m_bAllowRemoveOnInsert); + pPkgStream->SetPackageMember(true); + pPkgStream->setZipEntryOnLoading(rEntry); + pPkgStream->setName(sTemp); + pPkgStream->doSetParent(pCurrent); + } + } + } + + if ( m_nFormat == embed::StorageFormats::PACKAGE ) + parseManifest(); + else if ( m_nFormat == embed::StorageFormats::OFOPXML ) + parseContentType(); +} + +void SAL_CALL ZipPackage::initialize( const uno::Sequence< Any >& aArguments ) +{ + beans::NamedValue aNamedValue; + + if ( !aArguments.hasElements() ) + return; + + bool bHaveZipFile = true; + + for( const auto& rArgument : aArguments ) + { + OUString aParamUrl; + if ( rArgument >>= aParamUrl ) + { + m_eMode = e_IMode_URL; + try + { + sal_Int32 nParam = aParamUrl.indexOf( '?' ); + if ( nParam >= 0 ) + { + m_aURL = aParamUrl.copy( 0, nParam ); + std::u16string_view aParam = aParamUrl.subView( nParam + 1 ); + + sal_Int32 nIndex = 0; + do + { + std::u16string_view aCommand = o3tl::getToken(aParam, 0, '&', nIndex ); + if ( aCommand == u"repairpackage" ) + { + m_bForceRecovery = true; + break; + } + else if ( aCommand == u"purezip" ) + { + m_nFormat = embed::StorageFormats::ZIP; + m_xRootFolder->setPackageFormat_Impl( m_nFormat ); + break; + } + else if ( aCommand == u"ofopxml" ) + { + m_nFormat = embed::StorageFormats::OFOPXML; + m_xRootFolder->setPackageFormat_Impl( m_nFormat ); + break; + } + } + while ( nIndex >= 0 ); + } + else + m_aURL = aParamUrl; + + Content aContent( + m_aURL, uno::Reference< XCommandEnvironment >(), + m_xContext ); + Any aAny = aContent.getPropertyValue("Size"); + sal_uInt64 aSize = 0; + // kind of optimization: treat empty files as nonexistent files + // and write to such files directly. Note that "Size" property is optional. + bool bHasSizeProperty = aAny >>= aSize; + if( !bHasSizeProperty || aSize ) + { + uno::Reference < XActiveDataSink > xSink = new ZipPackageSink; + if ( aContent.openStream ( xSink ) ) + m_xContentStream = xSink->getInputStream(); + } + else + bHaveZipFile = false; + } + catch ( css::uno::Exception& ) + { + // Exception derived from uno::Exception thrown. This probably + // means the file doesn't exist...we'll create it at + // commitChanges time + bHaveZipFile = false; + } + } + else if ( rArgument >>= m_xStream ) + { + // a writable stream can implement both XStream & XInputStream + m_eMode = e_IMode_XStream; + m_xContentStream = m_xStream->getInputStream(); + } + else if ( rArgument >>= m_xContentStream ) + { + m_eMode = e_IMode_XInputStream; + } + else if ( rArgument >>= aNamedValue ) + { + if ( aNamedValue.Name == "RepairPackage" ) + aNamedValue.Value >>= m_bForceRecovery; + else if ( aNamedValue.Name == "PackageFormat" ) + { + // setting this argument to true means Package format + // setting it to false means plain Zip format + + bool bPackFormat = true; + aNamedValue.Value >>= bPackFormat; + if ( !bPackFormat ) + m_nFormat = embed::StorageFormats::ZIP; + + m_xRootFolder->setPackageFormat_Impl( m_nFormat ); + } + else if ( aNamedValue.Name == "StorageFormat" ) + { + OUString aFormatName; + sal_Int32 nFormatID = 0; + if ( aNamedValue.Value >>= aFormatName ) + { + if ( aFormatName == PACKAGE_STORAGE_FORMAT_STRING ) + m_nFormat = embed::StorageFormats::PACKAGE; + else if ( aFormatName == ZIP_STORAGE_FORMAT_STRING ) + m_nFormat = embed::StorageFormats::ZIP; + else if ( aFormatName == OFOPXML_STORAGE_FORMAT_STRING ) + m_nFormat = embed::StorageFormats::OFOPXML; + else + throw lang::IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); + } + else if ( aNamedValue.Value >>= nFormatID ) + { + if (nFormatID != embed::StorageFormats::PACKAGE + && nFormatID != embed::StorageFormats::ZIP + && nFormatID != embed::StorageFormats::OFOPXML) + throw lang::IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); + + m_nFormat = nFormatID; + } + else + throw lang::IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); + + m_xRootFolder->setPackageFormat_Impl( m_nFormat ); + } + else if ( aNamedValue.Name == "AllowRemoveOnInsert" ) + { + aNamedValue.Value >>= m_bAllowRemoveOnInsert; + m_xRootFolder->setRemoveOnInsertMode_Impl( m_bAllowRemoveOnInsert ); + } + else if (aNamedValue.Name == "NoFileSync") + aNamedValue.Value >>= m_bDisableFileSync; + + // for now the progress handler is not used, probably it will never be + // if ( aNamedValue.Name == "ProgressHandler" ) + } + else + { + // The URL is not acceptable + throw css::uno::Exception (THROW_WHERE "Bad arguments.", + getXWeak() ); + } + } + + try + { + if ( m_xContentStream.is() ) + { + // the stream must be seekable, if it is not it will be wrapped + m_xContentStream = ::comphelper::OSeekableInputWrapper::CheckSeekableCanWrap( m_xContentStream, m_xContext ); + m_xContentSeek.set( m_xContentStream, UNO_QUERY_THROW ); + if ( !m_xContentSeek->getLength() ) + bHaveZipFile = false; + } + else + bHaveZipFile = false; + } + catch ( css::uno::Exception& ) + { + // Exception derived from uno::Exception thrown. This probably + // means the file doesn't exist...we'll create it at + // commitChanges time + bHaveZipFile = false; + } + if ( !bHaveZipFile ) + return; + + bool bBadZipFile = false; + OUString message; + try + { + m_pZipFile.emplace(m_aMutexHolder, m_xContentStream, m_xContext, true, m_bForceRecovery); + getZipFileContents(); + } + catch ( IOException & e ) + { + bBadZipFile = true; + message = "IOException: " + e.Message; + } + catch ( ZipException & e ) + { + bBadZipFile = true; + message = "ZipException: " + e.Message; + } + catch ( Exception & ) + { + m_pZipFile.reset(); + throw; + } + + if ( bBadZipFile ) + { + // clean up the memory, and tell the UCB about the error + m_pZipFile.reset(); + + throw css::packages::zip::ZipIOException ( + THROW_WHERE "Bad Zip File, " + message, + getXWeak() ); + } +} + +Any SAL_CALL ZipPackage::getByHierarchicalName( const OUString& aName ) +{ + OUString sTemp, sDirName; + sal_Int32 nOldIndex, nStreamIndex; + FolderHash::iterator aIter; + + sal_Int32 nIndex = aName.getLength(); + + if (aName == "/") + // root directory. + return Any ( uno::Reference( cppu::getXWeak(m_xRootFolder.get()) ) ); + + nStreamIndex = aName.lastIndexOf ( '/' ); + bool bFolder = nStreamIndex == nIndex-1; // last character is '/'. + + if ( nStreamIndex != -1 ) + { + // The name contains '/'. + sDirName = aName.copy ( 0, nStreamIndex ); + aIter = m_aRecent.find ( sDirName ); + if ( aIter != m_aRecent.end() ) + { + // There is a cached entry for this name. + + ZipPackageFolder* pFolder = aIter->second; + + if ( bFolder ) + { + // Determine the directory name. + sal_Int32 nDirIndex = aName.lastIndexOf ( '/', nStreamIndex ); + sTemp = aName.copy ( nDirIndex == -1 ? 0 : nDirIndex+1, nStreamIndex-nDirIndex-1 ); + + if (pFolder && sTemp == pFolder->getName()) + return Any(uno::Reference(cppu::getXWeak(pFolder))); + } + else + { + // Determine the file name. + sTemp = aName.copy ( nStreamIndex + 1 ); + + if (pFolder && pFolder->hasByName(sTemp)) + return pFolder->getByName(sTemp); + } + + m_aRecent.erase( aIter ); + } + } + else if ( m_xRootFolder->hasByName ( aName ) ) + // top-level element. + return m_xRootFolder->getByName ( aName ); + + // Not in the cache. Search normally. + + nOldIndex = 0; + ZipPackageFolder * pCurrent = m_xRootFolder.get(); + ZipPackageFolder * pPrevious = nullptr; + + // Find the right directory for the given path. + + while ( ( nIndex = aName.indexOf( '/', nOldIndex )) != -1 ) + { + sTemp = aName.copy ( nOldIndex, nIndex - nOldIndex ); + if ( nIndex == nOldIndex ) + break; + if ( !pCurrent->hasByName( sTemp ) ) + throw NoSuchElementException(THROW_WHERE ); + + pPrevious = pCurrent; + ZipContentInfo& rInfo = pCurrent->doGetByName(sTemp); + if (!rInfo.bFolder) + throw css::packages::zip::ZipIOException("Bad Zip File, stream as folder"); + pCurrent = rInfo.pFolder; + nOldIndex = nIndex+1; + } + + if ( bFolder ) + { + if ( nStreamIndex != -1 ) + m_aRecent[sDirName] = pPrevious; // cache it. + return Any ( uno::Reference( cppu::getXWeak(pCurrent) ) ); + } + + sTemp = aName.copy( nOldIndex ); + + if ( pCurrent->hasByName ( sTemp ) ) + { + if ( nStreamIndex != -1 ) + m_aRecent[sDirName] = pCurrent; // cache it. + return pCurrent->getByName( sTemp ); + } + + throw NoSuchElementException(THROW_WHERE); +} + +sal_Bool SAL_CALL ZipPackage::hasByHierarchicalName( const OUString& aName ) +{ + OUString sTemp; + sal_Int32 nOldIndex; + FolderHash::iterator aIter; + + sal_Int32 nIndex = aName.getLength(); + + if (aName == "/") + // root directory + return true; + + try + { + OUString sDirName; + sal_Int32 nStreamIndex; + nStreamIndex = aName.lastIndexOf ( '/' ); + bool bFolder = nStreamIndex == nIndex-1; + if ( nStreamIndex != -1 ) + { + sDirName = aName.copy ( 0, nStreamIndex ); + aIter = m_aRecent.find ( sDirName ); + if ( aIter != m_aRecent.end() ) + { + if ( bFolder ) + { + sal_Int32 nDirIndex = aName.lastIndexOf ( '/', nStreamIndex ); + sTemp = aName.copy ( nDirIndex == -1 ? 0 : nDirIndex+1, nStreamIndex-nDirIndex-1 ); + if ( sTemp == ( *aIter ).second->getName() ) + return true; + else + m_aRecent.erase ( aIter ); + } + else + { + sTemp = aName.copy ( nStreamIndex + 1 ); + if ( ( *aIter ).second->hasByName( sTemp ) ) + return true; + else + m_aRecent.erase( aIter ); + } + } + } + else + { + if ( m_xRootFolder->hasByName ( aName ) ) + return true; + } + ZipPackageFolder * pCurrent = m_xRootFolder.get(); + ZipPackageFolder * pPrevious = nullptr; + nOldIndex = 0; + while ( ( nIndex = aName.indexOf( '/', nOldIndex )) != -1 ) + { + sTemp = aName.copy ( nOldIndex, nIndex - nOldIndex ); + if ( nIndex == nOldIndex ) + break; + if ( pCurrent->hasByName( sTemp ) ) + { + pPrevious = pCurrent; + ZipContentInfo& rInfo = pCurrent->doGetByName(sTemp); + if (!rInfo.bFolder) + throw css::packages::zip::ZipIOException("Bad Zip File, stream as folder"); + pCurrent = rInfo.pFolder; + } + else + return false; + nOldIndex = nIndex+1; + } + if ( bFolder ) + { + m_aRecent[sDirName] = pPrevious; + return true; + } + else + { + sTemp = aName.copy( nOldIndex ); + + if ( pCurrent->hasByName( sTemp ) ) + { + m_aRecent[sDirName] = pCurrent; + return true; + } + } + } + catch (const uno::RuntimeException &) + { + throw; + } + catch (const uno::Exception&) + { + uno::Any e(::cppu::getCaughtException()); + throw lang::WrappedTargetRuntimeException("ZipPackage::hasByHierarchicalName", nullptr, e); + } + return false; +} + +uno::Reference< XInterface > SAL_CALL ZipPackage::createInstance() +{ + uno::Reference < XInterface > xRef = *( new ZipPackageStream( *this, m_xContext, m_nFormat, m_bAllowRemoveOnInsert ) ); + return xRef; +} + +uno::Reference< XInterface > SAL_CALL ZipPackage::createInstanceWithArguments( const uno::Sequence< Any >& aArguments ) +{ + bool bArg = false; + uno::Reference < XInterface > xRef; + if ( aArguments.hasElements() ) + aArguments[0] >>= bArg; + if ( bArg ) + xRef = *new ZipPackageFolder( m_xContext, m_nFormat, m_bAllowRemoveOnInsert ); + else + xRef = *new ZipPackageStream( *this, m_xContext, m_nFormat, m_bAllowRemoveOnInsert ); + + return xRef; +} + +void ZipPackage::WriteMimetypeMagicFile( ZipOutputStream& aZipOut ) +{ + static constexpr OUString sMime (u"mimetype"_ustr); + if ( m_xRootFolder->hasByName( sMime ) ) + m_xRootFolder->removeByName( sMime ); + + ZipEntry * pEntry = new ZipEntry; + sal_Int32 nBufferLength = m_xRootFolder->GetMediaType().getLength(); + OString sMediaType = OUStringToOString( m_xRootFolder->GetMediaType(), RTL_TEXTENCODING_ASCII_US ); + const uno::Sequence< sal_Int8 > aType( reinterpret_cast<sal_Int8 const *>(sMediaType.getStr()), + nBufferLength ); + + pEntry->sPath = sMime; + pEntry->nMethod = STORED; + pEntry->nSize = pEntry->nCompressedSize = nBufferLength; + pEntry->nTime = ZipOutputStream::getCurrentDosTime(); + + CRC32 aCRC32; + aCRC32.update( aType ); + pEntry->nCrc = aCRC32.getValue(); + + try + { + ZipOutputStream::setEntry(pEntry); + aZipOut.writeLOC(pEntry); + aZipOut.rawWrite(aType); + aZipOut.rawCloseEntry(); + } + catch ( const css::io::IOException & ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw WrappedTargetException( + THROW_WHERE "Error adding mimetype to the ZipOutputStream!", + getXWeak(), + anyEx ); + } +} + +void ZipPackage::WriteManifest( ZipOutputStream& aZipOut, const std::vector< uno::Sequence < PropertyValue > >& aManList ) +{ + // Write the manifest + uno::Reference < XManifestWriter > xWriter = ManifestWriter::create( m_xContext ); + ZipEntry * pEntry = new ZipEntry; + rtl::Reference<ZipPackageBuffer> pBuffer = new ZipPackageBuffer; + + pEntry->sPath = "META-INF/manifest.xml"; + pEntry->nMethod = DEFLATED; + pEntry->nCrc = -1; + pEntry->nSize = pEntry->nCompressedSize = -1; + pEntry->nTime = ZipOutputStream::getCurrentDosTime(); + + xWriter->writeManifestSequence ( pBuffer, comphelper::containerToSequence(aManList) ); + + sal_Int32 nBufferLength = static_cast < sal_Int32 > ( pBuffer->getPosition() ); + pBuffer->realloc( nBufferLength ); + + // the manifest.xml is never encrypted - so pass an empty reference + ZipOutputStream::setEntry(pEntry); + aZipOut.writeLOC(pEntry); + ZipOutputEntry aZipEntry(aZipOut.getStream(), m_xContext, *pEntry, nullptr, /*bEncrypt*/false); + aZipEntry.write(pBuffer->getSequence()); + aZipEntry.closeEntry(); + aZipOut.rawCloseEntry(); +} + +void ZipPackage::WriteContentTypes( ZipOutputStream& aZipOut, const std::vector< uno::Sequence < PropertyValue > >& aManList ) +{ + ZipEntry* pEntry = new ZipEntry; + rtl::Reference<ZipPackageBuffer> pBuffer = new ZipPackageBuffer; + + pEntry->sPath = "[Content_Types].xml"; + pEntry->nMethod = DEFLATED; + pEntry->nCrc = -1; + pEntry->nSize = pEntry->nCompressedSize = -1; + pEntry->nTime = ZipOutputStream::getCurrentDosTime(); + + // Add default entries, the count must be updated manually when appending. + // Add at least the standard default entries. + uno::Sequence< beans::StringPair > aDefaultsSequence + { + { "xml", "application/xml" }, + { "rels", "application/vnd.openxmlformats-package.relationships+xml" }, + { "png", "image/png" }, + { "jpeg", "image/jpeg" } + }; + + uno::Sequence< beans::StringPair > aOverridesSequence(aManList.size()); + auto aOverridesSequenceRange = asNonConstRange(aOverridesSequence); + sal_Int32 nOverSeqLength = 0; + for (const auto& rMan : aManList) + { + OUString aType; + OSL_ENSURE( rMan[PKG_MNFST_MEDIATYPE].Name == "MediaType" && rMan[PKG_MNFST_FULLPATH].Name == "FullPath", + "The mediatype sequence format is wrong!" ); + rMan[PKG_MNFST_MEDIATYPE].Value >>= aType; + if ( !aType.isEmpty() ) + { + OUString aPath; + // only nonempty type makes sense here + rMan[PKG_MNFST_FULLPATH].Value >>= aPath; + //FIXME: For now we have no way of differentiating defaults from others. + aOverridesSequenceRange[nOverSeqLength].First = "/" + aPath; + aOverridesSequenceRange[nOverSeqLength].Second = aType; + ++nOverSeqLength; + } + } + aOverridesSequence.realloc(nOverSeqLength); + + ::comphelper::OFOPXMLHelper::WriteContentSequence( + pBuffer, aDefaultsSequence, aOverridesSequence, m_xContext ); + + sal_Int32 nBufferLength = static_cast < sal_Int32 > ( pBuffer->getPosition() ); + pBuffer->realloc( nBufferLength ); + + // there is no encryption in this format currently + ZipOutputStream::setEntry(pEntry); + aZipOut.writeLOC(pEntry); + ZipOutputEntry aZipEntry(aZipOut.getStream(), m_xContext, *pEntry, nullptr, /*bEncrypt*/false); + aZipEntry.write(pBuffer->getSequence()); + aZipEntry.closeEntry(); + aZipOut.rawCloseEntry(); +} + +void ZipPackage::ConnectTo( const uno::Reference< io::XInputStream >& xInStream ) +{ + m_xContentSeek.set( xInStream, uno::UNO_QUERY_THROW ); + m_xContentStream = xInStream; + + // seek back to the beginning of the temp file so we can read segments from it + m_xContentSeek->seek( 0 ); + if ( m_pZipFile ) + m_pZipFile->setInputStream( m_xContentStream ); + else + m_pZipFile.emplace(m_aMutexHolder, m_xContentStream, m_xContext, false); +} + +namespace +{ + class RandomPool + { + private: + rtlRandomPool m_aRandomPool; + public: + RandomPool() : m_aRandomPool(rtl_random_createPool ()) + { + } + rtlRandomPool get() + { + return m_aRandomPool; + } + ~RandomPool() + { + // Clean up random pool memory + rtl_random_destroyPool(m_aRandomPool); + } + }; +} + +uno::Reference< io::XInputStream > ZipPackage::writeTempFile() +{ + // In case the target local file does not exist or empty + // write directly to it otherwise create a temporary file to write to. + // If a temporary file is created it is returned back by the method. + // If the data written directly, xComponentStream will be switched here + + bool bUseTemp = true; + uno::Reference < io::XInputStream > xResult; + uno::Reference < io::XInputStream > xTempIn; + + uno::Reference < io::XOutputStream > xTempOut; + uno::Reference< io::XActiveDataStreamer > xSink; + + if ( m_eMode == e_IMode_URL && !m_pZipFile && isLocalFile() ) + { + xSink = openOriginalForOutput(); + if( xSink.is() ) + { + uno::Reference< io::XStream > xStr = xSink->getStream(); + if( xStr.is() ) + { + xTempOut = xStr->getOutputStream(); + if( xTempOut.is() ) + bUseTemp = false; + } + } + } + else if ( m_eMode == e_IMode_XStream && !m_pZipFile ) + { + // write directly to an empty stream + xTempOut = m_xStream->getOutputStream(); + if( xTempOut.is() ) + bUseTemp = false; + } + + if( bUseTemp ) + { + // create temporary file + rtl::Reference < utl::TempFileFastService > xTempFile( new utl::TempFileFastService ); + xTempOut.set( xTempFile ); + xTempIn.set( xTempFile ); + } + + // Hand it to the ZipOutputStream: + ZipOutputStream aZipOut( xTempOut ); + try + { + if ( m_nFormat == embed::StorageFormats::PACKAGE ) + { + // Remove the old manifest.xml file as the + // manifest will be re-generated and the + // META-INF directory implicitly created if does not exist + static constexpr OUString sMeta (u"META-INF"_ustr); + + if ( m_xRootFolder->hasByName( sMeta ) ) + { + static constexpr OUString sManifest (u"manifest.xml"_ustr); + + uno::Reference< XNameContainer > xMetaInfFolder; + Any aAny = m_xRootFolder->getByName( sMeta ); + aAny >>= xMetaInfFolder; + if ( xMetaInfFolder.is() && xMetaInfFolder->hasByName( sManifest ) ) + xMetaInfFolder->removeByName( sManifest ); + } + + // Write a magic file with mimetype + WriteMimetypeMagicFile( aZipOut ); + } + else if ( m_nFormat == embed::StorageFormats::OFOPXML ) + { + // Remove the old [Content_Types].xml file as the + // file will be re-generated + + static constexpr OUString aContentTypes(u"[Content_Types].xml"_ustr); + + if ( m_xRootFolder->hasByName( aContentTypes ) ) + m_xRootFolder->removeByName( aContentTypes ); + } + + // Create a vector to store data for the manifest.xml file + std::vector < uno::Sequence < PropertyValue > > aManList; + + static constexpr OUStringLiteral sMediaType(u"MediaType"); + static constexpr OUStringLiteral sVersion(u"Version"); + static constexpr OUStringLiteral sFullPath(u"FullPath"); + const bool bIsGpgEncrypt = m_aGpgProps.hasElements(); + + // note: this is always created here (needed for GPG), possibly + // filtered out later in ManifestExport + if ( m_nFormat == embed::StorageFormats::PACKAGE ) + { + uno::Sequence < PropertyValue > aPropSeq( + bIsGpgEncrypt ? PKG_SIZE_NOENCR_MNFST+1 : PKG_SIZE_NOENCR_MNFST ); + auto pPropSeq = aPropSeq.getArray(); + pPropSeq [PKG_MNFST_MEDIATYPE].Name = sMediaType; + pPropSeq [PKG_MNFST_MEDIATYPE].Value <<= m_xRootFolder->GetMediaType(); + pPropSeq [PKG_MNFST_VERSION].Name = sVersion; + pPropSeq [PKG_MNFST_VERSION].Value <<= m_xRootFolder->GetVersion(); + pPropSeq [PKG_MNFST_FULLPATH].Name = sFullPath; + pPropSeq [PKG_MNFST_FULLPATH].Value <<= OUString("/"); + + if( bIsGpgEncrypt ) + { + pPropSeq[PKG_SIZE_NOENCR_MNFST].Name = "KeyInfo"; + pPropSeq[PKG_SIZE_NOENCR_MNFST].Value <<= m_aGpgProps; + } + aManList.push_back( aPropSeq ); + } + + { + // This will be used to generate random salt and initialisation vectors + // for encrypted streams + RandomPool aRandomPool; + + ::std::optional<sal_Int32> oPBKDF2IterationCount; + ::std::optional<::std::tuple<sal_Int32, sal_Int32, sal_Int32>> oArgon2Args; + + if (!bIsGpgEncrypt) + { + if (m_nKeyDerivationFunctionID == xml::crypto::KDFID::PBKDF2) + { // if there is only one KDF invocation, increase the safety margin + oPBKDF2IterationCount.emplace(officecfg::Office::Common::Misc::ExperimentalMode::get() ? 600000 : 100000); + } + else + { + assert(m_nKeyDerivationFunctionID == xml::crypto::KDFID::Argon2id); + oArgon2Args.emplace(3, (1<<16), 4); + } + } + + // call saveContents - it will recursively save sub-directories + m_xRootFolder->saveContents("", aManList, aZipOut, GetEncryptionKey(), + oPBKDF2IterationCount, oArgon2Args, aRandomPool.get()); + } + + if( m_nFormat == embed::StorageFormats::PACKAGE ) + { + WriteManifest( aZipOut, aManList ); + } + else if( m_nFormat == embed::StorageFormats::OFOPXML ) + { + WriteContentTypes( aZipOut, aManList ); + } + + aZipOut.finish(); + + if( bUseTemp ) + xResult = xTempIn; + + // Update our References to point to the new temp file + if( !bUseTemp ) + { + // the case when the original contents were written directly + xTempOut->flush(); + + // in case the stream is based on a file it will implement the following interface + // the call should be used to be sure that the contents are written to the file system + uno::Reference< io::XAsyncOutputMonitor > asyncOutputMonitor( xTempOut, uno::UNO_QUERY ); + if (asyncOutputMonitor.is() && !m_bDisableFileSync) + asyncOutputMonitor->waitForCompletion(); + + // no need to postpone switching to the new stream since the target was written directly + uno::Reference< io::XInputStream > xNewStream; + if ( m_eMode == e_IMode_URL ) + xNewStream = xSink->getStream()->getInputStream(); + else if ( m_eMode == e_IMode_XStream && m_xStream.is() ) + xNewStream = m_xStream->getInputStream(); + + if ( xNewStream.is() ) + ConnectTo( xNewStream ); + } + } + catch ( uno::Exception& ) + { + if( bUseTemp ) + { + // no information loss appears, thus no special handling is required + uno::Any aCaught( ::cppu::getCaughtException() ); + + // it is allowed to throw WrappedTargetException + WrappedTargetException aException; + if ( aCaught >>= aException ) + throw aException; + + throw WrappedTargetException( + THROW_WHERE "Problem writing the original content!", + getXWeak(), + aCaught ); + } + else + { + // the document is written directly, although it was empty it is important to notify that the writing has failed + // TODO/LATER: let the package be able to recover in this situation + OUString aErrTxt(THROW_WHERE "This package is unusable!"); + embed::UseBackupException aException( aErrTxt, uno::Reference< uno::XInterface >(), OUString() ); + throw WrappedTargetException( aErrTxt, + getXWeak(), + Any ( aException ) ); + } + } + + return xResult; +} + +uno::Reference< XActiveDataStreamer > ZipPackage::openOriginalForOutput() +{ + // open and truncate the original file + Content aOriginalContent( + m_aURL, uno::Reference< XCommandEnvironment >(), + m_xContext ); + uno::Reference< XActiveDataStreamer > xSink = new ActiveDataStreamer; + + if ( m_eMode == e_IMode_URL ) + { + try + { + bool bTruncSuccess = false; + + try + { + Exception aDetect; + Any aAny = aOriginalContent.setPropertyValue("Size", Any( sal_Int64(0) ) ); + if( !( aAny >>= aDetect ) ) + bTruncSuccess = true; + } + catch( Exception& ) + { + } + + if( !bTruncSuccess ) + { + // the file is not accessible + // just try to write an empty stream to it + + uno::Reference< XInputStream > xTempIn = new DummyInputStream; //uno::Reference< XInputStream >( xTempOut, UNO_QUERY ); + aOriginalContent.writeStream( xTempIn , true ); + } + + OpenCommandArgument2 aArg; + aArg.Mode = OpenMode::DOCUMENT; + aArg.Priority = 0; // unused + aArg.Sink = xSink; + aArg.Properties = uno::Sequence< Property >( 0 ); // unused + + aOriginalContent.executeCommand("open", Any( aArg ) ); + } + catch( Exception& ) + { + // seems to be nonlocal file + // temporary file mechanics should be used + } + } + + return xSink; +} + +void SAL_CALL ZipPackage::commitChanges() +{ + // lock the component for the time of committing + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + if ( m_eMode == e_IMode_XInputStream ) + { + IOException aException; + throw WrappedTargetException(THROW_WHERE "This package is read only!", + getXWeak(), Any ( aException ) ); + } + // first the writeTempFile is called, if it returns a stream the stream should be written to the target + // if no stream was returned, the file was written directly, nothing should be done + uno::Reference< io::XInputStream > xTempInStream; + try + { + xTempInStream = writeTempFile(); + } + catch (const ucb::ContentCreationException&) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw WrappedTargetException(THROW_WHERE "Temporary file should be creatable!", + getXWeak(), anyEx ); + } + if ( xTempInStream.is() ) + { + uno::Reference< io::XSeekable > xTempSeek( xTempInStream, uno::UNO_QUERY_THROW ); + + try + { + xTempSeek->seek( 0 ); + } + catch( const uno::Exception& ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw WrappedTargetException(THROW_WHERE "Temporary file should be seekable!", + getXWeak(), anyEx ); + } + + try + { + // connect to the temporary stream + ConnectTo( xTempInStream ); + } + catch( const io::IOException& ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw WrappedTargetException(THROW_WHERE "Temporary file should be connectable!", + getXWeak(), anyEx ); + } + + if ( m_eMode == e_IMode_XStream ) + { + // First truncate our output stream + uno::Reference < XOutputStream > xOutputStream; + + // preparation for copy step + try + { + xOutputStream = m_xStream->getOutputStream(); + + // Make sure we avoid a situation where the current position is + // not zero, but the underlying file is truncated in the + // meantime. + uno::Reference<io::XSeekable> xSeekable(xOutputStream, uno::UNO_QUERY); + if (xSeekable.is()) + xSeekable->seek(0); + + uno::Reference < XTruncate > xTruncate ( xOutputStream, UNO_QUERY_THROW ); + + // after successful truncation the original file contents are already lost + xTruncate->truncate(); + } + catch( const uno::Exception& ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw WrappedTargetException(THROW_WHERE "This package is read only!", + getXWeak(), anyEx ); + } + + try + { + // then copy the contents of the tempfile to our output stream + ::comphelper::OStorageHelper::CopyInputToOutput( xTempInStream, xOutputStream ); + xOutputStream->flush(); + uno::Reference< io::XAsyncOutputMonitor > asyncOutputMonitor( + xOutputStream, uno::UNO_QUERY ); + if ( asyncOutputMonitor.is() ) { + asyncOutputMonitor->waitForCompletion(); + } + } + catch( uno::Exception& ) + { + // if anything goes wrong in this block the target file becomes corrupted + // so an exception should be thrown as a notification about it + // and the package must disconnect from the stream + DisconnectFromTargetAndThrowException_Impl( xTempInStream ); + } + } + else if ( m_eMode == e_IMode_URL ) + { + uno::Reference< XOutputStream > aOrigFileStream; + bool bCanBeCorrupted = false; + + if( isLocalFile() ) + { + // write directly in case of local file + uno::Reference< css::ucb::XSimpleFileAccess3 > xSimpleAccess( + SimpleFileAccess::create( m_xContext ) ); + OSL_ENSURE( xSimpleAccess.is(), "Can't instantiate SimpleFileAccess service!" ); + uno::Reference< io::XTruncate > xOrigTruncate; + if ( xSimpleAccess.is() ) + { + try + { + aOrigFileStream = xSimpleAccess->openFileWrite( m_aURL ); + xOrigTruncate.set( aOrigFileStream, uno::UNO_QUERY_THROW ); + // after successful truncation the file is already corrupted + xOrigTruncate->truncate(); + } + catch( uno::Exception& ) + {} + } + + if( xOrigTruncate.is() ) + { + try + { + ::comphelper::OStorageHelper::CopyInputToOutput( xTempInStream, aOrigFileStream ); + aOrigFileStream->closeOutput(); + } + catch( uno::Exception& ) + { + try { + aOrigFileStream->closeOutput(); + } catch ( uno::Exception& ) {} + + aOrigFileStream.clear(); + // the original file can already be corrupted + bCanBeCorrupted = true; + } + } + } + + if( !aOrigFileStream.is() ) + { + try + { + uno::Reference < XPropertySet > xPropSet ( xTempInStream, UNO_QUERY_THROW ); + + OUString sTargetFolder = m_aURL.copy ( 0, m_aURL.lastIndexOf ( u'/' ) ); + Content aContent( + sTargetFolder, uno::Reference< XCommandEnvironment >(), + m_xContext ); + + OUString sTempURL; + Any aAny = xPropSet->getPropertyValue ("Uri"); + aAny >>= sTempURL; + + TransferInfo aInfo; + aInfo.NameClash = NameClash::OVERWRITE; + aInfo.MoveData = false; + aInfo.SourceURL = sTempURL; + aInfo.NewTitle = rtl::Uri::decode ( m_aURL.copy ( 1 + m_aURL.lastIndexOf ( u'/' ) ), + rtl_UriDecodeWithCharset, + RTL_TEXTENCODING_UTF8 ); + // if the file is still not corrupted, it can become after the next step + aContent.executeCommand ("transfer", Any(aInfo) ); + } + catch ( const css::uno::Exception& ) + { + if ( bCanBeCorrupted ) + DisconnectFromTargetAndThrowException_Impl( xTempInStream ); + + css::uno::Any anyEx = cppu::getCaughtException(); + throw WrappedTargetException( + THROW_WHERE "This package may be read only!", + getXWeak(), + anyEx ); + } + } + } + } + + // after successful storing it can be set to false + m_bMediaTypeFallbackUsed = false; +} + +void ZipPackage::DisconnectFromTargetAndThrowException_Impl( const uno::Reference< io::XInputStream >& xTempStream ) +{ + m_xStream.set( xTempStream, uno::UNO_QUERY ); + if ( m_xStream.is() ) + m_eMode = e_IMode_XStream; + else + m_eMode = e_IMode_XInputStream; + + OUString aTempURL; + try { + uno::Reference< beans::XPropertySet > xTempFile( xTempStream, uno::UNO_QUERY_THROW ); + uno::Any aUrl = xTempFile->getPropertyValue("Uri"); + aUrl >>= aTempURL; + xTempFile->setPropertyValue("RemoveFile", + uno::Any( false ) ); + } + catch ( uno::Exception& ) + { + OSL_FAIL( "These calls are pretty simple, they should not fail!" ); + } + + OUString aErrTxt(THROW_WHERE "This package is read only!"); + embed::UseBackupException aException( aErrTxt, uno::Reference< uno::XInterface >(), aTempURL ); + throw WrappedTargetException( aErrTxt, + getXWeak(), + Any ( aException ) ); +} + +uno::Sequence< sal_Int8 > ZipPackage::GetEncryptionKey() +{ + uno::Sequence< sal_Int8 > aResult; + + if ( m_aStorageEncryptionKeys.hasElements() ) + { + OUString aNameToFind; + if ( m_nStartKeyGenerationID == xml::crypto::DigestID::SHA256 ) + aNameToFind = PACKAGE_ENCRYPTIONDATA_SHA256UTF8; + else if ( m_nStartKeyGenerationID == xml::crypto::DigestID::SHA1 ) + aNameToFind = PACKAGE_ENCRYPTIONDATA_SHA1CORRECT; + else + throw uno::RuntimeException(THROW_WHERE "No expected key is provided!" ); + + for ( const auto& rKey : std::as_const(m_aStorageEncryptionKeys) ) + if ( rKey.Name == aNameToFind ) + rKey.Value >>= aResult; + } + else + aResult = m_aEncryptionKey; + + return aResult; +} + +sal_Bool SAL_CALL ZipPackage::hasPendingChanges() +{ + return false; +} +Sequence< ElementChange > SAL_CALL ZipPackage::getPendingChanges() +{ + return uno::Sequence < ElementChange > (); +} + + +OUString ZipPackage::getImplementationName() +{ + return "com.sun.star.packages.comp.ZipPackage"; +} + +Sequence< OUString > ZipPackage::getSupportedServiceNames() +{ + return { "com.sun.star.packages.Package" }; +} + +sal_Bool SAL_CALL ZipPackage::supportsService( OUString const & rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Reference< XPropertySetInfo > SAL_CALL ZipPackage::getPropertySetInfo() +{ + return uno::Reference < XPropertySetInfo > (); +} + +void SAL_CALL ZipPackage::setPropertyValue( const OUString& aPropertyName, const Any& aValue ) +{ + if ( m_nFormat != embed::StorageFormats::PACKAGE ) + throw UnknownPropertyException(aPropertyName); + + if (aPropertyName == HAS_ENCRYPTED_ENTRIES_PROPERTY + ||aPropertyName == HAS_NONENCRYPTED_ENTRIES_PROPERTY + ||aPropertyName == IS_INCONSISTENT_PROPERTY + ||aPropertyName == MEDIATYPE_FALLBACK_USED_PROPERTY) + throw PropertyVetoException(THROW_WHERE ); + else if ( aPropertyName == ENCRYPTION_KEY_PROPERTY ) + { + if ( !( aValue >>= m_aEncryptionKey ) ) + throw IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 2 ); + + m_aStorageEncryptionKeys.realloc( 0 ); + } + else if ( aPropertyName == STORAGE_ENCRYPTION_KEYS_PROPERTY ) + { + // this property is only necessary to support raw passwords in storage API; + // because of this support the storage has to operate with more than one key dependent on storage generation algorithm; + // when this support is removed, the storage will get only one key from outside + if ( !( aValue >>= m_aStorageEncryptionKeys ) ) + throw IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 2 ); + + m_aEncryptionKey.realloc( 0 ); + } + else if ( aPropertyName == ENCRYPTION_ALGORITHMS_PROPERTY ) + { + uno::Sequence< beans::NamedValue > aAlgorithms; + if ( m_pZipFile || !( aValue >>= aAlgorithms ) || !aAlgorithms.hasElements() ) + { + // the algorithms can not be changed if the file has a persistence based on the algorithms ( m_pZipFile ) + throw IllegalArgumentException(THROW_WHERE "unexpected algorithms list is provided.", uno::Reference< uno::XInterface >(), 2 ); + } + + for ( const auto& rAlgorithm : std::as_const(aAlgorithms) ) + { + if ( rAlgorithm.Name == "StartKeyGenerationAlgorithm" ) + { + sal_Int32 nID = 0; + if ( !( rAlgorithm.Value >>= nID ) + || ( nID != xml::crypto::DigestID::SHA256 && nID != xml::crypto::DigestID::SHA1 ) ) + { + throw IllegalArgumentException(THROW_WHERE "Unexpected start key generation algorithm is provided!", uno::Reference<uno::XInterface>(), 2); + } + + m_nStartKeyGenerationID = nID; + } + else if (rAlgorithm.Name == "KeyDerivationFunction") + { + sal_Int32 nID = 0; + if (!(rAlgorithm.Value >>= nID) + || (nID != xml::crypto::KDFID::PBKDF2 + && nID != xml::crypto::KDFID::PGP_RSA_OAEP_MGF1P + && nID != xml::crypto::KDFID::Argon2id)) + { + throw IllegalArgumentException(THROW_WHERE "Unexpected key derivation function provided!", uno::Reference<uno::XInterface>(), 2); + } + m_nKeyDerivationFunctionID = nID; + } + else if ( rAlgorithm.Name == "EncryptionAlgorithm" ) + { + sal_Int32 nID = 0; + if ( !( rAlgorithm.Value >>= nID ) + || (nID != xml::crypto::CipherID::AES_GCM_W3C + && nID != xml::crypto::CipherID::AES_CBC_W3C_PADDING + && nID != xml::crypto::CipherID::BLOWFISH_CFB_8)) + { + throw IllegalArgumentException(THROW_WHERE "Unexpected encryption algorithm is provided!", uno::Reference<uno::XInterface>(), 2); + } + + m_nCommonEncryptionID = nID; + } + else if ( rAlgorithm.Name == "ChecksumAlgorithm" ) + { + sal_Int32 nID = 0; + if (!rAlgorithm.Value.hasValue()) + { + m_oChecksumDigestID.reset(); + continue; + } + if ( !( rAlgorithm.Value >>= nID ) + || ( nID != xml::crypto::DigestID::SHA1_1K && nID != xml::crypto::DigestID::SHA256_1K ) ) + { + throw IllegalArgumentException(THROW_WHERE "Unexpected checksum algorithm is provided!", uno::Reference<uno::XInterface>(), 2); + } + + m_oChecksumDigestID.emplace(nID); + } + else + { + OSL_ENSURE( false, "Unexpected encryption algorithm is provided!" ); + throw IllegalArgumentException(THROW_WHERE "unexpected algorithms list is provided.", uno::Reference< uno::XInterface >(), 2 ); + } + } + } + else if ( aPropertyName == ENCRYPTION_GPG_PROPERTIES ) + { + uno::Sequence< uno::Sequence< beans::NamedValue > > aGpgProps; + if ( !( aValue >>= aGpgProps ) || !aGpgProps.hasElements() ) + { + throw IllegalArgumentException(THROW_WHERE "unexpected Gpg properties are provided.", uno::Reference< uno::XInterface >(), 2 ); + } + + m_aGpgProps = aGpgProps; + + // override algorithm defaults (which are some legacy ODF + // defaults) with reasonable values + // note: these should be overridden by SfxObjectShell::SetupStorage() + m_nStartKeyGenerationID = 0; // this is unused for PGP + m_nKeyDerivationFunctionID = xml::crypto::KDFID::PGP_RSA_OAEP_MGF1P; + m_nCommonEncryptionID = xml::crypto::CipherID::AES_CBC_W3C_PADDING; + m_oChecksumDigestID.emplace(xml::crypto::DigestID::SHA512_1K); + } + else + throw UnknownPropertyException(aPropertyName); +} + +Any SAL_CALL ZipPackage::getPropertyValue( const OUString& PropertyName ) +{ + // TODO/LATER: Activate the check when zip-ucp is ready + // if ( m_nFormat != embed::StorageFormats::PACKAGE ) + // throw UnknownPropertyException(THROW_WHERE ); + + if ( PropertyName == ENCRYPTION_KEY_PROPERTY ) + { + return Any(m_aEncryptionKey); + } + else if ( PropertyName == ENCRYPTION_ALGORITHMS_PROPERTY ) + { + ::comphelper::SequenceAsHashMap aAlgorithms; + aAlgorithms["StartKeyGenerationAlgorithm"] <<= m_nStartKeyGenerationID; + aAlgorithms["KeyDerivationFunction"] <<= m_nKeyDerivationFunctionID; + aAlgorithms["EncryptionAlgorithm"] <<= m_nCommonEncryptionID; + if (m_oChecksumDigestID) + { + aAlgorithms["ChecksumAlgorithm"] <<= *m_oChecksumDigestID; + } + else + { + aAlgorithms["ChecksumAlgorithm"]; + } + return Any(aAlgorithms.getAsConstNamedValueList()); + } + if ( PropertyName == STORAGE_ENCRYPTION_KEYS_PROPERTY ) + { + return Any(m_aStorageEncryptionKeys); + } + else if ( PropertyName == HAS_ENCRYPTED_ENTRIES_PROPERTY ) + { + return Any(m_bHasEncryptedEntries); + } + else if ( PropertyName == ENCRYPTION_GPG_PROPERTIES ) + { + return Any(m_aGpgProps); + } + else if ( PropertyName == HAS_NONENCRYPTED_ENTRIES_PROPERTY ) + { + return Any(m_bHasNonEncryptedEntries); + } + else if ( PropertyName == IS_INCONSISTENT_PROPERTY ) + { + return Any(m_bInconsistent); + } + else if ( PropertyName == MEDIATYPE_FALLBACK_USED_PROPERTY ) + { + return Any(m_bMediaTypeFallbackUsed); + } + else if (PropertyName == "HasElements") + { + return Any(m_pZipFile && m_pZipFile->entries().hasMoreElements()); + } + throw UnknownPropertyException(PropertyName); +} +void SAL_CALL ZipPackage::addPropertyChangeListener( const OUString& /*aPropertyName*/, const uno::Reference< XPropertyChangeListener >& /*xListener*/ ) +{ +} +void SAL_CALL ZipPackage::removePropertyChangeListener( const OUString& /*aPropertyName*/, const uno::Reference< XPropertyChangeListener >& /*aListener*/ ) +{ +} +void SAL_CALL ZipPackage::addVetoableChangeListener( const OUString& /*PropertyName*/, const uno::Reference< XVetoableChangeListener >& /*aListener*/ ) +{ +} +void SAL_CALL ZipPackage::removeVetoableChangeListener( const OUString& /*PropertyName*/, const uno::Reference< XVetoableChangeListener >& /*aListener*/ ) +{ +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +package_ZipPackage_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new ZipPackage(context)); +} + +extern "C" bool TestImportZip(SvStream& rStream) +{ + // explicitly tests the "RepairPackage" recovery mode + rtl::Reference<ZipPackage> xPackage(new ZipPackage(comphelper::getProcessComponentContext())); + css::uno::Reference<css::io::XInputStream> xStream(new utl::OInputStreamWrapper(rStream)); + css::uno::Sequence<Any> aArgs{ Any(xStream), Any(NamedValue("RepairPackage", Any(true))) }; + xPackage->initialize(aArgs); + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zippackage/ZipPackageBuffer.cxx b/package/source/zippackage/ZipPackageBuffer.cxx new file mode 100644 index 0000000000..810daa737a --- /dev/null +++ b/package/source/zippackage/ZipPackageBuffer.cxx @@ -0,0 +1,128 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <ZipPackageBuffer.hxx> +#include <PackageConstants.hxx> +#include <string.h> +#include <sal/log.hxx> + +#include <com/sun/star/io/BufferSizeExceededException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> + +using namespace ::com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::io; +using com::sun::star::lang::IllegalArgumentException; + +#if OSL_DEBUG_LEVEL > 0 +#define THROW_WHERE SAL_WHERE +#else +#define THROW_WHERE "" +#endif + +ZipPackageBuffer::ZipPackageBuffer() +: m_nBufferSize (n_ConstBufferSize) +, m_nEnd(0) +, m_nCurrent(0) +, m_bMustInitBuffer ( true ) +{ +} +ZipPackageBuffer::~ZipPackageBuffer() +{ +} + +sal_Int32 SAL_CALL ZipPackageBuffer::readBytes( Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) +{ + if (nBytesToRead < 0) + throw BufferSizeExceededException(THROW_WHERE, *this ); + + if (nBytesToRead + m_nCurrent > m_nEnd) + nBytesToRead = static_cast < sal_Int32 > (m_nEnd - m_nCurrent); + + aData.realloc ( nBytesToRead ); + memcpy(aData.getArray(), m_aBuffer.getConstArray() + m_nCurrent, nBytesToRead); + m_nCurrent +=nBytesToRead; + return nBytesToRead; +} + +sal_Int32 SAL_CALL ZipPackageBuffer::readSomeBytes( Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) +{ + return readBytes(aData, nMaxBytesToRead); +} +void SAL_CALL ZipPackageBuffer::skipBytes( sal_Int32 nBytesToSkip ) +{ + if (nBytesToSkip < 0) + throw BufferSizeExceededException(THROW_WHERE, *this ); + + if (nBytesToSkip + m_nCurrent > m_nEnd) + nBytesToSkip = static_cast < sal_Int32 > (m_nEnd - m_nCurrent); + + m_nCurrent+=nBytesToSkip; +} +sal_Int32 SAL_CALL ZipPackageBuffer::available( ) +{ + return std::min<sal_Int64>(SAL_MAX_INT32, m_nEnd - m_nCurrent); +} +void SAL_CALL ZipPackageBuffer::closeInput( ) +{ +} +void SAL_CALL ZipPackageBuffer::writeBytes( const Sequence< sal_Int8 >& aData ) +{ + sal_Int64 nDataLen = aData.getLength(), nCombined = m_nEnd + nDataLen; + + if ( nCombined > m_nBufferSize) + { + do + m_nBufferSize *=2; + while (nCombined > m_nBufferSize); + m_aBuffer.realloc(static_cast < sal_Int32 > (m_nBufferSize)); + m_bMustInitBuffer = false; + } + else if (m_bMustInitBuffer) + { + m_aBuffer.realloc ( static_cast < sal_Int32 > ( m_nBufferSize ) ); + m_bMustInitBuffer = false; + } + memcpy( m_aBuffer.getArray() + m_nCurrent, aData.getConstArray(), static_cast < sal_Int32 > (nDataLen)); + m_nCurrent+=nDataLen; + if (m_nCurrent>m_nEnd) + m_nEnd = m_nCurrent; +} +void SAL_CALL ZipPackageBuffer::flush( ) +{ +} +void SAL_CALL ZipPackageBuffer::closeOutput( ) +{ +} +void SAL_CALL ZipPackageBuffer::seek( sal_Int64 location ) +{ + if ( location > m_nEnd || location < 0 ) + throw IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); + m_nCurrent = location; +} +sal_Int64 SAL_CALL ZipPackageBuffer::getPosition( ) +{ + return m_nCurrent; +} +sal_Int64 SAL_CALL ZipPackageBuffer::getLength( ) +{ + return m_nEnd; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zippackage/ZipPackageEntry.cxx b/package/source/zippackage/ZipPackageEntry.cxx new file mode 100644 index 0000000000..56337109ec --- /dev/null +++ b/package/source/zippackage/ZipPackageEntry.cxx @@ -0,0 +1,125 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <ZipPackageEntry.hxx> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <com/sun/star/packages/zip/ZipConstants.hpp> +#include <osl/diagnose.h> +#include <sal/log.hxx> + +#include <ZipPackageFolder.hxx> + +#include <comphelper/servicehelper.hxx> +#include <comphelper/storagehelper.hxx> + +using namespace com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::container; +using namespace com::sun::star::packages::zip; +using namespace com::sun::star::packages::zip::ZipConstants; + +#if OSL_DEBUG_LEVEL > 0 +#define THROW_WHERE SAL_WHERE +#else +#define THROW_WHERE "" +#endif + +ZipPackageEntry::ZipPackageEntry() +: mbIsFolder( false ) +, mbAllowRemoveOnInsert(false) +, mpParent ( nullptr ) +, m_nFormat(0) +{ +} + +ZipPackageEntry::~ZipPackageEntry() +{ + // When the entry is destroyed it must be already disconnected from the parent + OSL_ENSURE( !mpParent, "The parent must be disconnected already! Memory corruption is possible!" ); +} + +// XChild +OUString SAL_CALL ZipPackageEntry::getName( ) +{ + return msName; +} +void SAL_CALL ZipPackageEntry::setName( const OUString& aName ) +{ + if ( mpParent && !msName.isEmpty() && mpParent->hasByName ( msName ) ) + mpParent->removeByName ( msName ); + + // unfortunately no other exception than RuntimeException can be thrown here + // usually the package is used through storage implementation, the problem should be detected there + if ( !::comphelper::OStorageHelper::IsValidZipEntryFileName( aName, true ) ) + throw RuntimeException(THROW_WHERE "Unexpected character is used in file name." ); + + msName = aName; + + if ( mpParent ) + mpParent->doInsertByName ( this, false ); +} +uno::Reference< XInterface > SAL_CALL ZipPackageEntry::getParent( ) +{ + // return uno::Reference< XInterface >( xParent, UNO_QUERY ); + return cppu::getXWeak( mpParent ); +} + +void ZipPackageEntry::doSetParent ( ZipPackageFolder * pNewParent ) +{ + // xParent = mpParent = pNewParent; + mpParent = pNewParent; + if ( !msName.isEmpty() && !pNewParent->hasByName ( msName ) ) + pNewParent->doInsertByName ( this, false ); +} + +void SAL_CALL ZipPackageEntry::setParent( const uno::Reference< XInterface >& xNewParent ) +{ + if ( !xNewParent.is() ) + throw NoSupportException(THROW_WHERE ); + ZipPackageFolder* pNewParent = dynamic_cast<ZipPackageFolder*>(xNewParent.get()); + if (!pNewParent) + throw NoSupportException(THROW_WHERE ); + + if ( pNewParent != mpParent ) + { + if ( mpParent && !msName.isEmpty() && mpParent->hasByName ( msName ) && mbAllowRemoveOnInsert ) + mpParent->removeByName( msName ); + doSetParent ( pNewParent ); + } +} + //XPropertySet +uno::Reference< beans::XPropertySetInfo > SAL_CALL ZipPackageEntry::getPropertySetInfo( ) +{ + return uno::Reference < beans::XPropertySetInfo > (); +} +void SAL_CALL ZipPackageEntry::addPropertyChangeListener( const OUString& /*aPropertyName*/, const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/ ) +{ +} +void SAL_CALL ZipPackageEntry::removePropertyChangeListener( const OUString& /*aPropertyName*/, const uno::Reference< beans::XPropertyChangeListener >& /*aListener*/ ) +{ +} +void SAL_CALL ZipPackageEntry::addVetoableChangeListener( const OUString& /*PropertyName*/, const uno::Reference< beans::XVetoableChangeListener >& /*aListener*/ ) +{ +} +void SAL_CALL ZipPackageEntry::removeVetoableChangeListener( const OUString& /*PropertyName*/, const uno::Reference< beans::XVetoableChangeListener >& /*aListener*/ ) +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zippackage/ZipPackageFolder.cxx b/package/source/zippackage/ZipPackageFolder.cxx new file mode 100644 index 0000000000..bca4e46e1b --- /dev/null +++ b/package/source/zippackage/ZipPackageFolder.cxx @@ -0,0 +1,425 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <ZipPackageFolder.hxx> +#include <ZipOutputStream.hxx> +#include <ZipPackageStream.hxx> +#include <PackageConstants.hxx> +#include "ZipPackageFolderEnumeration.hxx" +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/packages/zip/ZipConstants.hpp> +#include <com/sun/star/packages/zip/ZipException.hpp> +#include <com/sun/star/embed/StorageFormats.hpp> +#include <comphelper/sequence.hxx> +#include <comphelper/servicehelper.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <sal/log.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> + +using namespace com::sun::star; +using namespace com::sun::star::packages::zip::ZipConstants; +using namespace com::sun::star::packages::zip; +using namespace com::sun::star::packages; +using namespace com::sun::star::container; +using namespace com::sun::star::beans; +using namespace com::sun::star::lang; +using namespace com::sun::star::io; +using namespace cppu; + +#if OSL_DEBUG_LEVEL > 0 +#define THROW_WHERE SAL_WHERE +#else +#define THROW_WHERE "" +#endif + +ZipPackageFolder::ZipPackageFolder( const css::uno::Reference < css::uno::XComponentContext >& xContext, + sal_Int32 nFormat, + bool bAllowRemoveOnInsert ) +{ + m_xContext = xContext; + m_nFormat = nFormat; + mbAllowRemoveOnInsert = bAllowRemoveOnInsert; + SetFolder ( true ); + aEntry.nVersion = -1; + aEntry.nFlag = 0; + aEntry.nMethod = STORED; + aEntry.nTime = -1; + aEntry.nCrc = 0; + aEntry.nCompressedSize = 0; + aEntry.nSize = 0; + aEntry.nOffset = -1; +} + +ZipPackageFolder::~ZipPackageFolder() +{ +} + +bool ZipPackageFolder::LookForUnexpectedODF12Streams( + std::u16string_view const aPath, bool const isWholesomeEncryption) +{ + bool bHasUnexpected = false; + + for (const auto& [rShortName, rInfo] : maContents) + { + if ( rInfo.bFolder ) + { + if ( aPath == u"META-INF/" ) + { + // META-INF is not allowed to contain subfolders + bHasUnexpected = true; + } + else if (isWholesomeEncryption && rShortName != u"META-INF") + { + bHasUnexpected = true; + } + else + { + OUString sOwnPath = aPath + rShortName + "/"; + bHasUnexpected = rInfo.pFolder->LookForUnexpectedODF12Streams(sOwnPath, isWholesomeEncryption); + } + } + else + { + if ( aPath == u"META-INF/" ) + { + if ( rShortName != "manifest.xml" + && rShortName.indexOf( "signatures" ) == -1 ) + { + // a stream from META-INF with unexpected name + bHasUnexpected = true; + } + + // streams from META-INF with expected names are allowed not to be registered in manifest.xml + } + else if (isWholesomeEncryption && rShortName != "mimetype" && rShortName != "encrypted-package") + { + bHasUnexpected = true; + } + else if ( !rInfo.pStream->IsFromManifest() ) + { + // the stream is not in META-INF and is not registered in manifest.xml, + // check whether it is an internal part of the package format + if ( !aPath.empty() || rShortName != "mimetype" ) + { + // if it is not "mimetype" from the root it is not a part of the package + bHasUnexpected = true; + } + } + } + + if (bHasUnexpected) + break; + } + + return bHasUnexpected; +} + +void ZipPackageFolder::setChildStreamsTypeByExtension( const beans::StringPair& aPair ) +{ + OUString aExt; + if ( aPair.First.toChar() == '.' ) + aExt = aPair.First; + else + aExt = "." + aPair.First; + + for (const auto& [rShortName, rInfo] : maContents) + { + if ( rInfo.bFolder ) + rInfo.pFolder->setChildStreamsTypeByExtension( aPair ); + else + { + sal_Int32 nPathLength = rShortName.getLength(); + sal_Int32 nExtLength = aExt.getLength(); + if ( nPathLength >= nExtLength && rShortName.match( aExt, nPathLength - nExtLength ) ) + rInfo.pStream->SetMediaType( aPair.Second ); + } + } +} + + // XNameContainer +void SAL_CALL ZipPackageFolder::insertByName( const OUString& aName, const uno::Any& aElement ) +{ + if (hasByName(aName)) + throw ElementExistException(THROW_WHERE ); + + uno::Reference < XInterface > xRef; + aElement >>= xRef; + if ( !(aElement >>= xRef) ) + throw IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 0 ); + + ZipPackageEntry* pEntry = dynamic_cast<ZipPackageFolder*>(xRef.get()); + if (!pEntry) + pEntry = dynamic_cast<ZipPackageStream*>(xRef.get()); + if (!pEntry) + throw IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 0 ); + + if (pEntry->getName() != aName ) + pEntry->setName (aName); + doInsertByName ( pEntry, true ); +} + +void SAL_CALL ZipPackageFolder::removeByName( const OUString& Name ) +{ + ContentHash::iterator aIter = maContents.find ( Name ); + if ( aIter == maContents.end() ) + throw NoSuchElementException(THROW_WHERE ); + maContents.erase( aIter ); +} + // XEnumerationAccess +uno::Reference< XEnumeration > SAL_CALL ZipPackageFolder::createEnumeration( ) +{ + return uno::Reference < XEnumeration> (new ZipPackageFolderEnumeration(maContents)); +} + // XElementAccess +uno::Type SAL_CALL ZipPackageFolder::getElementType( ) +{ + return cppu::UnoType<XInterface>::get(); +} +sal_Bool SAL_CALL ZipPackageFolder::hasElements( ) +{ + return !maContents.empty(); +} + // XNameAccess +ZipContentInfo& ZipPackageFolder::doGetByName( const OUString& aName ) +{ + ContentHash::iterator aIter = maContents.find ( aName ); + if ( aIter == maContents.end()) + throw NoSuchElementException(THROW_WHERE ); + return aIter->second; +} + +uno::Any SAL_CALL ZipPackageFolder::getByName( const OUString& aName ) +{ + return uno::Any ( uno::Reference(cppu::getXWeak(doGetByName ( aName ).xPackageEntry.get())) ); +} +uno::Sequence< OUString > SAL_CALL ZipPackageFolder::getElementNames( ) +{ + return comphelper::mapKeysToSequence(maContents); +} +sal_Bool SAL_CALL ZipPackageFolder::hasByName( const OUString& aName ) +{ + return maContents.find ( aName ) != maContents.end (); +} + // XNameReplace +void SAL_CALL ZipPackageFolder::replaceByName( const OUString& aName, const uno::Any& aElement ) +{ + if ( !hasByName( aName ) ) + throw NoSuchElementException(THROW_WHERE ); + + removeByName( aName ); + insertByName(aName, aElement); +} + +bool ZipPackageFolder::saveChild( + const OUString &rPath, + std::vector < uno::Sequence < PropertyValue > > &rManList, + ZipOutputStream & rZipOut, + const uno::Sequence < sal_Int8 >& rEncryptionKey, + ::std::optional<sal_Int32> const oPBKDF2IterationCount, + ::std::optional<::std::tuple<sal_Int32, sal_Int32, sal_Int32>> const oArgon2Args, + const rtlRandomPool &rRandomPool) +{ + uno::Sequence < PropertyValue > aPropSet (PKG_SIZE_NOENCR_MNFST); + OUString sTempName = rPath + "/"; + + if ( !GetMediaType().isEmpty() ) + { + auto pPropSet = aPropSet.getArray(); + pPropSet[PKG_MNFST_MEDIATYPE].Name = "MediaType"; + pPropSet[PKG_MNFST_MEDIATYPE].Value <<= GetMediaType(); + pPropSet[PKG_MNFST_VERSION].Name = "Version"; + pPropSet[PKG_MNFST_VERSION].Value <<= GetVersion(); + pPropSet[PKG_MNFST_FULLPATH].Name = "FullPath"; + pPropSet[PKG_MNFST_FULLPATH].Value <<= sTempName; + } + else + aPropSet.realloc( 0 ); + + saveContents(sTempName, rManList, rZipOut, rEncryptionKey, oPBKDF2IterationCount, oArgon2Args, rRandomPool); + + // folder can have a mediatype only in package format + if ( aPropSet.hasElements() && ( m_nFormat == embed::StorageFormats::PACKAGE ) ) + rManList.push_back( aPropSet ); + + return true; +} + +void ZipPackageFolder::saveContents( + const OUString &rPath, + std::vector < uno::Sequence < PropertyValue > > &rManList, + ZipOutputStream & rZipOut, + const uno::Sequence < sal_Int8 >& rEncryptionKey, + ::std::optional<sal_Int32> const oPBKDF2IterationCount, + ::std::optional<::std::tuple<sal_Int32, sal_Int32, sal_Int32>> const oArgon2Args, + const rtlRandomPool &rRandomPool ) const +{ + if ( maContents.empty() && !rPath.isEmpty() && m_nFormat != embed::StorageFormats::OFOPXML ) + { + // it is an empty subfolder, use workaround to store it + ZipEntry* pTempEntry = new ZipEntry(aEntry); + pTempEntry->nPathLen = static_cast<sal_Int16>( OUStringToOString( rPath, RTL_TEXTENCODING_UTF8 ).getLength() ); + pTempEntry->nExtraLen = -1; + pTempEntry->sPath = rPath; + + try + { + ZipOutputStream::setEntry(pTempEntry); + rZipOut.writeLOC(pTempEntry); + rZipOut.rawCloseEntry(); + } + catch ( ZipException& ) + { + throw uno::RuntimeException( THROW_WHERE ); + } + catch ( IOException& ) + { + throw uno::RuntimeException( THROW_WHERE ); + } + } + + bool bMimeTypeStreamStored = false; + OUString aMimeTypeStreamName("mimetype"); + if ( m_nFormat == embed::StorageFormats::ZIP && rPath.isEmpty() ) + { + // let the "mimetype" stream in root folder be stored as the first stream if it is zip format + ContentHash::const_iterator aIter = maContents.find ( aMimeTypeStreamName ); + if ( aIter != maContents.end() && !(*aIter).second.bFolder ) + { + bMimeTypeStreamStored = true; + if (!aIter->second.pStream->saveChild(rPath + aIter->first, rManList, rZipOut, + rEncryptionKey, oPBKDF2IterationCount, oArgon2Args, rRandomPool)) + { + throw uno::RuntimeException( THROW_WHERE ); + } + } + } + + for (const auto& [rShortName, rInfo] : maContents) + { + if ( !bMimeTypeStreamStored || rShortName != aMimeTypeStreamName ) + { + if (rInfo.bFolder) + { + if (!rInfo.pFolder->saveChild(rPath + rShortName, rManList, rZipOut, + rEncryptionKey, oPBKDF2IterationCount, oArgon2Args, rRandomPool)) + { + throw uno::RuntimeException( THROW_WHERE ); + } + } + else + { + if (!rInfo.pStream->saveChild(rPath + rShortName, rManList, rZipOut, + rEncryptionKey, oPBKDF2IterationCount, oArgon2Args, rRandomPool)) + { + throw uno::RuntimeException( THROW_WHERE ); + } + } + } + } +} + +void SAL_CALL ZipPackageFolder::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue ) +{ + if ( aPropertyName == "MediaType" ) + { + // TODO/LATER: activate when zip ucp is ready + // if ( m_nFormat != embed::StorageFormats::PACKAGE ) + // throw UnknownPropertyException(THROW_WHERE ); + + aValue >>= msMediaType; + } + else if ( aPropertyName == "Version" ) + aValue >>= m_sVersion; + else if ( aPropertyName == "Size" ) + aValue >>= aEntry.nSize; + else + throw UnknownPropertyException(aPropertyName); +} +uno::Any SAL_CALL ZipPackageFolder::getPropertyValue( const OUString& PropertyName ) +{ + if ( PropertyName == "MediaType" ) + { + // TODO/LATER: activate when zip ucp is ready + // if ( m_nFormat != embed::StorageFormats::PACKAGE ) + // throw UnknownPropertyException(THROW_WHERE ); + + return uno::Any ( msMediaType ); + } + else if ( PropertyName == "Version" ) + return uno::Any( m_sVersion ); + else if ( PropertyName == "Size" ) + return uno::Any ( aEntry.nSize ); + else + throw UnknownPropertyException(PropertyName); +} + +void ZipPackageFolder::doInsertByName ( ZipPackageEntry *pEntry, bool bSetParent ) +{ + if ( pEntry->IsFolder() ) + maContents.emplace(pEntry->getName(), ZipContentInfo(static_cast<ZipPackageFolder*>(pEntry))); + else + maContents.emplace(pEntry->getName(), ZipContentInfo(static_cast<ZipPackageStream*>(pEntry))); + if ( bSetParent ) + pEntry->setParent ( *this ); +} + +OUString ZipPackageFolder::getImplementationName() +{ + return "ZipPackageFolder"; +} + +uno::Sequence< OUString > ZipPackageFolder::getSupportedServiceNames() +{ + return { "com.sun.star.packages.PackageFolder" }; +} + +sal_Bool SAL_CALL ZipPackageFolder::supportsService( OUString const & rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + + +ZipContentInfo::ZipContentInfo ( ZipPackageStream * pNewStream ) +: xPackageEntry ( pNewStream ) +, bFolder ( false ) +, pStream ( pNewStream ) +{ +} + +ZipContentInfo::ZipContentInfo ( ZipPackageFolder * pNewFolder ) +: xPackageEntry ( pNewFolder ) +, bFolder ( true ) +, pFolder ( pNewFolder ) +{ +} + +ZipContentInfo::ZipContentInfo( const ZipContentInfo& ) = default; +ZipContentInfo::ZipContentInfo( ZipContentInfo&& ) = default; +ZipContentInfo& ZipContentInfo::operator=( const ZipContentInfo& ) = default; +ZipContentInfo& ZipContentInfo::operator=( ZipContentInfo&& ) = default; + +ZipContentInfo::~ZipContentInfo() +{ + if ( bFolder ) + pFolder->clearParent(); + else + pStream->clearParent(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zippackage/ZipPackageFolderEnumeration.cxx b/package/source/zippackage/ZipPackageFolderEnumeration.cxx new file mode 100644 index 0000000000..080592c95f --- /dev/null +++ b/package/source/zippackage/ZipPackageFolderEnumeration.cxx @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "ZipPackageFolderEnumeration.hxx" +#include <cppuhelper/supportsservice.hxx> +#include <sal/log.hxx> + +using namespace com::sun::star; + +#if OSL_DEBUG_LEVEL > 0 +#define THROW_WHERE SAL_WHERE +#else +#define THROW_WHERE "" +#endif + +ZipPackageFolderEnumeration::ZipPackageFolderEnumeration(ContentHash& rInput) + : rContents(rInput) + , aIterator(rContents.begin()) +{ +} + +ZipPackageFolderEnumeration::~ZipPackageFolderEnumeration() {} + +sal_Bool SAL_CALL ZipPackageFolderEnumeration::hasMoreElements() +{ + return (aIterator != rContents.end()); +} +uno::Any SAL_CALL ZipPackageFolderEnumeration::nextElement() +{ + uno::Any aAny; + if (aIterator == rContents.end()) + throw container::NoSuchElementException(THROW_WHERE); + aAny <<= uno::Reference(cppu::getXWeak((*aIterator).second.xPackageEntry.get())); + ++aIterator; + return aAny; +} + +OUString ZipPackageFolderEnumeration::getImplementationName() +{ + return "ZipPackageFolderEnumeration"; +} + +uno::Sequence<OUString> ZipPackageFolderEnumeration::getSupportedServiceNames() +{ + uno::Sequence<OUString> aNames{ "com.sun.star.packages.PackageFolderEnumeration" }; + return aNames; +} + +sal_Bool SAL_CALL ZipPackageFolderEnumeration::supportsService(OUString const& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zippackage/ZipPackageFolderEnumeration.hxx b/package/source/zippackage/ZipPackageFolderEnumeration.hxx new file mode 100644 index 0000000000..f3b805b2aa --- /dev/null +++ b/package/source/zippackage/ZipPackageFolderEnumeration.hxx @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_PACKAGE_SOURCE_ZIPPACKAGE_ZIPPACKAGEFOLDERENUMERATION_HXX +#define INCLUDED_PACKAGE_SOURCE_ZIPPACKAGE_ZIPPACKAGEFOLDERENUMERATION_HXX + +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/container/XEnumeration.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <ZipPackageFolder.hxx> + +class ZipPackageFolderEnumeration final + : public cppu::WeakImplHelper<css::container::XEnumeration, css::lang::XServiceInfo> +{ + ContentHash& rContents; + ContentHash::const_iterator aIterator; + +public: + //ZipPackageFolderEnumeration (unordered_map < OUString, css::uno::Reference < css::container::XNamed >, hashFunc, eqFunc > &rInput); + ZipPackageFolderEnumeration(ContentHash& rInput); + virtual ~ZipPackageFolderEnumeration() override; + + // XEnumeration + virtual sal_Bool SAL_CALL hasMoreElements() override; + virtual css::uno::Any SAL_CALL nextElement() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override; + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zippackage/ZipPackageSink.cxx b/package/source/zippackage/ZipPackageSink.cxx new file mode 100644 index 0000000000..103af9d6ed --- /dev/null +++ b/package/source/zippackage/ZipPackageSink.cxx @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "ZipPackageSink.hxx" + +ZipPackageSink::ZipPackageSink() + : xStream(css::uno::Reference<css::io::XInputStream>(nullptr)) +{ +} +ZipPackageSink::~ZipPackageSink() {} +void SAL_CALL +ZipPackageSink::setInputStream(const css::uno::Reference<css::io::XInputStream>& aStream) +{ + xStream = aStream; +} +css::uno::Reference<css::io::XInputStream> SAL_CALL ZipPackageSink::getInputStream() +{ + return xStream; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zippackage/ZipPackageSink.hxx b/package/source/zippackage/ZipPackageSink.hxx new file mode 100644 index 0000000000..9bc406c89e --- /dev/null +++ b/package/source/zippackage/ZipPackageSink.hxx @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_PACKAGE_SOURCE_ZIPPACKAGE_ZIPPACKAGESINK_HXX +#define INCLUDED_PACKAGE_SOURCE_ZIPPACKAGE_ZIPPACKAGESINK_HXX + +#include <com/sun/star/io/XActiveDataSink.hpp> +#include <cppuhelper/implbase.hxx> + +class ZipPackageSink final : public ::cppu::WeakImplHelper<css::io::XActiveDataSink> +{ + css::uno::Reference<css::io::XInputStream> xStream; + +public: + ZipPackageSink(); + virtual ~ZipPackageSink() override; + virtual void SAL_CALL + setInputStream(const css::uno::Reference<css::io::XInputStream>& aStream) override; + virtual css::uno::Reference<css::io::XInputStream> SAL_CALL getInputStream() override; +}; +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zippackage/ZipPackageStream.cxx b/package/source/zippackage/ZipPackageStream.cxx new file mode 100644 index 0000000000..d3068a6665 --- /dev/null +++ b/package/source/zippackage/ZipPackageStream.cxx @@ -0,0 +1,1359 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <ZipPackageStream.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/packages/NoRawFormatException.hpp> +#include <com/sun/star/packages/zip/ZipConstants.hpp> +#include <com/sun/star/embed/StorageFormats.hpp> +#include <com/sun/star/packages/zip/ZipIOException.hpp> +#include <com/sun/star/packages/NoEncryptionException.hpp> +#include <com/sun/star/packages/zip/ZipException.hpp> +#include <com/sun/star/packages/WrongPasswordException.hpp> +#include <com/sun/star/io/TempFile.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/io/XStream.hpp> +#include <com/sun/star/io/XSeekable.hpp> +#include <com/sun/star/xml/crypto/DigestID.hpp> +#include <com/sun/star/xml/crypto/CipherID.hpp> +#include <com/sun/star/xml/crypto/KDFID.hpp> + +#include <CRC32.hxx> +#include <ZipOutputEntry.hxx> +#include <ZipOutputStream.hxx> +#include <ZipPackage.hxx> +#include <ZipFile.hxx> +#include <EncryptedDataHeader.hxx> +#include <osl/diagnose.h> +#include "wrapstreamforshare.hxx" + +#include <comphelper/seekableinput.hxx> +#include <comphelper/servicehelper.hxx> +#include <comphelper/storagehelper.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <unotools/tempfile.hxx> + +#include <rtl/random.h> +#include <sal/log.hxx> +#include <o3tl/unreachable.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <PackageConstants.hxx> + +#include <algorithm> +#include <cstddef> + +using namespace com::sun::star::packages::zip::ZipConstants; +using namespace com::sun::star::packages::zip; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star; +using namespace cppu; + +#if OSL_DEBUG_LEVEL > 0 +#define THROW_WHERE SAL_WHERE +#else +#define THROW_WHERE "" +#endif + +ZipPackageStream::ZipPackageStream ( ZipPackage & rNewPackage, + const uno::Reference< XComponentContext >& xContext, + sal_Int32 nFormat, + bool bAllowRemoveOnInsert ) +: m_rZipPackage( rNewPackage ) +, m_bToBeCompressed ( true ) +, m_bToBeEncrypted ( false ) +, m_bHaveOwnKey ( false ) +, m_bIsEncrypted ( false ) +, m_nImportedStartKeyAlgorithm( 0 ) +, m_nImportedEncryptionAlgorithm( 0 ) +, m_nImportedDerivedKeySize( 0 ) +, m_nStreamMode( PACKAGE_STREAM_NOTSET ) +, m_nMagicalHackPos( 0 ) +, m_nMagicalHackSize( 0 ) +, m_nOwnStreamOrigSize( 0 ) +, m_bHasSeekable( false ) +, m_bCompressedIsSetFromOutside( false ) +, m_bFromManifest( false ) +, m_bUseWinEncoding( false ) +, m_bRawStream( false ) +{ + m_xContext = xContext; + m_nFormat = nFormat; + mbAllowRemoveOnInsert = bAllowRemoveOnInsert; + SetFolder ( false ); + aEntry.nVersion = -1; + aEntry.nFlag = 0; + aEntry.nMethod = -1; + aEntry.nTime = -1; + aEntry.nCrc = -1; + aEntry.nCompressedSize = -1; + aEntry.nSize = -1; + aEntry.nOffset = -1; + aEntry.nPathLen = -1; + aEntry.nExtraLen = -1; +} + +ZipPackageStream::~ZipPackageStream() +{ +} + +void ZipPackageStream::setZipEntryOnLoading( const ZipEntry &rInEntry ) +{ + aEntry.nVersion = rInEntry.nVersion; + aEntry.nFlag = rInEntry.nFlag; + aEntry.nMethod = rInEntry.nMethod; + aEntry.nTime = rInEntry.nTime; + aEntry.nCrc = rInEntry.nCrc; + aEntry.nCompressedSize = rInEntry.nCompressedSize; + aEntry.nSize = rInEntry.nSize; + aEntry.nOffset = rInEntry.nOffset; + aEntry.sPath = rInEntry.sPath; + aEntry.nPathLen = rInEntry.nPathLen; + aEntry.nExtraLen = rInEntry.nExtraLen; + + if ( aEntry.nMethod == STORED ) + m_bToBeCompressed = false; +} + +uno::Reference< io::XInputStream > const & ZipPackageStream::GetOwnSeekStream() +{ + if ( !m_bHasSeekable && m_xStream.is() ) + { + // The package component requires that every stream either be FROM a package or it must support XSeekable! + // The only exception is a nonseekable stream that is provided only for storing, if such a stream + // is accessed before commit it MUST be wrapped. + // Wrap the stream in case it is not seekable + m_xStream = ::comphelper::OSeekableInputWrapper::CheckSeekableCanWrap( m_xStream, m_xContext ); + uno::Reference< io::XSeekable > xSeek( m_xStream, UNO_QUERY_THROW ); + + m_bHasSeekable = true; + } + + return m_xStream; +} + +uno::Reference< io::XInputStream > ZipPackageStream::GetRawEncrStreamNoHeaderCopy() +{ + if ( m_nStreamMode != PACKAGE_STREAM_RAW || !GetOwnSeekStream().is() ) + throw io::IOException(THROW_WHERE ); + + if ( m_xBaseEncryptionData.is() ) + throw ZipIOException(THROW_WHERE "Encrypted stream without encryption data!" ); + + uno::Reference< io::XSeekable > xSeek( GetOwnSeekStream(), UNO_QUERY ); + if ( !xSeek.is() ) + throw ZipIOException(THROW_WHERE "The stream must be seekable!" ); + + // skip header + xSeek->seek( n_ConstHeaderSize + m_xBaseEncryptionData->m_aInitVector.getLength() + + m_xBaseEncryptionData->m_aSalt.getLength() + m_xBaseEncryptionData->m_aDigest.getLength() ); + + // create temporary stream + rtl::Reference < utl::TempFileFastService > xTempFile = new utl::TempFileFastService; + uno::Reference < io::XInputStream > xTempIn = xTempFile->getInputStream(); + + // copy the raw stream to the temporary file starting from the current position + ::comphelper::OStorageHelper::CopyInputToOutput( GetOwnSeekStream(), xTempFile ); + xTempFile->closeOutput(); + xTempFile->seek( 0 ); + + return xTempIn; +} + +sal_Int32 ZipPackageStream::GetEncryptionAlgorithm() const +{ + return m_nImportedEncryptionAlgorithm ? m_nImportedEncryptionAlgorithm : m_rZipPackage.GetEncAlgID(); +} + +sal_Int32 ZipPackageStream::GetIVSize() const +{ + switch (GetEncryptionAlgorithm()) + { + case css::xml::crypto::CipherID::BLOWFISH_CFB_8: + return 8; + case css::xml::crypto::CipherID::AES_CBC_W3C_PADDING: + return 16; + case css::xml::crypto::CipherID::AES_GCM_W3C: + return 12; + default: + O3TL_UNREACHABLE; + } +} + +::rtl::Reference<EncryptionData> ZipPackageStream::GetEncryptionData(Bugs const bugs) +{ + ::rtl::Reference< EncryptionData > xResult; + if ( m_xBaseEncryptionData.is() ) + xResult = new EncryptionData( + *m_xBaseEncryptionData, + GetEncryptionKey(bugs), + GetEncryptionAlgorithm(), + m_oImportedChecksumAlgorithm ? m_oImportedChecksumAlgorithm : m_rZipPackage.GetChecksumAlgID(), + m_nImportedDerivedKeySize ? m_nImportedDerivedKeySize : m_rZipPackage.GetDefaultDerivedKeySize(), + GetStartKeyGenID(), + bugs != Bugs::None); + + return xResult; +} + +uno::Sequence<sal_Int8> ZipPackageStream::GetEncryptionKey(Bugs const bugs) +{ + uno::Sequence< sal_Int8 > aResult; + sal_Int32 nKeyGenID = GetStartKeyGenID(); + bool const bUseWinEncoding = (bugs == Bugs::WinEncodingWrongSHA1 || m_bUseWinEncoding); + + if ( m_bHaveOwnKey && m_aStorageEncryptionKeys.hasElements() ) + { + OUString aNameToFind; + if ( nKeyGenID == xml::crypto::DigestID::SHA256 ) + aNameToFind = PACKAGE_ENCRYPTIONDATA_SHA256UTF8; + else if ( nKeyGenID == xml::crypto::DigestID::SHA1 ) + { + aNameToFind = bUseWinEncoding + ? PACKAGE_ENCRYPTIONDATA_SHA1MS1252 + : (bugs == Bugs::WrongSHA1) + ? PACKAGE_ENCRYPTIONDATA_SHA1UTF8 + : PACKAGE_ENCRYPTIONDATA_SHA1CORRECT; + } + else + throw uno::RuntimeException(THROW_WHERE "No expected key is provided!" ); + + for ( const auto& rKey : std::as_const(m_aStorageEncryptionKeys) ) + if ( rKey.Name == aNameToFind ) + rKey.Value >>= aResult; + + // empty keys are not allowed here + // so it is not important whether there is no key, or the key is empty, it is an error + if ( !aResult.hasElements() ) + throw uno::RuntimeException(THROW_WHERE "No expected key is provided!" ); + } + else + aResult = m_aEncryptionKey; + + if ( !aResult.hasElements() || !m_bHaveOwnKey ) + aResult = m_rZipPackage.GetEncryptionKey(); + + return aResult; +} + +sal_Int32 ZipPackageStream::GetStartKeyGenID() const +{ + // generally should all the streams use the same Start Key + // but if raw copy without password takes place, we should preserve the imported algorithm + return m_nImportedStartKeyAlgorithm ? m_nImportedStartKeyAlgorithm : m_rZipPackage.GetStartKeyGenID(); +} + +uno::Reference< io::XInputStream > ZipPackageStream::TryToGetRawFromDataStream( bool bAddHeaderForEncr ) +{ + if ( m_nStreamMode != PACKAGE_STREAM_DATA || !GetOwnSeekStream().is() || ( bAddHeaderForEncr && !m_bToBeEncrypted ) ) + throw packages::NoEncryptionException(THROW_WHERE ); + + Sequence< sal_Int8 > aKey; + + if ( m_bToBeEncrypted ) + { + aKey = GetEncryptionKey(); + if ( !aKey.hasElements() ) + throw packages::NoEncryptionException(THROW_WHERE ); + } + + try + { + // create temporary file + uno::Reference < io::XStream > xTempStream(new utl::TempFileFastService); + + // create a package based on it + rtl::Reference<ZipPackage> pPackage = new ZipPackage( m_xContext ); + + Sequence< Any > aArgs{ Any(xTempStream) }; + pPackage->initialize( aArgs ); + + // create a new package stream + uno::Reference< XDataSinkEncrSupport > xNewPackStream( pPackage->createInstance(), UNO_QUERY_THROW ); + xNewPackStream->setDataStream( + new WrapStreamForShare(GetOwnSeekStream(), m_rZipPackage.GetSharedMutexRef())); + + uno::Reference< XPropertySet > xNewPSProps( xNewPackStream, UNO_QUERY_THROW ); + + // copy all the properties of this stream to the new stream + xNewPSProps->setPropertyValue("MediaType", Any( msMediaType ) ); + xNewPSProps->setPropertyValue("Compressed", Any( m_bToBeCompressed ) ); + if ( m_bToBeEncrypted ) + { + xNewPSProps->setPropertyValue(ENCRYPTION_KEY_PROPERTY, Any( aKey ) ); + xNewPSProps->setPropertyValue("Encrypted", Any( true ) ); + } + + // insert a new stream in the package + uno::Reference< XInterface > xTmp; + Any aRoot = pPackage->getByHierarchicalName("/"); + aRoot >>= xTmp; + uno::Reference< container::XNameContainer > xRootNameContainer( xTmp, UNO_QUERY_THROW ); + + uno::Reference< XInterface > xNPSDummy( xNewPackStream, UNO_QUERY ); + xRootNameContainer->insertByName("dummy", Any( xNPSDummy ) ); + + // commit the temporary package + pPackage->commitChanges(); + + // get raw stream from the temporary package + uno::Reference< io::XInputStream > xInRaw; + if ( bAddHeaderForEncr ) + xInRaw = xNewPackStream->getRawStream(); + else + xInRaw = xNewPackStream->getPlainRawStream(); + + // create another temporary file + rtl::Reference < utl::TempFileFastService > xTempOut = new utl::TempFileFastService; + uno::Reference < io::XInputStream > xTempIn( xTempOut ); + + // copy the raw stream to the temporary file + ::comphelper::OStorageHelper::CopyInputToOutput( xInRaw, xTempOut ); + xTempOut->closeOutput(); + xTempOut->seek( 0 ); + + // close raw stream, package stream and folder + xInRaw.clear(); + xNewPSProps.clear(); + xNPSDummy.clear(); + xNewPackStream.clear(); + xTmp.clear(); + xRootNameContainer.clear(); + + // return the stream representing the first temporary file + return xTempIn; + } + catch ( RuntimeException& ) + { + throw; + } + catch ( Exception& ) + { + } + + throw io::IOException(THROW_WHERE ); +} + +// presumably the purpose of this is to transfer encrypted streams between +// storages, needed for password-protected macros in documents, which is +// tragically a feature that exists +bool ZipPackageStream::ParsePackageRawStream() +{ + OSL_ENSURE( GetOwnSeekStream().is(), "A stream must be provided!" ); + + if ( !GetOwnSeekStream().is() ) + return false; + + bool bOk = false; + + ::rtl::Reference< BaseEncryptionData > xTempEncrData; + Sequence < sal_Int8 > aHeader ( 4 ); + + try + { + if ( GetOwnSeekStream()->readBytes ( aHeader, 4 ) == 4 ) + { + const sal_Int8 *pHeader = aHeader.getConstArray(); + sal_uInt32 nHeader = ( pHeader [0] & 0xFF ) | + ( pHeader [1] & 0xFF ) << 8 | + ( pHeader [2] & 0xFF ) << 16 | + ( pHeader [3] & 0xFF ) << 24; + if ( nHeader == n_ConstHeader ) + { + // this is one of our god-awful, but extremely devious hacks, everyone cheer + xTempEncrData = new BaseEncryptionData; + + OUString aMediaType; + sal_Int32 nEncAlgorithm = 0; + sal_Int32 nChecksumAlgorithm = 0; + sal_Int32 nDerivedKeySize = 0; + sal_Int32 nStartKeyGenID = 0; + sal_Int32 nMagHackSize = 0; + if ( ZipFile::StaticFillData( xTempEncrData, nEncAlgorithm, nChecksumAlgorithm, nDerivedKeySize, nStartKeyGenID, nMagHackSize, aMediaType, GetOwnSeekStream() ) ) + { + // We'll want to skip the data we've just read, so calculate how much we just read + // and remember it + m_nMagicalHackPos = n_ConstHeaderSize + xTempEncrData->m_aSalt.getLength() + + xTempEncrData->m_aInitVector.getLength() + + xTempEncrData->m_aDigest.getLength() + + aMediaType.getLength() * sizeof( sal_Unicode ); + m_nImportedEncryptionAlgorithm = nEncAlgorithm; + if (nChecksumAlgorithm == 0) + { + m_oImportedChecksumAlgorithm.reset(); + } + else + { + m_oImportedChecksumAlgorithm.emplace(nChecksumAlgorithm); + } + m_nImportedDerivedKeySize = nDerivedKeySize; + m_nImportedStartKeyAlgorithm = nStartKeyGenID; + m_nMagicalHackSize = nMagHackSize; + msMediaType = aMediaType; + + bOk = true; + } + } + } + } + catch( Exception& ) + { + } + + if ( !bOk ) + { + // the provided stream is not a raw stream + return false; + } + + m_xBaseEncryptionData = xTempEncrData; + SetIsEncrypted ( true ); + // it's already compressed and encrypted + m_bToBeEncrypted = m_bToBeCompressed = false; + + return true; +} + +static void ImplSetStoredData( ZipEntry & rEntry, uno::Reference< io::XInputStream> const & rStream ) +{ + // It's very annoying that we have to do this, but lots of zip packages + // don't allow data descriptors for STORED streams, meaning we have to + // know the size and CRC32 of uncompressed streams before we actually + // write them ! + CRC32 aCRC32; + rEntry.nMethod = STORED; + rEntry.nCompressedSize = rEntry.nSize = aCRC32.updateStream ( rStream ); + rEntry.nCrc = aCRC32.getValue(); +} + +bool ZipPackageStream::saveChild( + const OUString &rPath, + std::vector < uno::Sequence < beans::PropertyValue > > &rManList, + ZipOutputStream & rZipOut, + const uno::Sequence < sal_Int8 >& rEncryptionKey, + ::std::optional<sal_Int32> const oPBKDF2IterationCount, + ::std::optional<::std::tuple<sal_Int32, sal_Int32, sal_Int32>> const oArgon2Args, + const rtlRandomPool &rRandomPool) +{ + bool bSuccess = true; + + static constexpr OUString sDigestProperty (u"Digest"_ustr); + static constexpr OUString sEncryptionAlgProperty (u"EncryptionAlgorithm"_ustr); + static constexpr OUString sStartKeyAlgProperty (u"StartKeyAlgorithm"_ustr); + static constexpr OUString sDigestAlgProperty (u"DigestAlgorithm"_ustr); + static constexpr OUString sDerivedKeySizeProperty (u"DerivedKeySize"_ustr); + + uno::Sequence < beans::PropertyValue > aPropSet (PKG_SIZE_NOENCR_MNFST); + + // In case the entry we are reading is also the entry we are writing, we will + // store the ZipEntry data in pTempEntry + + // if pTempEntry is necessary, it will be released and passed to the ZipOutputStream + // and be deleted in the ZipOutputStream destructor + std::unique_ptr < ZipEntry > pAutoTempEntry ( new ZipEntry(aEntry) ); + ZipEntry* pTempEntry = pAutoTempEntry.get(); + + pTempEntry->sPath = rPath; + pTempEntry->nPathLen = static_cast<sal_Int16>( OUStringToOString( pTempEntry->sPath, RTL_TEXTENCODING_UTF8 ).getLength() ); + + const bool bToBeEncrypted = m_bToBeEncrypted && (rEncryptionKey.hasElements() || m_bHaveOwnKey); + const bool bToBeCompressed = bToBeEncrypted || m_bToBeCompressed; + + auto pPropSet = aPropSet.getArray(); + pPropSet[PKG_MNFST_MEDIATYPE].Name = "MediaType"; + pPropSet[PKG_MNFST_MEDIATYPE].Value <<= GetMediaType( ); + pPropSet[PKG_MNFST_VERSION].Name = "Version"; + pPropSet[PKG_MNFST_VERSION].Value <<= OUString(); // no version is stored for streams currently + pPropSet[PKG_MNFST_FULLPATH].Name = "FullPath"; + pPropSet[PKG_MNFST_FULLPATH].Value <<= pTempEntry->sPath; + + OSL_ENSURE( m_nStreamMode != PACKAGE_STREAM_NOTSET, "Unacceptable ZipPackageStream mode!" ); + + m_bRawStream = false; + if ( m_nStreamMode == PACKAGE_STREAM_DETECT ) + m_bRawStream = ParsePackageRawStream(); + else if ( m_nStreamMode == PACKAGE_STREAM_RAW ) + m_bRawStream = true; + + bool bBackgroundThreadDeflate = false; + bool bTransportOwnEncrStreamAsRaw = false; + // During the storing the original size of the stream can be changed + // TODO/LATER: get rid of this hack + m_nOwnStreamOrigSize = m_bRawStream ? m_nMagicalHackSize : aEntry.nSize; + + bool bUseNonSeekableAccess = false; + uno::Reference < io::XInputStream > xStream; + if ( !IsPackageMember() && !m_bRawStream && !bToBeEncrypted && bToBeCompressed ) + { + // the stream is not a package member, not a raw stream, + // it should not be encrypted and it should be compressed, + // in this case nonseekable access can be used + + xStream = m_xStream; + uno::Reference < io::XSeekable > xSeek ( xStream, uno::UNO_QUERY ); + + bUseNonSeekableAccess = ( xStream.is() && !xSeek.is() ); + } + + if ( !bUseNonSeekableAccess ) + { + xStream = getRawData(); + + if ( !xStream.is() ) + { + OSL_FAIL( "ZipPackageStream didn't have a stream associated with it, skipping!" ); + bSuccess = false; + return bSuccess; + } + + uno::Reference < io::XSeekable > xSeek ( xStream, uno::UNO_QUERY ); + try + { + if ( xSeek.is() ) + { + // If the stream is a raw one, then we should be positioned + // at the beginning of the actual data + if ( !bToBeCompressed || m_bRawStream ) + { + // The raw stream can neither be encrypted nor connected + OSL_ENSURE( !m_bRawStream || !(bToBeCompressed || bToBeEncrypted), "The stream is already encrypted!" ); + xSeek->seek ( m_bRawStream ? m_nMagicalHackPos : 0 ); + ImplSetStoredData ( *pTempEntry, xStream ); + + // TODO/LATER: Get rid of hacks related to switching of Flag Method and Size properties! + } + else if ( bToBeEncrypted ) + { + // this is the correct original size + pTempEntry->nSize = xSeek->getLength(); + m_nOwnStreamOrigSize = pTempEntry->nSize; + } + + xSeek->seek ( 0 ); + } + else + { + // Okay, we don't have an xSeekable stream. This is possibly bad. + // check if it's one of our own streams, if it is then we know that + // each time we ask for it we'll get a new stream that will be + // at position zero...otherwise, assert and skip this stream... + if ( IsPackageMember() ) + { + // if the password has been changed then the stream should not be package member any more + if ( m_bIsEncrypted && m_bToBeEncrypted ) + { + // Should be handled close to the raw stream handling + bTransportOwnEncrStreamAsRaw = true; + pTempEntry->nMethod = STORED; + + // TODO/LATER: get rid of this situation + // this size should be different from the one that will be stored in manifest.xml + // it is used in storing algorithms and after storing the correct size will be set + pTempEntry->nSize = pTempEntry->nCompressedSize; + } + } + else + { + bSuccess = false; + return bSuccess; + } + } + } + catch ( uno::Exception& ) + { + bSuccess = false; + return bSuccess; + } + + if ( bToBeEncrypted || m_bRawStream || bTransportOwnEncrStreamAsRaw ) + { + if ( bToBeEncrypted && !bTransportOwnEncrStreamAsRaw ) + { + uno::Sequence<sal_Int8> aSalt(16); + // note: for GCM it's particularly important that IV is unique + uno::Sequence<sal_Int8> aVector(GetIVSize()); + rtl_random_getBytes ( rRandomPool, aSalt.getArray(), 16 ); + rtl_random_getBytes ( rRandomPool, aVector.getArray(), aVector.getLength() ); + if ( !m_bHaveOwnKey ) + { + m_aEncryptionKey = rEncryptionKey; + m_aStorageEncryptionKeys.realloc( 0 ); + } + + setInitialisationVector ( aVector ); + setSalt ( aSalt ); + setIterationCount(oPBKDF2IterationCount); + setArgon2Args(oArgon2Args); + } + + // last property is digest, which is inserted later if we didn't have + // a magic header + aPropSet.realloc(PKG_SIZE_ENCR_MNFST); + pPropSet = aPropSet.getArray(); + pPropSet[PKG_MNFST_INIVECTOR].Name = "InitialisationVector"; + pPropSet[PKG_MNFST_INIVECTOR].Value <<= m_xBaseEncryptionData->m_aInitVector; + pPropSet[PKG_MNFST_SALT].Name = "Salt"; + pPropSet[PKG_MNFST_SALT].Value <<= m_xBaseEncryptionData->m_aSalt; + if (m_xBaseEncryptionData->m_oArgon2Args) + { + pPropSet[PKG_MNFST_KDF].Name = "KeyDerivationFunction"; + pPropSet[PKG_MNFST_KDF].Value <<= xml::crypto::KDFID::Argon2id; + pPropSet[PKG_MNFST_ARGON2ARGS].Name = "Argon2Args"; + uno::Sequence<sal_Int32> const args{ + ::std::get<0>(*m_xBaseEncryptionData->m_oArgon2Args), + ::std::get<1>(*m_xBaseEncryptionData->m_oArgon2Args), + ::std::get<2>(*m_xBaseEncryptionData->m_oArgon2Args) }; + pPropSet[PKG_MNFST_ARGON2ARGS].Value <<= args; + } + else if (m_xBaseEncryptionData->m_oPBKDFIterationCount) + { + pPropSet[PKG_MNFST_KDF].Name = "KeyDerivationFunction"; + pPropSet[PKG_MNFST_KDF].Value <<= xml::crypto::KDFID::PBKDF2; + pPropSet[PKG_MNFST_ITERATION].Name = "IterationCount"; + pPropSet[PKG_MNFST_ITERATION].Value <<= *m_xBaseEncryptionData->m_oPBKDFIterationCount; + } + else + { + pPropSet[PKG_MNFST_KDF].Name = "KeyDerivationFunction"; + pPropSet[PKG_MNFST_KDF].Value <<= xml::crypto::KDFID::PGP_RSA_OAEP_MGF1P; + } + + // Need to store the uncompressed size in the manifest + OSL_ENSURE( m_nOwnStreamOrigSize >= 0, "The stream size was not correctly initialized!" ); + pPropSet[PKG_MNFST_UCOMPSIZE].Name = "Size"; + pPropSet[PKG_MNFST_UCOMPSIZE].Value <<= m_nOwnStreamOrigSize; + + if ( m_bRawStream || bTransportOwnEncrStreamAsRaw ) + { + ::rtl::Reference< EncryptionData > xEncData = GetEncryptionData(); + if ( !xEncData.is() ) + throw uno::RuntimeException(); + + pPropSet[PKG_MNFST_ENCALG].Name = sEncryptionAlgProperty; + pPropSet[PKG_MNFST_ENCALG].Value <<= xEncData->m_nEncAlg; + pPropSet[PKG_MNFST_STARTALG].Name = sStartKeyAlgProperty; + pPropSet[PKG_MNFST_STARTALG].Value <<= xEncData->m_nStartKeyGenID; + if (xEncData->m_oCheckAlg) + { + assert(xEncData->m_nEncAlg != xml::crypto::CipherID::AES_GCM_W3C); + pPropSet[PKG_MNFST_DIGEST].Name = sDigestProperty; + pPropSet[PKG_MNFST_DIGEST].Value <<= m_xBaseEncryptionData->m_aDigest; + pPropSet[PKG_MNFST_DIGESTALG].Name = sDigestAlgProperty; + pPropSet[PKG_MNFST_DIGESTALG].Value <<= *xEncData->m_oCheckAlg; + } + pPropSet[PKG_MNFST_DERKEYSIZE].Name = sDerivedKeySizeProperty; + pPropSet[PKG_MNFST_DERKEYSIZE].Value <<= xEncData->m_nDerivedKeySize; + } + } + } + + // If the entry is already stored in the zip file in the format we + // want for this write...copy it raw + if ( !bUseNonSeekableAccess + && ( m_bRawStream || bTransportOwnEncrStreamAsRaw + || ( IsPackageMember() && !bToBeEncrypted + && ( ( aEntry.nMethod == DEFLATED && bToBeCompressed ) + || ( aEntry.nMethod == STORED && !bToBeCompressed ) ) ) ) ) + { + // If it's a PackageMember, then it's an unbuffered stream and we need + // to get a new version of it as we can't seek backwards. + if ( IsPackageMember() ) + { + xStream = getRawData(); + if ( !xStream.is() ) + { + // Make sure that we actually _got_ a new one ! + bSuccess = false; + return bSuccess; + } + } + + try + { + if ( m_bRawStream ) + xStream->skipBytes( m_nMagicalHackPos ); + + ZipOutputStream::setEntry(pTempEntry); + rZipOut.writeLOC(pTempEntry); + // coverity[leaked_storage] - the entry is provided to the ZipOutputStream that will delete it + pAutoTempEntry.release(); + + uno::Sequence < sal_Int8 > aSeq ( n_ConstBufferSize ); + sal_Int32 nLength; + + do + { + nLength = xStream->readBytes( aSeq, n_ConstBufferSize ); + if (nLength != n_ConstBufferSize) + aSeq.realloc(nLength); + + rZipOut.rawWrite(aSeq); + } + while ( nLength == n_ConstBufferSize ); + + rZipOut.rawCloseEntry(); + } + catch ( ZipException& ) + { + bSuccess = false; + } + catch ( io::IOException& ) + { + bSuccess = false; + } + } + else + { + // This stream is definitely not a raw stream + + // If nonseekable access is used the stream should be at the beginning and + // is useless after the storing. Thus if the storing fails the package should + // be thrown away ( as actually it is done currently )! + // To allow to reuse the package after the error, the optimization must be removed! + + // If it's a PackageMember, then our previous reference held a 'raw' stream + // so we need to re-get it, unencrypted, uncompressed and positioned at the + // beginning of the stream + if ( IsPackageMember() ) + { + xStream = getInputStream(); + if ( !xStream.is() ) + { + // Make sure that we actually _got_ a new one ! + bSuccess = false; + return bSuccess; + } + } + + if ( bToBeCompressed ) + { + pTempEntry->nMethod = DEFLATED; + pTempEntry->nCrc = -1; + pTempEntry->nCompressedSize = pTempEntry->nSize = -1; + } + + uno::Reference< io::XSeekable > xSeek(xStream, uno::UNO_QUERY); + // It's not worth to deflate jpegs to save ~1% in a slow process + // Unfortunately, does not work for streams protected by password + if (xSeek.is() && msMediaType.endsWith("/jpeg") && !m_bToBeEncrypted && !m_bToBeCompressed) + { + ImplSetStoredData(*pTempEntry, xStream); + xSeek->seek(0); + } + + try + { + ZipOutputStream::setEntry(pTempEntry); + // the entry is provided to the ZipOutputStream that will delete it + pAutoTempEntry.release(); + + if (pTempEntry->nMethod == STORED) + { + sal_Int32 nLength; + uno::Sequence< sal_Int8 > aSeq(n_ConstBufferSize); + rZipOut.writeLOC(pTempEntry, bToBeEncrypted); + do + { + nLength = xStream->readBytes(aSeq, n_ConstBufferSize); + if (nLength != n_ConstBufferSize) + aSeq.realloc(nLength); + + rZipOut.rawWrite(aSeq); + } + while ( nLength == n_ConstBufferSize ); + rZipOut.rawCloseEntry(bToBeEncrypted); + } + else + { + // tdf#89236 Encrypting in a background thread does not work + bBackgroundThreadDeflate = !bToBeEncrypted; + // Do not deflate small streams using threads. XSeekable's getLength() + // gives the full size, XInputStream's available() may not be + // the full size, but it appears that at this point it usually is. + sal_Int64 estimatedSize = xSeek.is() ? xSeek->getLength() : xStream->available(); + + if (estimatedSize > 1000000) + { + // Use ThreadDeflater which will split the stream into blocks and compress + // them in threads, but not in background (i.e. writeStream() will block). + // This is suitable for large data. + bBackgroundThreadDeflate = false; + rZipOut.writeLOC(pTempEntry, bToBeEncrypted); + ZipOutputEntryParallel aZipEntry(rZipOut.getStream(), m_xContext, *pTempEntry, this, bToBeEncrypted); + aZipEntry.writeStream(xStream); + rZipOut.rawCloseEntry(bToBeEncrypted); + } + else if (bBackgroundThreadDeflate && estimatedSize > 100000) + { + // tdf#93553 limit to a useful amount of pending tasks. Having way too many + // tasks pending may use a lot of memory. Take number of available + // cores and allow 4-times the amount for having the queue well filled. The + // 2nd parameter is the time to wait between cleanups in 10th of a second. + // Both values may be added to the configuration settings if needed. + static std::size_t nAllowedTasks(comphelper::ThreadPool::getPreferredConcurrency() * 4); //TODO: overflow + rZipOut.reduceScheduledThreadTasksToGivenNumberOrLess(nAllowedTasks); + + // Start a new thread task deflating this zip entry + ZipOutputEntryInThread *pZipEntry = new ZipOutputEntryInThread( + m_xContext, *pTempEntry, this, bToBeEncrypted); + rZipOut.addDeflatingThreadTask( pZipEntry, + pZipEntry->createTask( rZipOut.getThreadTaskTag(), xStream) ); + } + else + { + bBackgroundThreadDeflate = false; + rZipOut.writeLOC(pTempEntry, bToBeEncrypted); + ZipOutputEntry aZipEntry(rZipOut.getStream(), m_xContext, *pTempEntry, this, bToBeEncrypted); + aZipEntry.writeStream(xStream); + rZipOut.rawCloseEntry(bToBeEncrypted); + } + } + } + catch ( ZipException& ) + { + bSuccess = false; + } + catch ( io::IOException& ) + { + bSuccess = false; + } + + if ( bToBeEncrypted ) + { + ::rtl::Reference< EncryptionData > xEncData = GetEncryptionData(); + if ( !xEncData.is() ) + throw uno::RuntimeException(); + + // very confusing: half the encryption properties are + // unconditionally added above and the other half conditionally; + // assert that we have the expected group and not duplicates + assert(std::any_of(aPropSet.begin(), aPropSet.end(), [](auto const& it){ return it.Name == "Salt"; })); + assert(!std::any_of(aPropSet.begin(), aPropSet.end(), [](auto const& it){ return it.Name == sEncryptionAlgProperty; })); + + pPropSet[PKG_MNFST_ENCALG].Name = sEncryptionAlgProperty; + pPropSet[PKG_MNFST_ENCALG].Value <<= xEncData->m_nEncAlg; + pPropSet[PKG_MNFST_STARTALG].Name = sStartKeyAlgProperty; + pPropSet[PKG_MNFST_STARTALG].Value <<= xEncData->m_nStartKeyGenID; + if (xEncData->m_oCheckAlg) + { + assert(xEncData->m_nEncAlg != xml::crypto::CipherID::AES_GCM_W3C); + pPropSet[PKG_MNFST_DIGEST].Name = sDigestProperty; + pPropSet[PKG_MNFST_DIGEST].Value <<= m_xBaseEncryptionData->m_aDigest; + pPropSet[PKG_MNFST_DIGESTALG].Name = sDigestAlgProperty; + pPropSet[PKG_MNFST_DIGESTALG].Value <<= *xEncData->m_oCheckAlg; + } + pPropSet[PKG_MNFST_DERKEYSIZE].Name = sDerivedKeySizeProperty; + pPropSet[PKG_MNFST_DERKEYSIZE].Value <<= xEncData->m_nDerivedKeySize; + + SetIsEncrypted ( true ); + } + } + + if (bSuccess && !bBackgroundThreadDeflate) + successfullyWritten(pTempEntry); + + if ( aPropSet.hasElements() + && ( m_nFormat == embed::StorageFormats::PACKAGE || m_nFormat == embed::StorageFormats::OFOPXML ) ) + rManList.push_back( aPropSet ); + + return bSuccess; +} + +void ZipPackageStream::successfullyWritten( ZipEntry const *pEntry ) +{ + if ( !IsPackageMember() ) + { + if ( m_xStream.is() ) + { + m_xStream->closeInput(); + m_xStream.clear(); + m_bHasSeekable = false; + } + SetPackageMember ( true ); + } + + if ( m_bRawStream ) + { + // the raw stream was integrated and now behaves + // as usual encrypted stream + SetToBeEncrypted( true ); + } + + // Then copy it back afterwards... + aEntry = *pEntry; + + // TODO/LATER: get rid of this hack ( the encrypted stream size property is changed during saving ) + if ( m_bIsEncrypted ) + setSize( m_nOwnStreamOrigSize ); + + aEntry.nOffset *= -1; +} + +void ZipPackageStream::SetPackageMember( bool bNewValue ) +{ + if ( bNewValue ) + { + m_nStreamMode = PACKAGE_STREAM_PACKAGEMEMBER; + m_nMagicalHackPos = 0; + m_nMagicalHackSize = 0; + } + else if ( m_nStreamMode == PACKAGE_STREAM_PACKAGEMEMBER ) + m_nStreamMode = PACKAGE_STREAM_NOTSET; // must be reset +} + +// XActiveDataSink +void SAL_CALL ZipPackageStream::setInputStream( const uno::Reference< io::XInputStream >& aStream ) +{ + // if seekable access is required the wrapping will be done on demand + m_xStream = aStream; + m_nImportedEncryptionAlgorithm = 0; + m_bHasSeekable = false; + SetPackageMember ( false ); + aEntry.nTime = -1; + m_nStreamMode = PACKAGE_STREAM_DETECT; +} + +uno::Reference< io::XInputStream > ZipPackageStream::getRawData() +{ + try + { + if ( IsPackageMember() ) + { + return m_rZipPackage.getZipFile().getRawData( aEntry, GetEncryptionData(), m_bIsEncrypted, m_rZipPackage.GetSharedMutexRef(), false/*bUseBufferedStream*/ ); + } + else if ( GetOwnSeekStream().is() ) + { + return new WrapStreamForShare( GetOwnSeekStream(), m_rZipPackage.GetSharedMutexRef() ); + } + else + return uno::Reference < io::XInputStream > (); + } + catch ( ZipException & )//rException ) + { + TOOLS_WARN_EXCEPTION( "package", "" ); + return uno::Reference < io::XInputStream > (); + } + catch ( Exception & ) + { + TOOLS_WARN_EXCEPTION( "package", "Exception is thrown during stream wrapping!" ); + return uno::Reference < io::XInputStream > (); + } +} + +uno::Reference< io::XInputStream > SAL_CALL ZipPackageStream::getInputStream() +{ + try + { + if ( IsPackageMember() ) + { + return m_rZipPackage.getZipFile().getInputStream( aEntry, GetEncryptionData(), m_bIsEncrypted, m_rZipPackage.GetSharedMutexRef() ); + } + else if ( GetOwnSeekStream().is() ) + { + return new WrapStreamForShare( GetOwnSeekStream(), m_rZipPackage.GetSharedMutexRef() ); + } + else + return uno::Reference < io::XInputStream > (); + } + catch ( ZipException & )//rException ) + { + TOOLS_WARN_EXCEPTION( "package", "" ); + return uno::Reference < io::XInputStream > (); + } + catch ( const Exception & ) + { + TOOLS_WARN_EXCEPTION( "package", "Exception is thrown during stream wrapping!"); + return uno::Reference < io::XInputStream > (); + } +} + +// XDataSinkEncrSupport +uno::Reference< io::XInputStream > SAL_CALL ZipPackageStream::getDataStream() +{ + // There is no stream attached to this object + if ( m_nStreamMode == PACKAGE_STREAM_NOTSET ) + return uno::Reference< io::XInputStream >(); + + // this method can not be used together with old approach + if ( m_nStreamMode == PACKAGE_STREAM_DETECT ) + throw packages::zip::ZipIOException(THROW_WHERE ); + + if ( IsPackageMember() ) + { + uno::Reference< io::XInputStream > xResult; + try + { + xResult = m_rZipPackage.getZipFile().getDataStream( aEntry, GetEncryptionData(Bugs::None), m_bIsEncrypted, m_rZipPackage.GetSharedMutexRef() ); + } + catch( const packages::WrongPasswordException& ) + { + // note: due to SHA1 check this fallback is only done for + // * ODF 1.2 files written by OOo < 3.4beta / LO < 3.5 + // * ODF 1.1/OOoXML files written by any version + if ( m_rZipPackage.GetStartKeyGenID() == xml::crypto::DigestID::SHA1 ) + { + SAL_WARN("package", "ZipPackageStream::getDataStream(): SHA1 mismatch, trying fallbacks..."); + try + { // tdf#114939 try with legacy StarOffice SHA1 bug + xResult = m_rZipPackage.getZipFile().getDataStream( aEntry, GetEncryptionData(Bugs::WrongSHA1), m_bIsEncrypted, m_rZipPackage.GetSharedMutexRef() ); + return xResult; + } + catch (const packages::WrongPasswordException&) + { + /* ignore and try next... */ + } + + try + { + // rhbz#1013844 / fdo#47482 workaround for the encrypted + // OpenOffice.org 1.0 documents generated by Libreoffice <= + // 3.6 with the new encryption format and using SHA256, but + // missing a specified startkey of SHA256 + + // force SHA256 and see if that works + m_nImportedStartKeyAlgorithm = xml::crypto::DigestID::SHA256; + xResult = m_rZipPackage.getZipFile().getDataStream( aEntry, GetEncryptionData(), m_bIsEncrypted, m_rZipPackage.GetSharedMutexRef() ); + return xResult; + } + catch (const packages::WrongPasswordException&) + { + // if that didn't work, restore to SHA1 and trundle through the *other* earlier + // bug fix + m_nImportedStartKeyAlgorithm = xml::crypto::DigestID::SHA1; + } + + // workaround for the encrypted documents generated with the old OOo1.x bug. + if ( !m_bUseWinEncoding ) + { + xResult = m_rZipPackage.getZipFile().getDataStream( aEntry, GetEncryptionData(Bugs::WinEncodingWrongSHA1), m_bIsEncrypted, m_rZipPackage.GetSharedMutexRef() ); + m_bUseWinEncoding = true; + } + else + throw; + } + else + throw; + } + return xResult; + } + else if ( m_nStreamMode == PACKAGE_STREAM_RAW ) + return ZipFile::StaticGetDataFromRawStream( m_rZipPackage.GetSharedMutexRef(), m_xContext, GetOwnSeekStream(), GetEncryptionData() ); + else if ( GetOwnSeekStream().is() ) + { + return new WrapStreamForShare( GetOwnSeekStream(), m_rZipPackage.GetSharedMutexRef() ); + } + else + return uno::Reference< io::XInputStream >(); +} + +uno::Reference< io::XInputStream > SAL_CALL ZipPackageStream::getRawStream() +{ + // There is no stream attached to this object + if ( m_nStreamMode == PACKAGE_STREAM_NOTSET ) + return uno::Reference< io::XInputStream >(); + + // this method can not be used together with old approach + if ( m_nStreamMode == PACKAGE_STREAM_DETECT ) + throw packages::zip::ZipIOException(THROW_WHERE ); + + if ( IsPackageMember() ) + { + if ( !m_bIsEncrypted || !GetEncryptionData().is() ) + throw packages::NoEncryptionException(THROW_WHERE ); + + return m_rZipPackage.getZipFile().getWrappedRawStream( aEntry, GetEncryptionData(), msMediaType, m_rZipPackage.GetSharedMutexRef() ); + } + else if ( GetOwnSeekStream().is() ) + { + if ( m_nStreamMode == PACKAGE_STREAM_RAW ) + { + return new WrapStreamForShare( GetOwnSeekStream(), m_rZipPackage.GetSharedMutexRef() ); + } + else if ( m_nStreamMode == PACKAGE_STREAM_DATA && m_bToBeEncrypted ) + return TryToGetRawFromDataStream( true ); + } + + throw packages::NoEncryptionException(THROW_WHERE ); +} + +void SAL_CALL ZipPackageStream::setDataStream( const uno::Reference< io::XInputStream >& aStream ) +{ + setInputStream( aStream ); + m_nStreamMode = PACKAGE_STREAM_DATA; +} + +void SAL_CALL ZipPackageStream::setRawStream( const uno::Reference< io::XInputStream >& aStream ) +{ + // wrap the stream in case it is not seekable + uno::Reference< io::XInputStream > xNewStream = ::comphelper::OSeekableInputWrapper::CheckSeekableCanWrap( aStream, m_xContext ); + uno::Reference< io::XSeekable > xSeek( xNewStream, UNO_QUERY_THROW ); + xSeek->seek( 0 ); + uno::Reference< io::XInputStream > xOldStream = m_xStream; + m_xStream = xNewStream; + if ( !ParsePackageRawStream() ) + { + m_xStream = xOldStream; + throw packages::NoRawFormatException(THROW_WHERE ); + } + + // the raw stream MUST have seekable access + m_bHasSeekable = true; + + SetPackageMember ( false ); + aEntry.nTime = -1; + m_nStreamMode = PACKAGE_STREAM_RAW; +} + +uno::Reference< io::XInputStream > SAL_CALL ZipPackageStream::getPlainRawStream() +{ + // There is no stream attached to this object + if ( m_nStreamMode == PACKAGE_STREAM_NOTSET ) + return uno::Reference< io::XInputStream >(); + + // this method can not be used together with old approach + if ( m_nStreamMode == PACKAGE_STREAM_DETECT ) + throw packages::zip::ZipIOException(THROW_WHERE ); + + if ( IsPackageMember() ) + { + return m_rZipPackage.getZipFile().getRawData( aEntry, GetEncryptionData(), m_bIsEncrypted, m_rZipPackage.GetSharedMutexRef() ); + } + else if ( GetOwnSeekStream().is() ) + { + if ( m_nStreamMode == PACKAGE_STREAM_RAW ) + { + // the header should not be returned here + return GetRawEncrStreamNoHeaderCopy(); + } + else if ( m_nStreamMode == PACKAGE_STREAM_DATA ) + return TryToGetRawFromDataStream( false ); + } + + return uno::Reference< io::XInputStream >(); +} + +// XPropertySet +void SAL_CALL ZipPackageStream::setPropertyValue( const OUString& aPropertyName, const Any& aValue ) +{ + if ( aPropertyName == "MediaType" ) + { + if ( m_rZipPackage.getFormat() != embed::StorageFormats::PACKAGE && m_rZipPackage.getFormat() != embed::StorageFormats::OFOPXML ) + throw beans::PropertyVetoException(THROW_WHERE ); + + if ( !(aValue >>= msMediaType) ) + throw IllegalArgumentException(THROW_WHERE "MediaType must be a string!", + uno::Reference< XInterface >(), + 2 ); + + if ( !msMediaType.isEmpty() ) + { + if ( msMediaType.indexOf ( "text" ) != -1 + || msMediaType == "application/vnd.sun.star.oleobject" ) + m_bToBeCompressed = true; + else if ( !m_bCompressedIsSetFromOutside ) + m_bToBeCompressed = false; + } + } + else if ( aPropertyName == "Size" ) + { + if ( !( aValue >>= aEntry.nSize ) ) + throw IllegalArgumentException(THROW_WHERE "Wrong type for Size property!", + uno::Reference< XInterface >(), + 2 ); + } + else if ( aPropertyName == "Encrypted" ) + { + if ( m_rZipPackage.getFormat() != embed::StorageFormats::PACKAGE ) + throw beans::PropertyVetoException(THROW_WHERE ); + + bool bEnc = false; + if ( !(aValue >>= bEnc) ) + throw IllegalArgumentException(THROW_WHERE "Wrong type for Encrypted property!", + uno::Reference< XInterface >(), + 2 ); + + // In case of new raw stream, the stream must not be encrypted on storing + if ( bEnc && m_nStreamMode == PACKAGE_STREAM_RAW ) + throw IllegalArgumentException(THROW_WHERE "Raw stream can not be encrypted on storing", + uno::Reference< XInterface >(), + 2 ); + + m_bToBeEncrypted = bEnc; + if ( m_bToBeEncrypted && !m_xBaseEncryptionData.is() ) + m_xBaseEncryptionData = new BaseEncryptionData; + + } + else if ( aPropertyName == ENCRYPTION_KEY_PROPERTY ) + { + if ( m_rZipPackage.getFormat() != embed::StorageFormats::PACKAGE ) + throw beans::PropertyVetoException(THROW_WHERE ); + + uno::Sequence< sal_Int8 > aNewKey; + + if ( !( aValue >>= aNewKey ) ) + { + OUString sTempString; + if ( !(aValue >>= sTempString) ) + throw IllegalArgumentException(THROW_WHERE "Wrong type for EncryptionKey property!", + uno::Reference< XInterface >(), + 2 ); + + sal_Int32 nPathLength = sTempString.getLength(); + Sequence < sal_Int8 > aSequence ( nPathLength ); + sal_Int8 *pArray = aSequence.getArray(); + const sal_Unicode *pChar = sTempString.getStr(); + for ( sal_Int32 i = 0; i < nPathLength; i++ ) + pArray[i] = static_cast < sal_Int8 > ( pChar[i] ); + aNewKey = aSequence; + } + + if ( aNewKey.hasElements() ) + { + if ( !m_xBaseEncryptionData.is() ) + m_xBaseEncryptionData = new BaseEncryptionData; + + m_aEncryptionKey = aNewKey; + // In case of new raw stream, the stream must not be encrypted on storing + m_bHaveOwnKey = true; + if ( m_nStreamMode != PACKAGE_STREAM_RAW ) + m_bToBeEncrypted = true; + } + else + { + m_bHaveOwnKey = false; + m_aEncryptionKey.realloc( 0 ); + } + + m_aStorageEncryptionKeys.realloc( 0 ); + } + else if ( aPropertyName == STORAGE_ENCRYPTION_KEYS_PROPERTY ) + { + if ( m_rZipPackage.getFormat() != embed::StorageFormats::PACKAGE ) + throw beans::PropertyVetoException(THROW_WHERE ); + + uno::Sequence< beans::NamedValue > aKeys; + if ( !( aValue >>= aKeys ) ) + { + throw IllegalArgumentException(THROW_WHERE "Wrong type for StorageEncryptionKeys property!", + uno::Reference< XInterface >(), + 2 ); + } + + if ( aKeys.hasElements() ) + { + if ( !m_xBaseEncryptionData.is() ) + m_xBaseEncryptionData = new BaseEncryptionData; + + m_aStorageEncryptionKeys = aKeys; + + // In case of new raw stream, the stream must not be encrypted on storing + m_bHaveOwnKey = true; + if ( m_nStreamMode != PACKAGE_STREAM_RAW ) + m_bToBeEncrypted = true; + } + else + { + m_bHaveOwnKey = false; + m_aStorageEncryptionKeys.realloc( 0 ); + } + + m_aEncryptionKey.realloc( 0 ); + } + else if ( aPropertyName == "Compressed" ) + { + bool bCompr = false; + + if ( !(aValue >>= bCompr) ) + throw IllegalArgumentException(THROW_WHERE "Wrong type for Compressed property!", + uno::Reference< XInterface >(), + 2 ); + + // In case of new raw stream, the stream must not be encrypted on storing + if ( bCompr && m_nStreamMode == PACKAGE_STREAM_RAW ) + throw IllegalArgumentException(THROW_WHERE "Raw stream can not be encrypted on storing", + uno::Reference< XInterface >(), + 2 ); + + m_bToBeCompressed = bCompr; + m_bCompressedIsSetFromOutside = true; + } + else + throw beans::UnknownPropertyException(aPropertyName); +} + +Any SAL_CALL ZipPackageStream::getPropertyValue( const OUString& PropertyName ) +{ + if ( PropertyName == "MediaType" ) + { + return Any(msMediaType); + } + else if ( PropertyName == "Size" ) + { + return Any(aEntry.nSize); + } + else if ( PropertyName == "Encrypted" ) + { + return Any((m_nStreamMode == PACKAGE_STREAM_RAW) || m_bToBeEncrypted); + } + else if ( PropertyName == "WasEncrypted" ) + { + return Any(m_bIsEncrypted); + } + else if ( PropertyName == "Compressed" ) + { + return Any(m_bToBeCompressed); + } + else if ( PropertyName == ENCRYPTION_KEY_PROPERTY ) + { + return Any(m_aEncryptionKey); + } + else if ( PropertyName == STORAGE_ENCRYPTION_KEYS_PROPERTY ) + { + return Any(m_aStorageEncryptionKeys); + } + else + throw beans::UnknownPropertyException(PropertyName); +} + +void ZipPackageStream::setSize ( const sal_Int64 nNewSize ) +{ + if ( aEntry.nCompressedSize != nNewSize ) + aEntry.nMethod = DEFLATED; + aEntry.nSize = nNewSize; +} +OUString ZipPackageStream::getImplementationName() +{ + return "ZipPackageStream"; +} + +Sequence< OUString > ZipPackageStream::getSupportedServiceNames() +{ + return { "com.sun.star.packages.PackageStream" }; +} + +sal_Bool SAL_CALL ZipPackageStream::supportsService( OUString const & rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zippackage/wrapstreamforshare.cxx b/package/source/zippackage/wrapstreamforshare.cxx new file mode 100644 index 0000000000..250ccb4ba5 --- /dev/null +++ b/package/source/zippackage/wrapstreamforshare.cxx @@ -0,0 +1,152 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <com/sun/star/io/IOException.hpp> +#include <utility> +#include <osl/diagnose.h> + +#include "wrapstreamforshare.hxx" + +using namespace ::com::sun::star; + +#if OSL_DEBUG_LEVEL > 0 +#define THROW_WHERE SAL_WHERE +#else +#define THROW_WHERE "" +#endif + +WrapStreamForShare::WrapStreamForShare( uno::Reference< io::XInputStream > xInStream, + rtl::Reference< comphelper::RefCountedMutex > xMutexRef ) +: m_xMutex(std::move( xMutexRef )) +, m_xInStream(std::move( xInStream )) +, m_nCurPos( 0 ) +{ + if ( !m_xMutex.is() || !m_xInStream.is() ) + { + OSL_FAIL( "Wrong initialization of wrapping stream!" ); + throw uno::RuntimeException(THROW_WHERE ); + } + m_xSeekable.set( m_xInStream, uno::UNO_QUERY_THROW ); +} + +WrapStreamForShare::~WrapStreamForShare() +{ +} + +// XInputStream +sal_Int32 SAL_CALL WrapStreamForShare::readBytes( uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) +{ + if ( !m_xInStream.is() ) + throw io::IOException(THROW_WHERE ); + + m_xSeekable->seek( m_nCurPos ); + + sal_Int32 nRead = m_xInStream->readBytes( aData, nBytesToRead ); + m_nCurPos += nRead; + + return nRead; +} + +sal_Int32 SAL_CALL WrapStreamForShare::readSomeBytes( uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) +{ + if ( !m_xInStream.is() ) + throw io::IOException(THROW_WHERE ); + + m_xSeekable->seek( m_nCurPos ); + + sal_Int32 nRead = m_xInStream->readSomeBytes( aData, nMaxBytesToRead ); + m_nCurPos += nRead; + + return nRead; +} + +void SAL_CALL WrapStreamForShare::skipBytes( sal_Int32 nBytesToSkip ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( !m_xInStream.is() ) + throw io::IOException(THROW_WHERE ); + + m_xSeekable->seek( m_nCurPos ); + + m_xInStream->skipBytes( nBytesToSkip ); + m_nCurPos = m_xSeekable->getPosition(); +} + +sal_Int32 SAL_CALL WrapStreamForShare::available() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( !m_xInStream.is() ) + throw io::IOException(THROW_WHERE ); + + return m_xInStream->available(); +} + +void SAL_CALL WrapStreamForShare::closeInput() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( !m_xInStream.is() ) + throw io::IOException(THROW_WHERE ); + + // the package is the owner so it will close the stream + // m_xInStream->closeInput(); + m_xInStream.clear(); + m_xSeekable.clear(); +} + +// XSeekable +void SAL_CALL WrapStreamForShare::seek( sal_Int64 location ) +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( !m_xInStream.is() ) + throw io::IOException(THROW_WHERE ); + + // let stream implementation do all the checking + m_xSeekable->seek( location ); + + m_nCurPos = m_xSeekable->getPosition(); +} + +sal_Int64 SAL_CALL WrapStreamForShare::getPosition() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( !m_xInStream.is() ) + throw io::IOException(THROW_WHERE ); + + return m_nCurPos; +} + +sal_Int64 SAL_CALL WrapStreamForShare::getLength() +{ + ::osl::MutexGuard aGuard( m_xMutex->GetMutex() ); + + if ( !m_xInStream.is() ) + throw io::IOException(THROW_WHERE ); + + return m_xSeekable->getLength(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zippackage/wrapstreamforshare.hxx b/package/source/zippackage/wrapstreamforshare.hxx new file mode 100644 index 0000000000..ac8de25884 --- /dev/null +++ b/package/source/zippackage/wrapstreamforshare.hxx @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_PACKAGE_SOURCE_ZIPPACKAGE_WRAPSTREAMFORSHARE_HXX +#define INCLUDED_PACKAGE_SOURCE_ZIPPACKAGE_WRAPSTREAMFORSHARE_HXX + +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XSeekable.hpp> +#include <comphelper/refcountedmutex.hxx> +#include <cppuhelper/implbase.hxx> +#include <rtl/ref.hxx> + +class WrapStreamForShare final : public cppu::WeakImplHelper < css::io::XInputStream + , css::io::XSeekable > +{ + rtl::Reference< comphelper::RefCountedMutex > m_xMutex; + css::uno::Reference < css::io::XInputStream > m_xInStream; + css::uno::Reference < css::io::XSeekable > m_xSeekable; + + sal_Int64 m_nCurPos; + +public: + WrapStreamForShare( css::uno::Reference< css::io::XInputStream > xInStream, + rtl::Reference< comphelper::RefCountedMutex > xMutexRef ); + virtual ~WrapStreamForShare() override; + + // XInputStream + virtual sal_Int32 SAL_CALL readBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) override; + virtual sal_Int32 SAL_CALL readSomeBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) override; + virtual void SAL_CALL skipBytes( sal_Int32 nBytesToSkip ) override; + virtual sal_Int32 SAL_CALL available( ) override; + virtual void SAL_CALL closeInput( ) override; + + //XSeekable + virtual void SAL_CALL seek( sal_Int64 location ) override; + virtual sal_Int64 SAL_CALL getPosition() override; + virtual sal_Int64 SAL_CALL getLength() override; + +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/source/zippackage/zipfileaccess.cxx b/package/source/zippackage/zipfileaccess.cxx new file mode 100644 index 0000000000..d693a51cec --- /dev/null +++ b/package/source/zippackage/zipfileaccess.cxx @@ -0,0 +1,479 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/io/NotConnectedException.hpp> +#include <com/sun/star/io/XActiveDataSink.hpp> +#include <com/sun/star/io/XStream.hpp> +#include <com/sun/star/io/XSeekable.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <comphelper/processfactory.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <zipfileaccess.hxx> +#include "ZipPackageSink.hxx" +#include <EncryptionData.hxx> + +#include <ucbhelper/content.hxx> +#include <rtl/ref.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +using namespace ::com::sun::star; + +#if OSL_DEBUG_LEVEL > 0 +#define THROW_WHERE SAL_WHERE +#else +#define THROW_WHERE "" +#endif + +OZipFileAccess::OZipFileAccess( const uno::Reference< uno::XComponentContext >& rxContext ) +: m_aMutexHolder( new comphelper::RefCountedMutex ) +, m_xContext( rxContext ) +, m_bDisposed( false ) +, m_bOwnContent( false ) +{ + if ( !rxContext.is() ) + throw uno::RuntimeException(THROW_WHERE ); +} + +OZipFileAccess::~OZipFileAccess() +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + if ( !m_bDisposed ) + { + try { + // dispose will use refcounting so the further destruction must be avoided + osl_atomic_increment(&m_refCount); + dispose(); + } catch( uno::Exception& ) + {} + } +} + +uno::Sequence< OUString > OZipFileAccess::GetPatternsFromString_Impl( const OUString& aString ) +{ + if ( aString.isEmpty() ) + return uno::Sequence< OUString >(); + + uno::Sequence< OUString > aPattern( 1 ); + auto pPattern = aPattern.getArray(); + sal_Int32 nInd = 0; + + const sal_Unicode* pString = aString.getStr(); + while( *pString ) + { + if ( *pString == '\\' ) + { + pString++; + + if ( *pString == '\\' ) + { + pPattern[nInd] += "\\"; + pString++; + } + else if ( *pString == '*' ) + { + pPattern[nInd] += "*"; + pString++; + } + else + { + OSL_FAIL( "The backslash is not guarded!" ); + pPattern[nInd] += "\\"; + } + } + else if ( *pString == '*' ) + { + aPattern.realloc( ( ++nInd ) + 1 ); + pPattern = aPattern.getArray(); + pString++; + } + else + { + pPattern[nInd] += OUStringChar( *pString ); + pString++; + } + } + + return aPattern; +} + +bool OZipFileAccess::StringGoodForPattern_Impl( std::u16string_view aString, + const uno::Sequence< OUString >& aPattern ) +{ + sal_Int32 nInd = aPattern.getLength() - 1; + if ( nInd < 0 ) + return false; + + if ( nInd == 0 ) + { + if ( aPattern[0].isEmpty() ) + return true; + + return aString == aPattern[0]; + } + + sal_Int32 nBeginInd = aPattern[0].getLength(); + sal_Int32 nEndInd = aString.size() - aPattern[nInd].getLength(); + if ( nEndInd < nBeginInd + || ( nEndInd != sal_Int32(aString.size()) && aString.substr( nEndInd ) != aPattern[nInd] ) + || ( nBeginInd != 0 && aString.substr( 0, nBeginInd ) != aPattern[0] ) ) + return false; + + for ( sal_Int32 nCurInd = aPattern.getLength() - 2; nCurInd > 0; nCurInd-- ) + { + if ( aPattern[nCurInd].isEmpty() ) + continue; + + if ( nEndInd == nBeginInd ) + return false; + + // check that search does not use nEndInd position + size_t nLastInd = aString.substr(0, nEndInd - 1).rfind( aPattern[nCurInd] ); + + if ( nLastInd == std::u16string_view::npos ) + return false; + + if ( sal_Int32(nLastInd) < nBeginInd ) + return false; + + nEndInd = nLastInd; + } + + return true; +} + +// XInitialization +void SAL_CALL OZipFileAccess::initialize( const uno::Sequence< uno::Any >& aArguments ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + if ( m_bDisposed ) + throw lang::DisposedException(THROW_WHERE ); + + if ( m_pZipFile ) + throw uno::RuntimeException(THROW_WHERE ); // initialization is allowed only one time + + if ( !aArguments.hasElements() ) + throw lang::IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); + + OSL_ENSURE( aArguments.getLength() == 1, "Too many arguments are provided, only the first one will be used!" ); + + OUString aParamURL; + uno::Reference< io::XStream > xStream; + uno::Reference< io::XSeekable > xSeekable; + uno::Sequence<beans::NamedValue> aArgs; + + auto openInputStream = [&]() + { + ::ucbhelper::Content aContent( + aParamURL, + uno::Reference< css::ucb::XCommandEnvironment >(), + m_xContext ); + uno::Reference < io::XActiveDataSink > xSink = new ZipPackageSink; + if ( aContent.openStream ( xSink ) ) + { + m_xContentStream = xSink->getInputStream(); + m_bOwnContent = true; + xSeekable.set( m_xContentStream, uno::UNO_QUERY ); + } + }; + + if ( aArguments[0] >>= aParamURL ) + { + openInputStream(); + } + else if ( aArguments[0] >>= xStream ) + { + // a writable stream can implement both XStream & XInputStream + m_xContentStream = xStream->getInputStream(); + xSeekable.set( xStream, uno::UNO_QUERY ); + } + else if ( aArguments[0] >>= m_xContentStream ) + { + xSeekable.set( m_xContentStream, uno::UNO_QUERY ); + } + else if (aArguments[0] >>= aArgs) + { + for (const beans::NamedValue& rArg : std::as_const(aArgs)) + { + if (rArg.Name == "URL") + rArg.Value >>= aParamURL; + } + + if (aParamURL.isEmpty()) + throw lang::IllegalArgumentException( + THROW_WHERE"required argument 'URL' is not given or invalid.", + uno::Reference<uno::XInterface>(), 1); + + openInputStream(); + } + else + throw lang::IllegalArgumentException(THROW_WHERE, uno::Reference< uno::XInterface >(), 1 ); + + if ( !m_xContentStream.is() ) + throw io::IOException(THROW_WHERE ); + + if ( !xSeekable.is() ) + { + // TODO: after fwkbugfix02 is integrated a helper class can be used to make the stream seekable + throw io::IOException(THROW_WHERE ); + } + + // TODO: in case xSeekable is implemented on separated XStream implementation a wrapper is required + m_pZipFile.emplace( + m_aMutexHolder, + m_xContentStream, + m_xContext, + true ); +} + +// XNameAccess +uno::Any SAL_CALL OZipFileAccess::getByName( const OUString& aName ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + if ( m_bDisposed ) + throw lang::DisposedException(THROW_WHERE ); + + if ( !m_pZipFile ) + throw uno::RuntimeException(THROW_WHERE); + + EntryHash::iterator aIter = m_pZipFile->GetEntryHash().find( aName ); + if ( aIter == m_pZipFile->GetEntryHash().end() ) + throw container::NoSuchElementException(THROW_WHERE ); + + uno::Reference< io::XInputStream > xEntryStream; + try + { + xEntryStream = m_pZipFile->getDataStream((*aIter).second, + ::rtl::Reference< EncryptionData >(), + false, + m_aMutexHolder); + } + catch (const container::NoSuchElementException&) + { + throw; + } + catch (const lang::WrappedTargetException&) + { + throw; + } + catch (const uno::RuntimeException&) + { + throw; + } + catch (const uno::Exception&) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw lang::WrappedTargetException( "This package is unusable!", + getXWeak(), anyEx); + } + + if ( !xEntryStream.is() ) + throw uno::RuntimeException(THROW_WHERE ); + + return uno::Any ( xEntryStream ); +} + +uno::Sequence< OUString > SAL_CALL OZipFileAccess::getElementNames() +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + if ( m_bDisposed ) + throw lang::DisposedException(THROW_WHERE ); + + if ( !m_pZipFile ) + throw uno::RuntimeException(THROW_WHERE); + + uno::Sequence< OUString > aNames( m_pZipFile->GetEntryHash().size() ); + auto pNames = aNames.getArray(); + sal_Int32 nLen = 0; + + for ( const auto& rEntry : m_pZipFile->GetEntryHash() ) + { + if ( aNames.getLength() < ++nLen ) + { + OSL_FAIL( "The size must be the same!" ); + aNames.realloc( nLen ); + pNames = aNames.getArray(); + } + + pNames[nLen-1] = rEntry.second.sPath; + } + + if ( aNames.getLength() != nLen ) + { + OSL_FAIL( "The size must be the same!" ); + aNames.realloc( nLen ); + } + + return aNames; +} + +sal_Bool SAL_CALL OZipFileAccess::hasByName( const OUString& aName ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + if ( m_bDisposed ) + throw lang::DisposedException(THROW_WHERE ); + + if ( !m_pZipFile ) + throw uno::RuntimeException(THROW_WHERE); + + EntryHash::iterator aIter = m_pZipFile->GetEntryHash().find( aName ); + + return ( aIter != m_pZipFile->GetEntryHash().end() ); +} + +uno::Type SAL_CALL OZipFileAccess::getElementType() +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + if ( m_bDisposed ) + throw lang::DisposedException(THROW_WHERE ); + + if ( !m_pZipFile ) + throw uno::RuntimeException(THROW_WHERE); + + return cppu::UnoType<io::XInputStream>::get(); +} + +sal_Bool SAL_CALL OZipFileAccess::hasElements() +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + if ( m_bDisposed ) + throw lang::DisposedException(THROW_WHERE ); + + if ( !m_pZipFile ) + throw uno::RuntimeException(THROW_WHERE); + + return ( !m_pZipFile->GetEntryHash().empty() ); +} + +// XZipFileAccess +uno::Reference< io::XInputStream > SAL_CALL OZipFileAccess::getStreamByPattern( const OUString& aPatternString ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + if ( m_bDisposed ) + throw lang::DisposedException(THROW_WHERE ); + + if ( !m_pZipFile ) + throw io::NotConnectedException(THROW_WHERE ); + + // Code to compare strings by patterns + uno::Sequence< OUString > aPattern = GetPatternsFromString_Impl( aPatternString ); + + auto aIter = std::find_if(m_pZipFile->GetEntryHash().begin(), m_pZipFile->GetEntryHash().end(), + [&aPattern](const EntryHash::value_type& rEntry) { return StringGoodForPattern_Impl(rEntry.second.sPath, aPattern); }); + if (aIter != m_pZipFile->GetEntryHash().end()) + { + uno::Reference< io::XInputStream > xEntryStream( m_pZipFile->getDataStream( (*aIter).second, + ::rtl::Reference< EncryptionData >(), + false, + m_aMutexHolder ) ); + + if ( !xEntryStream.is() ) + throw uno::RuntimeException(THROW_WHERE ); + return xEntryStream; + } + + throw container::NoSuchElementException(THROW_WHERE ); +} + +// XComponent +void SAL_CALL OZipFileAccess::dispose() +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + if ( m_bDisposed ) + throw lang::DisposedException(THROW_WHERE ); + + if ( m_pListenersContainer ) + { + lang::EventObject aSource( getXWeak() ); + m_pListenersContainer->disposeAndClear( aSource ); + m_pListenersContainer.reset(); + } + + m_pZipFile.reset(); + + if ( m_xContentStream.is() && m_bOwnContent ) + try { + m_xContentStream->closeInput(); + } catch( uno::Exception& ) + {} + + m_bDisposed = true; +} + +void SAL_CALL OZipFileAccess::addEventListener( const uno::Reference< lang::XEventListener >& xListener ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + if ( m_bDisposed ) + throw lang::DisposedException(THROW_WHERE ); + + if ( !m_pListenersContainer ) + m_pListenersContainer.reset( new ::comphelper::OInterfaceContainerHelper3<css::lang::XEventListener>( m_aMutexHolder->GetMutex() ) ); + m_pListenersContainer->addInterface( xListener ); +} + +void SAL_CALL OZipFileAccess::removeEventListener( const uno::Reference< lang::XEventListener >& xListener ) +{ + ::osl::MutexGuard aGuard( m_aMutexHolder->GetMutex() ); + + if ( m_bDisposed ) + throw lang::DisposedException(THROW_WHERE ); + + if ( m_pListenersContainer ) + m_pListenersContainer->removeInterface( xListener ); +} + +OUString SAL_CALL OZipFileAccess::getImplementationName() +{ + return "com.sun.star.comp.package.zip.ZipFileAccess"; +} + +sal_Bool SAL_CALL OZipFileAccess::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +uno::Sequence< OUString > SAL_CALL OZipFileAccess::getSupportedServiceNames() +{ + return { "com.sun.star.packages.zip.ZipFileAccess", + "com.sun.star.comp.packages.zip.ZipFileAccess" }; +} + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +package_OZipFileAccess_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new OZipFileAccess(context)); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/package/util/package2.component b/package/util/package2.component new file mode 100644 index 0000000000..d436d9a35a --- /dev/null +++ b/package/util/package2.component @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.package.zip.ZipFileAccess" + constructor="package_OZipFileAccess_get_implementation"> + <service name="com.sun.star.comp.packages.zip.ZipFileAccess"/> + <service name="com.sun.star.packages.zip.ZipFileAccess"/> + </implementation> + <implementation name="com.sun.star.packages.comp.ZipPackage" + constructor="package_ZipPackage_get_implementation"> + <service name="com.sun.star.packages.Package"/> + </implementation> + <implementation name="com.sun.star.packages.manifest.comp.ManifestReader" + constructor="package_ManifestReader_get_implementation"> + <service name="com.sun.star.packages.manifest.ManifestReader"/> + </implementation> + <implementation name="com.sun.star.packages.manifest.comp.ManifestWriter" + constructor="package_ManifestWriter_get_implementation"> + <service name="com.sun.star.packages.manifest.ManifestWriter"/> + </implementation> +</component> |