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 /tools | |
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 'tools')
89 files changed, 25711 insertions, 0 deletions
diff --git a/tools/CppunitTest_tools_config.mk b/tools/CppunitTest_tools_config.mk new file mode 100644 index 0000000000..79c07c7aba --- /dev/null +++ b/tools/CppunitTest_tools_config.mk @@ -0,0 +1,38 @@ +# -*- 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,tools_config)) + +$(eval $(call gb_CppunitTest_use_external,tools_config,boost_headers)) + +$(eval $(call gb_CppunitTest_add_exception_objects,tools_config, \ + tools/qa/cppunit/test_config \ +)) + +$(eval $(call gb_CppunitTest_use_sdk_api,tools_config)) + +$(eval $(call gb_CppunitTest_use_libraries,tools_config, \ + sal \ + tl \ + test \ + unotest \ +)) + +$(eval $(call gb_CppunitTest_use_static_libraries,tools_config, \ + ooopathutils \ +)) + +$(eval $(call gb_CppunitTest_set_include,tools_config,\ + $$(INCLUDE) \ + -I$(SRCDIR)/tools/inc \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/tools/CppunitTest_tools_test.mk b/tools/CppunitTest_tools_test.mk new file mode 100644 index 0000000000..1063b778ab --- /dev/null +++ b/tools/CppunitTest_tools_test.mk @@ -0,0 +1,83 @@ +# -*- 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,tools_test)) + +$(eval $(call gb_CppunitTest_use_external,tools_test,boost_headers)) + +$(eval $(call gb_CppunitTest_add_exception_objects,tools_test, \ + tools/qa/cppunit/test_bigint \ + tools/qa/cppunit/test_date \ + tools/qa/cppunit/test_time \ + tools/qa/cppunit/test_duration \ + tools/qa/cppunit/test_fract \ + tools/qa/cppunit/test_inetmime \ + tools/qa/cppunit/test_json_writer \ + tools/qa/cppunit/test_pathutils \ + tools/qa/cppunit/test_poly \ + tools/qa/cppunit/test_reversemap \ + tools/qa/cppunit/test_stream \ + tools/qa/cppunit/test_urlobj \ + tools/qa/cppunit/test_color \ + tools/qa/cppunit/test_rectangle \ + tools/qa/cppunit/test_100mm2twips \ + tools/qa/cppunit/test_fround \ + tools/qa/cppunit/test_xmlwalker \ + tools/qa/cppunit/test_xmlwriter \ + tools/qa/cppunit/test_GenericTypeSerializer \ + tools/qa/cppunit/test_guid \ + tools/qa/cppunit/test_cpuid \ + tools/qa/cppunit/test_cpu_runtime_detection_AVX2 \ + tools/qa/cppunit/test_cpu_runtime_detection_SSE2 \ + tools/qa/cppunit/test_cpu_runtime_detection_SSSE3 \ + tools/qa/cppunit/test_Wildcard \ + tools/qa/cppunit/test_zcodec \ +)) + +$(eval $(call gb_CppunitTest_add_exception_objects,tools_test,\ + tools/qa/cppunit/test_cpu_runtime_detection_AVX2_check, $(CXXFLAGS_INTRINSICS_AVX2) \ +)) + +$(eval $(call gb_CppunitTest_add_exception_objects,tools_test,\ + tools/qa/cppunit/test_cpu_runtime_detection_SSE2_check, $(CXXFLAGS_INTRINSICS_SSE2) \ +)) + +$(eval $(call gb_CppunitTest_add_exception_objects,tools_test,\ + tools/qa/cppunit/test_cpu_runtime_detection_SSSE3_check, $(CXXFLAGS_INTRINSICS_SSSE3) \ +)) + +$(eval $(call gb_CppunitTest_use_sdk_api,tools_test)) + +$(eval $(call gb_CppunitTest_use_libraries,tools_test, \ + basegfx \ + comphelper \ + sal \ + tl \ + test \ + unotest \ + utl \ + vcl \ +)) + +$(eval $(call gb_CppunitTest_use_static_libraries,tools_test, \ + ooopathutils \ +)) + +$(eval $(call gb_CppunitTest_set_include,tools_test,\ + $$(INCLUDE) \ + -I$(SRCDIR)/tools/inc \ +)) + +$(eval $(call gb_Library_use_externals,tools_test,\ + libxml2 \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/tools/CustomTarget_reversemap.mk b/tools/CustomTarget_reversemap.mk new file mode 100644 index 0000000000..94a697f1a6 --- /dev/null +++ b/tools/CustomTarget_reversemap.mk @@ -0,0 +1,22 @@ +# +# 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_CustomTarget_CustomTarget,tools/string)) + +$(call gb_CustomTarget_get_target,tools/string) : \ + $(call gb_CustomTarget_get_workdir,tools/string)/reversemap.cxx + +$(call gb_CustomTarget_get_workdir,tools/string)/reversemap.cxx : \ + $(call gb_Executable_get_runtime_dependencies,bestreversemap) \ + | $(call gb_CustomTarget_get_workdir,tools/string)/.dir + $(call gb_Output_announce,$(subst $(WORKDIR)/,,$@),$(true),BRM,1) + $(call gb_Trace_StartRange,$(subst $(WORKDIR)/,,$@),BRM) + $(call gb_Helper_execute,bestreversemap > $@) + $(call gb_Trace_EndRange,$(subst $(WORKDIR)/,,$@),BRM) + +# vim: set noet sw=4 ts=4: diff --git a/tools/Executable_bestreversemap.mk b/tools/Executable_bestreversemap.mk new file mode 100644 index 0000000000..6e06a51ef4 --- /dev/null +++ b/tools/Executable_bestreversemap.mk @@ -0,0 +1,20 @@ +# -*- 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_Executable_Executable,bestreversemap)) + +$(eval $(call gb_Executable_use_libraries,bestreversemap,\ + sal \ +)) + +$(eval $(call gb_Executable_add_exception_objects,bestreversemap,\ + tools/source/reversemap/bestreversemap \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/tools/IwyuFilter_tools.yaml b/tools/IwyuFilter_tools.yaml new file mode 100644 index 0000000000..5c5dbaaec1 --- /dev/null +++ b/tools/IwyuFilter_tools.yaml @@ -0,0 +1,42 @@ +--- +assumeFilename: tools/source/generic/gen.cxx +excludelist: + tools/qa/cppunit/test_pathutils.cxx: + # Needed for WIN32 specific unit test + - cppunit/TestAssert.h + - cppunit/plugin/TestPlugIn.h + - tools/pathutils.hxx + - cwchar + tools/qa/cppunit/test_cpu_runtime_detection_AVX2_check.cxx: + # Needed for LO_AVX2_AVAILABLE case + - sal/types.h + - tools/simdsupport.hxx + - stdlib.h + tools/qa/cppunit/test_cpu_runtime_detection_SSSE3_check.cxx: + # Needed for LO_SSSE3_AVAILABLE case + - tools/simdsupport.hxx + - stdlib.h + tools/source/debug/debug.cxx: + # Needed for linker visibility + - comphelper/diagnose_ex.hxx + # Keep for commented out "if defined __GLIBCXX__" path + - cxxabi.h + tools/source/misc/extendapplicationenvironment.cxx: + # Needed on MACOSX + - config_folders.h + # Needed for linker visibility + - tools/extendapplicationenvironment.hxx + tools/source/ref/ref.cxx: + # Don't replace with impl. detail + - tools/weakbase.hxx + tools/source/stream/strmwnt.cxx: + # WIN32-specific file + - string.h + - osl/thread.h + - o3tl/char16_t2wchar_t.hxx + tools/source/stream/strmunx.cxx: + # Needed for OSL_DEBUG_LEVEL > 1 + - osl/thread.h + tools/source/string/tenccvt.cxx: + # Needed for linker visibility + - tools/tenccvt.hxx diff --git a/tools/Library_tl.mk b/tools/Library_tl.mk new file mode 100644 index 0000000000..8269e6ae98 --- /dev/null +++ b/tools/Library_tl.mk @@ -0,0 +1,136 @@ +# -*- 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/. +# +# 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 . +# + +$(eval $(call gb_Library_Library,tl)) + +$(eval $(call gb_Library_set_include,tl,\ + -I$(SRCDIR)/tools/inc \ + $$(INCLUDE) \ +)) + +$(eval $(call gb_Library_set_precompiled_header,tl,tools/inc/pch/precompiled_tl)) + +$(eval $(call gb_Library_add_defs,tl,\ + -DTOOLS_DLLIMPLEMENTATION \ +)) + +$(eval $(call gb_Library_use_sdk_api,tl)) + +$(eval $(call gb_Library_use_libraries,tl,\ + basegfx \ + comphelper \ + i18nlangtag \ + cppu \ + cppuhelper \ + sal \ +)) + + +$(eval $(call gb_Library_add_exception_objects,tl,\ + tools/source/datetime/datetime \ + tools/source/datetime/datetimeutils \ + tools/source/datetime/duration \ + tools/source/datetime/systemdatetime \ + tools/source/datetime/tdate \ + tools/source/datetime/ttime \ + tools/source/debug/debug \ + tools/source/fsys/fileutil \ + tools/source/fsys/urlobj \ + tools/source/fsys/wldcrd \ + tools/source/generic/b3dtrans \ + tools/source/generic/bigint \ + tools/source/generic/color \ + tools/source/generic/config \ + tools/source/generic/fract \ + tools/source/generic/gen \ + tools/source/generic/line \ + tools/source/generic/point \ + tools/source/generic/poly \ + tools/source/generic/poly2 \ + tools/source/generic/svborder \ + tools/source/inet/inetmime \ + tools/source/inet/inetmsg \ + tools/source/inet/inetstrm \ + tools/source/memtools/multisel \ + tools/source/misc/cpuid \ + tools/source/misc/extendapplicationenvironment \ + tools/source/misc/json_writer \ + tools/source/ref/globname \ + tools/source/ref/ref \ + tools/source/stream/stream \ + tools/source/stream/vcompat \ + tools/source/stream/GenericTypeSerializer \ + tools/source/string/tenccvt \ + tools/source/zcodec/zcodec \ + tools/source/xml/XmlWriter \ + tools/source/xml/XmlWalker \ +)) + +ifneq ($(SYSTEM_LIBFIXMATH),TRUE) +$(eval $(call gb_Library_add_exception_objects,tl,\ + tools/source/misc/fix16 \ +)) +endif + +ifeq ($(OS),WNT) +$(eval $(call gb_Library_add_exception_objects,tl, \ + tools/source/stream/strmwnt \ +)) +else +$(eval $(call gb_Library_add_exception_objects,tl, \ + tools/source/stream/strmunx \ +)) +endif + +$(eval $(call gb_Library_add_generated_exception_objects,tl,\ + CustomTarget/tools/string/reversemap \ +)) + +$(eval $(call gb_Library_use_externals,tl,\ + boost_headers \ + zlib \ + libxml2 \ +)) + +ifeq ($(OS),LINUX) +$(eval $(call gb_Library_add_libs,tl,\ + -lrt \ +)) +endif + +ifeq ($(SYSTEM_LIBFIXMATH),TRUE) +$(eval $(call gb_Library_add_libs,tl,\ + -llibfixmath \ +)) +endif + +ifeq ($(OS),WNT) + +$(eval $(call gb_Library_use_system_win32_libs,tl,\ + mpr \ + netapi32 \ + ole32 \ + shell32 \ + uuid \ + winmm \ +)) + +endif + +# vim: set noet sw=4 ts=4: diff --git a/tools/Makefile b/tools/Makefile new file mode 100644 index 0000000000..ccb1c85a04 --- /dev/null +++ b/tools/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/tools/Module_tools.mk b/tools/Module_tools.mk new file mode 100644 index 0000000000..5632a5b94a --- /dev/null +++ b/tools/Module_tools.mk @@ -0,0 +1,38 @@ +# -*- 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/. +# +# 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 . +# + + +$(eval $(call gb_Module_Module,tools)) + +$(eval $(call gb_Module_add_targets,tools,\ + CustomTarget_reversemap \ + Library_tl \ + StaticLibrary_ooopathutils \ +)) + +$(eval $(call gb_Module_add_targets_for_build,tools,\ + Executable_bestreversemap \ +)) + +$(eval $(call gb_Module_add_check_targets,tools,\ + CppunitTest_tools_test \ + CppunitTest_tools_config \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 0000000000..5bb9f46d67 --- /dev/null +++ b/tools/README.md @@ -0,0 +1,11 @@ +# Old Tools (Deprecated) + +Predates sal - string functions, url manipulation, stream stuff, +polygons, etc. + +Exact history is lost before Sept. 18th, 2000, but old source code +comments show that part of the tools library dates back until at least +April 1991. + +This directory will be removed in the near future (see tdf#39445 or +tdf#63154). diff --git a/tools/StaticLibrary_ooopathutils.mk b/tools/StaticLibrary_ooopathutils.mk new file mode 100644 index 0000000000..1891c54e6d --- /dev/null +++ b/tools/StaticLibrary_ooopathutils.mk @@ -0,0 +1,26 @@ +# -*- 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/. +# +# 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 . +# + +$(eval $(call gb_StaticLibrary_StaticLibrary,ooopathutils)) + +$(eval $(call gb_StaticLibrary_add_exception_objects,ooopathutils,\ + tools/source/misc/pathutils \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/tools/inc/pch/precompiled_tl.cxx b/tools/inc/pch/precompiled_tl.cxx new file mode 100644 index 0000000000..cb05968098 --- /dev/null +++ b/tools/inc/pch/precompiled_tl.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_tl.hxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/inc/pch/precompiled_tl.hxx b/tools/inc/pch/precompiled_tl.hxx new file mode 100644 index 0000000000..a0a7a4e372 --- /dev/null +++ b/tools/inc/pch/precompiled_tl.hxx @@ -0,0 +1,81 @@ +/* -*- 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-06 09:21:31 using: + ./bin/update_pch tools tl --cutoff=5 --exclude:system --exclude:module --exclude:local + + If after updating build fails, use the following command to locate conflicting headers: + ./bin/update_pch_bisect ./tools/inc/pch/precompiled_tl.hxx "make tools.build" --find-conflicts +*/ + +#include <sal/config.h> +#if PCH_LEVEL >= 1 +#include <algorithm> +#include <cassert> +#include <cstddef> +#include <cstdlib> +#include <cstring> +#include <limits> +#include <memory> +#include <new> +#include <ostream> +#include <stddef.h> +#include <string.h> +#include <string_view> +#include <type_traits> +#include <utility> +#include <vector> +#include <boost/rational.hpp> +#endif // PCH_LEVEL >= 1 +#if PCH_LEVEL >= 2 +#include <osl/diagnose.h> +#include <osl/endian.h> +#include <osl/file.hxx> +#include <osl/process.h> +#include <osl/thread.h> +#include <rtl/bootstrap.hxx> +#include <rtl/character.hxx> +#include <rtl/math.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/string.h> +#include <rtl/string.hxx> +#include <rtl/stringconcat.hxx> +#include <rtl/stringutils.hxx> +#include <rtl/tencinfo.h> +#include <rtl/textenc.h> +#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/vector/b2enums.hxx> +#include <o3tl/cow_wrapper.hxx> +#endif // PCH_LEVEL >= 3 +#if PCH_LEVEL >= 4 +#include <tools/debug.hxx> +#include <tools/gen.hxx> +#include <tools/long.hxx> +#include <tools/stream.hxx> +#include <tools/toolsdllapi.h> +#endif // PCH_LEVEL >= 4 + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/inc/poly.h b/tools/inc/poly.h new file mode 100644 index 0000000000..772e854e31 --- /dev/null +++ b/tools/inc/poly.h @@ -0,0 +1,83 @@ +/* -*- 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 <sal/types.h> +#include <memory> +#include <tools/poly.hxx> + +class SAL_WARN_UNUSED ImplPolygon +{ +public: + std::unique_ptr<Point[]> mxPointAry; + std::unique_ptr<PolyFlags[]> mxFlagAry; + sal_uInt16 mnPoints; + +public: + ImplPolygon() : mnPoints(0) {} + ImplPolygon( sal_uInt16 nInitSize ); + ImplPolygon( sal_uInt16 nPoints, const Point* pPtAry, const PolyFlags* pInitFlags ); + ImplPolygon( const ImplPolygon& rImplPoly ); + ImplPolygon( const tools::Rectangle& rRect ); + ImplPolygon( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound); + ImplPolygon( const Point& rCenter, tools::Long nRadX, tools::Long nRadY ); + ImplPolygon( const tools::Rectangle& rBound, const Point& rStart, const Point& rEnd, + PolyStyle eStyle, bool bClockWiseArcDirection ); + ImplPolygon( const Point& rBezPt1, const Point& rCtrlPt1, const Point& rBezPt2, + const Point& rCtrlPt2, sal_uInt16 nPoints ); + ImplPolygon(const basegfx::B2DPolygon& rPolygon); + + bool operator==( const ImplPolygon& rCandidate ) const; + + void ImplInitSize(sal_uInt16 nInitSize, bool bFlags = false); + void ImplSetSize( sal_uInt16 nSize, bool bResize = true ); + void ImplCreateFlagArray(); + bool ImplSplit( sal_uInt16 nPos, sal_uInt16 nSpace, ImplPolygon const * pInitPoly = nullptr ); +}; + +#define MAX_POLYGONS SAL_MAX_UINT16 + +struct ImplPolyPolygon +{ + std::vector<tools::Polygon> mvPolyAry; + + ImplPolyPolygon( sal_uInt16 nInitSize ) + { + if ( !nInitSize ) + nInitSize = 1; + mvPolyAry.reserve(nInitSize); + } + + ImplPolyPolygon( const tools::Polygon& rPoly ) + { + if ( rPoly.GetSize() ) + mvPolyAry.push_back(rPoly); + else + mvPolyAry.reserve(16); + } + + ImplPolyPolygon(const basegfx::B2DPolyPolygon& rPolyPolygon); + + bool operator==(ImplPolyPolygon const & other) const + { + return mvPolyAry == other.mvPolyAry; + } +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/inc/systemdatetime.hxx b/tools/inc/systemdatetime.hxx new file mode 100644 index 0000000000..1b04e26ade --- /dev/null +++ b/tools/inc/systemdatetime.hxx @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ +#pragma once +#include <sal/types.h> + +constexpr sal_Int64 SEC_MASK = SAL_CONST_INT64(1000000000); +constexpr sal_Int64 MIN_MASK = SAL_CONST_INT64(100000000000); +constexpr sal_Int64 HOUR_MASK = SAL_CONST_INT64(10000000000000); + +/** Get current local timestamp. + Both pDate and pTime can be null. + Returns true if succeeded, false otherwise. + */ +bool GetSystemDateTime(sal_Int32* pDate, sal_Int64* pTime); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/tools/qa/cppunit/test_100mm2twips.cxx b/tools/qa/cppunit/test_100mm2twips.cxx new file mode 100644 index 0000000000..1fb69d9820 --- /dev/null +++ b/tools/qa/cppunit/test_100mm2twips.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 <sal/types.h> + +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> + +#include <tools/UnitConversion.hxx> + +class UnitConversionTest : public CppUnit::TestFixture +{ +public: + void testSanitiseMm100ToTwip() + { + CPPUNIT_ASSERT_EQUAL(sal_Int64(145), sanitiseMm100ToTwip(255)); + CPPUNIT_ASSERT_EQUAL(sal_Int64(-145), sanitiseMm100ToTwip(-255)); + } + + void testConvertPointToMm100() + { + CPPUNIT_ASSERT_DOUBLES_EQUAL(599.72, convertPointToMm100(17.0), 1E-2); + CPPUNIT_ASSERT_EQUAL(sal_Int64(600), convertPointToMm100(sal_Int64(17))); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(22930.55, convertPointToMm100(650.0), 1E-2); + CPPUNIT_ASSERT_EQUAL(sal_Int64(22931), convertPointToMm100(sal_Int64(650))); + } + + CPPUNIT_TEST_SUITE(UnitConversionTest); + CPPUNIT_TEST(testSanitiseMm100ToTwip); + CPPUNIT_TEST(testConvertPointToMm100); + CPPUNIT_TEST_SUITE_END(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(UnitConversionTest); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/qa/cppunit/test_GenericTypeSerializer.cxx b/tools/qa/cppunit/test_GenericTypeSerializer.cxx new file mode 100644 index 0000000000..d378dd7c30 --- /dev/null +++ b/tools/qa/cppunit/test_GenericTypeSerializer.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 <sal/types.h> + +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> + +#include <tools/GenericTypeSerializer.hxx> +#include <tools/stream.hxx> +#include <tools/gen.hxx> + +namespace tools +{ +class GenericTypeSerializerTest : public CppUnit::TestFixture +{ +public: + void testRoundtripPoint() + { + Point aPoint(20, 50); + SvMemoryStream aStream; + GenericTypeSerializer aSerializer(aStream); + aSerializer.writePoint(aPoint); + aStream.Seek(STREAM_SEEK_TO_BEGIN); + Point aReadPoint; + aSerializer.readPoint(aReadPoint); + CPPUNIT_ASSERT_EQUAL(aPoint, aReadPoint); + } + + void testRoundtripSize() + { + Size aSize(40, 80); + SvMemoryStream aStream; + GenericTypeSerializer aSerializer(aStream); + aSerializer.writeSize(aSize); + aStream.Seek(STREAM_SEEK_TO_BEGIN); + Size aReadSize; + aSerializer.readSize(aReadSize); + CPPUNIT_ASSERT_EQUAL(aSize, aReadSize); + } + + void testRoundtripRectangle() + { + { + Rectangle aRectangle; + CPPUNIT_ASSERT(aRectangle.IsEmpty()); + SvMemoryStream aStream; + aStream.Seek(STREAM_SEEK_TO_BEGIN); + GenericTypeSerializer aSerializer(aStream); + aSerializer.writeRectangle(aRectangle); + aStream.Seek(STREAM_SEEK_TO_BEGIN); + // Need to set the rectangle to non-empty, so it will be set to empty later + Rectangle aReadRectangle(Point(20, 50), Size(10, 30)); + aSerializer.readRectangle(aReadRectangle); + CPPUNIT_ASSERT(aRectangle.IsEmpty()); + } + + { + Rectangle aRectangle(Point(20, 50), Size(10, 30)); + SvMemoryStream aStream; + aStream.Seek(STREAM_SEEK_TO_BEGIN); + GenericTypeSerializer aSerializer(aStream); + aSerializer.writeRectangle(aRectangle); + aStream.Seek(STREAM_SEEK_TO_BEGIN); + Rectangle aReadRectangle; + aSerializer.readRectangle(aReadRectangle); + CPPUNIT_ASSERT_EQUAL(aRectangle.Top(), aReadRectangle.Top()); + CPPUNIT_ASSERT_EQUAL(aRectangle.Left(), aReadRectangle.Left()); + CPPUNIT_ASSERT_EQUAL(aRectangle.Right(), aReadRectangle.Right()); + CPPUNIT_ASSERT_EQUAL(aRectangle.Bottom(), aReadRectangle.Bottom()); + } + } + + void testRoundtripFraction() + { + { + Fraction aFraction(2, 5); + CPPUNIT_ASSERT_EQUAL(true, aFraction.IsValid()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), aFraction.GetNumerator()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(5), aFraction.GetDenominator()); + + SvMemoryStream aStream; + aStream.Seek(STREAM_SEEK_TO_BEGIN); + GenericTypeSerializer aSerializer(aStream); + aSerializer.writeFraction(aFraction); + + aStream.Seek(STREAM_SEEK_TO_BEGIN); + + Fraction aReadFraction(1, 2); + aSerializer.readFraction(aReadFraction); + CPPUNIT_ASSERT_EQUAL(true, aReadFraction.IsValid()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), aReadFraction.GetNumerator()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(5), aReadFraction.GetDenominator()); + } + { + Fraction aFraction(1, 0); + CPPUNIT_ASSERT_EQUAL(false, aFraction.IsValid()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aFraction.GetNumerator()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(-1), aFraction.GetDenominator()); + + SvMemoryStream aStream; + aStream.Seek(STREAM_SEEK_TO_BEGIN); + GenericTypeSerializer aSerializer(aStream); + aSerializer.writeFraction(aFraction); + + aStream.Seek(STREAM_SEEK_TO_BEGIN); + + Fraction aReadFraction(1, 2); + aSerializer.readFraction(aReadFraction); + CPPUNIT_ASSERT_EQUAL(false, aReadFraction.IsValid()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aReadFraction.GetNumerator()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(-1), aReadFraction.GetDenominator()); + } + } + + CPPUNIT_TEST_SUITE(GenericTypeSerializerTest); + CPPUNIT_TEST(testRoundtripPoint); + CPPUNIT_TEST(testRoundtripSize); + CPPUNIT_TEST(testRoundtripRectangle); + CPPUNIT_TEST(testRoundtripFraction); + CPPUNIT_TEST_SUITE_END(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(GenericTypeSerializerTest); + +} // namespace tools + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/qa/cppunit/test_Wildcard.cxx b/tools/qa/cppunit/test_Wildcard.cxx new file mode 100644 index 0000000000..1760ca6932 --- /dev/null +++ b/tools/qa/cppunit/test_Wildcard.cxx @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 <sal/config.h> + +#include <sal/types.h> +#include <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <tools/wldcrd.hxx> + +namespace +{ +class Test : public CppUnit::TestFixture +{ +public: + void test_Wildcard(); + + CPPUNIT_TEST_SUITE(Test); + CPPUNIT_TEST(test_Wildcard); + CPPUNIT_TEST_SUITE_END(); +}; + +void Test::test_Wildcard() +{ + WildCard wildcard(u"*.html;*??a;*\\*abc;*\\?xyz", ';'); // tdf#148253 + CPPUNIT_ASSERT(wildcard.Matches(u"foo.html")); + CPPUNIT_ASSERT(wildcard.Matches(u"foo.ht.html")); // test stepping back after partial match + CPPUNIT_ASSERT(wildcard.Matches(u"foo.html.html")); // test stepping back after full match + CPPUNIT_ASSERT(wildcard.Matches(u"??aa")); // test stepping back with question marks + CPPUNIT_ASSERT(wildcard.Matches(u"111*abc")); // test escaped asterisk + CPPUNIT_ASSERT(!wildcard.Matches(u"111-abc")); // test escaped asterisk + CPPUNIT_ASSERT(wildcard.Matches(u"111?xyz")); // test escaped question mark + CPPUNIT_ASSERT(!wildcard.Matches(u"111-xyz")); // test escaped question mark +} + +CPPUNIT_TEST_SUITE_REGISTRATION(Test); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/tools/qa/cppunit/test_bigint.cxx b/tools/qa/cppunit/test_bigint.cxx new file mode 100644 index 0000000000..3c7740fb76 --- /dev/null +++ b/tools/qa/cppunit/test_bigint.cxx @@ -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 . + */ + +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> + +#include <tools/bigint.hxx> + +#include <limits> + +namespace tools +{ +class BigIntTest : public CppUnit::TestFixture +{ +public: + void testConstructionFromLongLong(); + + CPPUNIT_TEST_SUITE(BigIntTest); + CPPUNIT_TEST(testConstructionFromLongLong); + CPPUNIT_TEST_SUITE_END(); +}; + +void BigIntTest::testConstructionFromLongLong() +{ + // small positive number + { + BigInt bi(static_cast<sal_Int64>(42)); + CPPUNIT_ASSERT(!bi.IsZero()); + CPPUNIT_ASSERT(!bi.IsNeg()); + CPPUNIT_ASSERT(bi.IsLong()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(42), static_cast<sal_Int32>(bi)); + } + + // small negative number + { + BigInt bi(static_cast<sal_Int64>(-42)); + CPPUNIT_ASSERT(!bi.IsZero()); + CPPUNIT_ASSERT(bi.IsNeg()); + CPPUNIT_ASSERT(bi.IsLong()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-42), static_cast<sal_Int32>(bi)); + } + + // positive number just fitting to sal_Int32 + { + BigInt bi(static_cast<sal_Int64>(std::numeric_limits<sal_Int32>::max())); + CPPUNIT_ASSERT(!bi.IsZero()); + CPPUNIT_ASSERT(!bi.IsNeg()); + CPPUNIT_ASSERT(bi.IsLong()); + CPPUNIT_ASSERT_EQUAL(std::numeric_limits<sal_Int32>::max(), static_cast<sal_Int32>(bi)); + } + + // negative number just fitting to sal_Int32 + { + BigInt bi(static_cast<sal_Int64>(std::numeric_limits<sal_Int32>::min())); + CPPUNIT_ASSERT(!bi.IsZero()); + CPPUNIT_ASSERT(bi.IsNeg()); + CPPUNIT_ASSERT(bi.IsLong()); + CPPUNIT_ASSERT_EQUAL(std::numeric_limits<sal_Int32>::min(), static_cast<sal_Int32>(bi)); + } + + // positive number not fitting to sal_Int32 + { + BigInt bi(static_cast<sal_Int64>(std::numeric_limits<sal_Int32>::max()) + 1); + CPPUNIT_ASSERT(!bi.IsZero()); + CPPUNIT_ASSERT(!bi.IsNeg()); + CPPUNIT_ASSERT(!bi.IsLong()); + } + + // negative number not fitting to sal_Int32 + { + BigInt bi(static_cast<sal_Int64>(std::numeric_limits<sal_Int32>::min()) - 1); + CPPUNIT_ASSERT(!bi.IsZero()); + CPPUNIT_ASSERT(bi.IsNeg()); + CPPUNIT_ASSERT(!bi.IsLong()); + } +} + +CPPUNIT_TEST_SUITE_REGISTRATION(BigIntTest); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/qa/cppunit/test_color.cxx b/tools/qa/cppunit/test_color.cxx new file mode 100644 index 0000000000..02f23fb3b8 --- /dev/null +++ b/tools/qa/cppunit/test_color.cxx @@ -0,0 +1,252 @@ +/* -*- 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 <sal/types.h> +#include <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <tools/color.hxx> + +namespace +{ + +class Test: public CppUnit::TestFixture +{ +public: + void testVariables(); + void test_asRGBColor(); + void test_ApplyTintOrShade(); + void test_ApplyLumModOff(); + void testGetColorError(); + void testInvert(); + void testBColor(); + void testLuminance(); + + CPPUNIT_TEST_SUITE(Test); + CPPUNIT_TEST(testVariables); + CPPUNIT_TEST(test_asRGBColor); + CPPUNIT_TEST(test_ApplyTintOrShade); + CPPUNIT_TEST(test_ApplyLumModOff); + CPPUNIT_TEST(testGetColorError); + CPPUNIT_TEST(testInvert); + CPPUNIT_TEST(testBColor); + CPPUNIT_TEST(testLuminance); + CPPUNIT_TEST_SUITE_END(); +}; + +void Test::testVariables() +{ + Color aColor(0x44, 0x88, 0xAA); + CPPUNIT_ASSERT_EQUAL(int(0x00), int(255 - aColor.GetAlpha())); + CPPUNIT_ASSERT_EQUAL(int(0x44), int(aColor.GetRed())); + CPPUNIT_ASSERT_EQUAL(int(0x88), int(aColor.GetGreen())); + CPPUNIT_ASSERT_EQUAL(int(0xAA), int(aColor.GetBlue())); + CPPUNIT_ASSERT_EQUAL(sal_uInt32(0x004488AA), sal_uInt32(aColor)); + + aColor = Color(ColorTransparency, 0xAABBCCDD); + CPPUNIT_ASSERT_EQUAL(int(0xAA), int(255 - aColor.GetAlpha())); + CPPUNIT_ASSERT_EQUAL(int(0xBB), int(aColor.GetRed())); + CPPUNIT_ASSERT_EQUAL(int(0xCC), int(aColor.GetGreen())); + CPPUNIT_ASSERT_EQUAL(int(0xDD), int(aColor.GetBlue())); + + aColor.SetAlpha(255 - 0x11); + CPPUNIT_ASSERT_EQUAL(sal_uInt32(0x11BBCCDD), sal_uInt32(aColor)); + + aColor.SetRed(0x22); + CPPUNIT_ASSERT_EQUAL(sal_uInt32(0x1122CCDD), sal_uInt32(aColor)); + + aColor.SetGreen(0x33); + CPPUNIT_ASSERT_EQUAL(sal_uInt32(0x112233DD), sal_uInt32(aColor)); + + aColor.SetBlue(0x44); + CPPUNIT_ASSERT_EQUAL(sal_uInt32(0x11223344), sal_uInt32(aColor)); + + aColor.SetAlpha(255 - 0x77); + CPPUNIT_ASSERT_EQUAL(sal_uInt32(0x77223344), sal_uInt32(aColor)); + + aColor.SetRed(0x88); + CPPUNIT_ASSERT_EQUAL(sal_uInt32(0x77883344), sal_uInt32(aColor)); + + aColor.SetGreen(0x99); + CPPUNIT_ASSERT_EQUAL(sal_uInt32(0x77889944), sal_uInt32(aColor)); + + aColor.SetBlue(0xAA); + CPPUNIT_ASSERT_EQUAL(sal_uInt32(0x778899AA), sal_uInt32(aColor)); +} + +void Test::test_asRGBColor() +{ + Color aColor; + aColor = COL_BLACK; + CPPUNIT_ASSERT_EQUAL(OUString("000000"), aColor.AsRGBHexString()); + + aColor = COL_WHITE; + CPPUNIT_ASSERT_EQUAL(OUString("ffffff"), aColor.AsRGBHexString()); + + aColor = COL_RED; + CPPUNIT_ASSERT_EQUAL(OUString("800000"), aColor.AsRGBHexString()); + + aColor = COL_TRANSPARENT; + CPPUNIT_ASSERT_EQUAL(OUString("ffffff"), aColor.AsRGBHexString()); + + aColor = COL_BLUE; + CPPUNIT_ASSERT_EQUAL(OUString("000080"), aColor.AsRGBHexString()); + + aColor.SetRed(0x12); + aColor.SetGreen(0x34); + aColor.SetBlue(0x56); + CPPUNIT_ASSERT_EQUAL(OUString("123456"), aColor.AsRGBHexString()); + + aColor = COL_AUTO; + CPPUNIT_ASSERT_EQUAL(OUString("ffffff"), aColor.AsRGBHexString()); +} + +OUString createTintShade(sal_uInt8 nR, sal_uInt8 nG, sal_uInt8 nB, std::u16string_view sReference, sal_Int16 nTintShade) +{ + Color aColor(nR, nG, nB); + if (sReference != aColor.AsRGBHexString()) + return OUString(); + aColor.ApplyTintOrShade(nTintShade); + return aColor.AsRGBHexString(); +} + +void Test::test_ApplyTintOrShade() +{ + // BLACK reference + + // 5% tint + CPPUNIT_ASSERT_EQUAL(OUString("0d0d0d"), createTintShade(0x00, 0x00, 0x00, u"000000", 500)); + // 15% tint + CPPUNIT_ASSERT_EQUAL(OUString("262626"), createTintShade(0x00, 0x00, 0x00, u"000000", 1500)); + // 25% tint + CPPUNIT_ASSERT_EQUAL(OUString("404040"), createTintShade(0x00, 0x00, 0x00, u"000000", 2500)); + // 50% tint + CPPUNIT_ASSERT_EQUAL(OUString("808080"), createTintShade(0x00, 0x00, 0x00, u"000000", 5000)); + // 100% tint + CPPUNIT_ASSERT_EQUAL(OUString("ffffff"), createTintShade(0x00, 0x00, 0x00, u"000000", 10000)); + + // WHITE reference + + // 5% shade + CPPUNIT_ASSERT_EQUAL(OUString("f2f2f2"), createTintShade(0xff, 0xff, 0xff, u"ffffff", -500)); + // 15% shade + CPPUNIT_ASSERT_EQUAL(OUString("d9d9d9"), createTintShade(0xff, 0xff, 0xff, u"ffffff", -1500)); + // 25% shade + CPPUNIT_ASSERT_EQUAL(OUString("bfbfbf"), createTintShade(0xff, 0xff, 0xff, u"ffffff", -2500)); + // 50% shade + CPPUNIT_ASSERT_EQUAL(OUString("808080"), createTintShade(0xff, 0xff, 0xff, u"ffffff", -5000)); + // 100% shade + CPPUNIT_ASSERT_EQUAL(OUString("000000"), createTintShade(0xff, 0xff, 0xff, u"ffffff", -10000)); + + // GREY reference + + // 0% - no change + CPPUNIT_ASSERT_EQUAL(OUString("808080"), createTintShade(0x80, 0x80, 0x80, u"808080", 0)); + + // 25% tint + CPPUNIT_ASSERT_EQUAL(OUString("a0a0a0"), createTintShade(0x80, 0x80, 0x80, u"808080", 2500)); + // 50% tint + //CPPUNIT_ASSERT_EQUAL(OUString("c0c0c0"), createTintShade(0x80, 0x80, 0x80, "808080", 5000)); + // disable for now - a rounding error happens on come platforms... + // 100% tint + CPPUNIT_ASSERT_EQUAL(OUString("ffffff"), createTintShade(0x80, 0x80, 0x80, u"808080", 10000)); + + // 25% shade + CPPUNIT_ASSERT_EQUAL(OUString("606060"), createTintShade(0x80, 0x80, 0x80, u"808080", -2500)); + // 50% shade + CPPUNIT_ASSERT_EQUAL(OUString("404040"), createTintShade(0x80, 0x80, 0x80, u"808080", -5000)); + // 100% shade + CPPUNIT_ASSERT_EQUAL(OUString("000000"), createTintShade(0x80, 0x80, 0x80, u"808080", -10000)); +} + +void Test::test_ApplyLumModOff() +{ + // Kind of blue. + Color aColor(0x44, 0x72, 0xC4); + + // PowerPoint calls this "Lighter 40%". + aColor.ApplyLumModOff(6000, 4000); + + CPPUNIT_ASSERT_EQUAL(OUString("8faadc"), aColor.AsRGBHexString()); +} + +void Test::testGetColorError() +{ + CPPUNIT_ASSERT_EQUAL(sal_uInt16(0), Color(0xAA, 0xBB, 0xCC).GetColorError(Color(0xAA, 0xBB, 0xCC))); + + CPPUNIT_ASSERT_EQUAL(sal_uInt16(1), Color(0xA0, 0xB0, 0xC0).GetColorError(Color(0xA1, 0xB0, 0xC0))); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(1), Color(0xA0, 0xB0, 0xC0).GetColorError(Color(0xA0, 0xB1, 0xC0))); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(1), Color(0xA0, 0xB0, 0xC0).GetColorError(Color(0xA0, 0xB0, 0xC1))); + + CPPUNIT_ASSERT_EQUAL(sal_uInt16(2), Color(0xA0, 0xB0, 0xC0).GetColorError(Color(0xA1, 0xB1, 0xC0))); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(2), Color(0xA0, 0xB0, 0xC0).GetColorError(Color(0xA0, 0xB1, 0xC1))); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(2), Color(0xA0, 0xB0, 0xC0).GetColorError(Color(0xA1, 0xB0, 0xC1))); + + CPPUNIT_ASSERT_EQUAL(sal_uInt16(3), Color(0xA0, 0xB0, 0xC0).GetColorError(Color(0xA1, 0xB1, 0xC1))); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(3), Color(0xA0, 0xB0, 0xC0).GetColorError(Color(0xA1, 0xB1, 0xC1))); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(3), Color(0xA0, 0xB0, 0xC0).GetColorError(Color(0xA1, 0xB1, 0xC1))); +} + +void Test::testInvert() +{ + Color aColor(0xFF, 0x00, 0x88); + aColor.Invert(); + CPPUNIT_ASSERT_EQUAL(Color(0x00, 0xFF, 0x77).AsRGBHexString(), aColor.AsRGBHexString()); + + // Alpha should be unaffected + aColor = Color(ColorTransparency, 0x22, 0xFF, 0x00, 0x88); + aColor.Invert(); + CPPUNIT_ASSERT_EQUAL(Color(ColorTransparency, 0x22, 0x00, 0xFF, 0x77).AsRGBHexString(), aColor.AsRGBHexString()); +} + +void Test::testBColor() +{ + Color aColor; + + aColor = Color(basegfx::BColor(0.0, 0.0, 0.0)); + + CPPUNIT_ASSERT_EQUAL(Color(0x00, 0x00, 0x00).AsRGBHexString(), aColor.AsRGBHexString()); + CPPUNIT_ASSERT_EQUAL(0.0, aColor.getBColor().getRed()); + CPPUNIT_ASSERT_EQUAL(0.0, aColor.getBColor().getGreen()); + CPPUNIT_ASSERT_EQUAL(0.0, aColor.getBColor().getBlue()); + + aColor = Color(basegfx::BColor(1.0, 1.0, 1.0)); + + CPPUNIT_ASSERT_EQUAL(Color(0xFF, 0xFF, 0xFF).AsRGBHexString(), aColor.AsRGBHexString()); + CPPUNIT_ASSERT_EQUAL(1.0, aColor.getBColor().getRed()); + CPPUNIT_ASSERT_EQUAL(1.0, aColor.getBColor().getGreen()); + CPPUNIT_ASSERT_EQUAL(1.0, aColor.getBColor().getBlue()); + + aColor = Color(basegfx::BColor(0.5, 0.25, 0.125)); + + CPPUNIT_ASSERT_EQUAL(Color(0x80, 0x40, 0x20).AsRGBHexString(), aColor.AsRGBHexString()); + // FP error is rather big, but that's normal + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.500, aColor.getBColor().getRed(), 1E-2); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.250, aColor.getBColor().getGreen(), 1E-2); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.125, aColor.getBColor().getBlue(), 1E-2); + +} + +void Test::testLuminance() +{ + CPPUNIT_ASSERT_EQUAL(sal_uInt8(0), COL_BLACK.GetLuminance()); + CPPUNIT_ASSERT_EQUAL(sal_uInt8(255), COL_WHITE.GetLuminance()); + CPPUNIT_ASSERT_EQUAL(sal_uInt8(128), Color(128, 128, 128).GetLuminance()); + CPPUNIT_ASSERT(COL_WHITE.IsBright()); + CPPUNIT_ASSERT(COL_BLACK.IsDark()); + CPPUNIT_ASSERT(Color(249, 250, 251).IsBright()); + CPPUNIT_ASSERT(Color(9, 10, 11).IsDark()); + CPPUNIT_ASSERT(COL_WHITE.GetLuminance() > COL_BLACK.GetLuminance()); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(Test); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/qa/cppunit/test_config.cxx b/tools/qa/cppunit/test_config.cxx new file mode 100644 index 0000000000..d54a2a93d5 --- /dev/null +++ b/tools/qa/cppunit/test_config.cxx @@ -0,0 +1,200 @@ +/* -*- 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 <test/bootstrapfixture.hxx> + +#include <osl/file.hxx> +#include <rtl/ustring.hxx> + +#include <tools/config.hxx> + +class ToolsConfigTest : public test::BootstrapFixture +{ +public: + ToolsConfigTest() + : BootstrapFixture(true, false) + { + } + + virtual void setUp() override + { + maOriginalConfigFile = m_directories.getURLFromSrc(u"/tools/qa/data/"); + maOriginalConfigFile += "testconfig.ini"; + + auto const e = osl::FileBase::getTempDirURL(maConfigFile); + CPPUNIT_ASSERT_EQUAL_MESSAGE("cannot create temp folder", osl::File::RC::E_None, e); + maConfigFile += "/config.ini"; + + osl::File::copy(maOriginalConfigFile, maConfigFile); + } + + virtual void tearDown() override { osl::File::remove(maConfigFile); } + + void testHasGroup() + { + Config aConfig(maConfigFile); + CPPUNIT_ASSERT(aConfig.HasGroup("TestGroup")); + CPPUNIT_ASSERT(aConfig.HasGroup("TestGroup2")); + } + + void testGetGroup() + { + Config aConfig(maConfigFile); + CPPUNIT_ASSERT_EQUAL(""_ostr, aConfig.GetGroup()); + + CPPUNIT_ASSERT_EQUAL("TestGroup"_ostr, aConfig.GetGroupName(0)); + CPPUNIT_ASSERT_EQUAL("TestGroup2"_ostr, aConfig.GetGroupName(1)); + CPPUNIT_ASSERT_EQUAL(""_ostr, aConfig.GetGroupName(2)); + } + + void testSetGroup() + { + Config aConfig(maConfigFile); + + aConfig.SetGroup("TestGroup"_ostr); + CPPUNIT_ASSERT_EQUAL("TestGroup"_ostr, aConfig.GetGroup()); + + // so this is a quirk of Config - you can set the group name, + // but it might not exist so you really should first check if + // it exists via HasGroup() + aConfig.SetGroup("TestGroupA"_ostr); + CPPUNIT_ASSERT(!aConfig.HasGroup("TestGroupA")); + CPPUNIT_ASSERT_EQUAL("TestGroupA"_ostr, aConfig.GetGroup()); + } + + void testDeleteGroup() + { + { + Config aConfig(maConfigFile); + + aConfig.DeleteGroup("TestGroup"); + CPPUNIT_ASSERT(!aConfig.HasGroup("TestGroup")); + CPPUNIT_ASSERT_EQUAL("TestGroup2"_ostr, aConfig.GetGroupName(0)); + + sal_uInt16 nActual = aConfig.GetGroupCount(); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(1), nActual); + } + + osl::File::copy(maOriginalConfigFile, maConfigFile); + + { + Config aConfig(maConfigFile); + + CPPUNIT_ASSERT(!aConfig.HasGroup("NonExistentTestGroup")); + aConfig.DeleteGroup("NonExistentTestGroup"); + CPPUNIT_ASSERT_EQUAL("TestGroup"_ostr, aConfig.GetGroupName(0)); + + sal_uInt16 nActual = aConfig.GetGroupCount(); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(2), nActual); + } + + osl::File::copy(maOriginalConfigFile, maConfigFile); + } + + void testGetGroupCount() + { + Config aConfig(maConfigFile); + sal_uInt16 nActual = aConfig.GetGroupCount(); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(2), nActual); + } + + void testReadKey() + { + Config aConfig(maConfigFile); + aConfig.SetGroup("TestGroup"_ostr); + CPPUNIT_ASSERT_EQUAL("testvalue"_ostr, aConfig.ReadKey("testkey"_ostr)); + CPPUNIT_ASSERT_EQUAL(OString(), aConfig.ReadKey("nonexistenttestkey"_ostr)); + CPPUNIT_ASSERT_EQUAL("notexists"_ostr, + aConfig.ReadKey("nonexistenttestkey"_ostr, "notexists"_ostr)); + + aConfig.SetGroup("TestGroup2"_ostr); + CPPUNIT_ASSERT_EQUAL("testvalue"_ostr, aConfig.ReadKey("testkey2"_ostr)); + CPPUNIT_ASSERT_EQUAL(OString(), aConfig.ReadKey("nonexistenttestkey"_ostr)); + CPPUNIT_ASSERT_EQUAL("notexists"_ostr, + aConfig.ReadKey("nonexistenttestkey"_ostr, "notexists"_ostr)); + } + + void testGetKeyName() + { + Config aConfig(maConfigFile); + aConfig.SetGroup("TestGroup"_ostr); + CPPUNIT_ASSERT_EQUAL("testkey"_ostr, aConfig.GetKeyName(0)); + + aConfig.SetGroup("TestGroup2"_ostr); + CPPUNIT_ASSERT_EQUAL("testkey2"_ostr, aConfig.GetKeyName(0)); + } + + void testWriteDeleteKey() + { + Config aConfig(maConfigFile); + aConfig.SetGroup("TestGroup"_ostr); + aConfig.WriteKey("testkey_new"_ostr, "testvalue"_ostr); + + sal_uInt16 nExpected = 2; + sal_uInt16 nActual = aConfig.GetKeyCount(); + CPPUNIT_ASSERT_EQUAL(nExpected, nActual); + CPPUNIT_ASSERT_EQUAL("testvalue"_ostr, aConfig.ReadKey("testkey_new"_ostr)); + + aConfig.DeleteKey("testkey_new"); + + nExpected = 1; + nActual = aConfig.GetKeyCount(); + CPPUNIT_ASSERT_EQUAL(nExpected, nActual); + CPPUNIT_ASSERT_EQUAL(OString(), aConfig.ReadKey("testkey_new"_ostr)); + + aConfig.SetGroup("TestGroup2"_ostr); + aConfig.WriteKey("testkey_new"_ostr, "testvalue"_ostr); + + nActual = aConfig.GetKeyCount(); + nExpected = 2; + CPPUNIT_ASSERT_EQUAL(nExpected, nActual); + CPPUNIT_ASSERT_EQUAL("testvalue"_ostr, aConfig.ReadKey("testkey_new"_ostr)); + + aConfig.DeleteKey("testkey_new"); + + nActual = aConfig.GetKeyCount(); + nExpected = 1; + CPPUNIT_ASSERT_EQUAL(nExpected, nActual); + CPPUNIT_ASSERT_EQUAL(OString(), aConfig.ReadKey("testkey_new"_ostr)); + + aConfig.SetGroup("TestGroup3"_ostr); + aConfig.WriteKey("testkey_new_group3"_ostr, "testvalue"_ostr); + + nActual = aConfig.GetKeyCount(); + nExpected = 1; + CPPUNIT_ASSERT_EQUAL(nExpected, nActual); + CPPUNIT_ASSERT_EQUAL("testvalue"_ostr, aConfig.ReadKey("testkey_new_group3"_ostr)); + + nExpected = 3; + CPPUNIT_ASSERT_EQUAL(nExpected, aConfig.GetGroupCount()); + + osl::File::copy(maOriginalConfigFile, maConfigFile); + } + + CPPUNIT_TEST_SUITE(ToolsConfigTest); + CPPUNIT_TEST(testHasGroup); + CPPUNIT_TEST(testGetGroup); + CPPUNIT_TEST(testSetGroup); + CPPUNIT_TEST(testDeleteGroup); + CPPUNIT_TEST(testReadKey); + CPPUNIT_TEST(testGetGroupCount); + CPPUNIT_TEST(testGetKeyName); + CPPUNIT_TEST(testWriteDeleteKey); + CPPUNIT_TEST_SUITE_END(); + +private: + OUString maOriginalConfigFile; + OUString maConfigFile; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ToolsConfigTest); + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/qa/cppunit/test_cpu_runtime_detection_AVX2.cxx b/tools/qa/cppunit/test_cpu_runtime_detection_AVX2.cxx new file mode 100644 index 0000000000..5ba8ed9ee2 --- /dev/null +++ b/tools/qa/cppunit/test_cpu_runtime_detection_AVX2.cxx @@ -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/. + */ + +#include "test_cpu_runtime_detection_x86_checks.hxx" + +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> + +#include <tools/cpuid.hxx> + +namespace +{ +class CpuRuntimeDetection_AVX2 : public CppUnit::TestFixture +{ +public: + void testCpuRuntimeDetection(); + + CPPUNIT_TEST_SUITE(CpuRuntimeDetection_AVX2); + CPPUNIT_TEST(testCpuRuntimeDetection); + CPPUNIT_TEST_SUITE_END(); +}; + +void CpuRuntimeDetection_AVX2::testCpuRuntimeDetection() +{ + // can only run this function if CPU supports AVX2 + if (cpuid::isCpuInstructionSetSupported(cpuid::InstructionSetFlags::AVX2)) + CpuRuntimeDetectionX86Checks::checkAVX2(); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(CpuRuntimeDetection_AVX2); + +} // end anonymous namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/qa/cppunit/test_cpu_runtime_detection_AVX2_check.cxx b/tools/qa/cppunit/test_cpu_runtime_detection_AVX2_check.cxx new file mode 100644 index 0000000000..7ed8495f36 --- /dev/null +++ b/tools/qa/cppunit/test_cpu_runtime_detection_AVX2_check.cxx @@ -0,0 +1,56 @@ +/* -*- 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 <sal/types.h> +#include <tools/simdsupport.hxx> + +#include <stdlib.h> + +#include "test_cpu_runtime_detection_x86_checks.hxx" + +/* WARNING: This file is compiled with AVX2 support, don't call + * any function without checking cpuid to check the CPU can actually + * handle it, and don't include any headers that contain templates + * or inline functions, which includes cppunit. + */ +#ifdef LO_AVX2_AVAILABLE +#define CPPUNIT_ASSERT_EQUAL(a, b) ((a) == (b) ? (void)0 : abort()) +#endif + +void CpuRuntimeDetectionX86Checks::checkAVX2() +{ +#ifdef LO_AVX2_AVAILABLE + __m256i a = _mm256_set_epi64x(1, 4, 8, 3); + __m256i b = _mm256_set_epi64x(2, 1, 1, 5); + __m256i c = _mm256_xor_si256(a, b); + + sal_Int64 values[4]; + _mm256_storeu_si256(reinterpret_cast<__m256i*>(&values), c); + + CPPUNIT_ASSERT_EQUAL(sal_Int64(6), values[0]); + CPPUNIT_ASSERT_EQUAL(sal_Int64(9), values[1]); + CPPUNIT_ASSERT_EQUAL(sal_Int64(5), values[2]); + CPPUNIT_ASSERT_EQUAL(sal_Int64(3), values[3]); + + __m256i d = _mm256_set_epi64x(3, 5, 1, 0); + + __m256i result = _mm256_cmpeq_epi64(d, c); + + // Compare equals + sal_Int64 compare[4]; + _mm256_storeu_si256(reinterpret_cast<__m256i*>(&compare), result); + + CPPUNIT_ASSERT_EQUAL(sal_Int64(0), compare[0]); + CPPUNIT_ASSERT_EQUAL(sal_Int64(0), compare[1]); + CPPUNIT_ASSERT_EQUAL(sal_Int64(-1), compare[2]); + CPPUNIT_ASSERT_EQUAL(sal_Int64(-1), compare[3]); +#endif +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/qa/cppunit/test_cpu_runtime_detection_SSE2.cxx b/tools/qa/cppunit/test_cpu_runtime_detection_SSE2.cxx new file mode 100644 index 0000000000..93bac6f2a1 --- /dev/null +++ b/tools/qa/cppunit/test_cpu_runtime_detection_SSE2.cxx @@ -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/. + */ + +#include "test_cpu_runtime_detection_x86_checks.hxx" + +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> + +#include <tools/cpuid.hxx> + +namespace +{ +class CpuRuntimeDetection_SSE2 : public CppUnit::TestFixture +{ +public: + void testCpuRuntimeDetection(); + + CPPUNIT_TEST_SUITE(CpuRuntimeDetection_SSE2); + CPPUNIT_TEST(testCpuRuntimeDetection); + CPPUNIT_TEST_SUITE_END(); +}; + +void CpuRuntimeDetection_SSE2::testCpuRuntimeDetection() +{ + // can only run this function if CPU supports SSE2 + if (cpuid::isCpuInstructionSetSupported(cpuid::InstructionSetFlags::SSE2)) + CpuRuntimeDetectionX86Checks::checkSSE2(); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(CpuRuntimeDetection_SSE2); + +} // end anonymous namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/qa/cppunit/test_cpu_runtime_detection_SSE2_check.cxx b/tools/qa/cppunit/test_cpu_runtime_detection_SSE2_check.cxx new file mode 100644 index 0000000000..12e39eabfd --- /dev/null +++ b/tools/qa/cppunit/test_cpu_runtime_detection_SSE2_check.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/. + */ + +#include <tools/simdsupport.hxx> + +#include <stdlib.h> + +#include "test_cpu_runtime_detection_x86_checks.hxx" + +/* WARNING: This file is compiled with SSE2 support, don't call + * any function without checking cpuid to check the CPU can actually + * handle it, and don't include any headers that contain templates + * or inline functions, which includes cppunit. + */ +#ifdef LO_SSE2_AVAILABLE +#define CPPUNIT_ASSERT_EQUAL(a, b) ((a) == (b) ? (void)0 : abort()) +#endif +void CpuRuntimeDetectionX86Checks::checkSSE2() +{ +#ifdef LO_SSE2_AVAILABLE + // Try some SSE2 intrinsics calculation + __m128i a = _mm_set1_epi32(15); + __m128i b = _mm_set1_epi32(15); + __m128i c = _mm_xor_si128(a, b); + + // Check zero + CPPUNIT_ASSERT_EQUAL(0xFFFF, _mm_movemask_epi8(_mm_cmpeq_epi32(c, _mm_setzero_si128()))); +#endif +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/qa/cppunit/test_cpu_runtime_detection_SSSE3.cxx b/tools/qa/cppunit/test_cpu_runtime_detection_SSSE3.cxx new file mode 100644 index 0000000000..3c0e9d2524 --- /dev/null +++ b/tools/qa/cppunit/test_cpu_runtime_detection_SSSE3.cxx @@ -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/. + */ + +#include "test_cpu_runtime_detection_x86_checks.hxx" + +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> + +#include <tools/cpuid.hxx> + +namespace +{ +class CpuRuntimeDetection_SSSE3 : public CppUnit::TestFixture +{ +public: + void testCpuRuntimeDetection(); + + CPPUNIT_TEST_SUITE(CpuRuntimeDetection_SSSE3); + CPPUNIT_TEST(testCpuRuntimeDetection); + CPPUNIT_TEST_SUITE_END(); +}; + +void CpuRuntimeDetection_SSSE3::testCpuRuntimeDetection() +{ + // can only run this function if CPU supports SSSE3 + if (cpuid::isCpuInstructionSetSupported(cpuid::InstructionSetFlags::SSSE3)) + CpuRuntimeDetectionX86Checks::checkSSSE3(); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(CpuRuntimeDetection_SSSE3); + +} // end anonymous namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/qa/cppunit/test_cpu_runtime_detection_SSSE3_check.cxx b/tools/qa/cppunit/test_cpu_runtime_detection_SSSE3_check.cxx new file mode 100644 index 0000000000..705e5c2ab3 --- /dev/null +++ b/tools/qa/cppunit/test_cpu_runtime_detection_SSSE3_check.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/. + */ + +#include <tools/simdsupport.hxx> + +#include <stdlib.h> + +#include "test_cpu_runtime_detection_x86_checks.hxx" + +/* WARNING: This file is compiled with SSSE3 support, don't call + * any function without checking cpuid to check the CPU can actually + * handle it, and don't include any headers that contain templates + * or inline functions, which includes cppunit. + */ +#ifdef LO_SSSE3_AVAILABLE +#define CPPUNIT_ASSERT_EQUAL(a, b) ((a) == (b) ? (void)0 : abort()) +#endif +void CpuRuntimeDetectionX86Checks::checkSSSE3() +{ +#ifdef LO_SSSE3_AVAILABLE + // Try some SSSE3 intrinsics calculation + __m128i a = _mm_set1_epi32(3); + __m128i b = _mm_set1_epi32(3); + __m128i c = _mm_maddubs_epi16(a, b); + + // Check result is 9 + CPPUNIT_ASSERT_EQUAL(0xFFFF, _mm_movemask_epi8(_mm_cmpeq_epi32(c, _mm_set1_epi32(9)))); +#endif +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/qa/cppunit/test_cpu_runtime_detection_x86_checks.hxx b/tools/qa/cppunit/test_cpu_runtime_detection_x86_checks.hxx new file mode 100644 index 0000000000..0046985acd --- /dev/null +++ b/tools/qa/cppunit/test_cpu_runtime_detection_x86_checks.hxx @@ -0,0 +1,23 @@ +/* -*- 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 INCLUDE_TOOLS_QA_CPPUNIT_TEST_CPU_RUNTIME_DETECTION_X86_CHECKS_H +#define INCLUDE_TOOLS_QA_CPPUNIT_TEST_CPU_RUNTIME_DETECTION_X86_CHECKS_H + +class CpuRuntimeDetectionX86Checks +{ +public: + static void checkAVX2(); + static void checkSSE2(); + static void checkSSSE3(); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/qa/cppunit/test_cpuid.cxx b/tools/qa/cppunit/test_cpuid.cxx new file mode 100644 index 0000000000..7d9b5c41cc --- /dev/null +++ b/tools/qa/cppunit/test_cpuid.cxx @@ -0,0 +1,57 @@ +/* -*- 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 <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <tools/cpuid.hxx> +#include <rtl/ustring.hxx> + +namespace +{ +class CpuInstructionSetSupport : public CppUnit::TestFixture +{ +public: + void testCpuInstructionSetSupport(); + + CPPUNIT_TEST_SUITE(CpuInstructionSetSupport); + CPPUNIT_TEST(testCpuInstructionSetSupport); + CPPUNIT_TEST_SUITE_END(); +}; + +void CpuInstructionSetSupport::testCpuInstructionSetSupport() +{ + OUString aString = cpuid::instructionSetSupportedString(); + + if (cpuid::isCpuInstructionSetSupported(cpuid::InstructionSetFlags::SSE2)) + { + CPPUNIT_ASSERT(aString.indexOf("SSE2") >= 0); + } + + if (cpuid::isCpuInstructionSetSupported(cpuid::InstructionSetFlags::SSSE3)) + { + CPPUNIT_ASSERT(aString.indexOf("SSSE3") >= 0); + } + + if (cpuid::isCpuInstructionSetSupported(cpuid::InstructionSetFlags::AVX)) + { + CPPUNIT_ASSERT(aString.indexOf("AVX") > 0); + } + + if (cpuid::isCpuInstructionSetSupported(cpuid::InstructionSetFlags::AVX2)) + { + CPPUNIT_ASSERT(aString.indexOf("AVX2") > 0); + } +} + +CPPUNIT_TEST_SUITE_REGISTRATION(CpuInstructionSetSupport); + +} // end anonymous namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/qa/cppunit/test_date.cxx b/tools/qa/cppunit/test_date.cxx new file mode 100644 index 0000000000..e11270e6a2 --- /dev/null +++ b/tools/qa/cppunit/test_date.cxx @@ -0,0 +1,564 @@ +/* -*- 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 <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> + +#include <tools/date.hxx> + +namespace tools +{ +class DateTest : public CppUnit::TestFixture +{ +public: + void testDate(); + void testLeapYear(); + void testGetDaysInYear(); + void testValidGregorianDate(); + void testValidDate(); + void testNormalize(); + void testGetDayOfWeek(); + void testGetDaysInMonth(); + void testIsBetween(); + void testIsEndOfMonth(); + + CPPUNIT_TEST_SUITE(DateTest); + CPPUNIT_TEST(testDate); + CPPUNIT_TEST(testLeapYear); + CPPUNIT_TEST(testGetDaysInYear); + CPPUNIT_TEST(testValidGregorianDate); + CPPUNIT_TEST(testValidDate); + CPPUNIT_TEST(testNormalize); + CPPUNIT_TEST(testGetDayOfWeek); + CPPUNIT_TEST(testGetDaysInMonth); + CPPUNIT_TEST(testIsBetween); + CPPUNIT_TEST(testIsEndOfMonth); + CPPUNIT_TEST_SUITE_END(); +}; + +void DateTest::testDate() +{ + const Date aCE(1, 1, 1); // first day CE + const Date aBCE(31, 12, -1); // last day BCE + const Date aMin(1, 1, -32768); // minimum date + const Date aMax(31, 12, 32767); // maximum date + Date aDate(Date::EMPTY); + const sal_Int32 kMinDays = -11968265; + const sal_Int32 kMaxDays = 11967900; + + // Last day BCE to first day CE is 1 day difference. + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), aCE - aBCE); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-1), aBCE - aCE); + aDate = aBCE; + aDate.AddDays(1); + CPPUNIT_ASSERT_EQUAL(aCE.GetDate(), aDate.GetDate()); + aDate = aCE; + aDate.AddDays(-1); + CPPUNIT_ASSERT_EQUAL(aBCE.GetDate(), aDate.GetDate()); + + // The entire BCE and CE ranges cover that many days. Day 0 is -0001-12-31 + CPPUNIT_ASSERT_EQUAL(kMaxDays, aMax - aBCE); + CPPUNIT_ASSERT_EQUAL(kMinDays, aMin - aBCE); + + // Truncate at limits, not under-/overflow or wrap. + aDate = aMin; + aDate.AddDays(-1); + CPPUNIT_ASSERT_EQUAL(aMin.GetDate(), aDate.GetDate()); + aDate = aMax; + aDate.AddDays(1); + CPPUNIT_ASSERT_EQUAL(aMax.GetDate(), aDate.GetDate()); + aDate = aBCE; + aDate.AddDays(kMinDays - 10); + CPPUNIT_ASSERT_EQUAL(aMin.GetDate(), aDate.GetDate()); + aDate = aBCE; + aDate.AddDays(kMaxDays + 10); + CPPUNIT_ASSERT_EQUAL(aMax.GetDate(), aDate.GetDate()); + aDate = aMax; + aDate.SetDay(32); + aDate.Normalize(); + CPPUNIT_ASSERT_EQUAL(aMax.GetDate(), aDate.GetDate()); + CPPUNIT_ASSERT(!aDate.IsEmpty()); + + // 0001-00-x normalized to -0001-12-x + aDate.SetYear(1); + aDate.SetMonth(0); + aDate.SetDay(22); + aDate.Normalize(); + CPPUNIT_ASSERT_EQUAL(Date(22, 12, -1).GetDate(), aDate.GetDate()); + + sal_uInt32 nExpected = 11222; + CPPUNIT_ASSERT_EQUAL(nExpected, aDate.GetDateUnsigned()); + // 1999-02-32 normalized to 1999-03-04 + aDate.SetYear(1999); + aDate.SetMonth(2); + aDate.SetDay(32); + aDate.Normalize(); + CPPUNIT_ASSERT_EQUAL(Date(4, 3, 1999).GetDate(), aDate.GetDate()); + + // Empty date is not normalized and stays empty date. + aDate = Date(Date::EMPTY); + aDate.Normalize(); + CPPUNIT_ASSERT_EQUAL(Date(Date::EMPTY).GetDate(), aDate.GetDate()); + CPPUNIT_ASSERT(!aDate.IsValidDate()); // GetDate() also shall have no normalizing side effect + + // 0000-01-00 normalized to -0001-12-31 + // SetYear(0) asserts, use empty date to force. + aDate = Date(Date::EMPTY); + aDate.SetMonth(1); + aDate.SetDay(0); + aDate.Normalize(); + CPPUNIT_ASSERT_EQUAL(Date(31, 12, -1).GetDate(), aDate.GetDate()); + + // 1999-00-00 normalized to 1998-12-31 (not 1998-11-30, or otherwise + // also 0001-00-00 should be -0001-11-30 which it should not, should it?) + aDate.SetYear(1999); + aDate.SetMonth(0); + aDate.SetDay(0); + aDate.Normalize(); + CPPUNIT_ASSERT_EQUAL(Date(31, 12, 1998).GetDate(), aDate.GetDate()); + + // 0001-00-00 normalized to -0001-12-31 + aDate.SetYear(1); + aDate.SetMonth(0); + aDate.SetDay(0); + aDate.Normalize(); + CPPUNIT_ASSERT_EQUAL(Date(31, 12, -1).GetDate(), aDate.GetDate()); +} + +void DateTest::testLeapYear() +{ + { + Date aDate(1, 1, 2000); + CPPUNIT_ASSERT(aDate.IsLeapYear()); + } + + { + Date aDate(1, 1, 1900); + CPPUNIT_ASSERT(!aDate.IsLeapYear()); + } + + { + Date aDate(1, 1, 1999); + CPPUNIT_ASSERT(!aDate.IsLeapYear()); + } + + { + Date aDate(1, 1, 2004); + CPPUNIT_ASSERT(aDate.IsLeapYear()); + } + + { + Date aDate(1, 1, 400); + CPPUNIT_ASSERT(aDate.IsLeapYear()); + } + + { + // Year -1 is a leap year. + Date aDate(28, 2, -1); + aDate.AddDays(1); + CPPUNIT_ASSERT(aDate.IsLeapYear()); + CPPUNIT_ASSERT_EQUAL(Date(29, 2, -1).GetDate(), aDate.GetDate()); + } + + { + Date aDate(1, 3, -1); + aDate.AddDays(-1); + CPPUNIT_ASSERT(aDate.IsLeapYear()); + CPPUNIT_ASSERT_EQUAL(Date(29, 2, -1).GetDate(), aDate.GetDate()); + } + + { + // Year -5 is a leap year. + Date aDate(28, 2, -5); + aDate.AddDays(1); + CPPUNIT_ASSERT(aDate.IsLeapYear()); + CPPUNIT_ASSERT_EQUAL(Date(29, 2, -5).GetDate(), aDate.GetDate()); + } + + { + Date aDate(1, 3, -5); + aDate.AddDays(-1); + CPPUNIT_ASSERT(aDate.IsLeapYear()); + CPPUNIT_ASSERT_EQUAL(Date(29, 2, -5).GetDate(), aDate.GetDate()); + } +} + +void DateTest::testGetDaysInYear() +{ + { + Date aDate(1, 1, 2000); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(366), aDate.GetDaysInYear()); + } + + { + Date aDate(1, 1, 1900); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(365), aDate.GetDaysInYear()); + } + + { + Date aDate(1, 1, 1999); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(365), aDate.GetDaysInYear()); + } + + { + Date aDate(1, 1, 2004); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(366), aDate.GetDaysInYear()); + } + + { + Date aDate(1, 1, 400); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(366), aDate.GetDaysInYear()); + } +} + +void DateTest::testValidGregorianDate() +{ + { + Date aDate(1, 0, 2000); + CPPUNIT_ASSERT(!aDate.IsValidAndGregorian()); + } + + { + Date aDate(1, 13, 2000); + CPPUNIT_ASSERT(!aDate.IsValidAndGregorian()); + } + + { + Date aDate(1, 1, 1581); + CPPUNIT_ASSERT(!aDate.IsValidAndGregorian()); + } + + { + Date aDate(1, 9, 1582); + CPPUNIT_ASSERT(!aDate.IsValidAndGregorian()); + } + + { + Date aDate(1, 10, 1582); + CPPUNIT_ASSERT(!aDate.IsValidAndGregorian()); + } + + { + Date aDate(32, 1, 2000); + CPPUNIT_ASSERT(!aDate.IsValidAndGregorian()); + } + + { + Date aDate(29, 2, 2000); + CPPUNIT_ASSERT(aDate.IsValidAndGregorian()); + } + + { + Date aDate(29, 2, 2001); + CPPUNIT_ASSERT(!aDate.IsValidAndGregorian()); + } + + { + Date aDate(32, 3, 2000); + CPPUNIT_ASSERT(!aDate.IsValidAndGregorian()); + } + + { + Date aDate(31, 4, 2000); + CPPUNIT_ASSERT(!aDate.IsValidAndGregorian()); + } + + { + Date aDate(32, 5, 2000); + CPPUNIT_ASSERT(!aDate.IsValidAndGregorian()); + } + + { + Date aDate(31, 6, 2000); + CPPUNIT_ASSERT(!aDate.IsValidAndGregorian()); + } + + { + Date aDate(32, 7, 2000); + CPPUNIT_ASSERT(!aDate.IsValidAndGregorian()); + } + + { + Date aDate(32, 8, 2000); + CPPUNIT_ASSERT(!aDate.IsValidAndGregorian()); + } + + { + Date aDate(31, 9, 2000); + CPPUNIT_ASSERT(!aDate.IsValidAndGregorian()); + } + + { + Date aDate(32, 10, 2000); + CPPUNIT_ASSERT(!aDate.IsValidAndGregorian()); + } + + { + Date aDate(31, 11, 2000); + CPPUNIT_ASSERT(!aDate.IsValidAndGregorian()); + } + + { + Date aDate(32, 12, 2000); + CPPUNIT_ASSERT(!aDate.IsValidAndGregorian()); + } +} + +void DateTest::testValidDate() +{ + { + // Empty date is not a valid date. + Date aDate(Date::EMPTY); + CPPUNIT_ASSERT(aDate.IsEmpty()); + CPPUNIT_ASSERT(!aDate.IsValidDate()); + } + + { + Date aDate(32, 1, 2000); + CPPUNIT_ASSERT(!aDate.IsValidDate()); + } + + { + Date aDate(29, 2, 2000); + CPPUNIT_ASSERT(aDate.IsValidDate()); + } + + { + Date aDate(29, 2, 2001); + CPPUNIT_ASSERT(!aDate.IsValidDate()); + } + + { + Date aDate(32, 3, 2000); + CPPUNIT_ASSERT(!aDate.IsValidDate()); + } + + { + Date aDate(31, 4, 2000); + CPPUNIT_ASSERT(!aDate.IsValidDate()); + } + + { + Date aDate(32, 5, 2000); + CPPUNIT_ASSERT(!aDate.IsValidDate()); + } + + { + Date aDate(31, 6, 2000); + CPPUNIT_ASSERT(!aDate.IsValidDate()); + } + + { + Date aDate(32, 7, 2000); + CPPUNIT_ASSERT(!aDate.IsValidDate()); + } + + { + Date aDate(32, 8, 2000); + CPPUNIT_ASSERT(!aDate.IsValidDate()); + } + + { + Date aDate(31, 9, 2000); + CPPUNIT_ASSERT(!aDate.IsValidDate()); + } + + { + Date aDate(32, 10, 2000); + CPPUNIT_ASSERT(!aDate.IsValidDate()); + } + + { + Date aDate(31, 11, 2000); + CPPUNIT_ASSERT(!aDate.IsValidDate()); + } + + { + Date aDate(32, 12, 2000); + CPPUNIT_ASSERT(!aDate.IsValidDate()); + } +} + +void DateTest::testNormalize() +{ + { + Date aDate(32, 2, 1999); + aDate.Normalize(); + Date aExpectedDate(4, 3, 1999); + CPPUNIT_ASSERT_EQUAL(aExpectedDate, aDate); + } + + { + Date aDate(1, 13, 1999); + aDate.Normalize(); + Date aExpectedDate(1, 1, 2000); + CPPUNIT_ASSERT_EQUAL(aExpectedDate, aDate); + } + + { + Date aDate(42, 13, 1999); + aDate.Normalize(); + Date aExpectedDate(11, 2, 2000); + CPPUNIT_ASSERT_EQUAL(aExpectedDate, aDate); + } + + { + Date aDate(1, 0, 1); + aDate.Normalize(); + Date aExpectedDate(1, 12, -1); + CPPUNIT_ASSERT_EQUAL(aExpectedDate, aDate); + } +} + +void DateTest::testGetDayOfWeek() +{ + { + DayOfWeek eExpectedDay = DayOfWeek::MONDAY; + Date aDate(30, 4, 2018); + CPPUNIT_ASSERT_EQUAL(eExpectedDay, aDate.GetDayOfWeek()); + } + + { + DayOfWeek eExpectedDay = DayOfWeek::TUESDAY; + Date aDate(1, 5, 2018); + CPPUNIT_ASSERT_EQUAL(eExpectedDay, aDate.GetDayOfWeek()); + } + + { + DayOfWeek eExpectedDay = DayOfWeek::WEDNESDAY; + Date aDate(2, 5, 2018); + CPPUNIT_ASSERT_EQUAL(eExpectedDay, aDate.GetDayOfWeek()); + } + + { + DayOfWeek eExpectedDay = DayOfWeek::THURSDAY; + Date aDate(3, 5, 2018); + CPPUNIT_ASSERT_EQUAL(eExpectedDay, aDate.GetDayOfWeek()); + } + + { + DayOfWeek eExpectedDay = DayOfWeek::FRIDAY; + Date aDate(4, 5, 2018); + CPPUNIT_ASSERT_EQUAL(eExpectedDay, aDate.GetDayOfWeek()); + } + + { + DayOfWeek eExpectedDay = DayOfWeek::SATURDAY; + Date aDate(5, 5, 2018); + CPPUNIT_ASSERT_EQUAL(eExpectedDay, aDate.GetDayOfWeek()); + } + + { + DayOfWeek eExpectedDay = DayOfWeek::SUNDAY; + Date aDate(6, 5, 2018); + CPPUNIT_ASSERT_EQUAL(eExpectedDay, aDate.GetDayOfWeek()); + } +} + +void DateTest::testGetDaysInMonth() +{ + { + Date aDate(1, 1, 2000); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(31), aDate.GetDaysInMonth()); + } + + { + Date aDate(1, 2, 2000); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(29), aDate.GetDaysInMonth()); + } + + { + Date aDate(1, 2, 1999); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(28), aDate.GetDaysInMonth()); + } + + { + Date aDate(1, 3, 2000); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(31), aDate.GetDaysInMonth()); + } + + { + Date aDate(1, 4, 2000); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(30), aDate.GetDaysInMonth()); + } + + { + Date aDate(1, 5, 2000); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(31), aDate.GetDaysInMonth()); + } + + { + Date aDate(1, 6, 2000); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(30), aDate.GetDaysInMonth()); + } + + { + Date aDate(1, 7, 2000); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(31), aDate.GetDaysInMonth()); + } + + { + Date aDate(1, 8, 2000); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(31), aDate.GetDaysInMonth()); + } + + { + Date aDate(1, 9, 2000); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(30), aDate.GetDaysInMonth()); + } + + { + Date aDate(1, 10, 2000); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(31), aDate.GetDaysInMonth()); + } + + { + Date aDate(1, 11, 2000); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(30), aDate.GetDaysInMonth()); + } + + { + Date aDate(1, 12, 2000); + CPPUNIT_ASSERT_EQUAL(sal_uInt16(31), aDate.GetDaysInMonth()); + } +} + +void DateTest::testIsBetween() +{ + Date aDate(6, 4, 2018); + CPPUNIT_ASSERT(aDate.IsBetween(Date(1, 1, 2018), Date(1, 12, 2018))); +} + +void DateTest::testIsEndOfMonth() +{ + { + Date aDate(31, 12, 2000); + CPPUNIT_ASSERT(aDate.IsEndOfMonth()); + } + + { + Date aDate(30, 12, 2000); + CPPUNIT_ASSERT(!aDate.IsEndOfMonth()); + } + + { + Date aDate(29, 2, 2000); + CPPUNIT_ASSERT(aDate.IsEndOfMonth()); + } + + { + Date aDate(28, 2, 2000); + CPPUNIT_ASSERT(!aDate.IsEndOfMonth()); + } +} + +CPPUNIT_TEST_SUITE_REGISTRATION(DateTest); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/tools/qa/cppunit/test_duration.cxx b/tools/qa/cppunit/test_duration.cxx new file mode 100644 index 0000000000..c4032be83a --- /dev/null +++ b/tools/qa/cppunit/test_duration.cxx @@ -0,0 +1,356 @@ +/* -*- 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 <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> + +#include <tools/duration.hxx> +#include <tools/datetime.hxx> + +namespace tools +{ +class DurationTest : public CppUnit::TestFixture +{ +public: + void testDuration(); + + CPPUNIT_TEST_SUITE(DurationTest); + CPPUNIT_TEST(testDuration); + CPPUNIT_TEST_SUITE_END(); +}; + +void DurationTest::testDuration() +{ + { + const Duration aD(Time(0), Time(12, 0, 0)); + CPPUNIT_ASSERT_EQUAL(0.5, aD.GetInDays()); + } + { + const Duration aD(Time(24, 0, 0), Time(12, 0, 0)); + CPPUNIT_ASSERT_EQUAL(-0.5, aD.GetInDays()); + } + { + const DateTime aS(Date(23, 11, 1999), Time(6, 0, 0)); + const DateTime aE(Date(24, 11, 1999), Time(18, 0, 0)); + const Duration aD(aS, aE); + CPPUNIT_ASSERT_EQUAL(1.5, aD.GetInDays()); + DateTime aDT1(aS); + const DateTime aDT2 = aDT1 + aD; + CPPUNIT_ASSERT_EQUAL(aE, aDT2); + aDT1 += aD; + CPPUNIT_ASSERT_EQUAL(aE, aDT1); + aDT1 += aD; + CPPUNIT_ASSERT_EQUAL(DateTime(Date(26, 11, 1999), Time(6, 0, 0)), aDT1); + } + { + const DateTime aS(Date(23, 11, 1999), Time(18, 0, 0)); + const DateTime aE(Date(24, 11, 1999), Time(6, 0, 0)); + const Duration aD(aS, aE); + CPPUNIT_ASSERT_EQUAL(0.5, aD.GetInDays()); + DateTime aDT1(aS); + const DateTime aDT2 = aDT1 + aD; + CPPUNIT_ASSERT_EQUAL(aE, aDT2); + aDT1 += aD; + CPPUNIT_ASSERT_EQUAL(aE, aDT1); + aDT1 += aD; + CPPUNIT_ASSERT_EQUAL(DateTime(Date(24, 11, 1999), Time(18, 0, 0)), aDT1); + } + { + const DateTime aS(Date(24, 11, 1999), Time(18, 0, 0)); + const DateTime aE(Date(23, 11, 1999), Time(6, 0, 0)); + const Duration aD(aS, aE); + CPPUNIT_ASSERT_EQUAL(-1.5, aD.GetInDays()); + DateTime aDT1(aS); + const DateTime aDT2 = aDT1 + aD; + CPPUNIT_ASSERT_EQUAL(aE, aDT2); + aDT1 += aD; + CPPUNIT_ASSERT_EQUAL(aE, aDT1); + aDT1 += aD; + CPPUNIT_ASSERT_EQUAL(DateTime(Date(21, 11, 1999), Time(18, 0, 0)), aDT1); + } + { + const DateTime aS(Date(24, 11, 1999), Time(6, 0, 0)); + const DateTime aE(Date(23, 11, 1999), Time(18, 0, 0)); + const Duration aD(aS, aE); + CPPUNIT_ASSERT_EQUAL(-0.5, aD.GetInDays()); + DateTime aDT1(aS); + const DateTime aDT2 = aDT1 + aD; + CPPUNIT_ASSERT_EQUAL(aE, aDT2); + aDT1 += aD; + CPPUNIT_ASSERT_EQUAL(aE, aDT1); + aDT1 += aD; + CPPUNIT_ASSERT_EQUAL(DateTime(Date(23, 11, 1999), Time(6, 0, 0)), aDT1); + } + { + const Duration aD(1.5); + CPPUNIT_ASSERT_EQUAL(1.5, aD.GetInDays()); + CPPUNIT_ASSERT_EQUAL(DateTime(Date(24, 11, 1999), Time(18, 0, 0)), + DateTime(Date(23, 11, 1999), Time(6, 0, 0)) + aD); + } + { + const Duration aD(-1.5); + CPPUNIT_ASSERT_EQUAL(-1.5, aD.GetInDays()); + CPPUNIT_ASSERT_EQUAL(DateTime(Date(23, 11, 1999), Time(6, 0, 0)), + DateTime(Date(24, 11, 1999), Time(18, 0, 0)) + aD); + } + { + const Duration aD(-1.5); + const Duration aN = -aD; + CPPUNIT_ASSERT_EQUAL(1.5, aN.GetInDays()); + } + { + const Duration aD(1, Time(2, 3, 4, 5)); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), aD.GetDays()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(2), aD.GetTime().GetHour()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(3), aD.GetTime().GetMin()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(4), aD.GetTime().GetSec()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(5), aD.GetTime().GetNanoSec()); + } + { + // 235929599 seconds == SAL_MAX_UINT16 hours + 59 minutes + 59 seconds + const Duration aD(0, Time(0, 0, 235929599, Time::nanoSecPerSec - 1)); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(2730), aD.GetDays()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(15), aD.GetTime().GetHour()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aD.GetTime().GetMin()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aD.GetTime().GetSec()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(999999999), aD.GetTime().GetNanoSec()); + } + { + // 235929599 seconds == SAL_MAX_UINT16 hours + 59 minutes + 59 seconds + const Duration aD(0, 0, 0, 235929599, Time::nanoSecPerSec - 1); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(2730), aD.GetDays()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(15), aD.GetTime().GetHour()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aD.GetTime().GetMin()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aD.GetTime().GetSec()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(999999999), aD.GetTime().GetNanoSec()); + } + { + const Duration aD(1, 2, 3, 4, 5); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), aD.GetDays()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(2), aD.GetTime().GetHour()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(3), aD.GetTime().GetMin()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(4), aD.GetTime().GetSec()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(5), aD.GetTime().GetNanoSec()); + } + { + const Duration aD(-1, 2, 3, 4, 5); + CPPUNIT_ASSERT(aD.IsNegative()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-1), aD.GetDays()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(2), aD.GetTime().GetHour()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(3), aD.GetTime().GetMin()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(4), aD.GetTime().GetSec()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(5), aD.GetTime().GetNanoSec()); + } + { + const Duration aD(1, SAL_MAX_UINT32, SAL_MAX_UINT32, SAL_MAX_UINT32, SAL_MAX_UINT64); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(182202802), aD.GetDays()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(1), aD.GetTime().GetHour()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(17), aD.GetTime().GetMin()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(48), aD.GetTime().GetSec()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(709551615), aD.GetTime().GetNanoSec()); + } + { + const Duration aD(-1, SAL_MAX_UINT32, SAL_MAX_UINT32, SAL_MAX_UINT32, SAL_MAX_UINT64); + CPPUNIT_ASSERT(aD.IsNegative()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-182202802), aD.GetDays()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(1), aD.GetTime().GetHour()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(17), aD.GetTime().GetMin()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(48), aD.GetTime().GetSec()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(709551615), aD.GetTime().GetNanoSec()); + } + { // Maximum days with all max possible. + const Duration aD(1965280846, SAL_MAX_UINT32, SAL_MAX_UINT32, SAL_MAX_UINT32, + SAL_MAX_UINT64); + CPPUNIT_ASSERT_EQUAL(SAL_MAX_INT32, aD.GetDays()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(1), aD.GetTime().GetHour()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(17), aD.GetTime().GetMin()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(48), aD.GetTime().GetSec()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(709551615), aD.GetTime().GetNanoSec()); + } + { // Maximum negative days with all max possible. + const Duration aD(-1965280847, SAL_MAX_UINT32, SAL_MAX_UINT32, SAL_MAX_UINT32, + SAL_MAX_UINT64); + CPPUNIT_ASSERT_EQUAL(SAL_MIN_INT32, aD.GetDays()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(1), aD.GetTime().GetHour()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(17), aD.GetTime().GetMin()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(48), aD.GetTime().GetSec()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(709551615), aD.GetTime().GetNanoSec()); + } + { // Add() + const DateTime aS(Date(23, 11, 1999), Time(0, 0, 0)); + const DateTime aE(Date(23, 11, 1999), Time(1, 23, 45)); + const Duration aD(aS, aE); + Duration aV = aD; + bool bOverflow = true; + aV.Add(aD, bOverflow); + CPPUNIT_ASSERT(!bOverflow); + CPPUNIT_ASSERT_EQUAL(DateTime(Date(23, 11, 1999), Time(2, 47, 30)), + DateTime(Date(23, 11, 1999), Time(0, 0, 0)) + aV); + for (int i = 0; i < 20; ++i) + aV.Add(aD, bOverflow); + CPPUNIT_ASSERT(!bOverflow); + CPPUNIT_ASSERT_EQUAL(DateTime(Date(24, 11, 1999), Time(6, 42, 30)), + DateTime(Date(23, 11, 1999), Time(0, 0, 0)) + aV); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), aV.GetDays()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(6), aV.GetTime().GetHour()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(42), aV.GetTime().GetMin()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(30), aV.GetTime().GetSec()); + CPPUNIT_ASSERT(aV.GetTime().GetTime() > 0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.27951388888889, aV.GetInDays(), 1E-14); + // Negative duration. + const Duration aN(aE, aS); + aV = aN; + aV.Add(aN, bOverflow); + CPPUNIT_ASSERT(!bOverflow); + CPPUNIT_ASSERT_EQUAL(DateTime(Date(22, 11, 1999), Time(21, 12, 30)), + DateTime(Date(23, 11, 1999), Time(0, 0, 0)) + aV); + for (int i = 0; i < 20; ++i) + aV.Add(aN, bOverflow); + CPPUNIT_ASSERT(!bOverflow); + CPPUNIT_ASSERT_EQUAL(DateTime(Date(21, 11, 1999), Time(17, 17, 30)), + DateTime(Date(23, 11, 1999), Time(0, 0, 0)) + aV); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-1), aV.GetDays()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(6), aV.GetTime().GetHour()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(42), aV.GetTime().GetMin()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(30), aV.GetTime().GetSec()); + CPPUNIT_ASSERT(aV.GetTime().GetTime() < 0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.27951388888889, aV.GetInDays(), 1E-14); + } + { // Mult() + const DateTime aS(Date(23, 11, 1999), Time(0, 0, 0)); + const DateTime aE(Date(23, 11, 1999), Time(1, 23, 45)); + const Duration aD(aS, aE); + bool bOverflow = true; + Duration aV = aD.Mult(22, bOverflow); + CPPUNIT_ASSERT(!bOverflow); + CPPUNIT_ASSERT_EQUAL(DateTime(Date(24, 11, 1999), Time(6, 42, 30)), + DateTime(Date(23, 11, 1999), Time(0, 0, 0)) + aV); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), aV.GetDays()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(6), aV.GetTime().GetHour()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(42), aV.GetTime().GetMin()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(30), aV.GetTime().GetSec()); + CPPUNIT_ASSERT(aV.GetTime().GetTime() > 0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.27951388888889, aV.GetInDays(), 1E-14); + // Negative duration. + const Duration aN(aE, aS); + bOverflow = true; + aV = aN.Mult(22, bOverflow); + CPPUNIT_ASSERT(!bOverflow); + CPPUNIT_ASSERT_EQUAL(DateTime(Date(21, 11, 1999), Time(17, 17, 30)), + DateTime(Date(23, 11, 1999), Time(0, 0, 0)) + aV); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-1), aV.GetDays()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(6), aV.GetTime().GetHour()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(42), aV.GetTime().GetMin()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(30), aV.GetTime().GetSec()); + CPPUNIT_ASSERT(aV.GetTime().GetTime() < 0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.27951388888889, aV.GetInDays(), 1E-14); + } + { // Mult() including days. + const Duration aD(1.5); + bool bOverflow = true; + Duration aV = aD.Mult(10, bOverflow); + CPPUNIT_ASSERT(!bOverflow); + CPPUNIT_ASSERT_DOUBLES_EQUAL(15.0, aV.GetInDays(), 0.0); + } + { // Mult() including days. + const Duration aD(-1.5); + bool bOverflow = true; + Duration aV = aD.Mult(10, bOverflow); + CPPUNIT_ASSERT(!bOverflow); + CPPUNIT_ASSERT_DOUBLES_EQUAL(-15.0, aV.GetInDays(), 0.0); + } + { // Mult() including days. + const Duration aD(1.5); + bool bOverflow = true; + Duration aV = aD.Mult(-10, bOverflow); + CPPUNIT_ASSERT(!bOverflow); + CPPUNIT_ASSERT_DOUBLES_EQUAL(-15.0, aV.GetInDays(), 0.0); + } + { // Mult() including days. + const Duration aD(-1.5); + bool bOverflow = true; + Duration aV = aD.Mult(-10, bOverflow); + CPPUNIT_ASSERT(!bOverflow); + CPPUNIT_ASSERT_DOUBLES_EQUAL(15.0, aV.GetInDays(), 0.0); + } + { // Mult() with overflow. + const Duration aD(SAL_MAX_INT32); + bool bOverflow = false; + Duration aV = aD.Mult(2, bOverflow); + CPPUNIT_ASSERT(bOverflow); + CPPUNIT_ASSERT_EQUAL(SAL_MAX_INT32, aV.GetDays()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(23), aV.GetTime().GetHour()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aV.GetTime().GetMin()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aV.GetTime().GetSec()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(Time::nanoSecPerSec - 1), + aV.GetTime().GetNanoSec()); + } + { // Mult() with overflow. + const Duration aD(SAL_MIN_INT32); + bool bOverflow = false; + Duration aV = aD.Mult(2, bOverflow); + CPPUNIT_ASSERT(bOverflow); + CPPUNIT_ASSERT_EQUAL(SAL_MIN_INT32, aV.GetDays()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(23), aV.GetTime().GetHour()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aV.GetTime().GetMin()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aV.GetTime().GetSec()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(Time::nanoSecPerSec - 1), + aV.GetTime().GetNanoSec()); + } + { // Mult() with overflow. + const Duration aD(SAL_MAX_INT32); + bool bOverflow = false; + Duration aV = aD.Mult(-2, bOverflow); + CPPUNIT_ASSERT(bOverflow); + CPPUNIT_ASSERT_EQUAL(SAL_MIN_INT32, aV.GetDays()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(23), aV.GetTime().GetHour()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aV.GetTime().GetMin()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aV.GetTime().GetSec()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(Time::nanoSecPerSec - 1), + aV.GetTime().GetNanoSec()); + } + { // Mult() with overflow. + const Duration aD(SAL_MIN_INT32); + bool bOverflow = false; + Duration aV = aD.Mult(-2, bOverflow); + CPPUNIT_ASSERT(bOverflow); + CPPUNIT_ASSERT_EQUAL(SAL_MAX_INT32, aV.GetDays()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(23), aV.GetTime().GetHour()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aV.GetTime().GetMin()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aV.GetTime().GetSec()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(Time::nanoSecPerSec - 1), + aV.GetTime().GetNanoSec()); + } + { // Inaccurate double yielding exact duration. + const Time aS(15, 0, 0); + const Time aE(16, 0, 0); + const Duration aD(aE.GetTimeInDays() - aS.GetTimeInDays()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(0), aD.GetDays()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(1), aD.GetTime().GetHour()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(0), aD.GetTime().GetMin()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(0), aD.GetTime().GetSec()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(0), aD.GetTime().GetNanoSec()); + } + { // Inaccurate double yielding exact duration, negative. + const Time aS(15, 0, 0); + const Time aE(16, 0, 0); + const Duration aD(aS.GetTimeInDays() - aE.GetTimeInDays()); + CPPUNIT_ASSERT(aD.IsNegative()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(0), aD.GetDays()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(1), aD.GetTime().GetHour()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(0), aD.GetTime().GetMin()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(0), aD.GetTime().GetSec()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(0), aD.GetTime().GetNanoSec()); + } +} + +CPPUNIT_TEST_SUITE_REGISTRATION(DurationTest); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/tools/qa/cppunit/test_fract.cxx b/tools/qa/cppunit/test_fract.cxx new file mode 100644 index 0000000000..1c3b8ad029 --- /dev/null +++ b/tools/qa/cppunit/test_fract.cxx @@ -0,0 +1,107 @@ +/* -*- 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/types.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> + +#include <rtl/math.hxx> +#include <tools/fract.hxx> + +namespace tools +{ + +class FractionTest : public CppUnit::TestFixture +{ +public: + + void testFraction() + { + const Fraction aFract(1082130431,1073741824); + CPPUNIT_ASSERT_MESSAGE( "Fraction #1 not approximately equal to 1.007812499068677", + rtl::math::approxEqual(static_cast<double>(aFract),1.007812499068677) ); + + Fraction aFract2( aFract ); + aFract2.ReduceInaccurate(8); + CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #2 not 1", + sal_Int32(1), aFract2.GetNumerator() ); + CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #2 not 1", + sal_Int32(1), aFract2.GetDenominator() ); + + Fraction aFract3( 0x7AAAAAAA, 0x35555555 ); + CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #3 cancellation wrong", + sal_Int32(0x7AAAAAAA), aFract3.GetNumerator() ); + CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #3 cancellation wrong", + sal_Int32(0x35555555), aFract3.GetDenominator() ); + aFract3.ReduceInaccurate(30); + CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #3 ReduceInaccurate erroneously cut precision", + sal_Int32(0x7AAAAAAA), aFract3.GetNumerator() ); + CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #3 ReduceInaccurate erroneously cut precision", + sal_Int32(0x35555555), aFract3.GetDenominator() ); + + aFract3.ReduceInaccurate(29); + CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #3 reduce to 29 bits failed", + sal_Int32(0x3D555555), aFract3.GetNumerator() ); + CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #3 reduce to 29 bits failed", + sal_Int32(0x1AAAAAAA), aFract3.GetDenominator() ); + + aFract3.ReduceInaccurate(9); + CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #3 reduce to 9 bits failed", + sal_Int32(0x0147), aFract3.GetNumerator() ); + CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #3 reduce to 9 bits failed", + sal_Int32(0x008E), aFract3.GetDenominator() ); + + aFract3.ReduceInaccurate(1); + CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #3 reduce to 1 bit failed", + sal_Int32(2), aFract3.GetNumerator() ); + CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #3 reduce to 1 bit failed", + sal_Int32(1), aFract3.GetDenominator() ); + + aFract3.ReduceInaccurate(0); + CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #3 reduce to 0 bits failed", + sal_Int32(2), aFract3.GetNumerator() ); + CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #3 reduce to 0 bits failed", + sal_Int32(1), aFract3.GetDenominator() ); + + } + + void testMinLongDouble() { + Fraction f(double(SAL_MIN_INT32)); + CPPUNIT_ASSERT_EQUAL(SAL_MIN_INT32, f.GetNumerator()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), f.GetDenominator()); + } + + void testCreateFromDoubleIn32BitsPlatform() { + // This pass in 64 bits but fail in 32 bits + Fraction f(0.960945); + CPPUNIT_ASSERT_EQUAL(true, f.IsValid()); + } + + CPPUNIT_TEST_SUITE(FractionTest); + CPPUNIT_TEST(testFraction); + CPPUNIT_TEST(testMinLongDouble); + CPPUNIT_TEST(testCreateFromDoubleIn32BitsPlatform); + CPPUNIT_TEST_SUITE_END(); +}; + + +CPPUNIT_TEST_SUITE_REGISTRATION(FractionTest); +} // namespace tools + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/qa/cppunit/test_fround.cxx b/tools/qa/cppunit/test_fround.cxx new file mode 100644 index 0000000000..4014ff3cb4 --- /dev/null +++ b/tools/qa/cppunit/test_fround.cxx @@ -0,0 +1,62 @@ +/* -*- 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/types.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> + +#include <tools/helpers.hxx> + +namespace tools +{ +class FRoundTest : public CppUnit::TestFixture +{ +public: + void testPositiveFRound() + { + sal_Int64 nExpected = 2; + sal_Int64 nActual = FRound(1.6); + CPPUNIT_ASSERT_EQUAL(nExpected, nActual); + + nExpected = 1; + nActual = FRound(1.4); + CPPUNIT_ASSERT_EQUAL(nExpected, nActual); + } + + void testNegativeFRound() + { + sal_Int64 nExpected = -2; + sal_Int64 nActual = FRound(-1.6); + CPPUNIT_ASSERT_EQUAL(nExpected, nActual); + + nExpected = -1; + nActual = FRound(-1.4); + CPPUNIT_ASSERT_EQUAL(nExpected, nActual); + } + + CPPUNIT_TEST_SUITE(FRoundTest); + CPPUNIT_TEST(testPositiveFRound); + CPPUNIT_TEST(testNegativeFRound); + CPPUNIT_TEST_SUITE_END(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(FRoundTest); +} // namespace tools + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/qa/cppunit/test_guid.cxx b/tools/qa/cppunit/test_guid.cxx new file mode 100644 index 0000000000..f16eb38931 --- /dev/null +++ b/tools/qa/cppunit/test_guid.cxx @@ -0,0 +1,123 @@ +/* -*- 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 <sal/types.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <tools/Guid.hxx> + +namespace tools +{ +class GuidTest : public CppUnit::TestFixture +{ +public: + void testGetString() + { + sal_uInt8 pArray[16] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; + Guid aGuid(pArray); + CPPUNIT_ASSERT_EQUAL("{01020304-0506-0708-090A-0B0C0D0E0F10}"_ostr, aGuid.getString()); + } + + void testCreate() + { + // data is generated when Guid is created + Guid aGuid1(Guid::Generate); + + // check it's not initialized to 0 + CPPUNIT_ASSERT(*std::max_element(aGuid1.begin(), aGuid1.end()) > 0u); + + // data is generated when Guid is created + Guid aGuid2(Guid::Generate); + + CPPUNIT_ASSERT_EQUAL(aGuid1, aGuid1); + CPPUNIT_ASSERT_EQUAL(aGuid2, aGuid2); + + CPPUNIT_ASSERT(aGuid1 != aGuid2); + } + + void testParse() + { + sal_uInt8 pArray1[16] = { 1, 1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 5, 5, 5, 5 }; + Guid aGuid1(pArray1); + + Guid aGuid2("{01010101-0202-0303-0404-050505050505}"); + CPPUNIT_ASSERT_EQUAL(aGuid1, aGuid2); + + sal_uInt8 pArray2[16] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + Guid aGuid3(pArray2); + + Guid aGuid4("{FFffFFff-FFff-FFff-FFff-FFffFFffFFff}"); + CPPUNIT_ASSERT_EQUAL(aGuid3, aGuid4); + + Guid aGuid5("{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}"); + CPPUNIT_ASSERT_EQUAL(aGuid5, aGuid4); + + Guid aGuid6("01010101-0202-0303-0404-0505050505005"); + CPPUNIT_ASSERT(aGuid6.isEmpty()); + + Guid aGuid7("Random"); + CPPUNIT_ASSERT(aGuid7.isEmpty()); + + Guid aGuid8("{0G010101-0202-0303-0404-050505050505}"); + CPPUNIT_ASSERT(aGuid8.isEmpty()); + + Guid aGuid9("{FFAAFFAA-FFAA-FFAA-FFAA-FF00FF11FF22}"); + CPPUNIT_ASSERT(!aGuid9.isEmpty()); + + Guid aGuid10("{FFAAFFAA?FFAA-FFAA-FFAA-FF00FF11FF22}"); + CPPUNIT_ASSERT(aGuid10.isEmpty()); + } + + void testEmpty() + { + sal_uInt8 pArray1[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + Guid aGuid1(pArray1); + CPPUNIT_ASSERT(aGuid1.isEmpty()); + + sal_uInt8 pArray2[16] = { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + Guid aGuid2(pArray2); + CPPUNIT_ASSERT(!aGuid2.isEmpty()); + + Guid aGuid3; + CPPUNIT_ASSERT(aGuid3.isEmpty()); + } + + void testCopyAndAssign() + { + sal_uInt8 pArray1[16] = { 1, 1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 5, 5, 5, 5 }; + Guid aGuid1(pArray1); + + // test copy constructor + Guid aGuid2(aGuid1); + CPPUNIT_ASSERT_EQUAL(aGuid1, aGuid2); + CPPUNIT_ASSERT(std::equal(aGuid1.cbegin(), aGuid1.cend(), aGuid2.cbegin(), aGuid2.cend())); + + // test assign + Guid aGuid3 = aGuid1; + CPPUNIT_ASSERT_EQUAL(aGuid3, aGuid1); + CPPUNIT_ASSERT(std::equal(aGuid3.cbegin(), aGuid3.cend(), aGuid1.cbegin(), aGuid1.cend())); + CPPUNIT_ASSERT_EQUAL(aGuid3, aGuid2); + CPPUNIT_ASSERT(std::equal(aGuid3.cbegin(), aGuid3.cend(), aGuid2.cbegin(), aGuid2.cend())); + } + + CPPUNIT_TEST_SUITE(GuidTest); + CPPUNIT_TEST(testGetString); + CPPUNIT_TEST(testCreate); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST(testEmpty); + CPPUNIT_TEST(testCopyAndAssign); + CPPUNIT_TEST_SUITE_END(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(GuidTest); + +} // namespace tools + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/qa/cppunit/test_inetmime.cxx b/tools/qa/cppunit/test_inetmime.cxx new file mode 100644 index 0000000000..d079f19cf2 --- /dev/null +++ b/tools/qa/cppunit/test_inetmime.cxx @@ -0,0 +1,167 @@ +/* -*- 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 <rtl/ustring.hxx> +#include <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> + +#include <tools/inetmime.hxx> + +namespace +{ + + class Test: public CppUnit::TestFixture + { + bool testDecode(char const * input, char const * expected); + public: + void test_decodeHeaderFieldBody(); + + void test_scanContentType_basic(); + void test_scanContentType_rfc2231(); + + CPPUNIT_TEST_SUITE(Test); + CPPUNIT_TEST(test_decodeHeaderFieldBody); + CPPUNIT_TEST(test_scanContentType_basic); + CPPUNIT_TEST(test_scanContentType_rfc2231); + CPPUNIT_TEST_SUITE_END(); + }; + + bool Test::testDecode(char const * input, char const * expected) + { + OUString result = INetMIME::decodeHeaderFieldBody(input); + return result.equalsAscii(expected); + } + + void Test::test_decodeHeaderFieldBody() + { + CPPUNIT_ASSERT(testDecode("=?iso-8859-1?B?QQ==?=", "A")); + CPPUNIT_ASSERT(testDecode("=?iso-8859-1?B?QUI=?=", "AB")); + CPPUNIT_ASSERT(testDecode("=?iso-8859-1?B?QUJD?=", "ABC")); + } + + void Test::test_scanContentType_basic() + { + OUString input + = "TEST/subTST; parm1=Value1; Parm2=\"unpacked value; %20\""; + // Just scan input for valid string: + auto end = INetMIME::scanContentType(input); + CPPUNIT_ASSERT(end != nullptr); + CPPUNIT_ASSERT_EQUAL(OUString(), OUString(end)); + // Scan input and parse type, subType and parameters: + OUString type; + OUString subType; + INetContentTypeParameterList parameters; + end = INetMIME::scanContentType(input, + &type, &subType, ¶meters); + CPPUNIT_ASSERT(end != nullptr); + CPPUNIT_ASSERT_EQUAL(OUString(), OUString(end)); + CPPUNIT_ASSERT_EQUAL(OUString("test"), type); + CPPUNIT_ASSERT_EQUAL(OUString("subtst"), subType); + CPPUNIT_ASSERT_EQUAL( + INetContentTypeParameterList::size_type(2), parameters.size()); + auto i = parameters.find("parm1"_ostr); + CPPUNIT_ASSERT(i != parameters.end()); + CPPUNIT_ASSERT_EQUAL(OString(), i->second.m_sCharset); + CPPUNIT_ASSERT_EQUAL(OString(), i->second.m_sLanguage); + CPPUNIT_ASSERT_EQUAL(OUString("Value1"), i->second.m_sValue); + CPPUNIT_ASSERT(i->second.m_bConverted); + i = parameters.find("parm2"_ostr); + CPPUNIT_ASSERT(i != parameters.end()); + CPPUNIT_ASSERT_EQUAL(OString(), i->second.m_sCharset); + CPPUNIT_ASSERT_EQUAL(OString(), i->second.m_sLanguage); + CPPUNIT_ASSERT_EQUAL(OUString("unpacked value; %20"), i->second.m_sValue); + CPPUNIT_ASSERT(i->second.m_bConverted); + } + + void Test::test_scanContentType_rfc2231() + { + // Test extended parameter with value split in 3 sections: + OUString input + = "TEST/subTST; " + "parm1*0*=US-ASCII'En'5%25%20; " + "Parm1*1*=of%2010;\t" + "parm1*2*=%20%3d%200.5"; + // Just scan input for valid string: + auto end = INetMIME::scanContentType(input); + CPPUNIT_ASSERT(end != nullptr); + CPPUNIT_ASSERT_EQUAL(OUString(), OUString(end)); + // Scan input and parse type, subType and parameters: + OUString type; + OUString subType; + INetContentTypeParameterList parameters; + end = INetMIME::scanContentType(input, + &type, &subType, ¶meters); + CPPUNIT_ASSERT(end != nullptr); + CPPUNIT_ASSERT_EQUAL(OUString(), OUString(end)); + CPPUNIT_ASSERT_EQUAL(OUString("test"), type); + CPPUNIT_ASSERT_EQUAL(OUString("subtst"), subType); + CPPUNIT_ASSERT_EQUAL( + INetContentTypeParameterList::size_type(1), parameters.size()); + auto i = parameters.find("parm1"_ostr); + CPPUNIT_ASSERT(i != parameters.end()); + CPPUNIT_ASSERT_EQUAL("us-ascii"_ostr, i->second.m_sCharset); + CPPUNIT_ASSERT_EQUAL("en"_ostr, i->second.m_sLanguage); + CPPUNIT_ASSERT_EQUAL(OUString("5% of 10 = 0.5"), i->second.m_sValue); + CPPUNIT_ASSERT(i->second.m_bConverted); + + // Test extended parameters with different value charsets: + input = "TEST/subTST;" + "parm1*0*=us-ascii'en'value;PARM1*1*=1;" + "parm2*0*=WINDOWS-1250'en-GB'value2%20%80;" + "parm3*0*=UNKNOWN'EN'value3;" + "parm1*1*=2"; // this parameter is a duplicate, + // the scan should end before this parameter + // Just scan input for valid string: + end = INetMIME::scanContentType(input); + CPPUNIT_ASSERT(end != nullptr); + CPPUNIT_ASSERT_EQUAL(OUString(";parm1*1*=2"), OUString(end)); // the invalid end of input + // Scan input and parse type, subType and parameters: + end = INetMIME::scanContentType(input, + &type, &subType, ¶meters); + CPPUNIT_ASSERT(end != nullptr); + CPPUNIT_ASSERT_EQUAL(OUString(";parm1*1*=2"), OUString(end)); // the invalid end of input + CPPUNIT_ASSERT_EQUAL(OUString("test"), type); + CPPUNIT_ASSERT_EQUAL(OUString("subtst"), subType); + CPPUNIT_ASSERT_EQUAL( + INetContentTypeParameterList::size_type(3), parameters.size()); + i = parameters.find("parm1"_ostr); + CPPUNIT_ASSERT(i != parameters.end()); + CPPUNIT_ASSERT_EQUAL("us-ascii"_ostr, i->second.m_sCharset); + CPPUNIT_ASSERT_EQUAL("en"_ostr, i->second.m_sLanguage); + CPPUNIT_ASSERT_EQUAL(OUString("value1"), i->second.m_sValue); + CPPUNIT_ASSERT(i->second.m_bConverted); + i = parameters.find("parm2"_ostr); + CPPUNIT_ASSERT(i != parameters.end()); + CPPUNIT_ASSERT_EQUAL("windows-1250"_ostr, i->second.m_sCharset); + CPPUNIT_ASSERT_EQUAL("en-gb"_ostr, i->second.m_sLanguage); + // Euro currency sign, windows-1250 x80 is converted to unicode u20AC: + CPPUNIT_ASSERT_EQUAL(u"value2 \u20AC"_ustr, i->second.m_sValue); + CPPUNIT_ASSERT(i->second.m_bConverted); + i = parameters.find("parm3"_ostr); + CPPUNIT_ASSERT(i != parameters.end()); + CPPUNIT_ASSERT_EQUAL("unknown"_ostr, i->second.m_sCharset); + CPPUNIT_ASSERT_EQUAL("en"_ostr, i->second.m_sLanguage); + // Conversion fails for unknown charsets: + CPPUNIT_ASSERT(!i->second.m_bConverted); + } + + CPPUNIT_TEST_SUITE_REGISTRATION(Test); +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/qa/cppunit/test_json_writer.cxx b/tools/qa/cppunit/test_json_writer.cxx new file mode 100644 index 0000000000..a82fc769be --- /dev/null +++ b/tools/qa/cppunit/test_json_writer.cxx @@ -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/. + */ + +#include <sal/config.h> + +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> + +#include <o3tl/deleter.hxx> +#include <rtl/ustring.hxx> +#include <tools/json_writer.hxx> + +namespace +{ +class JsonWriterTest : public CppUnit::TestFixture +{ +public: + void test1(); + void test2(); + void testArray(); + + CPPUNIT_TEST_SUITE(JsonWriterTest); + CPPUNIT_TEST(test1); + CPPUNIT_TEST(test2); + CPPUNIT_TEST(testArray); + CPPUNIT_TEST_SUITE_END(); +}; + +void JsonWriterTest::test1() +{ + tools::JsonWriter aJson; + + { + auto testNode = aJson.startNode("node"); + aJson.put("oustring", OUString("val1")); + aJson.put("charptr", "val3"); + aJson.put("int", static_cast<sal_Int32>(12)); + } + + OString result(aJson.finishAndGetAsOString()); + + CPPUNIT_ASSERT_EQUAL("{ \"node\": { \"oustring\": \"val1\", " + "\"charptr\": \"val3\", \"int\": 12}}"_ostr, + result); +} + +void JsonWriterTest::test2() +{ + tools::JsonWriter aJson; + + { + auto testNode = aJson.startNode("node"); + aJson.put("field1", OUString("val1")); + aJson.put("field2", OUString("val2")); + { + auto testNode2 = aJson.startNode("node"); + aJson.put("field3", OUString("val3")); + { + auto testNode3 = aJson.startNode("node"); + aJson.put("field4", OUString("val4")); + aJson.put("field5", OUString("val5")); + } + } + } + + OString result(aJson.finishAndGetAsOString()); + + CPPUNIT_ASSERT_EQUAL("{ \"node\": { \"field1\": \"val1\", \"field2\": \"val2\", " + "\"node\": { \"field3\": \"val3\", \"node\": { \"field4\": " + "\"val4\", \"field5\": \"val5\"}}}}"_ostr, + result); +} + +void JsonWriterTest::testArray() +{ + tools::JsonWriter aJson; + { + tools::ScopedJsonWriterArray aArray = aJson.startArray("items"); + aJson.putSimpleValue("foo"); + aJson.putSimpleValue("bar"); + } + + OString aResult(aJson.finishAndGetAsOString()); + + CPPUNIT_ASSERT_EQUAL("{ \"items\": [ \"foo\", \"bar\"]}"_ostr, aResult); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(JsonWriterTest); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/qa/cppunit/test_pathutils.cxx b/tools/qa/cppunit/test_pathutils.cxx new file mode 100644 index 0000000000..7ad75f1e8d --- /dev/null +++ b/tools/qa/cppunit/test_pathutils.cxx @@ -0,0 +1,66 @@ +/* -*- 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 <cwchar> + +#include <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <tools/pathutils.hxx> + +namespace +{ +void buildPath(wchar_t const* front, wchar_t const* back, wchar_t const* path) +{ +#if defined(_WIN32) + wchar_t p[MAX_PATH]; + wchar_t* e = tools::buildPath(p, front, front + std::wcslen(front), back, std::wcslen(back)); + CPPUNIT_ASSERT_EQUAL(static_cast<void*>(p + std::wcslen(path)), static_cast<void*>(e)); + CPPUNIT_ASSERT_EQUAL(0, std::wcscmp(path, p)); +#else + (void)front; + (void)back; + (void)path; +#endif +} + +class Test : public CppUnit::TestFixture +{ +public: + void testBuildPath(); + + CPPUNIT_TEST_SUITE(Test); + CPPUNIT_TEST(testBuildPath); + CPPUNIT_TEST_SUITE_END(); +}; + +void Test::testBuildPath() +{ + buildPath(L"a:\\b\\", L"..", L"a:\\"); + buildPath(L"a:\\b\\", L"..\\", L"a:\\"); + buildPath(L"a:\\b\\c\\", L"..\\..\\..\\d", L"a:\\..\\d"); + buildPath(L"\\\\a\\b\\", L"..\\..\\..\\c", L"\\\\..\\c"); + buildPath(L"\\", L"..\\a", L"\\..\\a"); + buildPath(L"", L"..\\a", L"..\\a"); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(Test); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/qa/cppunit/test_poly.cxx b/tools/qa/cppunit/test_poly.cxx new file mode 100644 index 0000000000..f966f5317a --- /dev/null +++ b/tools/qa/cppunit/test_poly.cxx @@ -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/. + */ + +#include <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> + +#include <tools/poly.hxx> + +namespace +{ +class Test : public CppUnit::TestFixture +{ +public: + void testTdf137068(); + + CPPUNIT_TEST_SUITE(Test); + CPPUNIT_TEST(testTdf137068); + CPPUNIT_TEST_SUITE_END(); +}; + +void Test::testTdf137068() +{ + // Make sure PolyPolygon::Clip() does not break bezier curves. + const Point points[] = { { 1337, 411 }, { 1337, 471 }, { 1313, 530 }, { 1268, 582 } }; + const PolyFlags flags[] + = { PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control, PolyFlags::Normal }; + tools::Polygon polygon(4, points, flags); + tools::PolyPolygon polyPolygon(polygon); + polyPolygon.Clip(tools::Rectangle(Point(0, 0), Size(1920, 1080))); + // operator== is stupid and just compares pointers, compare data manually + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(1), polyPolygon.Count()); + tools::Polygon result = polyPolygon.GetObject(0); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(4), result.GetSize()); + CPPUNIT_ASSERT_EQUAL(points[0], result.GetPoint(0)); + CPPUNIT_ASSERT_EQUAL(points[1], result.GetPoint(1)); + CPPUNIT_ASSERT_EQUAL(points[2], result.GetPoint(2)); + CPPUNIT_ASSERT_EQUAL(points[3], result.GetPoint(3)); + CPPUNIT_ASSERT_EQUAL(flags[0], result.GetFlags(0)); + CPPUNIT_ASSERT_EQUAL(flags[1], result.GetFlags(1)); + CPPUNIT_ASSERT_EQUAL(flags[2], result.GetFlags(2)); + CPPUNIT_ASSERT_EQUAL(flags[3], result.GetFlags(3)); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(Test); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/qa/cppunit/test_rectangle.cxx b/tools/qa/cppunit/test_rectangle.cxx new file mode 100644 index 0000000000..12e46910bc --- /dev/null +++ b/tools/qa/cppunit/test_rectangle.cxx @@ -0,0 +1,254 @@ +/* -*- 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 <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <tools/gen.hxx> + +namespace +{ +class RectangleTest : public CppUnit::TestFixture +{ +public: + void testConstruction(); + void testOpenClosedSize(); + void testUnitConvesion(); + void testSetOperators(); + void test_rectnormalize_alreadynormal(); + void test_rectnormalize_zerorect(); + void test_rectnormalize_reverse_topleft_bottomright(); + void test_rectnormalize_topright_bottomleft(); + void test_rectnormalize_bottomleft_topright(); + void test_rectnormalize_zerowidth_top_bottom_reversed(); + void test_rectnormalize_zeroheight_left_right_reversed(); + + CPPUNIT_TEST_SUITE(RectangleTest); + CPPUNIT_TEST(testConstruction); + CPPUNIT_TEST(testOpenClosedSize); + CPPUNIT_TEST(testUnitConvesion); + CPPUNIT_TEST(testSetOperators); + CPPUNIT_TEST(test_rectnormalize_zerorect); + CPPUNIT_TEST(test_rectnormalize_alreadynormal); + CPPUNIT_TEST(test_rectnormalize_reverse_topleft_bottomright); + CPPUNIT_TEST(test_rectnormalize_topright_bottomleft); + CPPUNIT_TEST(test_rectnormalize_bottomleft_topright); + CPPUNIT_TEST(test_rectnormalize_zerowidth_top_bottom_reversed); + CPPUNIT_TEST(test_rectnormalize_zeroheight_left_right_reversed); + CPPUNIT_TEST_SUITE_END(); +}; + +void RectangleTest::testConstruction() +{ + { + tools::Rectangle aRect1(Point(), Size(0, 20)); + CPPUNIT_ASSERT_EQUAL(true, aRect1.IsEmpty()); + CPPUNIT_ASSERT_EQUAL(tools::Long(0), aRect1.getOpenWidth()); + + tools::Rectangle aRect2{ Point(), Point(0, 20) }; + CPPUNIT_ASSERT_EQUAL(false, aRect2.IsEmpty()); + CPPUNIT_ASSERT_EQUAL(tools::Long(0), aRect2.getOpenWidth()); + + tools::Rectangle aRect3(0, 0, 0, 20); + CPPUNIT_ASSERT_EQUAL(false, aRect3.IsEmpty()); + CPPUNIT_ASSERT_EQUAL(tools::Long(0), aRect3.getOpenWidth()); + } + { + constexpr tools::Rectangle aRect(Point(), Size(-1, -2)); + static_assert(!aRect.IsEmpty()); + static_assert(aRect.Right() == 0); + static_assert(aRect.Bottom() == -1); + + tools::Rectangle aRect2; + aRect2.SetSize(Size(-1, -2)); + CPPUNIT_ASSERT_EQUAL(aRect, aRect2); + + constexpr tools::Rectangle aRect3(Point(), Size(0, 0)); + static_assert(aRect3.IsEmpty()); + static_assert(aRect3.Right() == 0); + static_assert(aRect3.Bottom() == 0); + + constexpr tools::Rectangle aRect4(Point(), Size(1, 1)); + static_assert(!aRect4.IsEmpty()); + static_assert(aRect4.Right() == 0); + static_assert(aRect4.Bottom() == 0); + + constexpr tools::Rectangle aRect5(Point(), Size(-1, -1)); + static_assert(!aRect5.IsEmpty()); + static_assert(aRect5.Right() == 0); + static_assert(aRect5.Bottom() == 0); + } +} + +void RectangleTest::testOpenClosedSize() +{ + { + tools::Rectangle aRect(1, 1, 1, 1); + + CPPUNIT_ASSERT_EQUAL(tools::Long(1), aRect.GetWidth()); + CPPUNIT_ASSERT_EQUAL(tools::Long(1), aRect.GetHeight()); + + CPPUNIT_ASSERT_EQUAL(tools::Long(0), aRect.getOpenWidth()); + CPPUNIT_ASSERT_EQUAL(tools::Long(0), aRect.getOpenHeight()); + } + + { + tools::Rectangle aRect(Point(), Size(1, 1)); + + CPPUNIT_ASSERT_EQUAL(tools::Long(0), aRect.Left()); + CPPUNIT_ASSERT_EQUAL(tools::Long(0), aRect.Top()); + CPPUNIT_ASSERT_EQUAL(tools::Long(0), aRect.Right()); + CPPUNIT_ASSERT_EQUAL(tools::Long(0), aRect.Bottom()); + + CPPUNIT_ASSERT_EQUAL(tools::Long(1), aRect.GetWidth()); + CPPUNIT_ASSERT_EQUAL(tools::Long(1), aRect.GetHeight()); + + // getOpenWidth and getOpenHeight return the size that excludes one of the bounds, + // unlike the ctor and GetWidth / GetHeight that operate on inclusive size + CPPUNIT_ASSERT_EQUAL(tools::Long(0), aRect.getOpenWidth()); + CPPUNIT_ASSERT_EQUAL(tools::Long(0), aRect.getOpenHeight()); + + aRect.SetPosX(12); + CPPUNIT_ASSERT_EQUAL(tools::Long(1), aRect.GetHeight()); + aRect.SetPosY(12); + CPPUNIT_ASSERT_EQUAL(tools::Long(1), aRect.GetWidth()); + } +} + +void RectangleTest::testUnitConvesion() +{ + { + constexpr tools::Rectangle aRectTwip(100, 100, 100, 100); + constexpr tools::Rectangle aRectMm100( + o3tl::convert(aRectTwip, o3tl::Length::twip, o3tl::Length::mm100)); + static_assert(!aRectMm100.IsEmpty()); + // Make sure that we use coordinates for conversion, not width/height: + // the latter is ambiguous, and e.g. GetWidth(aRectTwip) gives 1, which + // would had been converted to 2, resulting in different LR coordinates + static_assert(aRectMm100.Left() == aRectMm100.Right()); + static_assert(aRectMm100.Top() == aRectMm100.Bottom()); + } + + { + constexpr tools::Rectangle aRectTwip(1, 1); + constexpr tools::Rectangle aRectMm100( + o3tl::convert(aRectTwip, o3tl::Length::twip, o3tl::Length::mm100)); + // Make sure that result keeps the empty flag + static_assert(aRectMm100.IsEmpty()); + static_assert(aRectMm100.IsWidthEmpty()); + static_assert(aRectMm100.IsHeightEmpty()); + static_assert(aRectMm100.GetWidth() == 0); + static_assert(aRectMm100.GetHeight() == 0); + } +} + +void RectangleTest::testSetOperators() +{ + constexpr tools::Rectangle rect(Point(0, 0), Size(20, 20)); + constexpr tools::Rectangle inside(Point(10, 10), Size(10, 10)); + constexpr tools::Rectangle overlap(Point(10, 10), Size(20, 20)); + constexpr tools::Rectangle outside(Point(20, 20), Size(10, 10)); + CPPUNIT_ASSERT(rect.Contains(inside)); + CPPUNIT_ASSERT(rect.Contains(rect)); + CPPUNIT_ASSERT(!rect.Contains(overlap)); + CPPUNIT_ASSERT(!rect.Contains(outside)); + CPPUNIT_ASSERT(rect.Overlaps(inside)); + CPPUNIT_ASSERT(rect.Overlaps(rect)); + CPPUNIT_ASSERT(rect.Overlaps(overlap)); + CPPUNIT_ASSERT(!rect.Overlaps(outside)); +} + +void RectangleTest::test_rectnormalize_alreadynormal() +{ + Point aTopLeft(0, 0); + Point aBottomRight(1, 1); + + tools::Rectangle aRect(aTopLeft, aBottomRight); + aRect.Normalize(); + + CPPUNIT_ASSERT_EQUAL(aRect.TopLeft(), aTopLeft); + CPPUNIT_ASSERT_EQUAL(aRect.BottomRight(), aBottomRight); +} + +void RectangleTest::test_rectnormalize_zerorect() +{ + Point aTopLeft(53, 53); + Point aBottomRight(53, 53); + + tools::Rectangle aRect(aTopLeft, aBottomRight); + aRect.Normalize(); + + CPPUNIT_ASSERT_EQUAL(aRect.TopLeft(), aTopLeft); + CPPUNIT_ASSERT_EQUAL(aRect.BottomRight(), aBottomRight); +} + +void RectangleTest::test_rectnormalize_reverse_topleft_bottomright() +{ + Point aPoint1(1, 1); + Point aPoint2(0, 0); + + tools::Rectangle aRect(aPoint1, aPoint2); + aRect.Normalize(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("TopLeft() is wrong", Point(0, 0), aRect.TopLeft()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("BottomRight() is wrong", Point(1, 1), aRect.BottomRight()); +} + +void RectangleTest::test_rectnormalize_topright_bottomleft() +{ + Point aPoint1(1, 0); + Point aPoint2(0, 1); + + tools::Rectangle aRect(aPoint1, aPoint2); + aRect.Normalize(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("TopLeft() is wrong", Point(0, 0), aRect.TopLeft()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("BottomRight() is wrong", Point(1, 1), aRect.BottomRight()); +} + +void RectangleTest::test_rectnormalize_bottomleft_topright() +{ + Point aPoint1(0, 1); + Point aPoint2(1, 0); + + tools::Rectangle aRect(aPoint1, aPoint2); + aRect.Normalize(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("TopLeft() is wrong", Point(0, 0), aRect.TopLeft()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("BottomRight() is wrong", Point(1, 1), aRect.BottomRight()); +} + +void RectangleTest::test_rectnormalize_zerowidth_top_bottom_reversed() +{ + Point aPoint1(0, 1); + Point aPoint2(0, 0); + + tools::Rectangle aRect(aPoint1, aPoint2); + aRect.Normalize(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("TopLeft() is wrong", Point(0, 0), aRect.TopLeft()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("BottomRight() is wrong", Point(0, 1), aRect.BottomRight()); +} + +void RectangleTest::test_rectnormalize_zeroheight_left_right_reversed() +{ + Point aPoint1(1, 0); + Point aPoint2(0, 0); + + tools::Rectangle aRect(aPoint1, aPoint2); + aRect.Normalize(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("TopLeft() is wrong", Point(0, 0), aRect.TopLeft()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("BottomRight() is wrong", Point(1, 0), aRect.BottomRight()); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(RectangleTest); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/qa/cppunit/test_reversemap.cxx b/tools/qa/cppunit/test_reversemap.cxx new file mode 100644 index 0000000000..be70ade795 --- /dev/null +++ b/tools/qa/cppunit/test_reversemap.cxx @@ -0,0 +1,154 @@ +/* -*- 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 <sal/types.h> +#include <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/plugin/TestPlugIn.h> +#include <rtl/ustring.hxx> +#include <vector> + +#include <tools/tenccvt.hxx> + +//Tests for getBestMSEncodingByChar + +namespace +{ + + class Test: public CppUnit::TestFixture + { + public: + void testEncoding(rtl_TextEncoding eEncoding); + + void test1258(); + void test1257(); + void test1256(); + void test1255(); + void test1254(); + void test1253(); + void test1252(); + void test1251(); + void test1250(); + void test874(); + + CPPUNIT_TEST_SUITE(Test); + CPPUNIT_TEST(test1258); + CPPUNIT_TEST(test1257); + CPPUNIT_TEST(test1256); + CPPUNIT_TEST(test1255); + CPPUNIT_TEST(test1254); + CPPUNIT_TEST(test1253); + CPPUNIT_TEST(test1252); + CPPUNIT_TEST(test1251); + CPPUNIT_TEST(test1250); + CPPUNIT_TEST(test874); + CPPUNIT_TEST_SUITE_END(); + }; + + void Test::testEncoding(rtl_TextEncoding eEncoding) + { + //Taking the single byte legacy encodings, fill in all possible values + std::vector<char> aAllChars(255); + for (int i = 1; i <= 255; ++i) + aAllChars[i-1] = static_cast<char>(i); + + //Some slots are unused, so don't map to private, just set them to 'X' + sal_uInt32 const convertFlags = OSTRING_TO_OUSTRING_CVTFLAGS ^ RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_MAPTOPRIVATE; + OUString sOrigText(aAllChars.data(), aAllChars.size(), eEncoding, convertFlags); + sOrigText = sOrigText.replace( 0xfffd, 'X' ); + + //Should clearly be equal + sal_Int32 nLength = aAllChars.size(); + CPPUNIT_ASSERT_EQUAL(sOrigText.getLength(), nLength); + + OUString sFinalText; + + //Split up in chunks of the same encoding returned by + //getBestMSEncodingByChar, convert to it, and back + rtl_TextEncoding ePrevEncoding = RTL_TEXTENCODING_DONTKNOW; + const sal_Unicode *pStr = sOrigText.getStr(); + sal_Int32 nChunkStart=0; + for (int i = 0; i < 255; ++i) + { + rtl_TextEncoding eCurrEncoding = getBestMSEncodingByChar(pStr[i]); + if (eCurrEncoding != ePrevEncoding) + { + OString aChunk(pStr+nChunkStart, i-nChunkStart, ePrevEncoding); + sFinalText += OStringToOUString(aChunk, ePrevEncoding); + nChunkStart = i; + } + ePrevEncoding = eCurrEncoding; + } + if (nChunkStart < 255) + { + OString aChunk(pStr+nChunkStart, 255-nChunkStart, ePrevEncoding); + sFinalText += OStringToOUString(aChunk, ePrevEncoding); + } + + //Final text should be the same as original + CPPUNIT_ASSERT_EQUAL(sOrigText, sFinalText); + } + + void Test::test1252() + { + testEncoding(RTL_TEXTENCODING_MS_1252); + } + + void Test::test874() + { + testEncoding(RTL_TEXTENCODING_MS_874); + } + + void Test::test1258() + { + testEncoding(RTL_TEXTENCODING_MS_1258); + } + + void Test::test1257() + { + testEncoding(RTL_TEXTENCODING_MS_1257); + } + + void Test::test1256() + { + testEncoding(RTL_TEXTENCODING_MS_1256); + } + + void Test::test1255() + { + testEncoding(RTL_TEXTENCODING_MS_1255); + } + + void Test::test1254() + { + testEncoding(RTL_TEXTENCODING_MS_1254); + } + + void Test::test1253() + { + testEncoding(RTL_TEXTENCODING_MS_1253); + } + + void Test::test1251() + { + testEncoding(RTL_TEXTENCODING_MS_1251); + } + + void Test::test1250() + { + testEncoding(RTL_TEXTENCODING_MS_1250); + } + + CPPUNIT_TEST_SUITE_REGISTRATION(Test); +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/qa/cppunit/test_stream.cxx b/tools/qa/cppunit/test_stream.cxx new file mode 100644 index 0000000000..edec9c0fb7 --- /dev/null +++ b/tools/qa/cppunit/test_stream.cxx @@ -0,0 +1,354 @@ +/* -*- 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 <sal/types.h> +#include <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <tools/stream.hxx> +#include <unotools/tempfile.hxx> +#include <sstream> + +//Tests for eofbit/badbit/goodbit/failbit + +namespace +{ + + class Test: public CppUnit::TestFixture + { + public: + void test_stdstream(); + void test_fastostring(); + void test_read_cstring(); + void test_read_pstring(); + void test_readline(); + void test_makereadonly(); + void test_write_unicode(); + + CPPUNIT_TEST_SUITE(Test); + CPPUNIT_TEST(test_stdstream); + CPPUNIT_TEST(test_fastostring); + CPPUNIT_TEST(test_read_cstring); + CPPUNIT_TEST(test_read_pstring); + CPPUNIT_TEST(test_readline); + CPPUNIT_TEST(test_makereadonly); + CPPUNIT_TEST(test_write_unicode); + CPPUNIT_TEST_SUITE_END(); + }; + + void Test::test_stdstream() + { + char foo[] = "foo"; + std::istringstream iss(foo, std::istringstream::in); + SvMemoryStream aMemStream(foo, SAL_N_ELEMENTS(foo)-1, StreamMode::READ); + + char std_a(78); + iss >> std_a; + CPPUNIT_ASSERT_EQUAL('f', std_a); + + char tools_a(78); + aMemStream.ReadChar( tools_a ); + CPPUNIT_ASSERT_EQUAL('f', tools_a); + + iss.seekg(0, std::ios_base::end); + //seeking to end doesn't set eof, reading past eof does + CPPUNIT_ASSERT(!iss.eof()); + CPPUNIT_ASSERT(iss.good()); + + aMemStream.Seek(STREAM_SEEK_TO_END); + //seeking to end doesn't set eof, reading past eof does + CPPUNIT_ASSERT(!aMemStream.eof()); + CPPUNIT_ASSERT(aMemStream.good()); + + std_a = 78; + iss >> std_a; + //so, now eof is set + CPPUNIT_ASSERT(iss.eof()); + //a failed read doesn't change the data, it remains unchanged + CPPUNIT_ASSERT_EQUAL(static_cast<char>(78), std_a); + //nothing wrong with the stream, so not bad + CPPUNIT_ASSERT(!iss.bad()); + //yet, the read didn't succeed + CPPUNIT_ASSERT(!iss.good()); + CPPUNIT_ASSERT_EQUAL((std::ios::failbit|std::ios::eofbit), iss.rdstate()); + + tools_a = 78; + aMemStream.ReadChar( tools_a ); + //so, now eof is set + CPPUNIT_ASSERT(aMemStream.eof()); + //a failed read doesn't change the data, it remains unchanged + CPPUNIT_ASSERT_EQUAL(static_cast<char>(78), tools_a); + //nothing wrong with the stream, so not bad + CPPUNIT_ASSERT(!aMemStream.bad()); + //yet, the read didn't succeed + CPPUNIT_ASSERT(!aMemStream.good()); + + //set things up so that there is only one byte available on an attempt + //to read a two-byte sal_uInt16. The byte should be consumed, but the + //operation should fail, and tools_b should remain unchanged, + sal_uInt16 tools_b = 0x1122; + aMemStream.SeekRel(-1); + CPPUNIT_ASSERT(!aMemStream.eof()); + CPPUNIT_ASSERT(aMemStream.good()); + aMemStream.ReadUInt16( tools_b ); + CPPUNIT_ASSERT(!aMemStream.good()); + CPPUNIT_ASSERT(aMemStream.eof()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(0x1122), tools_b); + + iss.clear(); + iss.seekg(0); + CPPUNIT_ASSERT(iss.good()); + iss >> std_a; + CPPUNIT_ASSERT_EQUAL('f', std_a); + + aMemStream.Seek(0); + CPPUNIT_ASSERT(aMemStream.good()); + aMemStream.ReadChar( tools_a ); + CPPUNIT_ASSERT_EQUAL('f', tools_a); + + //failbit is rather subtle wrt e.g seeks + + char buffer[1024]; + + iss.clear(); + iss.seekg(0); + CPPUNIT_ASSERT(iss.good()); + iss.read(buffer, sizeof(buffer)); + CPPUNIT_ASSERT_EQUAL(static_cast<std::streamsize>(3), iss.gcount()); + CPPUNIT_ASSERT(!iss.good()); + CPPUNIT_ASSERT(!iss.bad()); + CPPUNIT_ASSERT(iss.eof()); + + aMemStream.Seek(0); + CPPUNIT_ASSERT(aMemStream.good()); + std::size_t nRet = aMemStream.ReadBytes(buffer, sizeof(buffer)); + CPPUNIT_ASSERT_EQUAL(static_cast<std::size_t>(3), nRet); + CPPUNIT_ASSERT(!aMemStream.good()); + CPPUNIT_ASSERT(!aMemStream.bad()); + CPPUNIT_ASSERT(aMemStream.eof()); + } + + void Test::test_fastostring() + { + char foo[] = "foobar"; + SvMemoryStream aMemStream(foo, SAL_N_ELEMENTS(foo)-1, StreamMode::READ); + + OString aOne = read_uInt8s_ToOString(aMemStream, 3); + CPPUNIT_ASSERT_EQUAL("foo"_ostr, aOne); + + OString aTwo = read_uInt8s_ToOString(aMemStream, 3); + CPPUNIT_ASSERT_EQUAL("bar"_ostr, aTwo); + + OString aThree = read_uInt8s_ToOString(aMemStream, 3); + CPPUNIT_ASSERT(aThree.isEmpty()); + + aMemStream.Seek(0); + + OString aFour = read_uInt8s_ToOString(aMemStream, 100); + CPPUNIT_ASSERT_EQUAL(OString(foo), aFour); + } + + void Test::test_read_cstring() + { + char foo[] = "foobar"; + SvMemoryStream aMemStream(foo, SAL_N_ELEMENTS(foo)-1, StreamMode::READ); + + OString aOne = read_zeroTerminated_uInt8s_ToOString(aMemStream); + CPPUNIT_ASSERT_EQUAL("foobar"_ostr, aOne); + CPPUNIT_ASSERT(!aMemStream.good()); + CPPUNIT_ASSERT(!aMemStream.bad()); + CPPUNIT_ASSERT(aMemStream.eof()); + + aMemStream.Seek(0); + foo[3] = 0; + OString aTwo = read_zeroTerminated_uInt8s_ToOString(aMemStream); + CPPUNIT_ASSERT_EQUAL("foo"_ostr, aTwo); + CPPUNIT_ASSERT(aMemStream.good()); + } + + void Test::test_read_pstring() + { + char foo[] = "\3foobar"; + SvMemoryStream aMemStream(foo, SAL_N_ELEMENTS(foo)-1, StreamMode::READ); + + OString aFoo = read_uInt8_lenPrefixed_uInt8s_ToOString(aMemStream); + CPPUNIT_ASSERT_EQUAL("foo"_ostr, aFoo); + CPPUNIT_ASSERT(aMemStream.good()); + CPPUNIT_ASSERT(!aMemStream.bad()); + CPPUNIT_ASSERT(!aMemStream.eof()); + + aMemStream.Seek(0); + foo[0] = 10; + aFoo = read_uInt8_lenPrefixed_uInt8s_ToOString(aMemStream); + CPPUNIT_ASSERT_EQUAL("foobar"_ostr, aFoo); + CPPUNIT_ASSERT(!aMemStream.good()); + CPPUNIT_ASSERT(!aMemStream.bad()); + CPPUNIT_ASSERT(aMemStream.eof()); + + aMemStream.SetEndian(SvStreamEndian::BIG); + aMemStream.Seek(0); + foo[0] = 0; + foo[1] = 3; + aFoo = read_uInt16_lenPrefixed_uInt8s_ToOString(aMemStream); + CPPUNIT_ASSERT_EQUAL("oob"_ostr, aFoo); + CPPUNIT_ASSERT(aMemStream.good()); + CPPUNIT_ASSERT(!aMemStream.bad()); + CPPUNIT_ASSERT(!aMemStream.eof()); + } + + void Test::test_readline() + { + char foo[] = "foo\nbar\n\n"; + SvMemoryStream aMemStream(foo, SAL_N_ELEMENTS(foo)-1, StreamMode::READ); + + OString aFoo; + bool bRet; + + bRet = aMemStream.ReadLine(aFoo); + CPPUNIT_ASSERT(bRet); + CPPUNIT_ASSERT_EQUAL("foo"_ostr, aFoo); + CPPUNIT_ASSERT(aMemStream.good()); + + bRet = aMemStream.ReadLine(aFoo); + CPPUNIT_ASSERT(bRet); + CPPUNIT_ASSERT_EQUAL("bar"_ostr, aFoo); + CPPUNIT_ASSERT(aMemStream.good()); + + bRet = aMemStream.ReadLine(aFoo); + CPPUNIT_ASSERT(bRet); + CPPUNIT_ASSERT(aFoo.isEmpty()); + CPPUNIT_ASSERT(aMemStream.good()); + + bRet = aMemStream.ReadLine(aFoo); + CPPUNIT_ASSERT(!bRet); + CPPUNIT_ASSERT(aFoo.isEmpty()); + CPPUNIT_ASSERT(aMemStream.eof()); + + foo[3] = 0; //test reading embedded nulls + + aMemStream.Seek(0); + bRet = aMemStream.ReadLine(aFoo); + CPPUNIT_ASSERT(bRet); + CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aFoo.getLength()); + CPPUNIT_ASSERT_EQUAL('\0', aFoo[3]); + CPPUNIT_ASSERT(aMemStream.good()); + + std::string sStr(foo, RTL_CONSTASCII_LENGTH(foo)); + std::istringstream iss(sStr, std::istringstream::in); + std::getline(iss, sStr, '\n'); + //embedded null read as expected + CPPUNIT_ASSERT_EQUAL(std::string::size_type(7), sStr.size()); + CPPUNIT_ASSERT_EQUAL('\0', sStr[3]); + CPPUNIT_ASSERT(iss.good()); + + bRet = aMemStream.ReadLine(aFoo); + CPPUNIT_ASSERT(bRet); + CPPUNIT_ASSERT(aFoo.isEmpty()); + CPPUNIT_ASSERT(aMemStream.good()); + + std::getline(iss, sStr, '\n'); + CPPUNIT_ASSERT(sStr.empty()); + CPPUNIT_ASSERT(iss.good()); + + bRet = aMemStream.ReadLine(aFoo); + CPPUNIT_ASSERT(!bRet); + CPPUNIT_ASSERT(aFoo.isEmpty()); + CPPUNIT_ASSERT(aMemStream.eof()); + CPPUNIT_ASSERT(!aMemStream.bad()); + + std::getline(iss, sStr, '\n'); + CPPUNIT_ASSERT(sStr.empty()); + CPPUNIT_ASSERT(iss.eof()); + CPPUNIT_ASSERT(!iss.bad()); + + char bar[] = "foo"; + SvMemoryStream aMemStreamB(bar, SAL_N_ELEMENTS(bar)-1, StreamMode::READ); + bRet = aMemStreamB.ReadLine(aFoo); + CPPUNIT_ASSERT(bRet); + CPPUNIT_ASSERT_EQUAL("foo"_ostr, aFoo); + CPPUNIT_ASSERT(!aMemStreamB.eof()); //<-- diff A + + std::istringstream issB(bar, std::istringstream::in); + std::getline(issB, sStr, '\n'); + CPPUNIT_ASSERT_EQUAL(std::string("foo"), sStr); + CPPUNIT_ASSERT(issB.eof()); //<-- diff A + } + + void Test::test_makereadonly() + { + SvMemoryStream aMemStream; + CPPUNIT_ASSERT(aMemStream.IsWritable()); + CPPUNIT_ASSERT_EQUAL(sal_uInt64(0), aMemStream.GetSize()); + aMemStream.WriteInt64(21); + CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, aMemStream.GetError()); + CPPUNIT_ASSERT_EQUAL(sal_uInt64(8), aMemStream.GetSize()); + + aMemStream.MakeReadOnly(); + CPPUNIT_ASSERT(!aMemStream.IsWritable()); + CPPUNIT_ASSERT_EQUAL(sal_uInt64(8), aMemStream.GetSize()); + aMemStream.WriteInt64(42); + CPPUNIT_ASSERT_EQUAL(ERRCODE_IO_CANTWRITE, aMemStream.GetError()); + CPPUNIT_ASSERT_EQUAL(sal_uInt64(8), aMemStream.GetSize()); + + aMemStream.ResetError(); + // Check that seeking past the end doesn't resize a read-only stream. + // This apparently doesn't set an error, but at least it shouldn't + // change the size. + aMemStream.Seek(1024LL*1024*1024*3); + CPPUNIT_ASSERT_EQUAL(sal_uInt64(8), aMemStream.GetSize()); + + aMemStream.ResetError(); + aMemStream.Seek(0); + sal_Int64 res; + aMemStream.ReadInt64(res); + CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, aMemStream.GetError()); + CPPUNIT_ASSERT_EQUAL(sal_Int64(21), res); + } + + void Test::test_write_unicode() + { + constexpr OUString write(u"abc"_ustr); + utl::TempFileNamed aTempFile(u"test_write_unicode"); + aTempFile.EnableKillingFile(); + { + SvStream& s = *aTempFile.GetStream(StreamMode::WRITE); + s.SetEndian(SvStreamEndian::BIG); + if (!s.IsEndianSwap()) + s.SetEndian(SvStreamEndian::LITTLE); + CPPUNIT_ASSERT(s.IsEndianSwap()); + // StartWritingUnicodeText must switch to no endian swapping and write 0xfeff + s.StartWritingUnicodeText(); + // Without the fix in place, this would fail + CPPUNIT_ASSERT(!s.IsEndianSwap()); + s.WriteUnicodeOrByteText(write, RTL_TEXTENCODING_UNICODE); + aTempFile.CloseStream(); + } + { + SvStream& s = *aTempFile.GetStream(StreamMode::READ); + s.SetEndian(SvStreamEndian::BIG); + if (!s.IsEndianSwap()) + s.SetEndian(SvStreamEndian::LITTLE); + CPPUNIT_ASSERT(s.IsEndianSwap()); + s.StartReadingUnicodeText(RTL_TEXTENCODING_DONTKNOW); + CPPUNIT_ASSERT(!s.IsEndianSwap()); + CPPUNIT_ASSERT_EQUAL(sal_uInt64(2), s.Tell()); // after BOM + OUString read; + CPPUNIT_ASSERT(s.ReadUniOrByteStringLine(read, RTL_TEXTENCODING_UNICODE)); + // Without the fix in place, this would fail with + // - Expected: abc + // - Actual : 愀戀挀 + CPPUNIT_ASSERT_EQUAL(write, read); + aTempFile.CloseStream(); + } + } + + CPPUNIT_TEST_SUITE_REGISTRATION(Test); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/qa/cppunit/test_time.cxx b/tools/qa/cppunit/test_time.cxx new file mode 100644 index 0000000000..a21f98d7b4 --- /dev/null +++ b/tools/qa/cppunit/test_time.cxx @@ -0,0 +1,154 @@ +/* -*- 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 <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> + +#include <tools/time.hxx> + +namespace tools +{ +class TimeTest : public CppUnit::TestFixture +{ +public: + void testTime(); + void testClockValues(); + + CPPUNIT_TEST_SUITE(TimeTest); + CPPUNIT_TEST(testTime); + CPPUNIT_TEST(testClockValues); + CPPUNIT_TEST_SUITE_END(); +}; + +void TimeTest::testTime() +{ + Time aOrigTime(1, 56, 10); + auto nMS = aOrigTime.GetMSFromTime(); + + Time aNewTime(0); + aNewTime.MakeTimeFromMS(nMS); + + CPPUNIT_ASSERT(bool(aOrigTime == aNewTime)); +} + +void TimeTest::testClockValues() +{ + double fTime, fFractionOfSecond; + sal_uInt16 nHour, nMinute, nSecond; + + fTime = 0.0; + Time::GetClock(fTime, nHour, nMinute, nSecond, fFractionOfSecond, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Hour value.", sal_uInt16(0), nHour); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Minute value.", sal_uInt16(0), nMinute); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Second value.", sal_uInt16(0), nSecond); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Fraction value.", 0.0, fFractionOfSecond, 0.0); + + fTime = 1.0; + Time::GetClock(fTime, nHour, nMinute, nSecond, fFractionOfSecond, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Hour value.", sal_uInt16(0), nHour); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Minute value.", sal_uInt16(0), nMinute); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Second value.", sal_uInt16(0), nSecond); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Fraction value.", 0.0, fFractionOfSecond, 0.0); + + fTime = -1.0; + Time::GetClock(fTime, nHour, nMinute, nSecond, fFractionOfSecond, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Hour value.", sal_uInt16(0), nHour); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Minute value.", sal_uInt16(0), nMinute); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Second value.", sal_uInt16(0), nSecond); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Fraction value.", 0.0, fFractionOfSecond, 0.0); + + fTime = 1.5; + Time::GetClock(fTime, nHour, nMinute, nSecond, fFractionOfSecond, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Hour value.", sal_uInt16(12), nHour); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Minute value.", sal_uInt16(0), nMinute); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Second value.", sal_uInt16(0), nSecond); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Fraction value.", 0.0, fFractionOfSecond, 0.0); + + fTime = -1.5; + Time::GetClock(fTime, nHour, nMinute, nSecond, fFractionOfSecond, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Hour value.", sal_uInt16(12), nHour); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Minute value.", sal_uInt16(0), nMinute); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Second value.", sal_uInt16(0), nSecond); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Fraction value.", 0.0, fFractionOfSecond, 0.0); + + fTime = 0.75; + Time::GetClock(fTime, nHour, nMinute, nSecond, fFractionOfSecond, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Hour value.", sal_uInt16(18), nHour); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Minute value.", sal_uInt16(0), nMinute); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Second value.", sal_uInt16(0), nSecond); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Fraction value.", 0.0, fFractionOfSecond, 0.0); + + fTime = 0.0208333333333333; + Time::GetClock(fTime, nHour, nMinute, nSecond, fFractionOfSecond, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Hour value.", sal_uInt16(0), nHour); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Minute value.", sal_uInt16(30), nMinute); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Second value.", sal_uInt16(0), nSecond); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Fraction value.", 0.0, fFractionOfSecond, 0.0); + + fTime = 0.0000115740625; + Time::GetClock(fTime, nHour, nMinute, nSecond, fFractionOfSecond, 3); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Hour value.", sal_uInt16(0), nHour); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Minute value.", sal_uInt16(0), nMinute); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Second value.", sal_uInt16(0), nSecond); + // Expect this to be a truncated 0.999999 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Fraction value.", 0.999, fFractionOfSecond, 0.0); + + fTime = 0.524268391203704; + Time::GetClock(fTime, nHour, nMinute, nSecond, fFractionOfSecond, 3); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Hour value.", sal_uInt16(12), nHour); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Minute value.", sal_uInt16(34), nMinute); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Second value.", sal_uInt16(56), nSecond); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Fraction value.", 0.789, fFractionOfSecond, 0.0); + + fTime = -0.000001; + Time::GetClock(fTime, nHour, nMinute, nSecond, fFractionOfSecond, 13); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Hour value.", sal_uInt16(23), nHour); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Minute value.", sal_uInt16(59), nMinute); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Second value.", sal_uInt16(59), nSecond); + // Expect this to be exact within floating point accuracy. + // This is a hairy rounding condition, if it yields problems on any + // platform feel free to disable the test for that platform. + // At least when doing a 32-bit build on Linux x86 with GCC 8.2.1, when -Os from + // gb_COMPILEROPTFLAGS in solenv/gbuild/platform/LINUX_INTEL_GCC.mk is overridden by -O1 (or + // higher) passed into CXXFLAGS, the test fails with an actual value of 0.9136, for reasons not + // investigated further: +#if !(defined __GNUC__ && !defined __clang__ && defined X86 && defined __OPTIMIZE__ \ + && !defined __OPTIMIZE_SIZE__) + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Fraction value.", 0.9135999999999, fFractionOfSecond, + 1e-14); +#endif + + fTime = -0.000001; + Time::GetClock(fTime, nHour, nMinute, nSecond, fFractionOfSecond, 4); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Hour value.", sal_uInt16(23), nHour); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Minute value.", sal_uInt16(59), nMinute); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Second value.", sal_uInt16(59), nSecond); + // Expect this to be rounded. + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Fraction value.", 0.9136, fFractionOfSecond, 0.0); + + fTime = -0.00000000001; + Time::GetClock(fTime, nHour, nMinute, nSecond, fFractionOfSecond, 4); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Hour value.", sal_uInt16(23), nHour); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Minute value.", sal_uInt16(59), nMinute); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Second value.", sal_uInt16(59), nSecond); + // Expect this to be a truncated 0.999999 + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Fraction value.", 0.9999, fFractionOfSecond, 0.0); + + fTime = -1e-24; // value insignificant for time + Time::GetClock(fTime, nHour, nMinute, nSecond, fFractionOfSecond, 0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Hour value.", sal_uInt16(0), nHour); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Minute value.", sal_uInt16(0), nMinute); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Second value.", sal_uInt16(0), nSecond); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Fraction value.", 0.0, fFractionOfSecond, 0.0); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(TimeTest); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/tools/qa/cppunit/test_urlobj.cxx b/tools/qa/cppunit/test_urlobj.cxx new file mode 100644 index 0000000000..fff77e41f5 --- /dev/null +++ b/tools/qa/cppunit/test_urlobj.cxx @@ -0,0 +1,382 @@ +/* -*- 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 <memory> +#include <string> + +#include <sal/types.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <tools/stream.hxx> +#include <tools/urlobj.hxx> + +#define OUSTR_TO_STDSTR( oustr ) std::string( OUStringToOString( oustr, RTL_TEXTENCODING_ASCII_US ) ) + +template<> inline std::string CPPUNIT_NS::assertion_traits<INetProtocol>::toString( + const INetProtocol& x ) +{ + OStringStream ost; + ost << static_cast<unsigned int>(x); + return ost.str(); +} + +namespace tools_urlobj +{ + + class urlobjTest:public CppUnit::TestFixture + { + + public: + // insert your test code here. + // this is only demonstration code + void urlobjTest_001( ) + { + INetURLObject aUrl( u"file://10.10.1.1/sampledir/sample.file" ); + CPPUNIT_ASSERT_EQUAL(INetProtocol::File, aUrl.GetProtocol()); + CPPUNIT_ASSERT_EQUAL(OUString("10.10.1.1"), + aUrl.GetHost(INetURLObject::DecodeMechanism::NONE)); + CPPUNIT_ASSERT_EQUAL(OUString("/sampledir/sample.file"), + aUrl.GetURLPath(INetURLObject::DecodeMechanism::NONE)); + CPPUNIT_ASSERT_EQUAL(OUString("sample.file"), + aUrl.getName()); + CPPUNIT_ASSERT_EQUAL(OUString("sample"), aUrl.getBase()); + CPPUNIT_ASSERT_EQUAL(OUString("file"), aUrl.getExtension()); + } + + void urlobjTest_004( ) + { + INetURLObject aUrl( u"smb://10.10.1.1/sampledir/sample.file" ); + CPPUNIT_ASSERT_EQUAL( INetProtocol::Smb, aUrl.GetProtocol( ) ); + CPPUNIT_ASSERT_EQUAL(OUString("10.10.1.1"), + aUrl.GetHost(INetURLObject::DecodeMechanism::NONE)); + CPPUNIT_ASSERT_EQUAL(OUString("/sampledir/sample.file"), + aUrl.GetURLPath(INetURLObject::DecodeMechanism::NONE)); + CPPUNIT_ASSERT_EQUAL(OUString("sample.file"), aUrl.getName()); + CPPUNIT_ASSERT_EQUAL(OUString("sample"), aUrl.getBase()); + CPPUNIT_ASSERT_EQUAL(OUString("file"), aUrl.getExtension()); + } + + void urlobjCmisTest( ) + { + // Test with a username part + { + INetURLObject aUrl( u"vnd.libreoffice.cmis://username@http:%2F%2Ffoo.bar.com:8080%2Fmy%2Fcmis%2Fatom%23repo-id-encoded/path/to/content" ); + CPPUNIT_ASSERT_EQUAL( std::string( "http://foo.bar.com:8080/my/cmis/atom#repo-id-encoded" ), + OUSTR_TO_STDSTR( aUrl.GetHost( INetURLObject::DecodeMechanism::WithCharset ) ) ); + CPPUNIT_ASSERT_EQUAL( std::string( "username" ), OUSTR_TO_STDSTR( aUrl.GetUser( ) ) ); + CPPUNIT_ASSERT_EQUAL( std::string( "/path/to/content" ), + OUSTR_TO_STDSTR( aUrl.GetURLPath( INetURLObject::DecodeMechanism::NONE ) ) ); + CPPUNIT_ASSERT_EQUAL_MESSAGE( "Wrong protocol found", INetProtocol::Cmis, aUrl.GetProtocol( ) ); + } + + // Test without a username part + { + INetURLObject aUrl( + u"vnd.libreoffice.cmis://http:%2F%2Ffoo.bar.com:8080%2Fmy%2Fcmis%2Fatom%23repo-id-encoded/path/to/content" ); + CPPUNIT_ASSERT_EQUAL( std::string( "http://foo.bar.com:8080/my/cmis/atom#repo-id-encoded" ), + OUSTR_TO_STDSTR( aUrl.GetHost( INetURLObject::DecodeMechanism::WithCharset ) ) ); + CPPUNIT_ASSERT( !aUrl.HasUserData() ); + CPPUNIT_ASSERT_EQUAL( std::string( "/path/to/content" ), + OUSTR_TO_STDSTR( aUrl.GetURLPath( INetURLObject::DecodeMechanism::NONE ) ) ); + CPPUNIT_ASSERT_EQUAL_MESSAGE( "Wrong protocol found", INetProtocol::Cmis, aUrl.GetProtocol( ) ); + } + } + + void urlobjTest_emptyPath() { + { + INetURLObject url(u"http://example.com"); + CPPUNIT_ASSERT_EQUAL(INetProtocol::Http, url.GetProtocol()); + CPPUNIT_ASSERT_EQUAL(OUString("example.com"), url.GetHost()); + CPPUNIT_ASSERT_EQUAL(OUString("/"), url.GetURLPath()); + } + { + // This is a valid http URL per RFC 7230: + INetURLObject url(u"http://example.com?query"); + CPPUNIT_ASSERT(!url.HasError()); + } + { + INetURLObject url(u"http://example.com#fragment"); + CPPUNIT_ASSERT_EQUAL(INetProtocol::Http, url.GetProtocol()); + CPPUNIT_ASSERT_EQUAL(OUString("example.com"), url.GetHost()); + CPPUNIT_ASSERT_EQUAL(OUString("/"), url.GetURLPath()); + CPPUNIT_ASSERT_EQUAL(OUString("fragment"), url.GetMark()); + } + } + + void urlobjTest_data() { + INetURLObject url; + std::unique_ptr<SvMemoryStream> strm; + unsigned char const * buf; + + url = INetURLObject(u"data:"); + //TODO: CPPUNIT_ASSERT(url.HasError()); + strm = url.getData(); + CPPUNIT_ASSERT(!strm); + + url = INetURLObject(u"data:,"); + CPPUNIT_ASSERT(!url.HasError()); + strm = url.getData(); + CPPUNIT_ASSERT(strm != nullptr); + CPPUNIT_ASSERT_EQUAL(sal_uInt64(0), strm->GetSize()); + strm.reset(); + + url = INetURLObject(u"data:,,%C3%A4%90"); + CPPUNIT_ASSERT(!url.HasError()); + strm = url.getData(); + CPPUNIT_ASSERT(strm != nullptr); + CPPUNIT_ASSERT_EQUAL(sal_uInt64(4), strm->GetSize()); + buf = static_cast<unsigned char const *>(strm->GetData()); + CPPUNIT_ASSERT_EQUAL(0x2C, int(buf[0])); + CPPUNIT_ASSERT_EQUAL(0xC3, int(buf[1])); + CPPUNIT_ASSERT_EQUAL(0xA4, int(buf[2])); + CPPUNIT_ASSERT_EQUAL(0x90, int(buf[3])); + strm.reset(); + + url = INetURLObject(u"data:base64,"); + //TODO: CPPUNIT_ASSERT(url.HasError()); + strm = url.getData(); + CPPUNIT_ASSERT(!strm); + + url = INetURLObject(u"data:;base64,"); + CPPUNIT_ASSERT(!url.HasError()); + strm = url.getData(); + CPPUNIT_ASSERT(strm != nullptr); + CPPUNIT_ASSERT_EQUAL(sal_uInt64(0), strm->GetSize()); + strm.reset(); + + url = INetURLObject(u"data:;bAsE64,"); + CPPUNIT_ASSERT(!url.HasError()); + strm = url.getData(); + CPPUNIT_ASSERT(strm != nullptr); + CPPUNIT_ASSERT_EQUAL(sal_uInt64(0), strm->GetSize()); + strm.reset(); + + url = INetURLObject(u"data:;base64,YWJjCg=="); + CPPUNIT_ASSERT(!url.HasError()); + strm = url.getData(); + CPPUNIT_ASSERT(strm != nullptr); + CPPUNIT_ASSERT_EQUAL(sal_uInt64(4), strm->GetSize()); + buf = static_cast<unsigned char const *>(strm->GetData()); + CPPUNIT_ASSERT_EQUAL(0x61, int(buf[0])); + CPPUNIT_ASSERT_EQUAL(0x62, int(buf[1])); + CPPUNIT_ASSERT_EQUAL(0x63, int(buf[2])); + CPPUNIT_ASSERT_EQUAL(0x0A, int(buf[3])); + strm.reset(); + + url = INetURLObject(u"data:;base64,YWJjCg="); + CPPUNIT_ASSERT(!url.HasError()); + strm = url.getData(); + CPPUNIT_ASSERT(!strm); + + url = INetURLObject(u"data:;base64,YWJ$Cg=="); + CPPUNIT_ASSERT(!url.HasError()); + strm = url.getData(); + CPPUNIT_ASSERT(!strm); + + url = INetURLObject(u"data:text/plain;param=%22;base64,%22,YQ=="); + CPPUNIT_ASSERT(!url.HasError()); + strm = url.getData(); + CPPUNIT_ASSERT(strm != nullptr); + CPPUNIT_ASSERT_EQUAL(sal_uInt64(4), strm->GetSize()); + buf = static_cast<unsigned char const *>(strm->GetData()); + CPPUNIT_ASSERT_EQUAL(0x59, int(buf[0])); + CPPUNIT_ASSERT_EQUAL(0x51, int(buf[1])); + CPPUNIT_ASSERT_EQUAL(0x3D, int(buf[2])); + CPPUNIT_ASSERT_EQUAL(0x3D, int(buf[3])); + strm.reset(); + + url = INetURLObject(u"http://example.com"); + CPPUNIT_ASSERT(!url.HasError()); + strm = url.getData(); + CPPUNIT_ASSERT(!strm); + } + + void urlobjTest_isSchemeEqualTo() { + CPPUNIT_ASSERT(INetURLObject().isSchemeEqualTo(INetProtocol::NotValid)); + CPPUNIT_ASSERT(!INetURLObject().isSchemeEqualTo(u"")); + CPPUNIT_ASSERT( + INetURLObject(u"http://example.org").isSchemeEqualTo( + INetProtocol::Http)); + CPPUNIT_ASSERT( + !INetURLObject(u"http://example.org").isSchemeEqualTo( + INetProtocol::Https)); + CPPUNIT_ASSERT( + INetURLObject(u"http://example.org").isSchemeEqualTo(u"Http")); + CPPUNIT_ASSERT( + !INetURLObject(u"http://example.org").isSchemeEqualTo(u"dav")); + CPPUNIT_ASSERT( + INetURLObject(u"dav://example.org").isSchemeEqualTo(u"dav")); + } + + void urlobjTest_isAnyKnownWebDAVScheme() { + CPPUNIT_ASSERT( + INetURLObject(u"http://example.org").isAnyKnownWebDAVScheme()); + CPPUNIT_ASSERT( + INetURLObject(u"https://example.org").isAnyKnownWebDAVScheme()); + CPPUNIT_ASSERT( + INetURLObject(u"vnd.sun.star.webdav://example.org").isAnyKnownWebDAVScheme()); + CPPUNIT_ASSERT( + INetURLObject(u"vnd.sun.star.webdavs://example.org").isAnyKnownWebDAVScheme()); + CPPUNIT_ASSERT( + !INetURLObject(u"ftp://example.org").isAnyKnownWebDAVScheme()); + CPPUNIT_ASSERT( + !INetURLObject(u"file://example.org").isAnyKnownWebDAVScheme()); + CPPUNIT_ASSERT( + !INetURLObject(u"dav://example.org").isAnyKnownWebDAVScheme()); + CPPUNIT_ASSERT( + !INetURLObject(u"davs://example.org").isAnyKnownWebDAVScheme()); + CPPUNIT_ASSERT( + !INetURLObject(u"vnd.sun.star.pkg://example.org").isAnyKnownWebDAVScheme()); + } + + void testSetName() { + { + INetURLObject obj(u"file:///"); + bool ok = obj.setName(u"foo"); + CPPUNIT_ASSERT(!ok); + } + { + INetURLObject obj(u"file:///foo"); + bool ok = obj.setName(u"bar"); + CPPUNIT_ASSERT(ok); + CPPUNIT_ASSERT_EQUAL( + OUString("file:///bar"), obj.GetMainURL(INetURLObject::DecodeMechanism::NONE)); + } + { + INetURLObject obj(u"file:///foo/"); + bool ok = obj.setName(u"bar"); + CPPUNIT_ASSERT(ok); + CPPUNIT_ASSERT_EQUAL( + OUString("file:///bar/"), obj.GetMainURL(INetURLObject::DecodeMechanism::NONE)); + } + { + INetURLObject obj(u"file:///foo/bar"); + bool ok = obj.setName(u"baz"); + CPPUNIT_ASSERT(ok); + CPPUNIT_ASSERT_EQUAL( + OUString("file:///foo/baz"), + obj.GetMainURL(INetURLObject::DecodeMechanism::NONE)); + } + { + INetURLObject obj(u"file:///foo/bar/"); + bool ok = obj.setName(u"baz"); + CPPUNIT_ASSERT(ok); + CPPUNIT_ASSERT_EQUAL( + OUString("file:///foo/baz/"), + obj.GetMainURL(INetURLObject::DecodeMechanism::NONE)); + } + } + + void testSetExtension() { + INetURLObject obj(u"file:///foo/bar.baz/"); + bool ok = obj.setExtension( + u"other", INetURLObject::LAST_SEGMENT, false); + CPPUNIT_ASSERT(ok); + CPPUNIT_ASSERT_EQUAL( + OUString("file:///foo/bar.baz/.other"), + obj.GetMainURL(INetURLObject::DecodeMechanism::NONE)); + } + + void testChangeScheme() { + INetURLObject obj(u"unknown://example.com/foo/bar"); + CPPUNIT_ASSERT(!obj.HasError()); + obj.changeScheme(INetProtocol::Http); + CPPUNIT_ASSERT_EQUAL( + OUString("http://example.com/foo/bar"), + obj.GetMainURL(INetURLObject::DecodeMechanism::NONE)); + obj.changeScheme(INetProtocol::Https); + CPPUNIT_ASSERT_EQUAL( + OUString("https://example.com/foo/bar"), + obj.GetMainURL(INetURLObject::DecodeMechanism::NONE)); + obj.changeScheme(INetProtocol::Ftp); + CPPUNIT_ASSERT_EQUAL( + OUString("ftp://example.com/foo/bar"), + obj.GetMainURL(INetURLObject::DecodeMechanism::NONE)); + } + + void testTd146382() { + INetURLObject obj(u"file://share.allotropia.de@SSL/DavWWWRoot/remote.php"); + CPPUNIT_ASSERT(!obj.HasError()); + CPPUNIT_ASSERT_EQUAL( + OUString("file://share.allotropia.de@SSL/DavWWWRoot/remote.php"), + obj.GetMainURL(INetURLObject::DecodeMechanism::NONE)); + } + + void testParseSmart() + { + { + // host:port must not be misinterpreted as scheme:path + INetURLObject obj(u"example.com:8080/foo", INetProtocol::Http); + CPPUNIT_ASSERT(!obj.HasError()); + CPPUNIT_ASSERT_EQUAL(OUString("http://example.com:8080/foo"), + obj.GetMainURL(INetURLObject::DecodeMechanism::NONE)); + CPPUNIT_ASSERT_EQUAL(INetProtocol::Http, obj.GetProtocol()); + CPPUNIT_ASSERT_EQUAL(OUString("example.com"), obj.GetHost()); + CPPUNIT_ASSERT_EQUAL(sal_uInt32(8080), obj.GetPort()); + CPPUNIT_ASSERT_EQUAL(OUString("/foo"), obj.GetURLPath()); + } + { + // port may only contain decimal digits, so this must be treated as unknown scheme + INetURLObject obj(u"example.com:80a0/foo", INetProtocol::Http); + CPPUNIT_ASSERT(!obj.HasError()); + CPPUNIT_ASSERT_EQUAL(OUString("example.com:80a0/foo"), + obj.GetMainURL(INetURLObject::DecodeMechanism::NONE)); + CPPUNIT_ASSERT_EQUAL(INetProtocol::Generic, obj.GetProtocol()); + CPPUNIT_ASSERT(obj.isSchemeEqualTo(u"example.com")); + CPPUNIT_ASSERT_EQUAL(OUString(""), obj.GetHost()); + CPPUNIT_ASSERT_EQUAL(OUString("80a0/foo"), obj.GetURLPath()); + } + { + // Test Windows \\?\C:... long path scheme + INetURLObject base(u"file:///C:/foo"); // set up base path + bool bWasAbsolute = false; + INetURLObject obj + = base.smartRel2Abs(u"\\\\?\\D:\\bar\\baz.ext"_ustr, bWasAbsolute); + CPPUNIT_ASSERT(bWasAbsolute); + CPPUNIT_ASSERT_EQUAL(u"file:///D:/bar/baz.ext"_ustr, + obj.GetMainURL(INetURLObject::DecodeMechanism::NONE)); + } + { + // Test Windows \\?\UNC\Server... long path scheme + INetURLObject base(u"file://ServerFoo/fooShare"); // set up base path + bool bWasAbsolute = false; + INetURLObject obj = base.smartRel2Abs( + u"\\\\?\\UNC\\ServerBar\\barShare\\baz.ext"_ustr, bWasAbsolute); + CPPUNIT_ASSERT(bWasAbsolute); + CPPUNIT_ASSERT_EQUAL(u"file://ServerBar/barShare/baz.ext"_ustr, + obj.GetMainURL(INetURLObject::DecodeMechanism::NONE)); + } + } + + // Change the following lines only, if you add, remove or rename + // member functions of the current class, + // because these macros are need by auto register mechanism. + + CPPUNIT_TEST_SUITE( urlobjTest ); + CPPUNIT_TEST( urlobjTest_001 ); + CPPUNIT_TEST( urlobjTest_004 ); + CPPUNIT_TEST( urlobjCmisTest ); + CPPUNIT_TEST( urlobjTest_emptyPath ); + CPPUNIT_TEST( urlobjTest_data ); + CPPUNIT_TEST( urlobjTest_isSchemeEqualTo ); + CPPUNIT_TEST( urlobjTest_isAnyKnownWebDAVScheme ); + CPPUNIT_TEST( testSetName ); + CPPUNIT_TEST( testSetExtension ); + CPPUNIT_TEST( testChangeScheme ); + CPPUNIT_TEST( testTd146382 ); + CPPUNIT_TEST( testParseSmart ); + CPPUNIT_TEST_SUITE_END( ); + }; // class createPool + + + CPPUNIT_TEST_SUITE_REGISTRATION( urlobjTest ); +} // namespace rtl_random + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/qa/cppunit/test_xmlwalker.cxx b/tools/qa/cppunit/test_xmlwalker.cxx new file mode 100644 index 0000000000..6df278c21e --- /dev/null +++ b/tools/qa/cppunit/test_xmlwalker.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/. + */ + +#include <cppunit/extensions/HelperMacros.h> +#include <test/bootstrapfixture.hxx> +#include <rtl/ustring.hxx> +#include <tools/stream.hxx> +#include <tools/XmlWalker.hxx> + +namespace +{ +class XmlWalkerTest : public test::BootstrapFixture +{ + OUString maBasePath; + +public: + XmlWalkerTest() + : BootstrapFixture(true, false) + { + } + + virtual void setUp() override { maBasePath = m_directories.getURLFromSrc(u"/tools/qa/data/"); } + + void testReadXML(); + + CPPUNIT_TEST_SUITE(XmlWalkerTest); + CPPUNIT_TEST(testReadXML); + CPPUNIT_TEST_SUITE_END(); +}; + +void XmlWalkerTest::testReadXML() +{ + OUString aXmlFilePath = maBasePath + "test.xml"; + + tools::XmlWalker aWalker; + SvFileStream aFileStream(aXmlFilePath, StreamMode::READ); + CPPUNIT_ASSERT(aWalker.open(&aFileStream)); + CPPUNIT_ASSERT_EQUAL("root"_ostr, aWalker.name()); + CPPUNIT_ASSERT_EQUAL("Hello World"_ostr, aWalker.attribute("root-attr"_ostr)); + + int nNumberOfChildNodes = 0; + + aWalker.children(); + while (aWalker.isValid()) + { + if (aWalker.name() == "child") + { + nNumberOfChildNodes++; + + CPPUNIT_ASSERT_EQUAL(OString(OString::number(nNumberOfChildNodes)), + aWalker.attribute("number"_ostr)); + + if (nNumberOfChildNodes == 1) // only the first node has the attribute + CPPUNIT_ASSERT_EQUAL("123"_ostr, aWalker.attribute("attribute"_ostr)); + else + CPPUNIT_ASSERT_EQUAL(OString(), aWalker.attribute("attribute"_ostr)); + + aWalker.children(); + while (aWalker.isValid()) + { + if (aWalker.name() == "grandchild") + { + CPPUNIT_ASSERT_EQUAL("ABC"_ostr, aWalker.attribute("attribute1"_ostr)); + CPPUNIT_ASSERT_EQUAL("CDE"_ostr, aWalker.attribute("attribute2"_ostr)); + CPPUNIT_ASSERT_EQUAL("Content"_ostr, aWalker.content()); + } + aWalker.next(); + } + aWalker.parent(); + } + else if (aWalker.name() == "with-namespace") + { + CPPUNIT_ASSERT_EQUAL("adobe:ns:meta/"_ostr, aWalker.namespaceHref()); + CPPUNIT_ASSERT_EQUAL("xx"_ostr, aWalker.namespacePrefix()); + } + aWalker.next(); + } + aWalker.parent(); + + CPPUNIT_ASSERT_EQUAL(3, nNumberOfChildNodes); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(XmlWalkerTest); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/qa/cppunit/test_xmlwriter.cxx b/tools/qa/cppunit/test_xmlwriter.cxx new file mode 100644 index 0000000000..eeb475030f --- /dev/null +++ b/tools/qa/cppunit/test_xmlwriter.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/. + */ + +#include <cppunit/extensions/HelperMacros.h> +#include <test/bootstrapfixture.hxx> +#include <tools/stream.hxx> +#include <tools/XmlWriter.hxx> + +namespace +{ +class XmlWriterTest : public test::BootstrapFixture +{ +public: + XmlWriterTest() + : BootstrapFixture(true, false) + { + } + + virtual void setUp() override {} + + void testSimpleRoot(); + void testSpecialChars(); + + CPPUNIT_TEST_SUITE(XmlWriterTest); + CPPUNIT_TEST(testSimpleRoot); + CPPUNIT_TEST(testSpecialChars); + CPPUNIT_TEST_SUITE_END(); +}; + +void XmlWriterTest::testSimpleRoot() +{ + SvMemoryStream aMemoryStream; + + tools::XmlWriter aWriter(&aMemoryStream); + aWriter.startDocument(0, false); + aWriter.startElement("test"); + aWriter.endElement(); + aWriter.endDocument(); + + aMemoryStream.Seek(0); + OString aString(static_cast<const char*>(aMemoryStream.GetData()), aMemoryStream.GetSize()); + CPPUNIT_ASSERT_EQUAL("<test/>"_ostr, aString); +} + +void XmlWriterTest::testSpecialChars() +{ + SvMemoryStream aMemoryStream; + + tools::XmlWriter aWriter(&aMemoryStream); + aWriter.startDocument(0, false); + aWriter.startElement("test"); + aWriter.content("<>"_ostr); + aWriter.endElement(); + aWriter.endDocument(); + + aMemoryStream.Seek(0); + OString aString(static_cast<const char*>(aMemoryStream.GetData()), aMemoryStream.GetSize()); + CPPUNIT_ASSERT_EQUAL("<test><></test>"_ostr, aString); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(XmlWriterTest); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/qa/cppunit/test_zcodec.cxx b/tools/qa/cppunit/test_zcodec.cxx new file mode 100644 index 0000000000..935a234d4f --- /dev/null +++ b/tools/qa/cppunit/test_zcodec.cxx @@ -0,0 +1,123 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 <tools/zcodec.hxx> +#include <tools/stream.hxx> +#include <rtl/crc.h> +#include <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/plugin/TestPlugIn.h> + +namespace +{ +// Sample text for compression +constexpr const char* DUMMY_TEXT + = "Lorem ipsum dolor sit amet, consectetur adipiscing elit,\n" + "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n" + "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea " + "commodo consequat.\n"; +constexpr auto DUMMY_SIZE = std::char_traits<char>::length(DUMMY_TEXT); + +class ZCodecTest : public CppUnit::TestFixture +{ +private: + void testGzCompressDecompress(); + void testMakeDummyFile(); + void testZlibCompressDecompress(); + + CPPUNIT_TEST_SUITE(ZCodecTest); + CPPUNIT_TEST(testMakeDummyFile); + CPPUNIT_TEST(testGzCompressDecompress); + CPPUNIT_TEST(testZlibCompressDecompress); + CPPUNIT_TEST_SUITE_END(); +}; + +// Creates a stream from DUMMY_TEXT to compress and decompress +std::unique_ptr<SvMemoryStream> makeDummyFile() +{ + SvMemoryStream* pRet = new SvMemoryStream(); + pRet->WriteBytes(DUMMY_TEXT, DUMMY_SIZE); + return std::unique_ptr<SvMemoryStream>(pRet); +} + +// Test that the stream generated from makeDummyFile is valid +void ZCodecTest::testMakeDummyFile() +{ + auto pStream = makeDummyFile(); + // Check for null + CPPUNIT_ASSERT_MESSAGE("pStream is null", pStream); + decltype(DUMMY_SIZE) size = pStream->GetSize(); + // Check size + CPPUNIT_ASSERT_EQUAL(DUMMY_SIZE, size); +} + +// Test that ZCodec::Compress and ZCodec::Decompress work for gzlib = true +// Compares the CRC32 checksum of the initial stream and the compressed->decompressed stream +void ZCodecTest::testGzCompressDecompress() +{ + auto pInitialStream = makeDummyFile(); + SvMemoryStream pCompressedStream; + SvMemoryStream pDecompressedStream; + auto nInitialStreamCRC32 = rtl_crc32(0, pInitialStream->GetData(), pInitialStream->GetSize()); + ZCodec aCodec; + aCodec.BeginCompression(ZCODEC_DEFAULT_COMPRESSION, true); + // Set compression metadata for compressing a GZ file + aCodec.SetCompressionMetadata("dummy.txt"_ostr, 0, nInitialStreamCRC32); + aCodec.Compress(*pInitialStream, pCompressedStream); + auto nCompressedSize + = aCodec.EndCompression(); // returns compressed size or -1 if compression failed + // Check that the compression succeeded + CPPUNIT_ASSERT_GREATER(static_cast<tools::Long>(0), nCompressedSize); + // No need to Seek(0) here because ZCodec::InitDecompress does that already for gz + aCodec.BeginCompression(ZCODEC_DEFAULT_COMPRESSION, true); + aCodec.Decompress(pCompressedStream, pDecompressedStream); + auto nDecompressedSize + = aCodec.EndCompression(); // returns decompressed size or -1 if decompression failed + // Check that the decompressed text size is equal to the original + CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(DUMMY_SIZE), nDecompressedSize); + auto nCompressedDecompressedStreamCRC32 + = rtl_crc32(0, pDecompressedStream.GetData(), pDecompressedStream.GetSize()); + // Check that the initial and decompressed CRC32 checksums are the same -> gz (de)compression works + CPPUNIT_ASSERT_EQUAL(nInitialStreamCRC32, nCompressedDecompressedStreamCRC32); +} + +// Test that ZCodec::Compress and ZCodec::Decompress work for gzlib = false +// Compares the CRC32 checksum of the initial stream and the compressed->decompressed stream +void ZCodecTest::testZlibCompressDecompress() +{ + auto pInitialStream = makeDummyFile(); + SvMemoryStream pCompressedStream; + SvMemoryStream pDecompressedStream; + auto nInitialStreamCRC32 = rtl_crc32(0, pInitialStream->GetData(), pInitialStream->GetSize()); + ZCodec aCodec; + aCodec.BeginCompression(ZCODEC_DEFAULT_COMPRESSION, false); + aCodec.Compress(*pInitialStream, pCompressedStream); + auto nCompressedSize + = aCodec.EndCompression(); // returns compressed size or -1 if compression failed + // Check that the compression succeeded + CPPUNIT_ASSERT_GREATER(static_cast<tools::Long>(0), nCompressedSize); + pCompressedStream.Seek(0); + aCodec.BeginCompression(ZCODEC_DEFAULT_COMPRESSION, false); + aCodec.Decompress(pCompressedStream, pDecompressedStream); + auto nDecompressedSize + = aCodec.EndCompression(); // returns decompressed size or -1 if decompression failed + // Check that the decompressed text size is equal to the original + CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(DUMMY_SIZE), nDecompressedSize); + auto nCompressedDecompressedStreamCRC32 + = rtl_crc32(0, pDecompressedStream.GetData(), pDecompressedStream.GetSize()); + // Check that the initial and decompressed CRC32 checksums are the same -> zlib (de)compression works + CPPUNIT_ASSERT_EQUAL(nInitialStreamCRC32, nCompressedDecompressedStreamCRC32); +} + +} // namespace + +CPPUNIT_TEST_SUITE_REGISTRATION(ZCodecTest); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/tools/qa/data/test.xml b/tools/qa/data/test.xml new file mode 100644 index 0000000000..c2736e8bb6 --- /dev/null +++ b/tools/qa/data/test.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<root root-attr="Hello World"> + <child number="1" attribute="123"> + <grandchild attribute1="ABC" attribute2="CDE">Content</grandchild> + </child> + <child number="2"> + </child> + <child number="3"> + </child> + <xx:with-namespace xmlns:xx="adobe:ns:meta/"> + </xx:with-namespace> +</root> diff --git a/tools/qa/data/testconfig.ini b/tools/qa/data/testconfig.ini new file mode 100644 index 0000000000..6307532164 --- /dev/null +++ b/tools/qa/data/testconfig.ini @@ -0,0 +1,5 @@ +[TestGroup]
+testkey=testvalue
+
+[TestGroup2]
+testkey2=testvalue
diff --git a/tools/source/datetime/datetime.cxx b/tools/source/datetime/datetime.cxx new file mode 100644 index 0000000000..6f9dea26c6 --- /dev/null +++ b/tools/source/datetime/datetime.cxx @@ -0,0 +1,291 @@ +/* -*- 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 <tools/datetime.hxx> +#include <tools/duration.hxx> +#include <rtl/math.hxx> +#include <sal/log.hxx> + +#include <systemdatetime.hxx> + +DateTime::DateTime(DateTimeInitSystem) + : Date( Date::EMPTY ) + , Time( Time::EMPTY ) +{ + sal_Int32 nD = 0; + sal_Int64 nT = 0; + if ( GetSystemDateTime( &nD, &nT ) ) + { + Date::operator=( Date( nD ) ); + SetTime( nT ); + } + else + Date::operator=( Date( 1, 1, 1900 ) ); // Time::nTime is already 0 +} + +DateTime::DateTime( const css::util::DateTime& rDateTime ) + : Date( rDateTime.Day, rDateTime.Month, rDateTime.Year ), + Time( rDateTime.Hours, rDateTime.Minutes, rDateTime.Seconds, rDateTime.NanoSeconds ) +{ +} + +DateTime& DateTime::operator =( const css::util::DateTime& rUDateTime ) +{ + Date::operator=( Date( rUDateTime.Day, rUDateTime.Month, rUDateTime.Year)); + Time::operator=( Time( rUDateTime)); + return *this; +} + +bool DateTime::IsBetween( const DateTime& rFrom, const DateTime& rTo ) const +{ + return (*this >= rFrom) && (*this <= rTo); +} + +bool DateTime::operator >( const DateTime& rDateTime ) const +{ + return (Date::operator>( rDateTime )) || + (Date::operator==( rDateTime ) && tools::Time::operator>( rDateTime )); +} + +bool DateTime::operator <( const DateTime& rDateTime ) const +{ + return (Date::operator<( rDateTime )) || + (Date::operator==( rDateTime ) && tools::Time::operator<( rDateTime )); +} + +bool DateTime::operator >=( const DateTime& rDateTime ) const +{ + return (Date::operator>( rDateTime )) || + (Date::operator==( rDateTime ) && tools::Time::operator>=( rDateTime )); +} + +bool DateTime::operator <=( const DateTime& rDateTime ) const +{ + return (Date::operator<( rDateTime )) || + (Date::operator==( rDateTime ) && tools::Time::operator<=( rDateTime )); +} + +sal_Int64 DateTime::GetSecFromDateTime( const Date& rDate ) const +{ + if ( Date::operator<( rDate ) ) + return 0; + else + { + sal_Int64 nSec = Date( *this ) - rDate; + nSec *= 24UL*60*60; + sal_Int64 nHour = GetHour(); + sal_Int64 nMin = GetMin(); + nSec += (nHour*3600)+(nMin*60)+GetSec(); + return nSec; + } +} + +void DateTime::NormalizeTimeRemainderAndApply( tools::Time& rTime ) +{ + sal_uInt16 nHours = rTime.GetHour(); + if ( rTime.GetTime() > 0 ) + { + if (nHours >= 24) + { + AddDays( nHours / 24 ); + nHours %= 24; + rTime.SetHour( nHours ); + } + } + else if ( rTime.GetTime() != 0 ) + { + if (nHours >= 24) + { + AddDays( -static_cast<sal_Int32>(nHours) / 24 ); + nHours %= 24; + rTime.SetHour( nHours ); + } + Date::operator--(); + rTime = Time( 24, 0, 0 ) + rTime; + } + tools::Time::operator=( rTime ); +} + +DateTime& DateTime::operator +=( const tools::Time& rTime ) +{ + tools::Time aTime = *this; + aTime += rTime; + NormalizeTimeRemainderAndApply(aTime); + return *this; +} + +DateTime& DateTime::operator -=( const tools::Time& rTime ) +{ + tools::Time aTime = *this; + aTime -= rTime; + NormalizeTimeRemainderAndApply(aTime); + return *this; +} + +DateTime& DateTime::operator +=( const tools::Duration& rDuration ) +{ + AddDays(rDuration.GetDays()); + operator+=(rDuration.GetTime()); + return *this; +} + +DateTime operator +( const DateTime& rDateTime, sal_Int32 nDays ) +{ + DateTime aDateTime( rDateTime ); + aDateTime.AddDays( nDays ); + return aDateTime; +} + +DateTime operator -( const DateTime& rDateTime, sal_Int32 nDays ) +{ + DateTime aDateTime( rDateTime ); + aDateTime.AddDays( -nDays ); + return aDateTime; +} + +DateTime operator +( const DateTime& rDateTime, const tools::Time& rTime ) +{ + DateTime aDateTime( rDateTime ); + aDateTime += rTime; + return aDateTime; +} + +DateTime operator -( const DateTime& rDateTime, const tools::Time& rTime ) +{ + DateTime aDateTime( rDateTime ); + aDateTime -= rTime; + return aDateTime; +} + +DateTime operator +( const DateTime& rDateTime, const tools::Duration& rDuration ) +{ + DateTime aDateTime(rDateTime); + aDateTime.AddDays( rDuration.GetDays()); + aDateTime += rDuration.GetTime(); + return aDateTime; +} + +void DateTime::AddTime( double fTimeInDays ) +{ + // Use Duration to diminish floating point accuracy errors. + tools::Duration aDuration(fTimeInDays); + operator+=(aDuration); +} + +DateTime operator +( const DateTime& rDateTime, double fTimeInDays ) +{ + DateTime aDateTime( rDateTime ); + aDateTime.AddTime( fTimeInDays ); + return aDateTime; +} + +tools::Duration operator -( const DateTime& rDateTime1, const DateTime& rDateTime2 ) +{ + return tools::Duration( rDateTime2, rDateTime1); +} + +// static +double DateTime::Sub( const DateTime& rDateTime1, const DateTime& rDateTime2 ) +{ + if (static_cast<const tools::Time&>(rDateTime1) != static_cast<const tools::Time&>(rDateTime2)) + { + // Use Duration to diminish floating point accuracy errors. + const tools::Duration aDuration( rDateTime2, rDateTime1); + return aDuration.GetInDays(); + } + return static_cast<const Date&>(rDateTime1) - static_cast<const Date&>(rDateTime2); +} + +void DateTime::GetWin32FileDateTime( sal_uInt32 & rLower, sal_uInt32 & rUpper ) const +{ + const sal_Int64 a100nPerSecond = SAL_CONST_INT64( 10000000 ); + const sal_Int64 a100nPerDay = a100nPerSecond * sal_Int64( 60 * 60 * 24 ); + + // FILETIME is indirectly documented as uint64, see + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724284.aspx + // mentioning the ULARGE_INTEGER structure. + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724280.aspx + // mentions that if FILETIME is not less than 0x8000000000000000 then the + // FileTimeToSystemTime function fails, which is another indicator. + // Unless there's evidence that FILETIME can represent a signed offset from + // 1601-01-01 truncate at 0. (reading part below in + // CreateFromWin32FileDateTime() would had to be adapted to signed as + // well). + sal_Int16 nYear = GetYear(); + SAL_WARN_IF( nYear < 1601, "tools.datetime", "DateTime::GetWin32FileDateTime - year < 1601: " << nYear); + + sal_Int64 aTime = (nYear < 1601 ? 0 : ( + a100nPerDay * (*this - Date(1,1,1601)) + + GetNSFromTime()/100)); + + rLower = sal_uInt32( aTime % SAL_CONST_UINT64( 0x100000000 ) ); + rUpper = sal_uInt32( aTime / SAL_CONST_UINT64( 0x100000000 ) ); +} + +DateTime DateTime::CreateFromWin32FileDateTime( sal_uInt32 rLower, sal_uInt32 rUpper ) +{ + // (rUpper|rLower) = 100-nanosecond intervals since 1601-01-01 00:00 + const sal_uInt64 a100nPerSecond = SAL_CONST_UINT64( 10000000 ); + const sal_uInt64 a100nPerDay = a100nPerSecond * sal_uInt64( 60 * 60 * 24 ); + + sal_uInt64 aTime = + sal_uInt64( rUpper ) * SAL_CONST_UINT64( 0x100000000 ) + + sal_uInt64( rLower ); + + SAL_WARN_IF( static_cast<sal_Int64>(aTime) < 0, "tools.datetime", + "DateTime::CreateFromWin32FileDateTime - absurdly high value expected?"); + + sal_uInt64 nDays = aTime / a100nPerDay; + + Date aDate(1,1,1601); + // (0xffffffffffffffff / a100nPerDay = 21350398) fits into sal_Int32 + // (0x7fffffff = 2147483647) + aDate.AddDays(nDays); + + SAL_WARN_IF( aDate - Date(1,1,1601) != static_cast<sal_Int32>(nDays), "tools.datetime", + "DateTime::CreateFromWin32FileDateTime - date truncated to max"); + + sal_uInt64 nNanos = (aTime - (nDays * a100nPerDay)) * 100; + return DateTime( aDate, tools::Time( + static_cast<sal_uInt32>((nNanos / tools::Time::nanoSecPerHour) % sal_uInt64( 24 )), + static_cast<sal_uInt32>((nNanos / tools::Time::nanoSecPerMinute) % sal_uInt64( 60 )), + static_cast<sal_uInt32>((nNanos / tools::Time::nanoSecPerSec) % sal_uInt64( 60 )), + static_cast<sal_uInt64>( nNanos % tools::Time::nanoSecPerSec))); +} + +DateTime DateTime::CreateFromUnixTime(const double fSecondsSinceEpoch) +{ + double fValue = fSecondsSinceEpoch / Time::secondPerDay; + const sal_Int32 nDays = static_cast <sal_Int32>(::rtl::math::approxFloor(fValue)); + + Date aDate (1, 1, 1970); + aDate.AddDays(nDays); + SAL_WARN_IF(aDate - Date(1, 1, 1970) != nDays, "tools.datetime", + "DateTime::CreateFromUnixTime - date truncated to max"); + + fValue -= nDays; + + const sal_uInt64 nNanos = fValue * tools::Time::nanoSecPerDay; + return DateTime( aDate, tools::Time( + static_cast<sal_uInt32>((nNanos / tools::Time::nanoSecPerHour) % sal_uInt64( 24 )), + static_cast<sal_uInt32>((nNanos / tools::Time::nanoSecPerMinute) % sal_uInt64( 60 )), + static_cast<sal_uInt32>((nNanos / tools::Time::nanoSecPerSec) % sal_uInt64( 60 )), + static_cast<sal_uInt64>( nNanos % tools::Time::nanoSecPerSec))); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/datetime/datetimeutils.cxx b/tools/source/datetime/datetimeutils.cxx new file mode 100644 index 0000000000..4c3b28d49d --- /dev/null +++ b/tools/source/datetime/datetimeutils.cxx @@ -0,0 +1,82 @@ +/* -*- 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 <tools/datetimeutils.hxx> +#include <rtl/strbuf.hxx> + + +/// Append the number as 2-digit when less than 10. +static void lcl_AppendTwoDigits( OStringBuffer &rBuffer, sal_Int32 nNum ) +{ + if ( nNum < 0 || nNum > 99 ) + { + rBuffer.append( "00" ); + return; + } + + if ( nNum < 10 ) + rBuffer.append( '0' ); + + rBuffer.append( nNum ); +} + +OString DateTimeToOString( const DateTime& rDateTime ) +{ + const DateTime& aInUTC( rDateTime ); +// HACK: this is correct according to the spec, but MSOffice believes everybody lives +// in UTC+0 when reading it back +// aInUTC.ConvertToUTC(); + + OStringBuffer aBuffer( 25 ); + aBuffer.append( sal_Int32( aInUTC.GetYear() ) ); + aBuffer.append( '-' ); + + lcl_AppendTwoDigits( aBuffer, aInUTC.GetMonth() ); + aBuffer.append( '-' ); + + lcl_AppendTwoDigits( aBuffer, aInUTC.GetDay() ); + aBuffer.append( 'T' ); + + lcl_AppendTwoDigits( aBuffer, aInUTC.GetHour() ); + aBuffer.append( ':' ); + + lcl_AppendTwoDigits( aBuffer, aInUTC.GetMin() ); + aBuffer.append( ':' ); + + lcl_AppendTwoDigits( aBuffer, aInUTC.GetSec() ); + aBuffer.append( 'Z' ); // we are in UTC + + return aBuffer.makeStringAndClear(); +} + +OString DateToOString( const Date& rDate ) +{ + tools::Time aTime( tools::Time::EMPTY ); + return DateTimeToOString( DateTime( rDate, aTime ) ); +} + +OString DateToDDMMYYYYOString( const Date& rDate ) +{ + OStringBuffer aBuffer( 25 ); + lcl_AppendTwoDigits( aBuffer, rDate.GetDay() ); + aBuffer.append( '/' ); + + lcl_AppendTwoDigits( aBuffer, rDate.GetMonth() ); + aBuffer.append( '/' ); + + aBuffer.append( sal_Int32( rDate.GetYear() ) ); + + return aBuffer.makeStringAndClear(); +} + +std::ostream& operator<<(std::ostream& os, const Date& rDate) +{ + os << rDate.GetYear() << "-" << rDate.GetMonth() << "-" << rDate.GetDay(); + return os; +} diff --git a/tools/source/datetime/duration.cxx b/tools/source/datetime/duration.cxx new file mode 100644 index 0000000000..a655f016a1 --- /dev/null +++ b/tools/source/datetime/duration.cxx @@ -0,0 +1,328 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 <tools/duration.hxx> +#include <tools/datetime.hxx> +#include <rtl/math.hxx> +#include <o3tl/safeint.hxx> +#include <cmath> + +namespace tools +{ +Duration::Duration(const ::DateTime& rStart, const ::DateTime& rEnd) + : mnDays(static_cast<const Date&>(rEnd) - static_cast<const Date&>(rStart)) +{ + SetTimeDiff(rStart, rEnd); +} + +Duration::Duration(const Time& rStart, const Time& rEnd) +{ + const sal_uInt16 nStartHour = rStart.GetHour(); + const sal_uInt16 nEndHour = rEnd.GetHour(); + if (nStartHour >= 24 || nEndHour >= 24) + { + Time aEnd(rEnd); + if (nEndHour >= 24) + { + mnDays = (nEndHour / 24) * (aEnd.GetTime() < 0 ? -1 : 1); + aEnd.SetHour(nEndHour % 24); + } + Time aStart(rStart); + if (nStartHour >= 24) + { + mnDays -= (nStartHour / 24) * (aStart.GetTime() < 0 ? -1 : 1); + aStart.SetHour(nStartHour % 24); + } + SetTimeDiff(aStart, aEnd); + } + else + { + SetTimeDiff(rStart, rEnd); + } +} + +Duration::Duration(double fTimeInDays, sal_uInt64 nAccuracyEpsilonNanoseconds) +{ + assert(nAccuracyEpsilonNanoseconds <= Time::nanoSecPerSec - 1); + double fInt, fFrac; + if (fTimeInDays < 0.0) + { + fInt = ::rtl::math::approxCeil(fTimeInDays); + fFrac = fInt <= fTimeInDays ? 0.0 : fTimeInDays - fInt; + } + else + { + fInt = ::rtl::math::approxFloor(fTimeInDays); + fFrac = fInt >= fTimeInDays ? 0.0 : fTimeInDays - fInt; + } + mnDays = static_cast<sal_Int32>(fInt); + if (fFrac) + { + fFrac *= Time::nanoSecPerDay; + fFrac = ::rtl::math::approxFloor(fFrac); + sal_Int64 nNS = static_cast<sal_Int64>(fFrac); + const sal_Int64 nN = nNS % Time::nanoSecPerSec; + if (nN) + { + const sal_uInt64 nA = std::abs(nN); + if (nA <= nAccuracyEpsilonNanoseconds) + nNS -= (nNS < 0) ? -nN : nN; + else if (nA >= Time::nanoSecPerSec - nAccuracyEpsilonNanoseconds) + { + const sal_Int64 nD = Time::nanoSecPerSec - nA; + nNS += (nNS < 0) ? -nD : nD; + if (std::abs(nNS) >= Time::nanoSecPerDay) + { + mnDays += nNS / Time::nanoSecPerDay; + nNS %= Time::nanoSecPerDay; + } + } + } + maTime.MakeTimeFromNS(nNS); + assert(mnDays == 0 || maTime.GetTime() == 0 || (mnDays < 0) == (nNS < 0)); + } +} + +Duration::Duration(sal_Int32 nDays, const Time& rTime) + : mnDays(nDays) +{ + assert(nDays == 0 || rTime.GetTime() == 0 || (nDays < 0) == (rTime.GetTime() < 0)); + Normalize(rTime.GetHour(), rTime.GetMin(), rTime.GetSec(), rTime.GetNanoSec(), + ((nDays < 0) || (rTime.GetTime() < 0))); +} + +Duration::Duration(sal_Int32 nDays, sal_uInt32 nHours, sal_uInt32 nMinutes, sal_uInt32 nSeconds, + sal_uInt64 nNanoseconds) + : mnDays(nDays) +{ + Normalize(nHours, nMinutes, nSeconds, nNanoseconds, nDays < 0); +} + +Duration::Duration(sal_Int32 nDays, sal_Int64 nTime) + : maTime(nTime) + , mnDays(nDays) +{ +} + +void Duration::Normalize(sal_uInt64 nHours, sal_uInt64 nMinutes, sal_uInt64 nSeconds, + sal_uInt64 nNanoseconds, bool bNegative) +{ + if (nNanoseconds >= Time::nanoSecPerSec) + { + nSeconds += nNanoseconds / Time::nanoSecPerSec; + nNanoseconds %= Time::nanoSecPerSec; + } + if (nSeconds >= Time::secondPerMinute) + { + nMinutes += nSeconds / Time::secondPerMinute; + nSeconds %= Time::secondPerMinute; + } + if (nMinutes >= Time::minutePerHour) + { + nHours += nMinutes / Time::minutePerHour; + nMinutes %= Time::minutePerHour; + } + if (nHours >= Time::hourPerDay) + { + sal_Int64 nDiff = nHours / Time::hourPerDay; + nHours %= Time::hourPerDay; + bool bOverflow = false; + if (bNegative) + { + nDiff = -nDiff; + bOverflow = (nDiff < SAL_MIN_INT32); + bOverflow |= o3tl::checked_add(mnDays, static_cast<sal_Int32>(nDiff), mnDays); + if (bOverflow) + mnDays = SAL_MIN_INT32; + } + else + { + bOverflow = (nDiff > SAL_MAX_INT32); + bOverflow |= o3tl::checked_add(mnDays, static_cast<sal_Int32>(nDiff), mnDays); + if (bOverflow) + mnDays = SAL_MAX_INT32; + } + assert(!bOverflow); + if (bOverflow) + { + nHours = Time::hourPerDay - 1; + nMinutes = Time::minutePerHour - 1; + nSeconds = Time::secondPerMinute - 1; + nNanoseconds = Time::nanoSecPerSec - 1; + } + } + maTime = Time(nHours, nMinutes, nSeconds, nNanoseconds); + if (bNegative) + maTime = -maTime; + assert(mnDays == 0 || maTime.GetTime() == 0 || (mnDays < 0) == (maTime.GetTime() < 0)); +} + +void Duration::ApplyTime(sal_Int64 nNS) +{ + if (mnDays > 0 && nNS < 0) + { + --mnDays; + nNS = Time::nanoSecPerDay + nNS; + } + else if (mnDays < 0 && nNS > 0) + { + ++mnDays; + nNS = -Time::nanoSecPerDay + nNS; + } + maTime.MakeTimeFromNS(nNS); + assert(mnDays == 0 || maTime.GetTime() == 0 || (mnDays < 0) == (maTime.GetTime() < 0)); +} + +void Duration::SetTimeDiff(const Time& rStart, const Time& rEnd) +{ + const sal_Int64 nNS = rEnd.GetNSFromTime() - rStart.GetNSFromTime(); + ApplyTime(nNS); +} + +Duration Duration::operator-() const +{ + Duration aD(-mnDays, -maTime.GetTime()); + return aD; +} + +Duration& Duration::Add(const Duration& rDuration, bool& rbOverflow) +{ + rbOverflow = o3tl::checked_add(mnDays, rDuration.mnDays, mnDays); + // Duration is always normalized, time values >= 24h don't occur. + sal_Int64 nNS = maTime.GetNSFromTime() + rDuration.maTime.GetNSFromTime(); + if (nNS < -Time::nanoSecPerDay) + { + rbOverflow |= o3tl::checked_sub(mnDays, sal_Int32(1), mnDays); + nNS += Time::nanoSecPerDay; + } + else if (nNS > Time::nanoSecPerDay) + { + rbOverflow |= o3tl::checked_add(mnDays, sal_Int32(1), mnDays); + nNS -= Time::nanoSecPerDay; + } + ApplyTime(nNS); + return *this; +} + +Duration Duration::Mult(sal_Int32 nMult, bool& rbOverflow) const +{ + // First try a simple calculation in nanoseconds. + bool bBadNS = false; + sal_Int64 nNS; + sal_Int64 nDays; + if (o3tl::checked_multiply(static_cast<sal_Int64>(mnDays), static_cast<sal_Int64>(nMult), nDays) + || o3tl::checked_multiply(nDays, Time::nanoSecPerDay, nDays) + || o3tl::checked_multiply(maTime.GetNSFromTime(), static_cast<sal_Int64>(nMult), nNS) + || o3tl::checked_add(nDays, nNS, nNS)) + { + bBadNS = rbOverflow = true; + } + else + { + const sal_Int64 nD = nNS / Time::nanoSecPerDay; + if (nD < SAL_MIN_INT32 || SAL_MAX_INT32 < nD) + rbOverflow = true; + else + { + rbOverflow = false; + nNS -= nD * Time::nanoSecPerDay; + Duration aD(static_cast<sal_Int32>(nD), 0); + aD.ApplyTime(nNS); + return aD; + } + } + if (bBadNS) + { + // Simple calculation in overall nanoseconds overflowed, try with + // individual components. + const sal_uInt64 nMult64 = (nMult < 0) ? -nMult : nMult; + do + { + sal_uInt64 nN; + if (o3tl::checked_multiply(static_cast<sal_uInt64>(maTime.GetNanoSec()), nMult64, nN)) + break; + sal_uInt64 nS; + if (o3tl::checked_multiply(static_cast<sal_uInt64>(maTime.GetSec()), nMult64, nS)) + break; + sal_uInt64 nM; + if (o3tl::checked_multiply(static_cast<sal_uInt64>(maTime.GetMin()), nMult64, nM)) + break; + sal_uInt64 nH; + if (o3tl::checked_multiply(static_cast<sal_uInt64>(maTime.GetHour()), nMult64, nH)) + break; + sal_uInt64 nD; + if (o3tl::checked_multiply( + mnDays < 0 ? static_cast<sal_uInt64>(-static_cast<sal_Int64>(mnDays)) + : static_cast<sal_uInt64>(mnDays), + nMult64, nD)) + break; + if (nN > Time::nanoSecPerSec) + { + const sal_uInt64 nC = nN / Time::nanoSecPerSec; + if (o3tl::checked_add(nS, nC, nS)) + break; + nN -= nC * Time::nanoSecPerSec; + } + if (nS > Time::secondPerMinute) + { + const sal_uInt64 nC = nS / Time::secondPerMinute; + if (o3tl::checked_add(nM, nC, nM)) + break; + nS -= nC * Time::secondPerMinute; + } + if (nM > Time::minutePerHour) + { + const sal_uInt64 nC = nM / Time::minutePerHour; + if (o3tl::checked_add(nH, nC, nH)) + break; + nM -= nC * Time::minutePerHour; + } + if (nH > Time::hourPerDay) + { + const sal_uInt64 nC = nH / Time::hourPerDay; + if (o3tl::checked_add(nD, nC, nD)) + break; + nH -= nC * Time::hourPerDay; + } + if (IsNegative() ? (static_cast<sal_uInt64>(SAL_MAX_INT32) + 1) < nD + || -static_cast<sal_Int64>(nD) < SAL_MIN_INT32 + : SAL_MAX_INT32 < nD) + break; + + rbOverflow = false; + Time aTime(nH, nM, nS, nN); + if (IsNegative() == (nMult < 0)) + { + Duration aD(nD, aTime.GetTime()); + return aD; + } + else + { + Duration aD(-static_cast<sal_Int64>(nD), -aTime.GetTime()); + return aD; + } + } while (false); + } + assert(rbOverflow); + if (IsNegative() == (nMult < 0)) + { + Duration aD(SAL_MAX_INT32, 0); + aD.ApplyTime(Time::nanoSecPerDay - 1); + return aD; + } + else + { + Duration aD(SAL_MIN_INT32, 0); + aD.ApplyTime(-(Time::nanoSecPerDay - 1)); + return aD; + } +} +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/tools/source/datetime/systemdatetime.cxx b/tools/source/datetime/systemdatetime.cxx new file mode 100644 index 0000000000..2115f4b620 --- /dev/null +++ b/tools/source/datetime/systemdatetime.cxx @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 . + */ +#if defined(_WIN32) +#if !defined WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> +#elif defined UNX +#include <sys/time.h> +#endif + +#include <time.h> +#ifdef __MACH__ +#include <mach/clock.h> +#include <mach/mach.h> +#include <mach/mach_time.h> +#endif + +#include <osl/diagnose.h> +#include <systemdatetime.hxx> + +namespace +{ +constexpr sal_Int32 ConvertYMDToInt(sal_Int32 nYear, sal_Int32 nMonth, sal_Int32 nDay) +{ + return (nYear * 10000) + (nMonth * 100) + nDay; +} + +constexpr sal_Int64 ConvertHMSnToInt(sal_Int64 nHour, sal_Int64 nMin, sal_Int64 nSec, + sal_Int64 nNanoSec) +{ + return (nHour * HOUR_MASK) + (nMin * MIN_MASK) + (nSec * SEC_MASK) + nNanoSec; +} +} + +bool GetSystemDateTime(sal_Int32* pDate, sal_Int64* pTime) +{ +#if defined(_WIN32) + SYSTEMTIME aDateTime; + GetLocalTime(&aDateTime); + + if (pDate) + *pDate = ConvertYMDToInt(static_cast<sal_Int32>(aDateTime.wYear), + static_cast<sal_Int32>(aDateTime.wMonth), + static_cast<sal_Int32>(aDateTime.wDay)); + if (pTime) + *pTime = ConvertHMSnToInt(aDateTime.wHour, aDateTime.wMinute, aDateTime.wSecond, + aDateTime.wMilliseconds * 1000000); + + return true; +#else + struct timespec tsTime; +#if defined(__MACH__) + // macOS does not have clock_gettime, use clock_get_time + clock_serv_t cclock; + mach_timespec_t mts; + host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); + clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + tsTime.tv_sec = mts.tv_sec; + tsTime.tv_nsec = mts.tv_nsec; +#else + // CLOCK_REALTIME should be supported + // on any modern Unix, but be extra cautious + if (clock_gettime(CLOCK_REALTIME, &tsTime) != 0) + { + struct timeval tvTime; + OSL_VERIFY(gettimeofday(&tvTime, nullptr) != 0); + tsTime.tv_sec = tvTime.tv_sec; + tsTime.tv_nsec = tvTime.tv_usec * 1000; + } +#endif + + struct tm aTime; + time_t nTmpTime = tsTime.tv_sec; + if (localtime_r(&nTmpTime, &aTime)) + { + if (pDate) + *pDate = ConvertYMDToInt(static_cast<sal_Int32>(aTime.tm_year + 1900), + static_cast<sal_Int32>(aTime.tm_mon + 1), + static_cast<sal_Int32>(aTime.tm_mday)); + if (pTime) + *pTime = ConvertHMSnToInt(aTime.tm_hour, aTime.tm_min, aTime.tm_sec, tsTime.tv_nsec); + return true; + } + + return false; +#endif +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/tools/source/datetime/tdate.cxx b/tools/source/datetime/tdate.cxx new file mode 100644 index 0000000000..e20add4303 --- /dev/null +++ b/tools/source/datetime/tdate.cxx @@ -0,0 +1,412 @@ +/* -*- 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 <tools/date.hxx> +#include <sal/log.hxx> +#include <com/sun/star/util/DateTime.hpp> + +#include <systemdatetime.hxx> +#include <comphelper/date.hxx> + +namespace +{ + +const sal_Int16 kYearMax = SAL_MAX_INT16; +const sal_Int16 kYearMin = SAL_MIN_INT16; + +} + +void Date::setDateFromDMY( sal_uInt16 nDay, sal_uInt16 nMonth, sal_Int16 nYear ) +{ + // don't warn about 0/0/0, commonly used as a default-value/no-value + SAL_WARN_IF( nYear == 0 && !(nYear == 0 && nMonth == 0 && nDay == 0), + "tools.datetime", "Date::setDateFromDMY - sure about 0 year? It's not in the calendar."); + assert( nMonth < 100 && "nMonth % 100 not representable" ); + assert( nDay < 100 && "nDay % 100 not representable" ); + if (nYear < 0) + mnDate = + (static_cast<sal_Int32>( nYear ) * 10000) - + (static_cast<sal_Int32>( nMonth % 100 ) * 100) - + static_cast<sal_Int32>( nDay % 100 ); + else + mnDate = + (static_cast<sal_Int32>( nYear ) * 10000) + + (static_cast<sal_Int32>( nMonth % 100 ) * 100) + + static_cast<sal_Int32>( nDay % 100 ); +} + +void Date::SetDate( sal_Int32 nNewDate ) +{ + assert( ((nNewDate / 10000) != 0) && "you don't want to set a 0 year, do you?" ); + mnDate = nNewDate; +} + +// static +sal_uInt16 Date::GetDaysInMonth( sal_uInt16 nMonth, sal_Int16 nYear ) +{ + SAL_WARN_IF( nMonth < 1 || 12 < nMonth, "tools.datetime", "Date::GetDaysInMonth - nMonth out of bounds " << nMonth); + if (nMonth < 1) + nMonth = 1; + else if (12 < nMonth) + nMonth = 12; + return comphelper::date::getDaysInMonth( nMonth, nYear); +} + +sal_Int32 Date::GetAsNormalizedDays() const +{ + // This is a very common datum we often calculate from. + if (mnDate == 18991230) // 1899-12-30 + { +#ifndef NDEBUG + static sal_Int32 nDays = DateToDays( GetDay(), GetMonth(), GetYear()); + assert(nDays == 693594); +#endif + return 693594; + } + // Not calling comphelper::date::convertDateToDaysNormalizing() here just + // avoids a second check on null-date handling like above. + sal_uInt16 nDay = GetDay(); + sal_uInt16 nMonth = GetMonth(); + sal_Int16 nYear = GetYear(); + comphelper::date::normalize( nDay, nMonth, nYear); + return comphelper::date::convertDateToDays( nDay, nMonth, nYear); +} + +sal_Int32 Date::DateToDays( sal_uInt16 nDay, sal_uInt16 nMonth, sal_Int16 nYear ) +{ + return comphelper::date::convertDateToDaysNormalizing( nDay, nMonth, nYear); +} + +static Date lcl_DaysToDate( sal_Int32 nDays ) +{ + sal_uInt16 nDay; + sal_uInt16 nMonth; + sal_Int16 nYear; + comphelper::date::convertDaysToDate( nDays, nDay, nMonth, nYear); + return Date( nDay, nMonth, nYear); +} + +Date::Date( DateInitSystem ) +{ + if ( !GetSystemDateTime( &mnDate, nullptr ) ) + setDateFromDMY( 1, 1, 1900 ); +} + +Date::Date( const css::util::DateTime& rDateTime ) +{ + setDateFromDMY( rDateTime.Day, rDateTime.Month, rDateTime.Year ); +} + +void Date::SetDay( sal_uInt16 nNewDay ) +{ + setDateFromDMY( nNewDay, GetMonth(), GetYear() ); +} + +void Date::SetMonth( sal_uInt16 nNewMonth ) +{ + setDateFromDMY( GetDay(), nNewMonth, GetYear() ); +} + +void Date::SetYear( sal_Int16 nNewYear ) +{ + assert( nNewYear != 0 ); + setDateFromDMY( GetDay(), GetMonth(), nNewYear ); +} + +void Date::AddYears( sal_Int16 nAddYears ) +{ + sal_Int16 nYear = GetYear(); + if (nYear < 0) + { + if (nAddYears < 0) + { + if (nYear < kYearMin - nAddYears) + nYear = kYearMin; + else + nYear += nAddYears; + } + else + { + nYear += nAddYears; + if (nYear == 0) + nYear = 1; + } + } + else + { + if (nAddYears > 0) + { + if (kYearMax - nAddYears < nYear) + nYear = kYearMax; + else + nYear += nAddYears; + } + else + { + nYear += nAddYears; + if (nYear == 0) + nYear = -1; + } + } + + SetYear( nYear ); + if (GetMonth() == 2 && GetDay() == 29 && !comphelper::date::isLeapYear( nYear)) + SetDay(28); +} + +void Date::AddMonths( sal_Int32 nAddMonths ) +{ + sal_Int32 nMonths = GetMonth() + nAddMonths; + sal_Int32 nNewMonth = nMonths % 12; + sal_Int32 nYear = GetYear() + nMonths / 12; + if( nMonths <= 0 || nNewMonth == 0 ) + --nYear; + if( nNewMonth <= 0 ) + nNewMonth += 12; + if (nYear == 0) + nYear = (nAddMonths < 0 ? -1 : 1); + else if (nYear < kYearMin) + nYear = kYearMin; + else if (nYear > kYearMax) + nYear = kYearMax; + SetMonth( static_cast<sal_uInt16>(nNewMonth) ); + SetYear( static_cast<sal_Int16>(nYear) ); + Normalize(); +} + +DayOfWeek Date::GetDayOfWeek() const +{ + return static_cast<DayOfWeek>((GetAsNormalizedDays()-1) % 7); +} + +sal_uInt16 Date::GetDayOfYear() const +{ + sal_uInt16 nDay = GetDay(); + sal_uInt16 nMonth = GetMonth(); + sal_Int16 nYear = GetYear(); + Normalize( nDay, nMonth, nYear); + + for( sal_uInt16 i = 1; i < nMonth; i++ ) + nDay += comphelper::date::getDaysInMonth( i, nYear ); + return nDay; +} + +sal_uInt16 Date::GetWeekOfYear( DayOfWeek eStartDay, + sal_Int16 nMinimumNumberOfDaysInWeek ) const +{ + short nWeek; + short n1WDay = static_cast<short>(Date( 1, 1, GetYear() ).GetDayOfWeek()); + short nDayOfYear = static_cast<short>(GetDayOfYear()); + + // weekdays start at 0, thus decrement one + nDayOfYear--; + // account for StartDay + n1WDay = (n1WDay+(7-static_cast<short>(eStartDay))) % 7; + + if (nMinimumNumberOfDaysInWeek < 1 || 7 < nMinimumNumberOfDaysInWeek) + { + SAL_WARN( "tools.datetime", "Date::GetWeekOfYear: invalid nMinimumNumberOfDaysInWeek" ); + nMinimumNumberOfDaysInWeek = 4; + } + + if ( nMinimumNumberOfDaysInWeek == 1 ) + { + nWeek = ((n1WDay+nDayOfYear)/7) + 1; + // Set to 53rd week only if we're not in the + // first week of the new year + if ( nWeek == 54 ) + nWeek = 1; + else if ( nWeek == 53 ) + { + short nDaysInYear = static_cast<short>(GetDaysInYear()); + short nDaysNextYear = static_cast<short>(Date( 1, 1, GetNextYear() ).GetDayOfWeek()); + nDaysNextYear = (nDaysNextYear+(7-static_cast<short>(eStartDay))) % 7; + if ( nDayOfYear > (nDaysInYear-nDaysNextYear-1) ) + nWeek = 1; + } + } + else if ( nMinimumNumberOfDaysInWeek == 7 ) + { + nWeek = ((n1WDay+nDayOfYear)/7); + // First week of a year is equal to the last week of the previous year + if ( nWeek == 0 ) + { + Date aLastDatePrevYear( 31, 12, GetPrevYear() ); + nWeek = aLastDatePrevYear.GetWeekOfYear( eStartDay, nMinimumNumberOfDaysInWeek ); + } + } + else // ( nMinimumNumberOfDaysInWeek == something_else, commentary examples for 4 ) + { + // x_monday - thursday + if ( n1WDay < nMinimumNumberOfDaysInWeek ) + nWeek = 1; + // Friday + else if ( n1WDay == nMinimumNumberOfDaysInWeek ) + nWeek = 53; + // Saturday + else if ( n1WDay == nMinimumNumberOfDaysInWeek + 1 ) + { + // Year after leap year + if ( Date( 1, 1, GetPrevYear() ).IsLeapYear() ) + nWeek = 53; + else + nWeek = 52; + } + // Sunday + else + nWeek = 52; + + if ( (nWeek == 1) || (nDayOfYear + n1WDay > 6) ) + { + if ( nWeek == 1 ) + nWeek += (nDayOfYear + n1WDay) / 7; + else + nWeek = (nDayOfYear + n1WDay) / 7; + if ( nWeek == 53 ) + { + // next x_Sunday == first x_Sunday in the new year + // == still the same week! + sal_Int32 nTempDays = GetAsNormalizedDays(); + + nTempDays += 6 - (GetDayOfWeek()+(7-static_cast<short>(eStartDay))) % 7; + nWeek = lcl_DaysToDate( nTempDays ).GetWeekOfYear( eStartDay, nMinimumNumberOfDaysInWeek ); + } + } + } + + return static_cast<sal_uInt16>(nWeek); +} + +sal_uInt16 Date::GetDaysInMonth() const +{ + sal_uInt16 nDay = GetDay(); + sal_uInt16 nMonth = GetMonth(); + sal_Int16 nYear = GetYear(); + Normalize( nDay, nMonth, nYear); + + return comphelper::date::getDaysInMonth( nMonth, nYear ); +} + +bool Date::IsLeapYear() const +{ + sal_Int16 nYear = GetYear(); + return comphelper::date::isLeapYear( nYear ); +} + +bool Date::IsValidAndGregorian() const +{ + sal_uInt16 nDay = GetDay(); + sal_uInt16 nMonth = GetMonth(); + sal_Int16 nYear = GetYear(); + + if ( !nMonth || (nMonth > 12) ) + return false; + if ( !nDay || (nDay > comphelper::date::getDaysInMonth( nMonth, nYear )) ) + return false; + else if ( nYear <= 1582 ) + { + if ( nYear < 1582 ) + return false; + else if ( nMonth < 10 ) + return false; + else if ( (nMonth == 10) && (nDay < 15) ) + return false; + } + + return true; +} + +bool Date::IsValidDate() const +{ + return comphelper::date::isValidDate( GetDay(), GetMonth(), GetYear()); +} + +//static +bool Date::IsValidDate( sal_uInt16 nDay, sal_uInt16 nMonth, sal_Int16 nYear ) +{ + return comphelper::date::isValidDate( nDay, nMonth, nYear); +} + +bool Date::IsEndOfMonth() const +{ + return IsEndOfMonth(GetDay(), GetMonth(), GetYear()); +} + +//static +bool Date::IsEndOfMonth(sal_uInt16 nDay, sal_uInt16 nMonth, sal_Int16 nYear) +{ + return comphelper::date::isValidDate(nDay, nMonth, nYear) + && comphelper::date::getDaysInMonth(nMonth, nYear) == nDay; +} + +void Date::Normalize() +{ + sal_uInt16 nDay = GetDay(); + sal_uInt16 nMonth = GetMonth(); + sal_Int16 nYear = GetYear(); + + if (!Normalize( nDay, nMonth, nYear)) + return; + + setDateFromDMY( nDay, nMonth, nYear ); +} + +//static +bool Date::Normalize( sal_uInt16 & rDay, sal_uInt16 & rMonth, sal_Int16 & rYear ) +{ + return comphelper::date::normalize( rDay, rMonth, rYear); +} + +void Date::AddDays( sal_Int32 nDays ) +{ + if (nDays != 0) + *this = lcl_DaysToDate( GetAsNormalizedDays() + nDays ); +} + +Date& Date::operator ++() +{ + *this = lcl_DaysToDate( GetAsNormalizedDays() + 1 ); + return *this; +} + +Date& Date::operator --() +{ + *this = lcl_DaysToDate( GetAsNormalizedDays() - 1 ); + return *this; +} + +Date operator +( const Date& rDate, sal_Int32 nDays ) +{ + Date aDate( rDate ); + aDate.AddDays( nDays ); + return aDate; +} + +Date operator -( const Date& rDate, sal_Int32 nDays ) +{ + Date aDate( rDate ); + aDate.AddDays( -nDays ); + return aDate; +} + +sal_Int32 operator -( const Date& rDate1, const Date& rDate2 ) +{ + return rDate1.GetAsNormalizedDays() - rDate2.GetAsNormalizedDays(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/datetime/ttime.cxx b/tools/source/datetime/ttime.cxx new file mode 100644 index 0000000000..fcfa2e080e --- /dev/null +++ b/tools/source/datetime/ttime.cxx @@ -0,0 +1,506 @@ +/* -*- 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 <algorithm> + +#if defined(_WIN32) +#if !defined WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> +#include <mmsystem.h> +#elif defined UNX +#include <sys/time.h> +#include <unistd.h> +#endif + +#include <time.h> +#ifdef __MACH__ +#include <mach/clock.h> +#include <mach/mach.h> +#include <mach/mach_time.h> +#endif + +#include <rtl/math.hxx> +#include <tools/time.hxx> +#include <com/sun/star/util/DateTime.hpp> + +#include <systemdatetime.hxx> + +#if defined(__sun) && defined(__GNUC__) +extern long altzone; +#endif + +namespace { + + const sal_Int64 nanoSecInSec = 1000000000; + const sal_Int16 secInMin = 60; + const sal_Int16 minInHour = 60; + + sal_Int64 TimeToNanoSec( const tools::Time& rTime ) + { + short nSign = (rTime.GetTime() >= 0) ? +1 : -1; + sal_Int32 nHour = rTime.GetHour(); + sal_Int32 nMin = rTime.GetMin(); + sal_Int32 nSec = rTime.GetSec(); + sal_Int32 nNanoSec = rTime.GetNanoSec(); + + sal_Int64 nRet = nNanoSec; + nRet += nSec * nanoSecInSec; + nRet += nMin * secInMin * nanoSecInSec; + nRet += nHour * minInHour * secInMin * nanoSecInSec; + + return (nRet * nSign); + } + + tools::Time NanoSecToTime( sal_Int64 nNanoSec ) + { + short nSign; + if ( nNanoSec < 0 ) + { + nNanoSec *= -1; + nSign = -1; + } + else + nSign = 1; + + tools::Time aTime( 0, 0, 0, nNanoSec ); + aTime.SetTime( aTime.GetTime() * nSign ); + return aTime; + } + +} // anonymous namespace + +namespace tools { + +Time::Time( TimeInitSystem ) +{ + if ( !GetSystemDateTime( nullptr, &nTime ) ) + nTime = 0; +} + +Time::Time( const tools::Time& rTime ) +{ + nTime = rTime.nTime; +} + +Time::Time( sal_uInt32 nHour, sal_uInt32 nMin, sal_uInt32 nSec, sal_uInt64 nNanoSec ) +{ + init(nHour, nMin, nSec, nNanoSec); +} +Time::Time( const css::util::Time &_rTime ) +{ + init(_rTime.Hours, _rTime.Minutes, _rTime.Seconds, _rTime.NanoSeconds); +} +Time::Time( const css::util::DateTime &_rDateTime ) +{ + init(_rDateTime.Hours, _rDateTime.Minutes, _rDateTime.Seconds, _rDateTime.NanoSeconds); +} + +void tools::Time::init( sal_uInt32 nHour, sal_uInt32 nMin, sal_uInt32 nSec, sal_uInt64 nNanoSec ) +{ + // normalize time + nSec += nNanoSec / nanoSecInSec; + nNanoSec %= nanoSecInSec; + nMin += nSec / secInMin; + nSec %= secInMin; + nHour += nMin / minInHour; + nMin %= minInHour; + + // 922337 * HOUR_MASK = 9223370000000000000 largest possible value, 922338 + // would be -9223364073709551616. + assert(HOUR_MASK * nHour >= 0 && "use tools::Duration with days instead!"); + if (HOUR_MASK * nHour < 0) + nHour = 922337; + + // But as is, GetHour() retrieves only sal_uInt16. Though retrieving in + // nanoseconds or milliseconds might be possible this is all crap. + assert(nHour <= SAL_MAX_UINT16 && "use tools::Duration with days instead!"); + if (nHour > SAL_MAX_UINT16) + nHour = SAL_MAX_UINT16; + + // construct time + nTime = nNanoSec + + nSec * SEC_MASK + + nMin * MIN_MASK + + nHour * HOUR_MASK; +} + +void tools::Time::SetHour( sal_uInt16 nNewHour ) +{ + short nSign = (nTime >= 0) ? +1 : -1; + sal_Int32 nMin = GetMin(); + sal_Int32 nSec = GetSec(); + sal_Int32 nNanoSec = GetNanoSec(); + + nTime = nSign * + ( nNanoSec + + nSec * SEC_MASK + + nMin * MIN_MASK + + nNewHour * HOUR_MASK ); +} + +void tools::Time::SetMin( sal_uInt16 nNewMin ) +{ + short nSign = (nTime >= 0) ? +1 : -1; + sal_Int32 nHour = GetHour(); + sal_Int32 nSec = GetSec(); + sal_Int32 nNanoSec = GetNanoSec(); + + // no overflow + nNewMin = nNewMin % minInHour; + + nTime = nSign * + ( nNanoSec + + nSec * SEC_MASK + + nNewMin * MIN_MASK + + nHour * HOUR_MASK ); +} + +void tools::Time::SetSec( sal_uInt16 nNewSec ) +{ + short nSign = (nTime >= 0) ? +1 : -1; + sal_Int32 nHour = GetHour(); + sal_Int32 nMin = GetMin(); + sal_Int32 nNanoSec = GetNanoSec(); + + // no overflow + nNewSec = nNewSec % secInMin; + + nTime = nSign * + ( nNanoSec + + nNewSec * SEC_MASK + + nMin * MIN_MASK + + nHour * HOUR_MASK ); +} + +void tools::Time::SetNanoSec( sal_uInt32 nNewNanoSec ) +{ + short nSign = (nTime >= 0) ? +1 : -1; + sal_Int32 nHour = GetHour(); + sal_Int32 nMin = GetMin(); + sal_Int32 nSec = GetSec(); + + // no overflow + nNewNanoSec = nNewNanoSec % nanoSecInSec; + + nTime = nSign * + ( nNewNanoSec + + nSec * SEC_MASK + + nMin * MIN_MASK + + nHour * HOUR_MASK ); +} + +sal_Int64 tools::Time::GetNSFromTime() const +{ + short nSign = (nTime >= 0) ? +1 : -1; + sal_Int32 nHour = GetHour(); + sal_Int32 nMin = GetMin(); + sal_Int32 nSec = GetSec(); + sal_Int32 nNanoSec = GetNanoSec(); + + return nSign * + ( nNanoSec + + nSec * nanoSecInSec + + nMin * (secInMin * nanoSecInSec) + + nHour * (minInHour * secInMin * nanoSecInSec) ); +} + +void tools::Time::MakeTimeFromNS( sal_Int64 nNS ) +{ + short nSign; + if ( nNS < 0 ) + { + nNS *= -1; + nSign = -1; + } + else + nSign = 1; + + // avoid overflow when sal_uIntPtr is 32 bits + tools::Time aTime( 0, 0, nNS/nanoSecInSec, nNS % nanoSecInSec ); + SetTime( aTime.GetTime() * nSign ); +} + +sal_Int32 tools::Time::GetMSFromTime() const +{ + short nSign = (nTime >= 0) ? +1 : -1; + sal_Int32 nHour = GetHour(); + sal_Int32 nMin = GetMin(); + sal_Int32 nSec = GetSec(); + sal_Int32 nNanoSec = GetNanoSec(); + + return nSign * + ( nNanoSec/1000000 + + nSec * 1000 + + nMin * 60000 + + nHour * 3600000 ); +} + +void tools::Time::MakeTimeFromMS( sal_Int32 nMS ) +{ + short nSign; + if ( nMS < 0 ) + { + nMS *= -1; + nSign = -1; + } + else + nSign = 1; + + // avoid overflow when sal_uIntPtr is 32 bits + tools::Time aTime( 0, 0, nMS/1000, (nMS % 1000) * 1000000 ); + SetTime( aTime.GetTime() * nSign ); +} + +double tools::Time::GetTimeInDays() const +{ + short nSign = (nTime >= 0) ? +1 : -1; + double nHour = GetHour(); + double nMin = GetMin(); + double nSec = GetSec(); + double nNanoSec = GetNanoSec(); + + return (nHour + (nMin / 60) + (nSec / (minInHour * secInMin)) + (nNanoSec / (minInHour * secInMin * nanoSecInSec))) / 24 * nSign; +} + +// static +void tools::Time::GetClock( double fTimeInDays, + sal_uInt16& nHour, sal_uInt16& nMinute, sal_uInt16& nSecond, + double& fFractionOfSecond, int nFractionDecimals ) +{ + const double fTime = fTimeInDays - rtl::math::approxFloor(fTimeInDays); // date part absent + + // If 0 then full day (or no day), shortcut. + // If < 0 then approxFloor() effectively returned the ceiling (note this + // also holds for negative fTimeInDays values) because of a near identical + // value, shortcut this to a full day as well. + // If >= 1.0 (actually == 1.0) then fTimeInDays is a negative small value + // not significant for a representable time and approxFloor() returned -1, + // shortcut to 0:0:0, otherwise it would become 24:0:0. + if (fTime <= 0.0 || fTime >= 1.0) + { + nHour = nMinute = nSecond = 0; + fFractionOfSecond = 0.0; + return; + } + + // In seconds, including milli and nano. + const double fRawSeconds = fTime * tools::Time::secondPerDay; + + // Round to nanoseconds most, which is the highest resolution this could be + // influenced by, but if the original value included a date round to at + // most 14 significant digits (including adding 4 for *86400), otherwise we + // might end up with a fake precision of h:m:s.999999986 which in fact + // should had been h:m:s+1 + // BUT, leave at least 2 decimals to round. Which shouldn't be a problem in + // practice because class Date can calculate only 8-digit days for it's + // sal_Int16 year range, which exactly leaves us with 14-4-8=2. + int nDec = 9; + const double fAbsTimeInDays = fabs( fTimeInDays); + if (fAbsTimeInDays >= 1.0) + { + const int nDig = static_cast<int>(ceil( log10( fAbsTimeInDays))); + nDec = std::clamp( 10 - nDig, 2, 9 ); + } + double fSeconds = rtl::math::round( fRawSeconds, nDec); + + // If this ended up as a full day the original value was very very close + // but not quite. Take that. + if (fSeconds >= tools::Time::secondPerDay) + fSeconds = fRawSeconds; + + // Now do not round values (specifically not up), but truncate to the next + // magnitude, so 23:59:59.99 is still 23:59:59 and not 24:00:00 (or even + // 00:00:00 which Excel does). + nHour = fSeconds / tools::Time::secondPerHour; + fSeconds -= nHour * tools::Time::secondPerHour; + nMinute = fSeconds / tools::Time::secondPerMinute; + fSeconds -= nMinute * tools::Time::secondPerMinute; + nSecond = fSeconds; + fSeconds -= nSecond; + + assert(fSeconds < 1.0); // or back to the drawing board... + + if (nFractionDecimals > 0) + { + // Do not simply round the fraction, otherwise .999 would end up as .00 + // again. Truncate instead if rounding would round up into an integer + // value. + fFractionOfSecond = rtl::math::round( fSeconds, nFractionDecimals); + if (fFractionOfSecond >= 1.0) + fFractionOfSecond = rtl::math::pow10Exp( std::trunc( + rtl::math::pow10Exp( fSeconds, nFractionDecimals)), -nFractionDecimals); + } + else + fFractionOfSecond = fSeconds; +} + +Time& tools::Time::operator =( const tools::Time& rTime ) +{ + nTime = rTime.nTime; + return *this; +} + +Time& tools::Time::operator +=( const tools::Time& rTime ) +{ + nTime = NanoSecToTime( TimeToNanoSec( *this ) + + TimeToNanoSec( rTime ) ).GetTime(); + return *this; +} + +Time& tools::Time::operator -=( const tools::Time& rTime ) +{ + nTime = NanoSecToTime( TimeToNanoSec( *this ) - + TimeToNanoSec( rTime ) ).GetTime(); + return *this; +} + +Time operator +( const tools::Time& rTime1, const tools::Time& rTime2 ) +{ + return NanoSecToTime( TimeToNanoSec( rTime1 ) + + TimeToNanoSec( rTime2 ) ); +} + +Time operator -( const tools::Time& rTime1, const tools::Time& rTime2 ) +{ + return NanoSecToTime( TimeToNanoSec( rTime1 ) - + TimeToNanoSec( rTime2 ) ); +} + +bool tools::Time::IsEqualIgnoreNanoSec( const tools::Time& rTime ) const +{ + sal_Int32 n1 = (nTime < 0 ? -static_cast<sal_Int32>(GetNanoSec()) : GetNanoSec() ); + sal_Int32 n2 = (rTime.nTime < 0 ? -static_cast<sal_Int32>(rTime.GetNanoSec()) : rTime.GetNanoSec() ); + return (nTime - n1) == (rTime.nTime - n2); +} + +Time tools::Time::GetUTCOffset() +{ +#if defined(_WIN32) + TIME_ZONE_INFORMATION aTimeZone; + aTimeZone.Bias = 0; + DWORD nTimeZoneRet = GetTimeZoneInformation( &aTimeZone ); + sal_Int32 nTempTime = aTimeZone.Bias; + if ( nTimeZoneRet == TIME_ZONE_ID_STANDARD ) + nTempTime += aTimeZone.StandardBias; + else if ( nTimeZoneRet == TIME_ZONE_ID_DAYLIGHT ) + nTempTime += aTimeZone.DaylightBias; + tools::Time aTime( 0, static_cast<sal_uInt16>(abs( nTempTime )) ); + if ( nTempTime > 0 ) + aTime = -aTime; + return aTime; +#else + static sal_uInt64 nCacheTicks = 0; + static sal_Int32 nCacheSecOffset = -1; + sal_uInt64 nTicks = tools::Time::GetSystemTicks(); + time_t nTime; + tm aTM; + short nTempTime; + + // determine value again if needed + if ( (nCacheSecOffset == -1) || + ((nTicks - nCacheTicks) > 360000) || + ( nTicks < nCacheTicks ) // handle overflow + ) + { + nTime = time( nullptr ); + localtime_r( &nTime, &aTM ); + auto nLocalTime = mktime( &aTM ); +#if defined(__sun) + // Solaris gmtime_r() seems not to handle daylight saving time + // flags correctly + auto nUTC = nLocalTime + ( aTM.tm_isdst == 0 ? timezone : altzone ); +#elif defined( LINUX ) + // Linux mktime() seems not to handle tm_isdst correctly + auto nUTC = nLocalTime - aTM.tm_gmtoff; +#else + gmtime_r( &nTime, &aTM ); + auto nUTC = mktime( &aTM ); +#endif + nCacheTicks = nTicks; + nCacheSecOffset = (nLocalTime-nUTC) / 60; + } + + nTempTime = abs( nCacheSecOffset ); + tools::Time aTime( 0, static_cast<sal_uInt16>(nTempTime) ); + if ( nCacheSecOffset < 0 ) + aTime = -aTime; + return aTime; +#endif +} + +sal_uInt64 tools::Time::GetSystemTicks() +{ + return tools::Time::GetMonotonicTicks() / 1000; +} + +#ifdef _WIN32 +static LARGE_INTEGER initPerformanceFrequency() +{ + LARGE_INTEGER nTicksPerSecond = { 0, 0 }; + if (!QueryPerformanceFrequency(&nTicksPerSecond)) + nTicksPerSecond.QuadPart = 0; + return nTicksPerSecond; +} +#endif + +sal_uInt64 tools::Time::GetMonotonicTicks() +{ +#ifdef _WIN32 + static const LARGE_INTEGER nTicksPerSecond = initPerformanceFrequency(); + if (nTicksPerSecond.QuadPart > 0) + { + LARGE_INTEGER nPerformanceCount; + QueryPerformanceCounter(&nPerformanceCount); + return static_cast<sal_uInt64>( + ( nPerformanceCount.QuadPart * 1000 * 1000 ) / nTicksPerSecond.QuadPart ); + } + else + { + return static_cast<sal_uInt64>( timeGetTime() * 1000 ); + } +#else + sal_uInt64 nMicroSeconds; +#ifdef __MACH__ + static mach_timebase_info_data_t info = { 0, 0 }; + if ( 0 == info.numer ) + mach_timebase_info( &info ); + nMicroSeconds = mach_absolute_time() * static_cast<double>(info.numer / info.denom) / 1000; +#else +#if defined(_POSIX_TIMERS) + struct timespec currentTime; + clock_gettime( CLOCK_MONOTONIC, ¤tTime ); + nMicroSeconds + = static_cast<sal_uInt64>(currentTime.tv_sec) * 1000 * 1000 + currentTime.tv_nsec / 1000; +#else + struct timeval currentTime; + gettimeofday( ¤tTime, nullptr ); + nMicroSeconds = static_cast<sal_uInt64>(currentTime.tv_sec) * 1000 * 1000 + currentTime.tv_usec; +#endif +#endif // __MACH__ + return nMicroSeconds; +#endif // _WIN32 +} + +} /* namespace tools */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/debug/debug.cxx b/tools/source/debug/debug.cxx new file mode 100644 index 0000000000..197ba24504 --- /dev/null +++ b/tools/source/debug/debug.cxx @@ -0,0 +1,57 @@ +/* -*- 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 <tools/debug.hxx> +#include <sal/log.hxx> + +namespace { + +struct DebugData +{ + DbgTestSolarMutexProc pDbgTestSolarMutex; + bool bTestSolarMutexWasSet; + + DebugData() + :pDbgTestSolarMutex( nullptr ), bTestSolarMutexWasSet(false) + { + } +}; + +} + +static DebugData aDebugData; + +void DbgSetTestSolarMutex( DbgTestSolarMutexProc pParam ) +{ + aDebugData.pDbgTestSolarMutex = pParam; + if (pParam) + aDebugData.bTestSolarMutexWasSet = true; +} + +void DbgTestSolarMutex() +{ + // don't warn if it was set at least once, because then we're probably just post-DeInitVCL() + SAL_WARN_IF( + !aDebugData.bTestSolarMutexWasSet && aDebugData.pDbgTestSolarMutex == nullptr, "tools.debug", + "no DbgTestSolarMutex function set"); + if ( aDebugData.pDbgTestSolarMutex ) + aDebugData.pDbgTestSolarMutex(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/fsys/fileutil.cxx b/tools/source/fsys/fileutil.cxx new file mode 100644 index 0000000000..0e3512e5a1 --- /dev/null +++ b/tools/source/fsys/fileutil.cxx @@ -0,0 +1,100 @@ +/* -*- 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 <tools/fileutil.hxx> +#if defined _WIN32 +#include <osl/file.hxx> +#include <rtl/uri.hxx> +#include <o3tl/char16_t2wchar_t.hxx> +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> +#include <davclnt.h> +#endif + +namespace +{ +#if defined _WIN32 +OUString UNCToDavURL(LPCWSTR sUNC) +{ + DWORD nSize = 1024; + auto bufURL(std::make_unique<wchar_t[]>(nSize)); + DWORD nResult = DavGetHTTPFromUNCPath(sUNC, bufURL.get(), &nSize); + if (nResult == ERROR_INSUFFICIENT_BUFFER) + { + bufURL = std::make_unique<wchar_t[]>(nSize); + nResult = DavGetHTTPFromUNCPath(sUNC, bufURL.get(), &nSize); + } + // looks like on different Windowses this may or may not be URL encoded? + return nResult == ERROR_SUCCESS + ? ::rtl::Uri::encode(OUString(o3tl::toU(bufURL.get())), rtl_UriCharClassUric, + rtl_UriEncodeKeepEscapes, RTL_TEXTENCODING_UTF8) + : OUString(); +} +#endif +} + +namespace tools +{ +bool IsMappedWebDAVPath([[maybe_unused]] const OUString& rURL, [[maybe_unused]] OUString* pRealURL) +{ +#if defined _WIN32 + if (rURL.startsWithIgnoreAsciiCase("file:")) + { + OUString aSystemPath; + if (osl::FileBase::getSystemPathFromFileURL(rURL, aSystemPath) == osl::FileBase::E_None) + { + DWORD nSize = MAX_PATH; + auto bufUNC(std::make_unique<char[]>(nSize)); + DWORD nResult = WNetGetUniversalNameW(o3tl::toW(aSystemPath.getStr()), + UNIVERSAL_NAME_INFO_LEVEL, bufUNC.get(), &nSize); + if (nResult == ERROR_MORE_DATA) + { + bufUNC = std::make_unique<char[]>(nSize); + nResult = WNetGetUniversalNameW(o3tl::toW(aSystemPath.getStr()), + UNIVERSAL_NAME_INFO_LEVEL, bufUNC.get(), &nSize); + } + if (nResult == NO_ERROR || nResult == ERROR_BAD_DEVICE) + { + NETRESOURCEW aReq{}; + if (nResult == ERROR_BAD_DEVICE) // The path could already be an UNC + aReq.lpRemoteName = const_cast<LPWSTR>(o3tl::toW(aSystemPath.getStr())); + else + { + auto pInfo = reinterpret_cast<LPUNIVERSAL_NAME_INFOW>(bufUNC.get()); + aReq.lpRemoteName = pInfo->lpUniversalName; + } + nSize = 1024; + auto bufInfo(std::make_unique<char[]>(nSize)); + LPWSTR pSystem = nullptr; + nResult = WNetGetResourceInformationW(&aReq, bufInfo.get(), &nSize, &pSystem); + if (nResult == ERROR_MORE_DATA) + { + bufInfo = std::make_unique<char[]>(nSize); + nResult = WNetGetResourceInformationW(&aReq, bufInfo.get(), &nSize, &pSystem); + } + if (nResult == NO_ERROR) + { + LPNETRESOURCEW pInfo = reinterpret_cast<LPNETRESOURCEW>(bufInfo.get()); + if (wcscmp(pInfo->lpProvider, L"Web Client Network") == 0) + { + if (pRealURL) + *pRealURL = UNCToDavURL(aReq.lpRemoteName); + return true; + } + } + } + } + } +#endif + return false; +} + +} // namespace tools + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/fsys/urlobj.cxx b/tools/source/fsys/urlobj.cxx new file mode 100644 index 0000000000..18ee57b18e --- /dev/null +++ b/tools/source/fsys/urlobj.cxx @@ -0,0 +1,4892 @@ +/* -*- 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 <tools/urlobj.hxx> +#include <tools/debug.hxx> +#include <tools/inetmime.hxx> +#include <tools/stream.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/util/XStringWidth.hpp> +#include <o3tl/enumarray.hxx> +#include <osl/diagnose.h> +#include <osl/file.hxx> +#include <rtl/character.hxx> +#include <rtl/string.h> +#include <rtl/textenc.h> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <sal/types.h> + +#include <algorithm> +#include <cassert> +#include <limits> +#include <memory> +#include <string_view> + +#include <string.h> + +#include <com/sun/star/uno/Sequence.hxx> +#include <comphelper/base64.hxx> +#include <comphelper/string.hxx> + +using namespace css; + +// INetURLObject + +/* The URI grammar (using RFC 2234 conventions). + + Constructs of the form + {reference <rule1> using rule2} + stand for a rule matching the given rule1 specified in the given reference, + encoded to URI syntax using rule2 (as specified in this URI grammar). + + + ; RFC 1738, RFC 2396, RFC 2732, private + login = [user [":" password] "@"] hostport + user = *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / ";" / "=" / "_" / "~") + password = *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / ";" / "=" / "_" / "~") + hostport = host [":" port] + host = incomplete-hostname / hostname / IPv4address / IPv6reference + incomplete-hostname = *(domainlabel ".") domainlabel + hostname = *(domainlabel ".") toplabel ["."] + domainlabel = alphanum [*(alphanum / "-") alphanum] + toplabel = ALPHA [*(alphanum / "-") alphanum] + IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT + IPv6reference = "[" hexpart [":" IPv4address] "]" + hexpart = (hexseq ["::" [hexseq]]) / ("::" [hexseq]) + hexseq = hex4 *(":" hex4) + hex4 = 1*4HEXDIG + port = *DIGIT + escaped = "%" HEXDIG HEXDIG + reserved = "$" / "&" / "+" / "," / "/" / ":" / ";" / "=" / "?" / "@" / "[" / "]" + mark = "!" / "'" / "(" / ")" / "*" / "-" / "." / "_" / "~" + alphanum = ALPHA / DIGIT + unreserved = alphanum / mark + uric = escaped / reserved / unreserved + pchar = escaped / unreserved / "$" / "&" / "+" / "," / ":" / "=" / "@" + + + ; RFC 1738, RFC 2396 + ftp-url = "FTP://" login ["/" segment *("/" segment) [";TYPE=" ("A" / "D" / "I")]] + segment = *pchar + + + ; RFC 1738, RFC 2396 + http-url = "HTTP://" hostport ["/" segment *("/" segment) ["?" *uric]] + segment = *(pchar / ";") + + + ; RFC 1738, RFC 2396, <http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q188997&> + file-url = "FILE://" [host / "LOCALHOST" / netbios-name] ["/" segment *("/" segment)] + segment = *pchar + netbios-name = 1*{<alphanum / "!" / "#" / "$" / "%" / "&" / "'" / "(" / ")" / "-" / "." / "@" / "^" / "_" / "{" / "}" / "~"> using (escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "-" / "." / "@" / "_" / "~")} + + + ; RFC 2368, RFC 2396 + mailto-url = "MAILTO:" [to] [headers] + to = {RFC 822 <#mailbox> using *(escaped / alphanum / "!" / "$" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / "/" / ":" / ";" / "@" / "_" / "~")} + headers = "?" header *("&" header) + header = hname "=" hvalue + hname = {RFC 822 <field-name> using *(escaped / alphanum / "!" / "$" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / "/" / ":" / ";" / "@" / "_" / "~")} / "BODY" + hvalue = {RFC 822 <field-body> using *(escaped / alphanum / "!" / "$" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / "/" / ":" / ";" / "@" / "_" / "~")} + + + ; private (see RFC 1738, RFC 2396) + vnd-sun-star-webdav-url = "VND.SUN.STAR.WEBDAV://" hostport ["/" segment *("/" segment) ["?" *uric]] + segment = *(pchar / ";") + + + ; private + private-url = "PRIVATE:" path ["?" *uric] + path = *(escaped / alphanum / "!" / "$" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / "/" / ":" / ";" / "=" / "@" / "_" / "~") + + + ; private + vnd-sun-star-help-url = "VND.SUN.STAR.HELP://" name *("/" segment) ["?" *uric] + name = *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / ":" / ";" / "=" / "@" / "_" / "~") + segment = *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / ":" / ";" / "=" / "@" / "_" / "~") + + + ; private + https-url = "HTTPS://" hostport ["/" segment *("/" segment) ["?" *uric]] + segment = *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / ":" / ";" / "=" / "@" / "_" / "~") + + + ; private + slot-url = "SLOT:" path ["?" *uric] + path = *(escaped / alphanum / "!" / "$" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / "/" / ":" / ";" / "=" / "@" / "_" / "~") + + + ; private + macro-url = "MACRO:" path ["?" *uric] + path = *(escaped / alphanum / "!" / "$" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / "/" / ":" / ";" / "=" / "@" / "_" / "~") + + + ; private + javascript-url = "JAVASCRIPT:" *uric + + + ; RFC 2397 + data-url = "DATA:" [mediatype] [";BASE64"] "," *uric + mediatype = [type "/" subtype] *(";" attribute "=" value) + type = {RFC 2045 <type> using *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "-" / "." / ":" / "?" / "@" / "_" / "~")} + subtype = {RFC 2045 <subtype> using *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "-" / "." / ":" / "?" / "@" / "_" / "~")} + attribute = {RFC 2045 <subtype> using *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "-" / "." / ":" / "?" / "@" / "_" / "~")} + value = {RFC 2045 <subtype> using *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "-" / "." / ":" / "?" / "@" / "_" / "~")} + + + ; RFC 2392, RFC 2396 + cid-url = "CID:" {RFC 822 <addr-spec> using *uric} + + + ; private + vnd-sun-star-hier-url = "VND.SUN.STAR.HIER:" ["//"reg_name] *("/" *pchar) + reg_name = 1*(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / ":" / ";" / "=" / "@" / "_" / "~") + + + ; private + uno-url = ".UNO:" path ["?" *uric] + path = *(escaped / alphanum / "!" / "$" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / "/" / ":" / ";" / "=" / "@" / "_" / "~") + + + ; private + component-url = ".COMPONENT:" path ["?" *uric] + path = *(escaped / alphanum / "!" / "$" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / "/" / ":" / ";" / "=" / "@" / "_" / "~") + + + ; private + vnd-sun-star-pkg-url = "VND.SUN.STAR.PKG://" reg_name *("/" *pchar) ["?" *uric] + reg_name = 1*(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / ":" / ";" / "=" / "@" / "_" / "~") + + + ; RFC 2255 + ldap-url = "LDAP://" [hostport] ["/" [dn ["?" [attrdesct *("," attrdesc)] ["?" ["base" / "one" / "sub"] ["?" [filter] ["?" extension *("," extension)]]]]]] + dn = {RFC 2253 <distinguishedName> using *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / "/" / ":" / ";" / "=" / "@" / "_" / "~")} + attrdesc = {RFC 2251 <AttributeDescription> using *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "-" / "." / "/" / ":" / ";" / "=" / "@" / "_" / "~")} + filter = {RFC 2254 <filter> using *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / "/" / ":" / ";" / "=" / "@" / "_" / "~")} + extension = ["!"] ["X-"] extoken ["=" exvalue] + extoken = {RFC 2252 <oid> using *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "-" / "." / "/" / ":" / ";" / "@" / "_" / "~")} + exvalue = {RFC 2251 <LDAPString> using *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "-" / "." / "/" / ":" / ";" / "=" / "@" / "_" / "~")} + + + ; private + db-url = "DB:" *uric + + + ; private + vnd-sun-star-cmd-url = "VND.SUN.STAR.CMD:" opaque_part + opaque_part = uric_no_slash *uric + uric_no_slash = unreserved / escaped / ";" / "?" / ":" / "@" / "&" / "=" / "+" / "$" / "," + + + ; RFC 1738 + telnet-url = "TELNET://" login ["/"] + + + ; private + vnd-sun-star-expand-url = "VND.SUN.STAR.EXPAND:" opaque_part + opaque_part = uric_no_slash *uric + uric_no_slash = unreserved / escaped / ";" / "?" / ":" / "@" / "&" / "=" / "+" / "$" / "," + + + ; private + vnd-sun-star-tdoc-url = "VND.SUN.STAR.TDOC:/" segment *("/" segment) + segment = *pchar + + + ; private + unknown-url = scheme ":" 1*uric + scheme = ALPHA *(alphanum / "+" / "-" / ".") + + + ; private (http://ubiqx.org/cifs/Appendix-D.html): + smb-url = "SMB://" login ["/" segment *("/" segment) ["?" *uric]] + segment = *(pchar / ";") + */ + +sal_Int32 INetURLObject::SubString::clear() +{ + sal_Int32 nDelta = -m_nLength; + m_nBegin = -1; + m_nLength = 0; + return nDelta; +} + +sal_Int32 INetURLObject::SubString::set(OUStringBuffer & rString, + std::u16string_view rSubString) +{ + sal_Int32 nDelta = rSubString.size() - m_nLength; + + rString.remove(m_nBegin, m_nLength); + rString.insert(m_nBegin, rSubString); + + m_nLength = rSubString.size(); + return nDelta; +} + +sal_Int32 INetURLObject::SubString::set(OUString & rString, + std::u16string_view rSubString) +{ + sal_Int32 nDelta = rSubString.size() - m_nLength; + + rString = OUString::Concat(rString.subView(0, m_nBegin)) + + rSubString + rString.subView(m_nBegin + m_nLength); + + m_nLength = rSubString.size(); + return nDelta; +} + +sal_Int32 INetURLObject::SubString::set(OUStringBuffer & rString, + std::u16string_view rSubString, + sal_Int32 nTheBegin) +{ + m_nBegin = nTheBegin; + return set(rString, rSubString); +} + +inline void INetURLObject::SubString::operator +=(sal_Int32 nDelta) +{ + if (isPresent()) + m_nBegin = m_nBegin + nDelta; +} + +int INetURLObject::SubString::compare(SubString const & rOther, + OUStringBuffer const & rThisString, + OUStringBuffer const & rOtherString) const +{ + sal_Int32 len = std::min(m_nLength, rOther.m_nLength); + sal_Unicode const * p1 = rThisString.getStr() + m_nBegin; + sal_Unicode const * end = p1 + len; + sal_Unicode const * p2 = rOtherString.getStr() + rOther.m_nBegin; + while (p1 != end) { + if (*p1 < *p2) { + return -1; + } else if (*p1 > *p2) { + return 1; + } + ++p1; + ++p2; + } + return m_nLength < rOther.m_nLength ? -1 + : m_nLength > rOther.m_nLength ? 1 + : 0; +} + +struct INetURLObject::SchemeInfo +{ + OUString m_sScheme; + char const * m_pPrefix; + bool m_bAuthority; + bool m_bUser; + bool m_bAuth; + bool m_bPassword; + bool m_bHost; + bool m_bPort; + bool m_bHierarchical; + bool m_bQuery; +}; + +struct INetURLObject::PrefixInfo +{ + enum class Kind { Official, Internal, External }; // order is important! + + char const * m_pPrefix; + char const * m_pTranslatedPrefix; + INetProtocol m_eScheme; + Kind m_eKind; +}; + +// static +inline INetURLObject::SchemeInfo const & +INetURLObject::getSchemeInfo(INetProtocol eTheScheme) +{ + static constexpr OUString EMPTY = u""_ustr; + static constexpr OUString FTP = u"ftp"_ustr; + static constexpr OUString HTTP = u"http"_ustr; + static constexpr OUString FILE1 = u"file"_ustr; // because FILE is already defined + static constexpr OUString MAILTO = u"mailto"_ustr; + static constexpr OUString VND_WEBDAV = u"vnd.sun.star.webdav"_ustr; + static constexpr OUString PRIVATE = u"private"_ustr; + static constexpr OUString VND_HELP = u"vnd.sun.star.help"_ustr; + static constexpr OUString HTTPS = u"https"_ustr; + static constexpr OUString SLOT = u"slot"_ustr; + static constexpr OUString MACRO = u"macro"_ustr; + static constexpr OUString JAVASCRIPT = u"javascript"_ustr; + static constexpr OUString DATA = u"data"_ustr; + static constexpr OUString CID = u"cid"_ustr; + static constexpr OUString VND_HIER = u"vnd.sun.star.hier"_ustr; + static constexpr OUString UNO = u".uno"_ustr; + static constexpr OUString COMPONENT = u".component"_ustr; + static constexpr OUString VND_PKG = u"vnd.sun.star.pkg"_ustr; + static constexpr OUString LDAP = u"ldap"_ustr; + static constexpr OUString DB = u"db"_ustr; + static constexpr OUString VND_CMD = u"vnd.sun.star.cmd"_ustr; + static constexpr OUString TELNET = u"telnet"_ustr; + static constexpr OUString VND_EXPAND = u"vnd.sun.star.expand"_ustr; + static constexpr OUString VND_TDOC = u"vnd.sun.star.tdoc"_ustr; + static constexpr OUString SMB = u"smb"_ustr; + static constexpr OUString HID = u"hid"_ustr; + static constexpr OUString SFTP = u"sftp"_ustr; + static constexpr OUString VND_CMIS = u"vnd.libreoffice.cmis"_ustr; + + static o3tl::enumarray<INetProtocol, SchemeInfo> constexpr map = { + // [-loplugin:redundantfcast]: + SchemeInfo{ + EMPTY, "", false, false, false, false, false, false, false, false}, + SchemeInfo{ + FTP, "ftp://", true, true, false, true, true, true, true, + false}, + SchemeInfo{ + HTTP, "http://", true, false, false, false, true, true, true, + true}, + SchemeInfo{ + FILE1, "file://", true, false, false, false, true, false, true, + false}, + SchemeInfo{ + MAILTO, "mailto:", false, false, false, false, false, false, + false, true}, + SchemeInfo{ + VND_WEBDAV, "vnd.sun.star.webdav://", true, false, + false, false, true, true, true, true}, + SchemeInfo{ + PRIVATE, "private:", false, false, false, false, false, false, + false, true}, + SchemeInfo{ + VND_HELP, "vnd.sun.star.help://", true, false, false, + false, false, false, true, true}, + SchemeInfo{ + HTTPS, "https://", true, false, false, false, true, true, + true, true}, + SchemeInfo{ + SLOT, "slot:", false, false, false, false, false, false, false, + true}, + SchemeInfo{ + MACRO, "macro:", false, false, false, false, false, false, + false, true}, + SchemeInfo{ + JAVASCRIPT, "javascript:", false, false, false, false, false, + false, false, false}, + SchemeInfo{ + DATA, "data:", false, false, false, false, false, false, false, + false}, + SchemeInfo{ + CID, "cid:", false, false, false, false, false, false, false, + false}, + SchemeInfo{ + VND_HIER, "vnd.sun.star.hier:", true, false, false, + false, false, false, true, false}, + SchemeInfo{ + UNO, ".uno:", false, false, false, false, false, false, false, + true}, + SchemeInfo{ + COMPONENT, ".component:", false, false, false, false, false, + false, false, true}, + SchemeInfo{ + VND_PKG, "vnd.sun.star.pkg://", true, false, false, + false, false, false, true, true}, + SchemeInfo{ + LDAP, "ldap://", true, false, false, false, true, true, + false, true}, + SchemeInfo{ + DB, "db:", false, false, false, false, false, false, false, + false}, + SchemeInfo{ + VND_CMD, "vnd.sun.star.cmd:", false, false, false, + false, false, false, false, false}, + SchemeInfo{ + TELNET, "telnet://", true, true, false, true, true, true, + true, false}, + SchemeInfo{ + VND_EXPAND, "vnd.sun.star.expand:", false, false, + false, false, false, false, false, false}, + SchemeInfo{ + VND_TDOC, "vnd.sun.star.tdoc:", false, false, false, + false, false, false, true, false}, + SchemeInfo{ + EMPTY, "", false, false, false, false, true, true, true, false }, + SchemeInfo{ + SMB, "smb://", true, true, false, true, true, true, true, + true}, + SchemeInfo{ + HID, "hid:", false, false, false, false, false, false, false, + true}, + SchemeInfo{ + SFTP, "sftp://", true, true, false, true, true, true, true, + true}, + SchemeInfo{ + VND_CMIS, "vnd.libreoffice.cmis://", true, true, + false, false, true, false, true, true} }; + return map[eTheScheme]; +}; + +inline INetURLObject::SchemeInfo const & INetURLObject::getSchemeInfo() const +{ + return getSchemeInfo(m_eScheme); +} + +namespace { + +sal_Unicode getHexDigit(sal_uInt32 nWeight) +{ + assert(nWeight < 16); + static const sal_Unicode aDigits[16] + = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', + 'D', 'E', 'F' }; + return aDigits[nWeight]; +} + +} + +// static +inline void INetURLObject::appendEscape(OUStringBuffer & rTheText, + sal_uInt32 nOctet) +{ + rTheText.append( '%' ); + rTheText.append( getHexDigit(nOctet >> 4) ); + rTheText.append( getHexDigit(nOctet & 15) ); +} + +namespace { + +enum +{ + PA = INetURLObject::PART_USER_PASSWORD, + PD = INetURLObject::PART_FPATH, + PE = INetURLObject::PART_AUTHORITY, + PF = INetURLObject::PART_REL_SEGMENT_EXTRA, + PG = INetURLObject::PART_URIC, + PH = INetURLObject::PART_HTTP_PATH, + PI = INetURLObject::PART_MESSAGE_ID_PATH, + PJ = INetURLObject::PART_MAILTO, + PK = INetURLObject::PART_PATH_BEFORE_QUERY, + PL = INetURLObject::PART_PCHAR, + PM = INetURLObject::PART_VISIBLE, + PN = INetURLObject::PART_VISIBLE_NONSPECIAL, + PO = INetURLObject::PART_UNO_PARAM_VALUE, + PP = INetURLObject::PART_UNAMBIGUOUS, + PQ = INetURLObject::PART_URIC_NO_SLASH, + PR = INetURLObject::PART_HTTP_QUERY, +}; + +sal_uInt32 const aMustEncodeMap[128] + = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* */ PP, +/* ! */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* " */ PM+PN +PP, +/* # */ PM, +/* $ */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* % */ PM, +/* & */ PA +PD+PE+PF+PG+PH+PI +PK+PL+PM+PN+PO +PQ+PR, +/* ' */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* ( */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* ) */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* * */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* + */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO +PQ+PR, +/* , */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN +PQ+PR, +/* - */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* . */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* / */ +PD +PG+PH+PI+PJ+PK +PM+PN+PO, +/* 0 */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* 1 */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* 2 */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* 3 */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* 4 */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* 5 */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* 6 */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* 7 */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* 8 */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* 9 */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* : */ +PD+PE +PG+PH+PI+PJ+PK+PL+PM+PN+PO +PQ+PR, +/* ; */ PA +PE+PF+PG+PH+PI+PJ+PK +PM +PQ+PR, +/* < */ +PI +PM+PN +PP, +/* = */ PA +PD+PE+PF+PG+PH +PK+PL+PM+PN +PQ+PR, +/* > */ +PI +PM+PN +PP, +/* ? */ +PG +PM +PO +PQ, +/* @ */ +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* A */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* B */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* C */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* D */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* E */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* F */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* G */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* H */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* I */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* J */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* K */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* L */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* M */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* N */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* O */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* P */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* Q */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* R */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* S */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* T */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* U */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* V */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* W */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* X */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* Y */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* Z */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* [ */ PG +PM+PN+PO, +/* \ */ +PM+PN +PP, +/* ] */ PG +PM+PN+PO, +/* ^ */ PM+PN +PP, +/* _ */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* ` */ PM+PN +PP, +/* a */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* b */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* c */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* d */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* e */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* f */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* g */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* h */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* i */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* j */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* k */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* l */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* m */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* n */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* o */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* p */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* q */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* r */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* s */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* t */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* u */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* v */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* w */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* x */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* y */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* z */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR, +/* { */ PM+PN +PP, +/* | */ +PM+PN +PP, +/* } */ PM+PN +PP, +/* ~ */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ, + 0 }; + +bool mustEncode(sal_uInt32 nUTF32, INetURLObject::Part ePart) +{ + return !rtl::isAscii(nUTF32) || !(aMustEncodeMap[nUTF32] & ePart); +} + +} + +void INetURLObject::setInvalid() +{ + m_aAbsURIRef.setLength(0); + m_eScheme = INetProtocol::NotValid; + m_aScheme.clear(); + m_aUser.clear(); + m_aAuth.clear(); + m_aHost.clear(); + m_aPort.clear(); + m_aPath.clear(); + m_aQuery.clear(); + m_aFragment.clear(); +} + +namespace { + +std::unique_ptr<SvMemoryStream> memoryStream( + void const * data, sal_Int32 length) +{ + std::unique_ptr<char[]> b( + new char[length]); + memcpy(b.get(), data, length); + std::unique_ptr<SvMemoryStream> s( + new SvMemoryStream(b.get(), length, StreamMode::READ)); + s->ObjectOwnsMemory(true); + // coverity[leaked_storage : FALSE] - belongs to SvMemoryStream s at this point + b.release(); + return s; +} + +} + +std::unique_ptr<SvMemoryStream> INetURLObject::getData() const +{ + if( GetProtocol() != INetProtocol::Data ) + { + return nullptr; + } + + OUString sURLPath = GetURLPath( DecodeMechanism::WithCharset, RTL_TEXTENCODING_ISO_8859_1 ); + sal_Unicode const * pSkippedMediatype = INetMIME::scanContentType( sURLPath ); + sal_Int32 nCharactersSkipped = pSkippedMediatype == nullptr + ? 0 : pSkippedMediatype-sURLPath.getStr(); + if (sURLPath.match(",", nCharactersSkipped)) + { + nCharactersSkipped += strlen(","); + OString sURLEncodedData( + sURLPath.getStr() + nCharactersSkipped, + sURLPath.getLength() - nCharactersSkipped, + RTL_TEXTENCODING_ISO_8859_1, OUSTRING_TO_OSTRING_CVTFLAGS); + return memoryStream( + sURLEncodedData.getStr(), sURLEncodedData.getLength()); + } + else if (sURLPath.matchIgnoreAsciiCase(";base64,", nCharactersSkipped)) + { + nCharactersSkipped += strlen(";base64,"); + std::u16string_view sBase64Data = sURLPath.subView( nCharactersSkipped ); + css::uno::Sequence< sal_Int8 > aDecodedData; + if (comphelper::Base64::decodeSomeChars(aDecodedData, sBase64Data) + == sBase64Data.size()) + { + return memoryStream( + aDecodedData.getArray(), aDecodedData.getLength()); + } + } + return nullptr; +} + +namespace { + +FSysStyle guessFSysStyleByCounting(sal_Unicode const * pBegin, + sal_Unicode const * pEnd, + FSysStyle eStyle) +{ + DBG_ASSERT(eStyle + & (FSysStyle::Unix + | FSysStyle::Dos), + "guessFSysStyleByCounting(): Bad style"); + DBG_ASSERT(std::numeric_limits< sal_Int32 >::min() < pBegin - pEnd + && pEnd - pBegin <= std::numeric_limits< sal_Int32 >::max(), + "guessFSysStyleByCounting(): Too big"); + sal_Int32 nSlashCount + = (eStyle & FSysStyle::Unix) ? + 0 : std::numeric_limits< sal_Int32 >::min(); + sal_Int32 nBackslashCount + = (eStyle & FSysStyle::Dos) ? + 0 : std::numeric_limits< sal_Int32 >::min(); + while (pBegin != pEnd) + switch (*pBegin++) + { + case '/': + ++nSlashCount; + break; + + case '\\': + ++nBackslashCount; + break; + } + return nSlashCount >= nBackslashCount ? + FSysStyle::Unix : FSysStyle::Dos; +} + +OUString parseScheme( + sal_Unicode const ** begin, sal_Unicode const * end, + sal_uInt32 fragmentDelimiter) +{ + sal_Unicode const * p = *begin; + if (p != end && rtl::isAsciiAlpha(*p)) { + do { + ++p; + } while (p != end + && (rtl::isAsciiAlphanumeric(*p) || *p == '+' || *p == '-' + || *p == '.')); + // #i34835# To avoid problems with Windows file paths like "C:\foo", + // do not accept generic schemes that are only one character long: + if (end - p > 1 && p[0] == ':' && p[1] != fragmentDelimiter + && p - *begin >= 2) + { + OUString scheme( + OUString(*begin, p - *begin).toAsciiLowerCase()); + *begin = p + 1; + return scheme; + } + } + return OUString(); +} + +} + +bool INetURLObject::setAbsURIRef(std::u16string_view rTheAbsURIRef, + EncodeMechanism eMechanism, + rtl_TextEncoding eCharset, + bool bSmart, + FSysStyle eStyle) +{ + sal_Unicode const * pPos = rTheAbsURIRef.data(); + sal_Unicode const * pEnd = pPos + rTheAbsURIRef.size(); + + setInvalid(); + + sal_uInt32 nFragmentDelimiter = '#'; + + m_aAbsURIRef.setLength(0); + + // Parse <scheme>: + sal_Unicode const * p = pPos; + PrefixInfo const * pPrefix = getPrefix(p, pEnd); + if (pPrefix) + { + pPos = p; + m_eScheme = pPrefix->m_eScheme; + + char const * pTemp = pPrefix->m_eKind >= PrefixInfo::Kind::External ? + pPrefix->m_pTranslatedPrefix : + pPrefix->m_pPrefix; + m_aAbsURIRef.appendAscii(pTemp); + m_aScheme = SubString( 0, strstr(pTemp, ":") - pTemp ); + } + else + { + if (bSmart) + { + // For scheme detection, the first (if any) of the following + // productions that matches the input string (and for which the + // appropriate style bit is set in eStyle, if applicable) + // determines the scheme. The productions use the auxiliary rules + + // domain = label *("." label) + // label = alphanum [*(alphanum / "-") alphanum] + // alphanum = ALPHA / DIGIT + // IPv6reference = "[" IPv6address "]" + // IPv6address = hexpart [":" IPv4address] + // IPv4address = 1*3DIGIT 3("." 1*3DIGIT) + // hexpart = (hexseq ["::" [hexseq]]) / ("::" [hexseq]) + // hexseq = hex4 *(":" hex4) + // hex4 = 1*4HEXDIG + // UCS4 = <any UCS4 character> + + // 1st Production (known scheme; handled by the "if (pPrefix)" branch above): + // <one of the known schemes, ignoring case> ":" *UCS4 + // 2nd Production (mailto): + // domain "@" domain + // 3rd Production (ftp): + // "FTP" 2*("." label) ["/" *UCS4] + // 4th Production (http): + // label 2*("." label) ["/" *UCS4] + // 5th Production (file): + // "//" (domain / IPv6reference) ["/" *UCS4] + // 6th Production (Unix file): + // "/" *UCS4 + // 7th Production (UNC file; FSysStyle::Dos only): + // "\\" domain ["\" *UCS4] + // 8th Production (Unix-like DOS file; FSysStyle::Dos only): + // ALPHA ":" ["/" *UCS4] + // 9th Production (DOS file; FSysStyle::Dos only): + // ALPHA ":" ["\" *UCS4] + // 10th Production (any scheme; handled by the "m_eScheme = INetProtocol::Generic;" code + // after this else branch): + // <any scheme> ":" *UCS4 + + // For the 'non URL' file productions 6--9, the interpretation of + // the input as a (degenerate) URI is turned off, i.e., escape + // sequences and fragments are never detected as such, but are + // taken as literal characters. + + sal_Unicode const * p1 = pPos; + if (eStyle & FSysStyle::Dos + && pEnd - p1 >= 2 + && rtl::isAsciiAlpha(p1[0]) + && p1[1] == ':' + && (pEnd - p1 == 2 || p1[2] == '/' || p1[2] == '\\')) + { + m_eScheme = INetProtocol::File; // 8th, 9th + eMechanism = EncodeMechanism::All; + nFragmentDelimiter = 0x80000000; + } + else if (eStyle & FSysStyle::Dos + && pEnd - p1 >= 6 + && p1[0] == '\\' && p1[1] == '\\' && p1[2] == '?' && p1[3] == '\\' + && rtl::isAsciiAlpha(p1[4]) + && p1[5] == ':' + && (pEnd - p1 == 6 || p1[6] == '/' || p1[6] == '\\')) + { + m_eScheme = INetProtocol::File; // 8th, 9th + eMechanism = EncodeMechanism::All; + nFragmentDelimiter = 0x80000000; + } + else if (pEnd - p1 >= 2 && p1[0] == '/' && p1[1] == '/') + { + p1 += 2; + if ((scanDomain(p1, pEnd) > 0 || scanIPv6reference(p1, pEnd)) + && (p1 == pEnd || *p1 == '/')) + m_eScheme = INetProtocol::File; // 5th + } + else if (p1 != pEnd && *p1 == '/') + { + m_eScheme = INetProtocol::File; // 6th + eMechanism = EncodeMechanism::All; + nFragmentDelimiter = 0x80000000; + } + else if (eStyle & FSysStyle::Dos + && pEnd - p1 >= 2 + && p1[0] == '\\' + && p1[1] == '\\') + { + p1 += 2; + if (pEnd - p1 >= 6 && p1[0] == '?' && p1[1] == '\\' && p1[5] == '\\' + && rtl::toAsciiLowerCase(p1[2]) == 'u' + && rtl::toAsciiLowerCase(p1[3]) == 'n' + && rtl::toAsciiLowerCase(p1[4]) == 'c') + { + p1 += 6; // "\\?\UNC\Servername\..." + } + + sal_Int32 n = rtl_ustr_indexOfChar_WithLength( + p1, pEnd - p1, '\\'); + sal_Unicode const * pe = n == -1 ? pEnd : p1 + n; + if ( + parseHostOrNetBiosName( + p1, pe, EncodeMechanism::All, RTL_TEXTENCODING_DONTKNOW, + true, nullptr) || + (scanDomain(p1, pe) > 0 && p1 == pe) + ) + { + m_eScheme = INetProtocol::File; // 7th + eMechanism = EncodeMechanism::All; + nFragmentDelimiter = 0x80000000; + } + } + else + { + sal_Unicode const * pDomainEnd = p1; + sal_uInt32 nLabels = scanDomain(pDomainEnd, pEnd); + if (nLabels > 0 && pDomainEnd != pEnd && *pDomainEnd == '@') + { + ++pDomainEnd; + if (scanDomain(pDomainEnd, pEnd) > 0 + && pDomainEnd == pEnd) + m_eScheme = INetProtocol::Mailto; // 2nd + } + else if (nLabels >= 3 + && (pDomainEnd == pEnd || *pDomainEnd == '/')) + m_eScheme + = pDomainEnd - p1 >= 4 + && (p1[0] == 'f' || p1[0] == 'F') + && (p1[1] == 't' || p1[1] == 'T') + && (p1[2] == 'p' || p1[2] == 'P') + && p1[3] == '.' ? + INetProtocol::Ftp : INetProtocol::Http; // 3rd, 4th + } + } + + OUString aSynScheme; + if (m_eScheme == INetProtocol::NotValid) { + sal_Unicode const * p1 = pPos; + aSynScheme = parseScheme(&p1, pEnd, nFragmentDelimiter); + if (!aSynScheme.isEmpty()) + { + if (bSmart && m_eSmartScheme != m_eScheme && p1 != pEnd && rtl::isAsciiDigit(*p1)) + { + // rTheAbsURIRef doesn't define a known scheme (handled by the "if (pPrefix)" + // branch above); but a known scheme is defined in m_eSmartScheme. If this + // scheme may have a port in authority component, then avoid misinterpreting + // URLs like www.foo.bar:123/baz as using unknown "www.foo.bar" scheme with + // 123/baz rootless path. For now, do not try to handle possible colons in + // user information, require such ambiguous URLs to have explicit scheme part. + // Also ignore possibility of empty port. + const SchemeInfo& rInfo = getSchemeInfo(m_eSmartScheme); + if (rInfo.m_bAuthority && rInfo.m_bPort) + { + // Make sure that all characters from colon to [/?#] or to EOL are digits. + // Or maybe make it simple, and just assume that "xyz:1..." is more likely + // to be host "xyz" and port "1...", than scheme "xyz" and path "1..."? + sal_Unicode const* p2 = p1 + 1; + while (p2 != pEnd && rtl::isAsciiDigit(*p2)) + ++p2; + if (p2 == pEnd || *p2 == '/' || *p2 == '?' || *p2 == '#') + m_eScheme = m_eSmartScheme; + } + } + + if (m_eScheme == INetProtocol::NotValid) + { + m_eScheme = INetProtocol::Generic; + pPos = p1; + } + } + } + + if (bSmart && m_eScheme == INetProtocol::NotValid && pPos != pEnd + && *pPos != nFragmentDelimiter) + { + m_eScheme = m_eSmartScheme; + } + + if (m_eScheme == INetProtocol::NotValid) + { + setInvalid(); + return false; + } + + if (m_eScheme != INetProtocol::Generic) { + aSynScheme = getSchemeInfo().m_sScheme; + } + m_aScheme.set(m_aAbsURIRef, aSynScheme, m_aAbsURIRef.getLength()); + m_aAbsURIRef.append(':'); + } + + sal_uInt32 nSegmentDelimiter = '/'; + sal_uInt32 nAltSegmentDelimiter = 0x80000000; + bool bSkippedInitialSlash = false; + + // Parse //<user>;AUTH=<auth>@<host>:<port> or + // //<user>:<password>@<host>:<port> or + // //<reg_name> + if (getSchemeInfo().m_bAuthority) + { + sal_Unicode const * pUserInfoBegin = nullptr; + sal_Unicode const * pUserInfoEnd = nullptr; + sal_Unicode const * pHostPortBegin = nullptr; + sal_Unicode const * pHostPortEnd = nullptr; + + switch (m_eScheme) + { + case INetProtocol::VndSunStarHelp: + { + if (pEnd - pPos < 2 || *pPos++ != '/' || *pPos++ != '/') + { + setInvalid(); + return false; + } + m_aAbsURIRef.append("//"); + OUStringBuffer aSynAuthority; + while (pPos < pEnd + && *pPos != '/' && *pPos != '?' + && *pPos != nFragmentDelimiter) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 = getUTF32(pPos, pEnd, + eMechanism, + eCharset, eEscapeType); + appendUCS4(aSynAuthority, nUTF32, eEscapeType, + PART_AUTHORITY, eCharset, false); + } + m_aHost.set(m_aAbsURIRef, + aSynAuthority, + m_aAbsURIRef.getLength()); + // misusing m_aHost to store the authority + break; + } + + case INetProtocol::VndSunStarHier: + { + if (pEnd - pPos >= 2 && pPos[0] == '/' && pPos[1] == '/') + { + pPos += 2; + m_aAbsURIRef.append("//"); + OUStringBuffer aSynAuthority; + while (pPos < pEnd + && *pPos != '/' && *pPos != '?' + && *pPos != nFragmentDelimiter) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 = getUTF32(pPos, + pEnd, + eMechanism, + eCharset, + eEscapeType); + appendUCS4(aSynAuthority, + nUTF32, + eEscapeType, + PART_AUTHORITY, + eCharset, + false); + } + if (aSynAuthority.isEmpty()) + { + setInvalid(); + return false; + } + m_aHost.set(m_aAbsURIRef, + aSynAuthority, + m_aAbsURIRef.getLength()); + // misusing m_aHost to store the authority + } + break; + } + + case INetProtocol::VndSunStarPkg: + case INetProtocol::Cmis: + { + if (pEnd - pPos < 2 || *pPos++ != '/' || *pPos++ != '/') + { + setInvalid(); + return false; + } + m_aAbsURIRef.append("//"); + OUStringBuffer aSynUser(128); + + bool bHasUser = false; + while (pPos < pEnd && *pPos != '@' + && *pPos != '/' && *pPos != '?' + && *pPos != nFragmentDelimiter) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 = getUTF32(pPos, pEnd, + eMechanism, + eCharset, eEscapeType); + appendUCS4(aSynUser, nUTF32, eEscapeType, + PART_USER_PASSWORD, eCharset, false); + + bHasUser = *pPos == '@'; + } + + OUStringBuffer aSynAuthority(64); + if ( !bHasUser ) + { + aSynAuthority = aSynUser; + } + else + { + m_aUser.set(m_aAbsURIRef, + aSynUser, + m_aAbsURIRef.getLength()); + m_aAbsURIRef.append("@"); + ++pPos; + + while (pPos < pEnd + && *pPos != '/' && *pPos != '?' + && *pPos != nFragmentDelimiter) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 = getUTF32(pPos, pEnd, + eMechanism, + eCharset, eEscapeType); + appendUCS4(aSynAuthority, nUTF32, eEscapeType, + PART_AUTHORITY, eCharset, false); + } + } + if (aSynAuthority.isEmpty()) + { + setInvalid(); + return false; + } + m_aHost.set(m_aAbsURIRef, + aSynAuthority, + m_aAbsURIRef.getLength()); + // misusing m_aHost to store the authority + break; + } + + case INetProtocol::File: + if (bSmart) + { + // The first of the following seven productions that + // matches the rest of the input string (and for which the + // appropriate style bit is set in eStyle, if applicable) + // determines the used notation. The productions use the + // auxiliary rules + + // domain = label *("." label) + // label = alphanum [*(alphanum / "-") alphanum] + // alphanum = ALPHA / DIGIT + // IPv6reference = "[" IPv6address "]" + // IPv6address = hexpart [":" IPv4address] + // IPv4address = 1*3DIGIT 3("." 1*3DIGIT) + // hexpart = (hexseq ["::" [hexseq]]) / ("::" [hexseq]) + // hexseq = hex4 *(":" hex4) + // hex4 = 1*4HEXDIG + // path = <any UCS4 character except "#"> + // UCS4 = <any UCS4 character> + + // 1st Production (URL): + // "//" [domain / IPv6reference] ["/" *path] + // ["#" *UCS4] + // becomes + // "file://" domain "/" *path ["#" *UCS4] + if (pEnd - pPos >= 2 && pPos[0] == '/' && pPos[1] == '/') + { + sal_Unicode const * p1 = pPos + 2; + while (p1 != pEnd && *p1 != '/' && + *p1 != nFragmentDelimiter) + { + ++p1; + } + if (parseHostOrNetBiosName( + pPos + 2, p1, EncodeMechanism::All, + RTL_TEXTENCODING_DONTKNOW, true, nullptr)) + { + m_aAbsURIRef.append("//"); + pHostPortBegin = pPos + 2; + pHostPortEnd = p1; + pPos = p1; + break; + } + } + + // 2nd Production (MS IE generated 1; FSysStyle::Dos only): + // "//" ALPHA ":" ["/" *path] ["#" *UCS4] + // becomes + // "file:///" ALPHA ":" ["/" *path] ["#" *UCS4] + // replacing "\" by "/" within <*path> + // 3rd Production (MS IE generated 2; FSysStyle::Dos only): + // "//" ALPHA ":" ["\" *path] ["#" *UCS4] + // becomes + // "file:///" ALPHA ":" ["/" *path] ["#" *UCS4] + // replacing "\" by "/" within <*path> + // 4th Production (miscounted slashes): + // "//" *path ["#" *UCS4] + // becomes + // "file:///" *path ["#" *UCS4] + if (pEnd - pPos >= 2 && pPos[0] == '/' && pPos[1] == '/') + { + m_aAbsURIRef.append("//"); + pPos += 2; + bSkippedInitialSlash = true; + if ((eStyle & FSysStyle::Dos) + && pEnd - pPos >= 2 + && rtl::isAsciiAlpha(pPos[0]) + && pPos[1] == ':' + && (pEnd - pPos == 2 + || pPos[2] == '/' || pPos[2] == '\\')) + nAltSegmentDelimiter = '\\'; + break; + } + + // 5th Production (Unix): + // "/" *path ["#" *UCS4] + // becomes + // "file:///" *path ["#" *UCS4] + if (pPos < pEnd && *pPos == '/') + { + m_aAbsURIRef.append("//"); + break; + } + + // 6th Production (UNC; FSysStyle::Dos only): + // "\\" domain ["\" *path] ["#" *UCS4] + // becomes + // "file://" domain "/" *path ["#" *UCS4] + // replacing "\" by "/" within <*path> + if (eStyle & FSysStyle::Dos + && pEnd - pPos >= 2 + && pPos[0] == '\\' + && pPos[1] == '\\') + { + sal_Unicode const * p1 = pPos + 2; + sal_Unicode const * pHostPortTentativeBegin = p1; + if (pEnd - p1 >= 6 && p1[0] == '?' && p1[1] == '\\' && p1[5] == '\\' + && rtl::toAsciiLowerCase(p1[2]) == 'u' + && rtl::toAsciiLowerCase(p1[3]) == 'n' + && rtl::toAsciiLowerCase(p1[4]) == 'c') + { + p1 += 6; // "\\?\UNC\Servername\..." + pHostPortTentativeBegin = p1; + } + + sal_Unicode const * pe = p1; + while (pe < pEnd && *pe != '\\' && + *pe != nFragmentDelimiter) + { + ++pe; + } + if ( + parseHostOrNetBiosName( + p1, pe, EncodeMechanism::All, + RTL_TEXTENCODING_DONTKNOW, true, nullptr) || + (scanDomain(p1, pe) > 0 && p1 == pe) + ) + { + m_aAbsURIRef.append("//"); + pHostPortBegin = pHostPortTentativeBegin; + pHostPortEnd = pe; + pPos = pe; + nSegmentDelimiter = '\\'; + break; + } + } + + // 7th Production (Unix-like DOS; FSysStyle::Dos only): + // ALPHA ":" ["/" *path] ["#" *UCS4] + // becomes + // "file:///" ALPHA ":" ["/" *path] ["#" *UCS4] + // replacing "\" by "/" within <*path> + // 8th Production (DOS; FSysStyle::Dos only): + // ALPHA ":" ["\" *path] ["#" *UCS4] + // becomes + // "file:///" ALPHA ":" ["/" *path] ["#" *UCS4] + // replacing "\" by "/" within <*path> + if (eStyle & FSysStyle::Dos) + { + sal_Unicode const* p1 = pPos; + if (pEnd - p1 >= 4 && p1[0] == '\\' && p1[1] == '\\' && p1[2] == '?' + && p1[3] == '\\') + p1 += 4; // "\\?\c:\..." + + if (pEnd - p1 >= 2 + && rtl::isAsciiAlpha(p1[0]) + && p1[1] == ':' + && (pEnd - p1 == 2 + || p1[2] == '/' + || p1[2] == '\\')) + { + pPos = p1; + m_aAbsURIRef.append("//"); + nAltSegmentDelimiter = '\\'; + bSkippedInitialSlash = true; + break; + } + } + + // 9th Production (any): + // *path ["#" *UCS4] + // becomes + // "file:///" *path ["#" *UCS4] + // replacing the delimiter by "/" within <*path>. The + // delimiter is that character from the set { "/", "\"} + // which appears most often in <*path> (if FSysStyle::Unix + // is not among the style bits, "/" is removed from the + // set; if FSysStyle::Dos is not among the style bits, "\" is + // removed from the set). If two or + // more characters appear the same number of times, the + // character mentioned first in that set is chosen. If + // the first character of <*path> is the delimiter, that + // character is not copied + if (eStyle & (FSysStyle::Unix | FSysStyle::Dos)) + { + m_aAbsURIRef.append("//"); + switch (guessFSysStyleByCounting(pPos, pEnd, eStyle)) + { + case FSysStyle::Unix: + nSegmentDelimiter = '/'; + break; + + case FSysStyle::Dos: + nSegmentDelimiter = '\\'; + break; + + default: + OSL_FAIL( + "INetURLObject::setAbsURIRef():" + " Bad guessFSysStyleByCounting"); + break; + } + bSkippedInitialSlash + = pPos != pEnd && *pPos != nSegmentDelimiter; + break; + } + } + [[fallthrough]]; + default: + { + // For INetProtocol::File, allow an empty authority ("//") to be + // missing if the following path starts with an explicit "/" + // (Java is notorious in generating such file URLs, so be + // liberal here): + if (pEnd - pPos >= 2 && pPos[0] == '/' && pPos[1] == '/') + pPos += 2; + else if (!bSmart + && !(m_eScheme == INetProtocol::File + && pPos != pEnd && *pPos == '/')) + { + setInvalid(); + return false; + } + m_aAbsURIRef.append("//"); + + sal_Unicode const * pAuthority = pPos; + sal_uInt32 c = getSchemeInfo().m_bQuery ? '?' : 0x80000000; + while (pPos < pEnd && *pPos != '/' && *pPos != c + && *pPos != nFragmentDelimiter) + ++pPos; + if (getSchemeInfo().m_bUser) + if (getSchemeInfo().m_bHost) + { + sal_Unicode const * p1 = pAuthority; + while (p1 < pPos && *p1 != '@') + ++p1; + if (p1 == pPos) + { + pHostPortBegin = pAuthority; + pHostPortEnd = pPos; + } + else + { + pUserInfoBegin = pAuthority; + pUserInfoEnd = p1; + pHostPortBegin = p1 + 1; + pHostPortEnd = pPos; + } + } + else + { + pUserInfoBegin = pAuthority; + pUserInfoEnd = pPos; + } + else if (getSchemeInfo().m_bHost) + { + pHostPortBegin = pAuthority; + pHostPortEnd = pPos; + } + else if (pPos != pAuthority) + { + setInvalid(); + return false; + } + break; + } + } + + if (pUserInfoBegin) + { + Part ePart = PART_USER_PASSWORD; + bool bSupportsPassword = getSchemeInfo().m_bPassword; + bool bSupportsAuth + = !bSupportsPassword && getSchemeInfo().m_bAuth; + bool bHasAuth = false; + OUStringBuffer aSynUser; + sal_Unicode const * p1 = pUserInfoBegin; + while (p1 < pUserInfoEnd) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 = getUTF32(p1, pUserInfoEnd, + eMechanism, eCharset, eEscapeType); + if (eEscapeType == EscapeType::NONE) + { + if (nUTF32 == ':' && bSupportsPassword) + { + bHasAuth = true; + break; + } + else if (nUTF32 == ';' && bSupportsAuth + && pUserInfoEnd - p1 + > RTL_CONSTASCII_LENGTH("auth=") + && INetMIME::equalIgnoreCase( + p1, + p1 + RTL_CONSTASCII_LENGTH("auth="), + "auth=")) + { + p1 += RTL_CONSTASCII_LENGTH("auth="); + bHasAuth = true; + break; + } + } + appendUCS4(aSynUser, nUTF32, eEscapeType, ePart, + eCharset, false); + } + m_aUser.set(m_aAbsURIRef, aSynUser, m_aAbsURIRef.getLength()); + if (bHasAuth) + { + if (bSupportsPassword) + { + m_aAbsURIRef.append(':'); + OUStringBuffer aSynAuth; + while (p1 < pUserInfoEnd) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 = getUTF32(p1, pUserInfoEnd, + eMechanism, eCharset, + eEscapeType); + appendUCS4(aSynAuth, nUTF32, eEscapeType, + ePart, eCharset, false); + } + m_aAuth.set(m_aAbsURIRef, aSynAuth, m_aAbsURIRef.getLength()); + } + else + { + m_aAbsURIRef.append(";AUTH="); + OUStringBuffer aSynAuth; + while (p1 < pUserInfoEnd) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 = getUTF32(p1, pUserInfoEnd, + eMechanism, eCharset, + eEscapeType); + if (!INetMIME::isIMAPAtomChar(nUTF32)) + { + setInvalid(); + return false; + } + appendUCS4(aSynAuth, nUTF32, eEscapeType, + ePart, eCharset, false); + } + m_aAuth.set(m_aAbsURIRef, aSynAuth, m_aAbsURIRef.getLength()); + } + } + if (pHostPortBegin) + m_aAbsURIRef.append('@'); + } + + if (pHostPortBegin) + { + sal_Unicode const * pPort = pHostPortEnd; + if ( getSchemeInfo().m_bPort && pHostPortBegin < pHostPortEnd ) + { + sal_Unicode const * p1 = pHostPortEnd - 1; + while (p1 > pHostPortBegin && rtl::isAsciiDigit(*p1)) + --p1; + if (*p1 == ':') + pPort = p1; + } + bool bNetBiosName = false; + switch (m_eScheme) + { + case INetProtocol::File: + // If the host equals "LOCALHOST" (unencoded and ignoring + // case), turn it into an empty host: + if (INetMIME::equalIgnoreCase(pHostPortBegin, pPort, + "localhost")) + pHostPortBegin = pPort; + bNetBiosName = true; + break; + + case INetProtocol::Ldap: + case INetProtocol::Smb: + if (pHostPortBegin == pPort && pPort != pHostPortEnd) + { + setInvalid(); + return false; + } + break; + default: + if (pHostPortBegin == pPort) + { + setInvalid(); + return false; + } + break; + } + sal_Int32 nLenBeforeHost = m_aAbsURIRef.getLength(); + if (!parseHostOrNetBiosName( + pHostPortBegin, pPort, eMechanism, eCharset, + bNetBiosName, &m_aAbsURIRef)) + { + setInvalid(); + return false; + } + m_aHost = SubString(nLenBeforeHost, m_aAbsURIRef.getLength() - nLenBeforeHost); + if (pPort != pHostPortEnd) + { + m_aAbsURIRef.append(':'); + m_aPort.set(m_aAbsURIRef, + std::u16string_view{pPort + 1, static_cast<size_t>(pHostPortEnd - (pPort + 1))}, + m_aAbsURIRef.getLength()); + } + } + } + + // Parse <path> + sal_Int32 nBeforePathLength = m_aAbsURIRef.getLength(); + if (!parsePath(m_eScheme, &pPos, pEnd, eMechanism, eCharset, + bSkippedInitialSlash, nSegmentDelimiter, + nAltSegmentDelimiter, + getSchemeInfo().m_bQuery ? '?' : 0x80000000, + nFragmentDelimiter, m_aAbsURIRef)) + { + setInvalid(); + return false; + } + m_aPath = SubString(nBeforePathLength, m_aAbsURIRef.getLength() - nBeforePathLength); + + // Parse ?<query> + if (getSchemeInfo().m_bQuery && pPos < pEnd && *pPos == '?') + { + m_aAbsURIRef.append('?'); + OUStringBuffer aSynQuery; + for (++pPos; pPos < pEnd && *pPos != nFragmentDelimiter;) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 = getUTF32(pPos, pEnd, + eMechanism, eCharset, eEscapeType); + appendUCS4(aSynQuery, nUTF32, eEscapeType, + PART_URIC, eCharset, true); + } + m_aQuery.set(m_aAbsURIRef, aSynQuery, m_aAbsURIRef.getLength()); + } + + // Parse #<fragment> + if (pPos < pEnd && *pPos == nFragmentDelimiter) + { + m_aAbsURIRef.append(sal_Unicode(nFragmentDelimiter)); + OUStringBuffer aSynFragment; + for (++pPos; pPos < pEnd;) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 = getUTF32(pPos, pEnd, + eMechanism, eCharset, eEscapeType); + appendUCS4(aSynFragment, nUTF32, eEscapeType, PART_URIC, + eCharset, true); + } + m_aFragment.set(m_aAbsURIRef, aSynFragment, m_aAbsURIRef.getLength()); + } + + if (pPos != pEnd) + { + setInvalid(); + return false; + } + + return true; +} + +void INetURLObject::changeScheme(INetProtocol eTargetScheme) { + sal_Int32 oldSchemeLen = 0; + const OUString& rOldSchemeName = getSchemeInfo().m_sScheme; + if (m_eScheme == INetProtocol::Generic) + oldSchemeLen = m_aScheme.getLength(); + else + oldSchemeLen = rOldSchemeName.getLength(); + m_eScheme=eTargetScheme; + const OUString& rNewSchemeName = getSchemeInfo().m_sScheme; + sal_Int32 newSchemeLen = rNewSchemeName.getLength(); + m_aAbsURIRef.remove(0, oldSchemeLen); + m_aAbsURIRef.insert(0, rNewSchemeName); + sal_Int32 delta=newSchemeLen-oldSchemeLen; + m_aUser+=delta; + m_aAuth+=delta; + m_aHost+=delta; + m_aPort+=delta; + m_aPath+=delta; + m_aQuery+=delta; + m_aFragment+=delta; +} + +bool INetURLObject::convertRelToAbs(OUString const & rTheRelURIRef, + INetURLObject & rTheAbsURIRef, + bool & rWasAbsolute, + EncodeMechanism eMechanism, + rtl_TextEncoding eCharset, + bool bIgnoreFragment, bool bSmart, + bool bRelativeNonURIs, FSysStyle eStyle) + const +{ + sal_Unicode const * p = rTheRelURIRef.getStr(); + sal_Unicode const * pEnd = p + rTheRelURIRef.getLength(); + + sal_Unicode const * pPrefixBegin = p; + PrefixInfo const * pPrefix = getPrefix(pPrefixBegin, pEnd); + bool hasScheme = pPrefix != nullptr; + if (!hasScheme) { + pPrefixBegin = p; + hasScheme = !parseScheme(&pPrefixBegin, pEnd, '#').isEmpty(); + } + + sal_uInt32 nSegmentDelimiter = '/'; + sal_uInt32 nQueryDelimiter + = !bSmart || getSchemeInfo().m_bQuery ? '?' : 0x80000000; + sal_uInt32 nFragmentDelimiter = '#'; + Part ePart = PART_VISIBLE; + + if (!hasScheme && bSmart) + { + // If the input matches any of the following productions (for which + // the appropriate style bit is set in eStyle), it is assumed to be an + // absolute file system path, rather than a relative URI reference. + // (This is only a subset of the productions used for scheme detection + // in INetURLObject::setAbsURIRef(), because most of those productions + // interfere with the syntax of relative URI references.) The + // productions use the auxiliary rules + + // domain = label *("." label) + // label = alphanum [*(alphanum / "-") alphanum] + // alphanum = ALPHA / DIGIT + // UCS4 = <any UCS4 character> + + // 1st Production (UNC file; FSysStyle::Dos only): + // "\\" domain ["\" *UCS4] + // 2nd Production (Unix-like DOS file; FSysStyle::Dos only): + // ALPHA ":" ["/" *UCS4] + // 3rd Production (DOS file; FSysStyle::Dos only): + // ALPHA ":" ["\" *UCS4] + if (eStyle & FSysStyle::Dos) + { + bool bFSys = false; + sal_Unicode const * q = p; + if (pEnd - q >= 2 + && rtl::isAsciiAlpha(q[0]) + && q[1] == ':' + && (pEnd - q == 2 || q[2] == '/' || q[2] == '\\')) + bFSys = true; // 2nd, 3rd + else if (pEnd - q >= 2 && q[0] == '\\' && q[1] == '\\') + { + q += 2; + sal_Int32 n = rtl_ustr_indexOfChar_WithLength( + q, pEnd - q, '\\'); + if (n == 1 && q[0] == '?') + { + // "\\?\c:\..." or "\\?\UNC\servername\..." + q += 2; + if (pEnd - q >= 2 + && rtl::isAsciiAlpha(q[0]) + && q[1] == ':' + && (pEnd - q == 2 || q[2] == '/' || q[2] == '\\')) + { + bFSys = true; // 2nd, 3rd + } + else if (pEnd - q >= 4 + && q[3] == '\\' + && rtl::toAsciiLowerCase(q[0]) == 'u' + && rtl::toAsciiLowerCase(q[1]) == 'n' + && rtl::toAsciiLowerCase(q[2]) == 'c') + { + q += 4; // Check if it's 1st below + } + } + if (!bFSys) + { + sal_Unicode const * qe = n == -1 ? pEnd : q + n; + if (parseHostOrNetBiosName( + q, qe, EncodeMechanism::All, RTL_TEXTENCODING_DONTKNOW, + true, nullptr)) + { + bFSys = true; // 1st + } + } + } + if (bFSys) + { + INetURLObject aNewURI; + aNewURI.setAbsURIRef(rTheRelURIRef, eMechanism, + eCharset, true, eStyle); + if (!aNewURI.HasError()) + { + rTheAbsURIRef = aNewURI; + rWasAbsolute = true; + return true; + } + } + } + + // When the base URL is a file URL, accept relative file system paths + // using "\" or ":" as delimiter (and ignoring URI conventions for "%" + // and "#"), as well as relative URIs using "/" as delimiter: + if (m_eScheme == INetProtocol::File) + switch (guessFSysStyleByCounting(p, pEnd, eStyle)) + { + case FSysStyle::Unix: + nSegmentDelimiter = '/'; + break; + + case FSysStyle::Dos: + nSegmentDelimiter = '\\'; + bRelativeNonURIs = true; + break; + + default: + OSL_FAIL("INetURLObject::convertRelToAbs():" + " Bad guessFSysStyleByCounting"); + break; + } + + if (bRelativeNonURIs) + { + eMechanism = EncodeMechanism::All; + nQueryDelimiter = 0x80000000; + nFragmentDelimiter = 0x80000000; + ePart = PART_VISIBLE_NONSPECIAL; + } + } + + // If the relative URI has the same scheme as the base URI, and that + // scheme is hierarchical, then ignore its presence in the relative + // URI in order to be backward compatible (cf. RFC 2396 section 5.2 + // step 3): + if (pPrefix && pPrefix->m_eScheme == m_eScheme + && getSchemeInfo().m_bHierarchical) + { + hasScheme = false; + while (p != pEnd && *p++ != ':') ; + } + rWasAbsolute = hasScheme; + + // Fast solution for non-relative URIs: + if (hasScheme) + { + INetURLObject aNewURI(rTheRelURIRef, eMechanism, eCharset); + if (aNewURI.HasError()) + { + rWasAbsolute = false; + return false; + } + + if (bIgnoreFragment) + aNewURI.clearFragment(); + rTheAbsURIRef = aNewURI; + return true; + } + + enum State { STATE_AUTH, STATE_ABS_PATH, STATE_REL_PATH, STATE_FRAGMENT, + STATE_DONE }; + + OUStringBuffer aSynAbsURIRef(128); + // make sure that the scheme is copied for generic schemes: getSchemeInfo().m_pScheme + // is empty ("") in that case, so take the scheme from m_aAbsURIRef + if (m_eScheme != INetProtocol::Generic) + { + aSynAbsURIRef.append(getSchemeInfo().m_sScheme); + } + else + { + sal_Unicode const * pSchemeBegin + = m_aAbsURIRef.getStr(); + sal_Unicode const * pSchemeEnd = pSchemeBegin; + while (pSchemeEnd[0] != ':') + { + ++pSchemeEnd; + } + aSynAbsURIRef.append(pSchemeBegin, pSchemeEnd - pSchemeBegin); + } + aSynAbsURIRef.append(':'); + + State eState = STATE_AUTH; + bool bSameDoc = true; + + if (getSchemeInfo().m_bAuthority) + { + if (pEnd - p >= 2 && p[0] == '/' && p[1] == '/') + { + aSynAbsURIRef.append("//"); + p += 2; + eState = STATE_ABS_PATH; + bSameDoc = false; + while (p != pEnd) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 + = getUTF32(p, pEnd, eMechanism, + eCharset, eEscapeType); + if (eEscapeType == EscapeType::NONE) + { + if (nUTF32 == nSegmentDelimiter) + break; + else if (nUTF32 == nFragmentDelimiter) + { + eState = STATE_FRAGMENT; + break; + } + } + appendUCS4(aSynAbsURIRef, nUTF32, eEscapeType, + PART_VISIBLE, eCharset, true); + } + } + else + { + SubString aAuthority(getAuthority()); + aSynAbsURIRef.append(m_aAbsURIRef.getStr() + + aAuthority.getBegin(), + aAuthority.getLength()); + } + } + + if (eState == STATE_AUTH) + { + if (p == pEnd) + eState = STATE_DONE; + else if (*p == nFragmentDelimiter) + { + ++p; + eState = STATE_FRAGMENT; + } + else if (*p == nSegmentDelimiter) + { + ++p; + eState = STATE_ABS_PATH; + bSameDoc = false; + } + else + { + eState = STATE_REL_PATH; + bSameDoc = false; + } + } + + if (eState == STATE_ABS_PATH) + { + aSynAbsURIRef.append('/'); + eState = STATE_DONE; + while (p != pEnd) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 + = getUTF32(p, pEnd, eMechanism, eCharset, eEscapeType); + if (eEscapeType == EscapeType::NONE) + { + if (nUTF32 == nFragmentDelimiter) + { + eState = STATE_FRAGMENT; + break; + } + else if (nUTF32 == nSegmentDelimiter) + nUTF32 = '/'; + } + appendUCS4(aSynAbsURIRef, nUTF32, eEscapeType, ePart, + eCharset, true); + } + } + else if (eState == STATE_REL_PATH) + { + if (!getSchemeInfo().m_bHierarchical) + { + // Detect cases where a relative input could not be made absolute + // because the given base URL is broken (most probably because it is + // empty): + SAL_WARN_IF( + HasError(), "tools.urlobj", + "cannot make <" << rTheRelURIRef + << "> absolute against broken base <" + << GetMainURL(DecodeMechanism::NONE) << ">"); + rWasAbsolute = false; + return false; + } + + sal_Unicode const * pBasePathBegin + = m_aAbsURIRef.getStr() + m_aPath.getBegin(); + sal_Unicode const * pBasePathEnd + = pBasePathBegin + m_aPath.getLength(); + while (pBasePathEnd != pBasePathBegin) + if (*(--pBasePathEnd) == '/') + { + ++pBasePathEnd; + break; + } + + sal_Int32 nPathBegin = aSynAbsURIRef.getLength(); + aSynAbsURIRef.append(pBasePathBegin, pBasePathEnd - pBasePathBegin); + DBG_ASSERT(aSynAbsURIRef.getLength() > nPathBegin + && aSynAbsURIRef[aSynAbsURIRef.getLength() - 1] == '/', + "INetURLObject::convertRelToAbs(): Bad base path"); + + while (p != pEnd && *p != nQueryDelimiter && *p != nFragmentDelimiter) + { + if (*p == '.') + { + if (pEnd - p == 1 + || p[1] == nSegmentDelimiter + || p[1] == nQueryDelimiter + || p[1] == nFragmentDelimiter) + { + ++p; + if (p != pEnd && *p == nSegmentDelimiter) + ++p; + continue; + } + else if (pEnd - p >= 2 + && p[1] == '.' + && (pEnd - p == 2 + || p[2] == nSegmentDelimiter + || p[2] == nQueryDelimiter + || p[2] == nFragmentDelimiter) + && aSynAbsURIRef.getLength() - nPathBegin > 1) + { + p += 2; + if (p != pEnd && *p == nSegmentDelimiter) + ++p; + + sal_Int32 i = aSynAbsURIRef.getLength() - 2; + while (i > nPathBegin && aSynAbsURIRef[i] != '/') + --i; + aSynAbsURIRef.setLength(i + 1); + DBG_ASSERT( + aSynAbsURIRef.getLength() > nPathBegin + && aSynAbsURIRef[aSynAbsURIRef.getLength() - 1] == '/', + "INetURLObject::convertRelToAbs(): Bad base path"); + continue; + } + } + + while (p != pEnd + && *p != nSegmentDelimiter + && *p != nQueryDelimiter + && *p != nFragmentDelimiter) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 + = getUTF32(p, pEnd, eMechanism, + eCharset, eEscapeType); + appendUCS4(aSynAbsURIRef, nUTF32, eEscapeType, ePart, + eCharset, true); + } + if (p != pEnd && *p == nSegmentDelimiter) + { + aSynAbsURIRef.append('/'); + ++p; + } + } + + while (p != pEnd && *p != nFragmentDelimiter) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 + = getUTF32(p, pEnd, eMechanism, eCharset, eEscapeType); + appendUCS4(aSynAbsURIRef, nUTF32, eEscapeType, ePart, + eCharset, true); + } + + if (p == pEnd) + eState = STATE_DONE; + else + { + ++p; + eState = STATE_FRAGMENT; + } + } + else if (bSameDoc) + { + aSynAbsURIRef.append(m_aAbsURIRef.getStr() + m_aPath.getBegin(), + m_aPath.getLength()); + if (m_aQuery.isPresent()) + aSynAbsURIRef.append(m_aAbsURIRef.getStr() + + m_aQuery.getBegin() - 1, + m_aQuery.getLength() + 1); + } + + if (eState == STATE_FRAGMENT && !bIgnoreFragment) + { + aSynAbsURIRef.append('#'); + while (p != pEnd) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 + = getUTF32(p, pEnd, eMechanism, eCharset, eEscapeType); + appendUCS4(aSynAbsURIRef, nUTF32, eEscapeType, + PART_VISIBLE, eCharset, true); + } + } + + INetURLObject aNewURI(aSynAbsURIRef); + if (aNewURI.HasError()) + { + // Detect cases where a relative input could not be made absolute + // because the given base URL is broken (most probably because it is + // empty): + SAL_WARN_IF( + HasError(), "tools.urlobj", + "cannot make <" << rTheRelURIRef + << "> absolute against broken base <" << GetMainURL(DecodeMechanism::NONE) + << ">"); + rWasAbsolute = false; + return false; + } + + rTheAbsURIRef = aNewURI; + return true; +} + +bool INetURLObject::convertAbsToRel(OUString const & rTheAbsURIRef, + OUString & rTheRelURIRef, + EncodeMechanism eEncodeMechanism, + DecodeMechanism eDecodeMechanism, + rtl_TextEncoding eCharset, + FSysStyle eStyle) const +{ + // Check for hierarchical base URL: + if (!getSchemeInfo().m_bHierarchical) + { + rTheRelURIRef = decode(rTheAbsURIRef, eDecodeMechanism, eCharset); + return false; + } + + // Convert the input (absolute or relative URI ref) to an absolute URI + // ref: + INetURLObject aSubject; + bool bWasAbsolute; + if (!convertRelToAbs(rTheAbsURIRef, aSubject, bWasAbsolute, + eEncodeMechanism, eCharset, false, false, false, + eStyle)) + { + rTheRelURIRef = decode(rTheAbsURIRef, eDecodeMechanism, eCharset); + return false; + } + + // Check for differing scheme or authority parts: + if ((m_aScheme.compare( + aSubject.m_aScheme, m_aAbsURIRef, aSubject.m_aAbsURIRef) + != 0) + || (m_aUser.compare( + aSubject.m_aUser, m_aAbsURIRef, aSubject.m_aAbsURIRef) + != 0) + || (m_aAuth.compare( + aSubject.m_aAuth, m_aAbsURIRef, aSubject.m_aAbsURIRef) + != 0) + || (m_aHost.compare( + aSubject.m_aHost, m_aAbsURIRef, aSubject.m_aAbsURIRef) + != 0) + || (m_aPort.compare( + aSubject.m_aPort, m_aAbsURIRef, aSubject.m_aAbsURIRef) + != 0)) + { + rTheRelURIRef = aSubject.GetMainURL(eDecodeMechanism, eCharset); + return false; + } + + sal_Unicode const * pBasePathBegin + = m_aAbsURIRef.getStr() + m_aPath.getBegin(); + sal_Unicode const * pBasePathEnd = pBasePathBegin + m_aPath.getLength(); + sal_Unicode const * pSubjectPathBegin + = aSubject.m_aAbsURIRef.getStr() + aSubject.m_aPath.getBegin(); + sal_Unicode const * pSubjectPathEnd + = pSubjectPathBegin + aSubject.m_aPath.getLength(); + + // Make nMatch point past the last matching slash, or past the end of the + // paths, in case they are equal: + sal_Unicode const * pSlash = nullptr; + sal_Unicode const * p1 = pBasePathBegin; + sal_Unicode const * p2 = pSubjectPathBegin; + for (;;) + { + if (p1 == pBasePathEnd || p2 == pSubjectPathEnd) + { + if (p1 == pBasePathEnd && p2 == pSubjectPathEnd) + pSlash = p1; + break; + } + + sal_Unicode c = *p1++; + if (c != *p2++) + break; + if (c == '/') + pSlash = p1; + } + if (!pSlash) + { + // One of the paths does not start with '/': + rTheRelURIRef = aSubject.GetMainURL(eDecodeMechanism, eCharset); + return false; + } + sal_Int32 nMatch = pSlash - pBasePathBegin; + + // If the two URLs are DOS file URLs starting with different volumes + // (e.g., file:///a:/... and file:///b:/...), the subject is not made + // relative (it could be, but some people do not like that): + if (m_eScheme == INetProtocol::File + && nMatch <= 1 + && hasDosVolume(eStyle) + && aSubject.hasDosVolume(eStyle)) //TODO! ok to use eStyle for these? + { + rTheRelURIRef = aSubject.GetMainURL(eDecodeMechanism, eCharset); + return false; + } + + // For every slash in the base path after nMatch, a prefix of "../" is + // added to the new relative URL (if the common prefix of the two paths is + // only "/"---but see handling of file URLs above---, the complete subject + // path could go into the new relative URL instead, but some people don't + // like that): + OUStringBuffer aSynRelURIRef; + for (sal_Unicode const * p = pBasePathBegin + nMatch; p != pBasePathEnd; + ++p) + { + if (*p == '/') + aSynRelURIRef.append("../"); + } + + // If the new relative URL would start with "//" (i.e., it would be + // mistaken for a relative URL starting with an authority part), or if the + // new relative URL would neither be empty nor start with <"/"> nor start + // with <1*rseg> (i.e., it could be mistaken for an absolute URL starting + // with a scheme part), then the new relative URL is prefixed with "./": + if (aSynRelURIRef.isEmpty()) + { + if (pSubjectPathEnd - pSubjectPathBegin >= nMatch + 2 + && pSubjectPathBegin[nMatch] == '/' + && pSubjectPathBegin[nMatch + 1] == '/') + { + aSynRelURIRef.append("./"); + } + else + { + for (sal_Unicode const * p = pSubjectPathBegin + nMatch; + p != pSubjectPathEnd && *p != '/'; ++p) + { + if (mustEncode(*p, PART_REL_SEGMENT_EXTRA)) + { + aSynRelURIRef.append("./"); + break; + } + } + } + } + + // The remainder of the subject path, starting at nMatch, is appended to + // the new relative URL: + aSynRelURIRef.append(decode(pSubjectPathBegin + nMatch, pSubjectPathEnd, + eDecodeMechanism, eCharset)); + + // If the subject has defined query or fragment parts, they are appended + // to the new relative URL: + if (aSubject.m_aQuery.isPresent()) + { + aSynRelURIRef.append("?" + + aSubject.decode(aSubject.m_aQuery, eDecodeMechanism, eCharset)); + } + if (aSubject.m_aFragment.isPresent()) + { + aSynRelURIRef.append("#" + + aSubject.decode(aSubject.m_aFragment, eDecodeMechanism, eCharset)); + } + + rTheRelURIRef = aSynRelURIRef.makeStringAndClear(); + return true; +} + +// static +bool INetURLObject::convertIntToExt(std::u16string_view rTheIntURIRef, + OUString & rTheExtURIRef, + DecodeMechanism eDecodeMechanism, + rtl_TextEncoding eCharset) +{ + OUStringBuffer aSynExtURIRef(256); + encodeText(aSynExtURIRef, rTheIntURIRef, PART_VISIBLE, + EncodeMechanism::NotCanonical, eCharset, true); + sal_Unicode const * pBegin = aSynExtURIRef.getStr(); + sal_Unicode const * pEnd = pBegin + aSynExtURIRef.getLength(); + sal_Unicode const * p = pBegin; + PrefixInfo const * pPrefix = getPrefix(p, pEnd); + bool bConvert = pPrefix && pPrefix->m_eKind == PrefixInfo::Kind::Internal; + if (bConvert) + { + comphelper::string::replaceAt(aSynExtURIRef, 0, p - pBegin, + OUString::createFromAscii(pPrefix->m_pTranslatedPrefix)); + } + rTheExtURIRef = decode(aSynExtURIRef, eDecodeMechanism, eCharset); + return bConvert; +} + +// static +bool INetURLObject::convertExtToInt(std::u16string_view rTheExtURIRef, + OUString & rTheIntURIRef, + DecodeMechanism eDecodeMechanism, + rtl_TextEncoding eCharset) +{ + OUStringBuffer aSynIntURIRef(256); + encodeText(aSynIntURIRef, rTheExtURIRef, PART_VISIBLE, + EncodeMechanism::NotCanonical, eCharset, true); + sal_Unicode const * pBegin = aSynIntURIRef.getStr(); + sal_Unicode const * pEnd = pBegin + aSynIntURIRef.getLength(); + sal_Unicode const * p = pBegin; + PrefixInfo const * pPrefix = getPrefix(p, pEnd); + bool bConvert = pPrefix && pPrefix->m_eKind == PrefixInfo::Kind::External; + if (bConvert) + { + comphelper::string::replaceAt(aSynIntURIRef, 0, p - pBegin, + OUString::createFromAscii(pPrefix->m_pTranslatedPrefix)); + } + rTheIntURIRef = decode(aSynIntURIRef, eDecodeMechanism, eCharset); + return bConvert; +} + +// static +INetURLObject::PrefixInfo const * INetURLObject::getPrefix(sal_Unicode const *& rBegin, + sal_Unicode const * pEnd) +{ + static PrefixInfo const aMap[] + = { // dummy entry at front needed, because pLast may point here: + { nullptr, nullptr, INetProtocol::NotValid, PrefixInfo::Kind::Internal }, + { ".component:", "staroffice.component:", INetProtocol::Component, + PrefixInfo::Kind::Internal }, + { ".uno:", "staroffice.uno:", INetProtocol::Uno, + PrefixInfo::Kind::Internal }, + { "cid:", nullptr, INetProtocol::Cid, PrefixInfo::Kind::Official }, + { "data:", nullptr, INetProtocol::Data, PrefixInfo::Kind::Official }, + { "db:", "staroffice.db:", INetProtocol::Db, PrefixInfo::Kind::Internal }, + { "file:", nullptr, INetProtocol::File, PrefixInfo::Kind::Official }, + { "ftp:", nullptr, INetProtocol::Ftp, PrefixInfo::Kind::Official }, + { "hid:", "staroffice.hid:", INetProtocol::Hid, + PrefixInfo::Kind::Internal }, + { "http:", nullptr, INetProtocol::Http, PrefixInfo::Kind::Official }, + { "https:", nullptr, INetProtocol::Https, PrefixInfo::Kind::Official }, + { "javascript:", nullptr, INetProtocol::Javascript, PrefixInfo::Kind::Official }, + { "ldap:", nullptr, INetProtocol::Ldap, PrefixInfo::Kind::Official }, + { "macro:", "staroffice.macro:", INetProtocol::Macro, + PrefixInfo::Kind::Internal }, + { "mailto:", nullptr, INetProtocol::Mailto, PrefixInfo::Kind::Official }, + { "private:", "staroffice.private:", INetProtocol::PrivSoffice, + PrefixInfo::Kind::Internal }, + { "private:factory/", "staroffice.factory:", + INetProtocol::PrivSoffice, PrefixInfo::Kind::Internal }, + { "private:helpid/", "staroffice.helpid:", INetProtocol::PrivSoffice, + PrefixInfo::Kind::Internal }, + { "private:java/", "staroffice.java:", INetProtocol::PrivSoffice, + PrefixInfo::Kind::Internal }, + { "private:searchfolder:", "staroffice.searchfolder:", + INetProtocol::PrivSoffice, PrefixInfo::Kind::Internal }, + { "private:trashcan:", "staroffice.trashcan:", + INetProtocol::PrivSoffice, PrefixInfo::Kind::Internal }, + { "sftp:", nullptr, INetProtocol::Sftp, PrefixInfo::Kind::Official }, + { "slot:", "staroffice.slot:", INetProtocol::Slot, + PrefixInfo::Kind::Internal }, + { "smb:", nullptr, INetProtocol::Smb, PrefixInfo::Kind::Official }, + { "staroffice.component:", ".component:", INetProtocol::Component, + PrefixInfo::Kind::External }, + { "staroffice.db:", "db:", INetProtocol::Db, PrefixInfo::Kind::External }, + { "staroffice.factory:", "private:factory/", + INetProtocol::PrivSoffice, PrefixInfo::Kind::External }, + { "staroffice.helpid:", "private:helpid/", INetProtocol::PrivSoffice, + PrefixInfo::Kind::External }, + { "staroffice.hid:", "hid:", INetProtocol::Hid, + PrefixInfo::Kind::External }, + { "staroffice.java:", "private:java/", INetProtocol::PrivSoffice, + PrefixInfo::Kind::External }, + { "staroffice.macro:", "macro:", INetProtocol::Macro, + PrefixInfo::Kind::External }, + { "staroffice.private:", "private:", INetProtocol::PrivSoffice, + PrefixInfo::Kind::External }, + { "staroffice.searchfolder:", "private:searchfolder:", + INetProtocol::PrivSoffice, PrefixInfo::Kind::External }, + { "staroffice.slot:", "slot:", INetProtocol::Slot, + PrefixInfo::Kind::External }, + { "staroffice.trashcan:", "private:trashcan:", + INetProtocol::PrivSoffice, PrefixInfo::Kind::External }, + { "staroffice.uno:", ".uno:", INetProtocol::Uno, + PrefixInfo::Kind::External }, + { "staroffice:", "private:", INetProtocol::PrivSoffice, + PrefixInfo::Kind::External }, + { "telnet:", nullptr, INetProtocol::Telnet, PrefixInfo::Kind::Official }, + { "vnd.libreoffice.cmis:", nullptr, INetProtocol::Cmis, PrefixInfo::Kind::Internal }, + { "vnd.sun.star.cmd:", nullptr, INetProtocol::VndSunStarCmd, + PrefixInfo::Kind::Official }, + { "vnd.sun.star.expand:", nullptr, INetProtocol::VndSunStarExpand, + PrefixInfo::Kind::Official }, + { "vnd.sun.star.help:", nullptr, INetProtocol::VndSunStarHelp, + PrefixInfo::Kind::Official }, + { "vnd.sun.star.hier:", nullptr, INetProtocol::VndSunStarHier, + PrefixInfo::Kind::Official }, + { "vnd.sun.star.pkg:", nullptr, INetProtocol::VndSunStarPkg, + PrefixInfo::Kind::Official }, + { "vnd.sun.star.tdoc:", nullptr, INetProtocol::VndSunStarTdoc, + PrefixInfo::Kind::Official }, + { "vnd.sun.star.webdav:", nullptr, INetProtocol::VndSunStarWebdav, + PrefixInfo::Kind::Official } + }; +/* This list needs to be sorted, or you'll introduce serious bugs */ + + PrefixInfo const * pFirst = aMap + 1; + PrefixInfo const * pLast = aMap + sizeof aMap / sizeof (PrefixInfo) - 1; + PrefixInfo const * pMatch = nullptr; + sal_Unicode const * pMatched = rBegin; + sal_Unicode const * p = rBegin; + sal_Int32 i = 0; + for (; pFirst < pLast; ++i) + { + if (pFirst->m_pPrefix[i] == '\0') + { + pMatch = pFirst++; + pMatched = p; + } + if (p >= pEnd) + break; + sal_uInt32 nChar = rtl::toAsciiLowerCase(*p++); + while (pFirst <= pLast && static_cast<unsigned char>(pFirst->m_pPrefix[i]) < nChar) + ++pFirst; + while (pFirst <= pLast && static_cast<unsigned char>(pLast->m_pPrefix[i]) > nChar) + --pLast; + } + if (pFirst == pLast) + { + char const * q = pFirst->m_pPrefix + i; + while (p < pEnd && *q != '\0' + && rtl::toAsciiLowerCase(*p) == static_cast<unsigned char>(*q)) + { + ++p; + ++q; + } + if (*q == '\0') + { + rBegin = p; + return pFirst; + } + } + rBegin = pMatched; + return pMatch; +} + +sal_Int32 INetURLObject::getAuthorityBegin() const +{ + DBG_ASSERT(getSchemeInfo().m_bAuthority, + "INetURLObject::getAuthority(): Bad scheme"); + sal_Int32 nBegin; + if (m_aUser.isPresent()) + nBegin = m_aUser.getBegin(); + else if (m_aHost.isPresent()) + nBegin = m_aHost.getBegin(); + else + nBegin = m_aPath.getBegin(); + nBegin -= RTL_CONSTASCII_LENGTH("//"); + DBG_ASSERT(m_aAbsURIRef[nBegin] == '/' && m_aAbsURIRef[nBegin + 1] == '/', + "INetURLObject::getAuthority(): Bad authority"); + return nBegin; +} + +INetURLObject::SubString INetURLObject::getAuthority() const +{ + sal_Int32 nBegin = getAuthorityBegin(); + sal_Int32 nEnd = m_aPort.isPresent() ? m_aPort.getEnd() : + m_aHost.isPresent() ? m_aHost.getEnd() : + m_aAuth.isPresent() ? m_aAuth.getEnd() : + m_aUser.isPresent() ? m_aUser.getEnd() : + nBegin + RTL_CONSTASCII_LENGTH("//"); + return SubString(nBegin, nEnd - nBegin); +} + +bool INetURLObject::setUser(std::u16string_view rTheUser, + rtl_TextEncoding eCharset) +{ + if ( + !getSchemeInfo().m_bUser + ) + { + return false; + } + + OUStringBuffer aNewUser; + encodeText(aNewUser, rTheUser, PART_USER_PASSWORD, + EncodeMechanism::WasEncoded, eCharset, false); + sal_Int32 nDelta; + if (m_aUser.isPresent()) + nDelta = m_aUser.set(m_aAbsURIRef, aNewUser); + else if (m_aHost.isPresent()) + { + m_aAbsURIRef.insert(m_aHost.getBegin(), u'@'); + nDelta = m_aUser.set(m_aAbsURIRef, aNewUser, m_aHost.getBegin()) + 1; + } + else if (getSchemeInfo().m_bHost) + return false; + else + nDelta = m_aUser.set(m_aAbsURIRef, aNewUser, m_aPath.getBegin()); + m_aAuth += nDelta; + m_aHost += nDelta; + m_aPort += nDelta; + m_aPath += nDelta; + m_aQuery += nDelta; + m_aFragment += nDelta; + return true; +} + +namespace +{ + void lcl_Erase(OUStringBuffer &rBuf, sal_Int32 index, sal_Int32 count) + { + OUString sTemp(rBuf.makeStringAndClear()); + rBuf.append(sTemp.replaceAt(index, count, u"")); + } +} + +bool INetURLObject::clearPassword() +{ + if (!getSchemeInfo().m_bPassword) + return false; + if (m_aAuth.isPresent()) + { + lcl_Erase(m_aAbsURIRef, m_aAuth.getBegin() - 1, + m_aAuth.getLength() + 1); + sal_Int32 nDelta = m_aAuth.clear() - 1; + m_aHost += nDelta; + m_aPort += nDelta; + m_aPath += nDelta; + m_aQuery += nDelta; + m_aFragment += nDelta; + } + return true; +} + +bool INetURLObject::setPassword(std::u16string_view rThePassword, + rtl_TextEncoding eCharset) +{ + if (!getSchemeInfo().m_bPassword) + return false; + OUStringBuffer aNewAuth; + encodeText(aNewAuth, rThePassword, PART_USER_PASSWORD, + EncodeMechanism::WasEncoded, eCharset, false); + sal_Int32 nDelta; + if (m_aAuth.isPresent()) + nDelta = m_aAuth.set(m_aAbsURIRef, aNewAuth); + else if (m_aUser.isPresent()) + { + m_aAbsURIRef.insert(m_aUser.getEnd(), u':'); + nDelta + = m_aAuth.set(m_aAbsURIRef, aNewAuth, m_aUser.getEnd() + 1) + 1; + } + else if (m_aHost.isPresent()) + { + m_aAbsURIRef.insert(m_aHost.getBegin(), ":@" ); + m_aUser.set(m_aAbsURIRef, std::u16string_view{}, m_aHost.getBegin()); + nDelta + = m_aAuth.set(m_aAbsURIRef, aNewAuth, m_aHost.getBegin() + 1) + 2; + } + else if (getSchemeInfo().m_bHost) + return false; + else + { + m_aAbsURIRef.insert(m_aPath.getBegin(), u':'); + m_aUser.set(m_aAbsURIRef, std::u16string_view{}, m_aPath.getBegin()); + nDelta + = m_aAuth.set(m_aAbsURIRef, aNewAuth, m_aPath.getBegin() + 1) + 1; + } + m_aHost += nDelta; + m_aPort += nDelta; + m_aPath += nDelta; + m_aQuery += nDelta; + m_aFragment += nDelta; + return true; +} + +// static +bool INetURLObject::parseHost(sal_Unicode const *& rBegin, sal_Unicode const * pEnd, + OUStringBuffer* pCanonic) +{ + // RFC 2373 is inconsistent about how to write an IPv6 address in which an + // IPv4 address directly follows the abbreviating "::". The ABNF in + // Appendix B suggests ":::13.1.68.3", while an example in 2.2/3 explicitly + // mentions "::13:1.68.3". This algorithm accepts both variants: + enum State { STATE_INITIAL, STATE_LABEL, STATE_LABEL_HYPHEN, + STATE_LABEL_DOT, STATE_TOPLABEL, STATE_TOPLABEL_HYPHEN, + STATE_TOPLABEL_DOT, STATE_IP4, STATE_IP4_DOT, STATE_IP6, + STATE_IP6_COLON, STATE_IP6_2COLON, STATE_IP6_3COLON, + STATE_IP6_HEXSEQ1, STATE_IP6_HEXSEQ1_COLON, + STATE_IP6_HEXSEQ1_MAYBE_IP4, STATE_IP6_HEXSEQ2, + STATE_IP6_HEXSEQ2_COLON, STATE_IP6_HEXSEQ2_MAYBE_IP4, + STATE_IP6_IP4, STATE_IP6_IP4_DOT, STATE_IP6_DONE }; + sal_uInt32 nNumber = 0; + int nDigits = 0; + int nOctets = 0; + State eState = STATE_INITIAL; + sal_Unicode const * p = rBegin; + sal_Int32 nOriginalCanonicLength = pCanonic ? pCanonic->getLength() : 0; + for (; p != pEnd; ++p) + switch (eState) + { + case STATE_INITIAL: + if (*p == '[') + { + if (pCanonic) + pCanonic->append('['); + eState = STATE_IP6; + } + else if (rtl::isAsciiAlpha(*p) || *p == '_') + eState = STATE_TOPLABEL; + else if (rtl::isAsciiDigit(*p)) + { + nNumber = INetMIME::getWeight(*p); + nDigits = 1; + nOctets = 1; + eState = STATE_IP4; + } + else + goto done; + break; + + case STATE_LABEL: + if (*p == '.') + eState = STATE_LABEL_DOT; + else if (*p == '-') + eState = STATE_LABEL_HYPHEN; + else if (!rtl::isAsciiAlphanumeric(*p) && *p != '_') + goto done; + break; + + case STATE_LABEL_HYPHEN: + if (rtl::isAsciiAlphanumeric(*p) || *p == '_') + eState = STATE_LABEL; + else if (*p != '-') + goto done; + break; + + case STATE_LABEL_DOT: + if (rtl::isAsciiAlpha(*p) || *p == '_') + eState = STATE_TOPLABEL; + else if (rtl::isAsciiDigit(*p)) + eState = STATE_LABEL; + else + goto done; + break; + + case STATE_TOPLABEL: + if (*p == '.') + eState = STATE_TOPLABEL_DOT; + else if (*p == '-') + eState = STATE_TOPLABEL_HYPHEN; + else if (!rtl::isAsciiAlphanumeric(*p) && *p != '_') + goto done; + break; + + case STATE_TOPLABEL_HYPHEN: + if (rtl::isAsciiAlphanumeric(*p) || *p == '_') + eState = STATE_TOPLABEL; + else if (*p != '-') + goto done; + break; + + case STATE_TOPLABEL_DOT: + if (rtl::isAsciiAlpha(*p) || *p == '_') + eState = STATE_TOPLABEL; + else if (rtl::isAsciiDigit(*p)) + eState = STATE_LABEL; + else + goto done; + break; + + case STATE_IP4: + if (*p == '.') + if (nOctets < 4) + { + if (pCanonic) + { + pCanonic->append(static_cast<sal_Int64>(nNumber)); + pCanonic->append( '.' ); + } + ++nOctets; + eState = STATE_IP4_DOT; + } + else + eState = STATE_LABEL_DOT; + else if (*p == '-') + eState = STATE_LABEL_HYPHEN; + else if (rtl::isAsciiAlpha(*p) || *p == '_') + eState = STATE_LABEL; + else if (rtl::isAsciiDigit(*p)) + if (nDigits < 3) + { + nNumber = 10 * nNumber + INetMIME::getWeight(*p); + ++nDigits; + } + else + eState = STATE_LABEL; + else + goto done; + break; + + case STATE_IP4_DOT: + if (rtl::isAsciiAlpha(*p) || *p == '_') + eState = STATE_TOPLABEL; + else if (rtl::isAsciiDigit(*p)) + { + nNumber = INetMIME::getWeight(*p); + nDigits = 1; + eState = STATE_IP4; + } + else + goto done; + break; + + case STATE_IP6: + if (*p == ':') + eState = STATE_IP6_COLON; + else if (rtl::isAsciiHexDigit(*p)) + { + nNumber = INetMIME::getHexWeight(*p); + nDigits = 1; + eState = STATE_IP6_HEXSEQ1; + } + else + goto done; + break; + + case STATE_IP6_COLON: + if (*p == ':') + { + if (pCanonic) + pCanonic->append("::"); + eState = STATE_IP6_2COLON; + } + else + goto done; + break; + + case STATE_IP6_2COLON: + if (*p == ']') + eState = STATE_IP6_DONE; + else if (*p == ':') + { + if (pCanonic) + pCanonic->append(':'); + eState = STATE_IP6_3COLON; + } + else if (rtl::isAsciiDigit(*p)) + { + nNumber = INetMIME::getWeight(*p); + nDigits = 1; + eState = STATE_IP6_HEXSEQ2_MAYBE_IP4; + } + else if (rtl::isAsciiHexDigit(*p)) + { + nNumber = INetMIME::getHexWeight(*p); + nDigits = 1; + eState = STATE_IP6_HEXSEQ2; + } + else + goto done; + break; + + case STATE_IP6_3COLON: + if (rtl::isAsciiDigit(*p)) + { + nNumber = INetMIME::getWeight(*p); + nDigits = 1; + nOctets = 1; + eState = STATE_IP6_IP4; + } + else + goto done; + break; + + case STATE_IP6_HEXSEQ1: + if (*p == ']') + { + if (pCanonic) + pCanonic->append( + OUString::number(nNumber, 16)); + eState = STATE_IP6_DONE; + } + else if (*p == ':') + { + if (pCanonic) + { + pCanonic->append( + OUString::number(nNumber, 16)); + pCanonic->append(':'); + } + eState = STATE_IP6_HEXSEQ1_COLON; + } + else if (rtl::isAsciiHexDigit(*p) && nDigits < 4) + { + nNumber = 16 * nNumber + INetMIME::getHexWeight(*p); + ++nDigits; + } + else + goto done; + break; + + case STATE_IP6_HEXSEQ1_COLON: + if (*p == ':') + { + if (pCanonic) + pCanonic->append(':'); + eState = STATE_IP6_2COLON; + } + else if (rtl::isAsciiDigit(*p)) + { + nNumber = INetMIME::getWeight(*p); + nDigits = 1; + eState = STATE_IP6_HEXSEQ1_MAYBE_IP4; + } + else if (rtl::isAsciiHexDigit(*p)) + { + nNumber = INetMIME::getHexWeight(*p); + nDigits = 1; + eState = STATE_IP6_HEXSEQ1; + } + else + goto done; + break; + + case STATE_IP6_HEXSEQ1_MAYBE_IP4: + if (*p == ']') + { + if (pCanonic) + pCanonic->append( + OUString::number(nNumber, 16)); + eState = STATE_IP6_DONE; + } + else if (*p == ':') + { + if (pCanonic) + { + pCanonic->append( + OUString::number(nNumber, 16)); + pCanonic->append(':'); + } + eState = STATE_IP6_HEXSEQ1_COLON; + } + else if (*p == '.') + { + nNumber = 100 * (nNumber >> 8) + 10 * (nNumber >> 4 & 15) + + (nNumber & 15); + if (pCanonic) + { + pCanonic->append( + OUString::number(nNumber)); + pCanonic->append('.'); + } + nOctets = 2; + eState = STATE_IP6_IP4_DOT; + } + else if (rtl::isAsciiDigit(*p) && nDigits < 3) + { + nNumber = 16 * nNumber + INetMIME::getWeight(*p); + ++nDigits; + } + else if (rtl::isAsciiHexDigit(*p) && nDigits < 4) + { + nNumber = 16 * nNumber + INetMIME::getHexWeight(*p); + ++nDigits; + eState = STATE_IP6_HEXSEQ1; + } + else + goto done; + break; + + case STATE_IP6_HEXSEQ2: + if (*p == ']') + { + if (pCanonic) + pCanonic->append( + OUString::number(nNumber, 16)); + eState = STATE_IP6_DONE; + } + else if (*p == ':') + { + if (pCanonic) + { + pCanonic->append( + OUString::number(nNumber, 16)); + pCanonic->append(':'); + } + eState = STATE_IP6_HEXSEQ2_COLON; + } + else if (rtl::isAsciiHexDigit(*p) && nDigits < 4) + { + nNumber = 16 * nNumber + INetMIME::getHexWeight(*p); + ++nDigits; + } + else + goto done; + break; + + case STATE_IP6_HEXSEQ2_COLON: + if (rtl::isAsciiDigit(*p)) + { + nNumber = INetMIME::getWeight(*p); + nDigits = 1; + eState = STATE_IP6_HEXSEQ2_MAYBE_IP4; + } + else if (rtl::isAsciiHexDigit(*p)) + { + nNumber = INetMIME::getHexWeight(*p); + nDigits = 1; + eState = STATE_IP6_HEXSEQ2; + } + else + goto done; + break; + + case STATE_IP6_HEXSEQ2_MAYBE_IP4: + if (*p == ']') + { + if (pCanonic) + pCanonic->append( + OUString::number(nNumber, 16)); + eState = STATE_IP6_DONE; + } + else if (*p == ':') + { + if (pCanonic) + { + pCanonic->append( + OUString::number(nNumber, 16)); + pCanonic->append(':'); + } + eState = STATE_IP6_HEXSEQ2_COLON; + } + else if (*p == '.') + { + nNumber = 100 * (nNumber >> 8) + 10 * (nNumber >> 4 & 15) + + (nNumber & 15); + if (pCanonic) + { + pCanonic->append( + OUString::number(nNumber)); + pCanonic->append('.'); + } + nOctets = 2; + eState = STATE_IP6_IP4_DOT; + } + else if (rtl::isAsciiDigit(*p) && nDigits < 3) + { + nNumber = 16 * nNumber + INetMIME::getWeight(*p); + ++nDigits; + } + else if (rtl::isAsciiHexDigit(*p) && nDigits < 4) + { + nNumber = 16 * nNumber + INetMIME::getHexWeight(*p); + ++nDigits; + eState = STATE_IP6_HEXSEQ2; + } + else + goto done; + break; + + case STATE_IP6_IP4: + if (*p == ']') + if (nOctets == 4) + { + if (pCanonic) + pCanonic->append( + OUString::number(nNumber)); + eState = STATE_IP6_DONE; + } + else + goto done; + else if (*p == '.') + if (nOctets < 4) + { + if (pCanonic) + { + pCanonic->append( + OUString::number(nNumber)); + pCanonic->append('.'); + } + ++nOctets; + eState = STATE_IP6_IP4_DOT; + } + else + goto done; + else if (rtl::isAsciiDigit(*p) && nDigits < 3) + { + nNumber = 10 * nNumber + INetMIME::getWeight(*p); + ++nDigits; + } + else + goto done; + break; + + case STATE_IP6_IP4_DOT: + if (rtl::isAsciiDigit(*p)) + { + nNumber = INetMIME::getWeight(*p); + nDigits = 1; + eState = STATE_IP6_IP4; + } + else + goto done; + break; + + case STATE_IP6_DONE: + goto done; + } + done: + switch (eState) + { + case STATE_LABEL: + case STATE_TOPLABEL: + case STATE_TOPLABEL_DOT: + if (pCanonic) + { + pCanonic->setLength(nOriginalCanonicLength); + pCanonic->append(rBegin, p - rBegin); + } + rBegin = p; + return true; + + case STATE_IP4: + if (nOctets == 4) + { + if (pCanonic) + pCanonic->append( + OUString::number(nNumber)); + rBegin = p; + return true; + } + if (pCanonic) + pCanonic->setLength(nOriginalCanonicLength); + return false; + + case STATE_IP6_DONE: + if (pCanonic) + pCanonic->append(']'); + rBegin = p; + return true; + + default: + if (pCanonic) + pCanonic->setLength(nOriginalCanonicLength); + return false; + } +} + +// static +bool INetURLObject::parseHostOrNetBiosName( + sal_Unicode const * pBegin, sal_Unicode const * pEnd, + EncodeMechanism eMechanism, rtl_TextEncoding eCharset, bool bNetBiosName, + OUStringBuffer* pCanonic) +{ + if (pBegin >= pEnd) + return true; + sal_Int32 nOriginalCanonicLength = pCanonic ? pCanonic->getLength() : 0; + if (sal_Unicode const* p = pBegin; parseHost(p, pEnd, pCanonic) && p == pEnd) + return true; + if (pCanonic) + pCanonic->setLength(nOriginalCanonicLength); // discard parseHost results + if (!bNetBiosName) + return false; + while (pBegin < pEnd) + { + EscapeType eEscapeType; + switch (sal_uInt32 nUTF32 = getUTF32(pBegin, pEnd, eMechanism, eCharset, eEscapeType)) + { + default: + if (INetMIME::isVisible(nUTF32)) + { + if (pCanonic) + appendUCS4(*pCanonic, nUTF32, eEscapeType, PART_URIC, eCharset, true); + break; + } + [[fallthrough]]; + case '"': + case '*': + case '+': + case ',': + case '/': + case ':': + case ';': + case '<': + case '=': + case '>': + case '?': + case '[': + case '\\': + case ']': + case '`': + case '|': + if (pCanonic) + pCanonic->setLength(nOriginalCanonicLength); + return false; + } + } + return true; +} + +bool INetURLObject::setHost(std::u16string_view rTheHost, + rtl_TextEncoding eCharset) +{ + if (!getSchemeInfo().m_bHost) + return false; + OUStringBuffer aSynHost(rTheHost); + bool bNetBiosName = false; + switch (m_eScheme) + { + case INetProtocol::File: + { + if (OUString::unacquired(aSynHost).equalsIgnoreAsciiCase("localhost")) + { + aSynHost.setLength(0); + } + bNetBiosName = true; + } + break; + case INetProtocol::Ldap: + if (aSynHost.isEmpty() && m_aPort.isPresent()) + return false; + break; + + default: + if (aSynHost.isEmpty()) + return false; + break; + } + if (!parseHostOrNetBiosName( + aSynHost.getStr(), aSynHost.getStr() + aSynHost.getLength(), + EncodeMechanism::WasEncoded, eCharset, bNetBiosName, &aSynHost)) + return false; + sal_Int32 nDelta = m_aHost.set(m_aAbsURIRef, aSynHost); + m_aPort += nDelta; + m_aPath += nDelta; + m_aQuery += nDelta; + m_aFragment += nDelta; + return true; +} + +// static +bool INetURLObject::parsePath(INetProtocol eScheme, + sal_Unicode const ** pBegin, + sal_Unicode const * pEnd, + EncodeMechanism eMechanism, + rtl_TextEncoding eCharset, + bool bSkippedInitialSlash, + sal_uInt32 nSegmentDelimiter, + sal_uInt32 nAltSegmentDelimiter, + sal_uInt32 nQueryDelimiter, + sal_uInt32 nFragmentDelimiter, + OUStringBuffer &rSynPath) +{ + DBG_ASSERT(pBegin, "INetURLObject::parsePath(): Null output param"); + + sal_Unicode const * pPos = *pBegin; + const sal_Int32 nSynPathBeforeLen = rSynPath.getLength(); + switch (eScheme) + { + case INetProtocol::NotValid: + return false; + + case INetProtocol::Ftp: + if (pPos < pEnd && *pPos != '/' && *pPos != nFragmentDelimiter) + goto failed; + while (pPos < pEnd && *pPos != nFragmentDelimiter) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 = getUTF32(pPos, pEnd, eMechanism, + eCharset, eEscapeType); + appendUCS4(rSynPath, nUTF32, eEscapeType, + PART_HTTP_PATH, eCharset, true); + } + if (rSynPath.getLength() - nSynPathBeforeLen == 0) + rSynPath.append('/'); + break; + + case INetProtocol::Http: + case INetProtocol::VndSunStarWebdav: + case INetProtocol::Https: + case INetProtocol::Smb: + case INetProtocol::Cmis: + if (pPos < pEnd && *pPos != '/' && *pPos != nQueryDelimiter + && *pPos != nFragmentDelimiter) + goto failed; + while (pPos < pEnd && *pPos != nQueryDelimiter + && *pPos != nFragmentDelimiter) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 = getUTF32(pPos, pEnd, eMechanism, + eCharset, eEscapeType); + appendUCS4(rSynPath, nUTF32, eEscapeType, + PART_HTTP_PATH, eCharset, true); + } + if (rSynPath.getLength() - nSynPathBeforeLen == 0) + rSynPath.append('/'); + break; + + case INetProtocol::File: + { + if (bSkippedInitialSlash) + rSynPath.append('/'); + else if (pPos < pEnd + && *pPos != nSegmentDelimiter + && *pPos != nAltSegmentDelimiter) + goto failed; + while (pPos < pEnd && *pPos != nFragmentDelimiter) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 = getUTF32(pPos, pEnd, eMechanism, + eCharset, eEscapeType); + if (eEscapeType == EscapeType::NONE) + { + if (nUTF32 == nSegmentDelimiter + || nUTF32 == nAltSegmentDelimiter) + { + rSynPath.append('/'); + continue; + } + else if (nUTF32 == '|' + && (pPos == pEnd + || *pPos == nFragmentDelimiter + || *pPos == nSegmentDelimiter + || *pPos == nAltSegmentDelimiter) + && rSynPath.getLength() - nSynPathBeforeLen == 2 + && rtl::isAsciiAlpha(rSynPath[nSynPathBeforeLen + 1])) + { + // A first segment of <ALPHA "|"> is translated to + // <ALPHA ":">: + rSynPath.append(':'); + continue; + } + } + appendUCS4(rSynPath, nUTF32, eEscapeType, + PART_PCHAR, eCharset, true); + } + if (rSynPath.getLength() - nSynPathBeforeLen == 0) + rSynPath.append('/'); + break; + } + + case INetProtocol::Mailto: + while (pPos < pEnd && *pPos != nQueryDelimiter + && *pPos != nFragmentDelimiter) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 = getUTF32(pPos, pEnd, eMechanism, + eCharset, eEscapeType); + appendUCS4(rSynPath, nUTF32, eEscapeType, + PART_MAILTO, eCharset, true); + } + break; + + + case INetProtocol::PrivSoffice: + case INetProtocol::Slot: + case INetProtocol::Hid: + case INetProtocol::Macro: + case INetProtocol::Uno: + case INetProtocol::Component: + case INetProtocol::Ldap: + while (pPos < pEnd && *pPos != nQueryDelimiter + && *pPos != nFragmentDelimiter) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 = getUTF32(pPos, pEnd, eMechanism, + eCharset, eEscapeType); + appendUCS4(rSynPath, nUTF32, eEscapeType, + PART_PATH_BEFORE_QUERY, eCharset, true); + } + break; + + case INetProtocol::VndSunStarHelp: + if (pPos == pEnd + || *pPos == nQueryDelimiter + || *pPos == nFragmentDelimiter) + rSynPath.append('/'); + else + { + if (*pPos != '/') + goto failed; + while (pPos < pEnd && *pPos != nQueryDelimiter + && *pPos != nFragmentDelimiter) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 = getUTF32(pPos, pEnd, + eMechanism, + eCharset, eEscapeType); + appendUCS4(rSynPath, nUTF32, eEscapeType, + PART_HTTP_PATH, eCharset, true); + } + } + break; + + case INetProtocol::Javascript: + case INetProtocol::Data: + case INetProtocol::Cid: + case INetProtocol::Db: + while (pPos < pEnd && *pPos != nFragmentDelimiter) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 = getUTF32(pPos, pEnd, eMechanism, + eCharset, eEscapeType); + appendUCS4(rSynPath, nUTF32, eEscapeType, + PART_URIC, eCharset, true); + } + break; + + case INetProtocol::VndSunStarHier: + case INetProtocol::VndSunStarPkg: + if (pPos < pEnd && *pPos != '/' + && *pPos != nQueryDelimiter && *pPos != nFragmentDelimiter) + goto failed; + while (pPos < pEnd && *pPos != nQueryDelimiter + && *pPos != nFragmentDelimiter) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 = getUTF32(pPos, pEnd, eMechanism, + eCharset, eEscapeType); + if (eEscapeType == EscapeType::NONE && nUTF32 == '/') + rSynPath.append('/'); + else + appendUCS4(rSynPath, nUTF32, eEscapeType, + PART_PCHAR, eCharset, false); + } + if (rSynPath.getLength() - nSynPathBeforeLen == 0) + rSynPath.append('/'); + break; + + case INetProtocol::VndSunStarCmd: + case INetProtocol::VndSunStarExpand: + { + if (pPos == pEnd || *pPos == nFragmentDelimiter) + goto failed; + Part ePart = PART_URIC_NO_SLASH; + while (pPos != pEnd && *pPos != nFragmentDelimiter) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 = getUTF32(pPos, pEnd, eMechanism, + eCharset, eEscapeType); + appendUCS4(rSynPath, nUTF32, eEscapeType, ePart, + eCharset, true); + ePart = PART_URIC; + } + break; + } + + case INetProtocol::Telnet: + if (pPos < pEnd) + { + if (*pPos != '/' || pEnd - pPos > 1) + goto failed; + ++pPos; + } + rSynPath.append('/'); + break; + + case INetProtocol::VndSunStarTdoc: + if (pPos == pEnd || *pPos != '/') + goto failed; + while (pPos < pEnd && *pPos != nFragmentDelimiter) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 = getUTF32(pPos, pEnd, eMechanism, + eCharset, eEscapeType); + if (eEscapeType == EscapeType::NONE && nUTF32 == '/') + rSynPath.append('/'); + else + appendUCS4(rSynPath, nUTF32, eEscapeType, + PART_PCHAR, eCharset, false); + } + break; + + case INetProtocol::Generic: + case INetProtocol::Sftp: + while (pPos < pEnd && *pPos != nFragmentDelimiter) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 = getUTF32(pPos, pEnd, eMechanism, + eCharset, eEscapeType); + appendUCS4(rSynPath, nUTF32, eEscapeType, + PART_URIC, eCharset, true); + } + if (rSynPath.isEmpty()) + goto failed; + break; + default: + OSL_ASSERT(false); + break; + } + + *pBegin = pPos; + return true; +failed: + rSynPath.setLength(nSynPathBeforeLen); + return false; +} + +bool INetURLObject::setPath(std::u16string_view rThePath, + EncodeMechanism eMechanism, + rtl_TextEncoding eCharset) +{ + OUStringBuffer aSynPath(256); + sal_Unicode const * p = rThePath.data(); + sal_Unicode const * pEnd = p + rThePath.size(); + if (!parsePath(m_eScheme, &p, pEnd, eMechanism, eCharset, false, + '/', 0x80000000, 0x80000000, 0x80000000, aSynPath) + || p != pEnd) + return false; + sal_Int32 nDelta = m_aPath.set(m_aAbsURIRef, aSynPath); + m_aQuery += nDelta; + m_aFragment += nDelta; + return true; +} + +bool INetURLObject::checkHierarchical() const { + if (m_eScheme == INetProtocol::VndSunStarExpand) { + OSL_FAIL( + "INetURLObject::checkHierarchical vnd.sun.star.expand"); + return true; + } else { + return getSchemeInfo().m_bHierarchical; + } +} + +bool INetURLObject::Append(std::u16string_view rTheSegment, + EncodeMechanism eMechanism, + rtl_TextEncoding eCharset) +{ + return insertName(rTheSegment, false, LAST_SEGMENT, eMechanism, eCharset); +} + +INetURLObject::SubString INetURLObject::getSegment(sal_Int32 nIndex, + bool bIgnoreFinalSlash) + const +{ + DBG_ASSERT(nIndex >= 0 || nIndex == LAST_SEGMENT, + "INetURLObject::getSegment(): Bad index"); + + if (!checkHierarchical()) + return SubString(); + + sal_Unicode const * pPathBegin + = m_aAbsURIRef.getStr() + m_aPath.getBegin(); + sal_Unicode const * pPathEnd = pPathBegin + m_aPath.getLength(); + sal_Unicode const * pSegBegin; + sal_Unicode const * pSegEnd; + if (nIndex == LAST_SEGMENT) + { + pSegEnd = pPathEnd; + if (bIgnoreFinalSlash && pSegEnd > pPathBegin && pSegEnd[-1] == '/') + --pSegEnd; + if (pSegEnd <= pPathBegin) + return SubString(); + pSegBegin = pSegEnd - 1; + while (pSegBegin > pPathBegin && *pSegBegin != '/') + --pSegBegin; + } + else + { + pSegBegin = pPathBegin; + while (nIndex-- > 0) + do + { + ++pSegBegin; + if (pSegBegin >= pPathEnd) + return SubString(); + } + while (*pSegBegin != '/'); + pSegEnd = pSegBegin + 1; + while (pSegEnd < pPathEnd && *pSegEnd != '/') + ++pSegEnd; + } + + return SubString(pSegBegin - m_aAbsURIRef.getStr(), + pSegEnd - pSegBegin); +} + +bool INetURLObject::insertName(std::u16string_view rTheName, + bool bAppendFinalSlash, sal_Int32 nIndex, + EncodeMechanism eMechanism, + rtl_TextEncoding eCharset) +{ + DBG_ASSERT(nIndex >= 0 || nIndex == LAST_SEGMENT, + "INetURLObject::insertName(): Bad index"); + + if (!checkHierarchical()) + return false; + + sal_Unicode const * pPathBegin + = m_aAbsURIRef.getStr() + m_aPath.getBegin(); + sal_Unicode const * pPathEnd = pPathBegin + m_aPath.getLength(); + sal_Unicode const * pPrefixEnd; + bool bInsertSlash; + sal_Unicode const * pSuffixBegin; + if (nIndex == LAST_SEGMENT) + { + pPrefixEnd = pPathEnd; + if (pPrefixEnd > pPathBegin && + pPrefixEnd[-1] == '/') + { + --pPrefixEnd; + } + bInsertSlash = bAppendFinalSlash; + pSuffixBegin = pPathEnd; + } + else if (nIndex == 0) + { + pPrefixEnd = pPathBegin; + bInsertSlash = + (pPathBegin < pPathEnd && *pPathBegin != '/') || + (pPathBegin == pPathEnd && bAppendFinalSlash); + pSuffixBegin = + (pPathEnd - pPathBegin == 1 && *pPathBegin == '/' && + !bAppendFinalSlash) + ? pPathEnd : pPathBegin; + } + else + { + pPrefixEnd = pPathBegin; + sal_Unicode const * pEnd = pPathEnd; + if (pEnd > pPathBegin && pEnd[-1] == '/') + --pEnd; + bool bSkip = pPrefixEnd < pEnd && *pPrefixEnd == '/'; + bInsertSlash = false; + pSuffixBegin = pPathEnd; + while (nIndex-- > 0) + for (;;) + { + if (bSkip) + ++pPrefixEnd; + bSkip = true; + if (pPrefixEnd >= pEnd) + { + if (nIndex == 0) + { + bInsertSlash = bAppendFinalSlash; + break; + } + else + return false; + } + if (*pPrefixEnd == '/') + { + pSuffixBegin = pPrefixEnd; + break; + } + } + } + + OUStringBuffer aNewPath(256); + aNewPath.append( + OUString::Concat(std::u16string_view(pPathBegin, pPrefixEnd - pPathBegin)) + + "/"); + encodeText(aNewPath, rTheName, PART_PCHAR, + eMechanism, eCharset, true); + if (bInsertSlash) { + aNewPath.append('/'); + } + aNewPath.append(pSuffixBegin, pPathEnd - pSuffixBegin); + + return setPath(aNewPath, EncodeMechanism::NotCanonical, + RTL_TEXTENCODING_UTF8); +} + +void INetURLObject::clearQuery() +{ + if (HasError()) + return; + if (m_aQuery.isPresent()) + { + lcl_Erase(m_aAbsURIRef, m_aQuery.getBegin() - 1, + m_aQuery.getLength() + 1); + m_aFragment += m_aQuery.clear() - 1; + } +} + +bool INetURLObject::setQuery(std::u16string_view rTheQuery, + EncodeMechanism eMechanism, + rtl_TextEncoding eCharset) +{ + if (!getSchemeInfo().m_bQuery) + return false; + OUStringBuffer aNewQuery; + encodeText(aNewQuery, rTheQuery, PART_URIC, + eMechanism, eCharset, true); + sal_Int32 nDelta; + if (m_aQuery.isPresent()) + nDelta = m_aQuery.set(m_aAbsURIRef, aNewQuery); + else + { + m_aAbsURIRef.insert(m_aPath.getEnd(), u'?'); + nDelta = m_aQuery.set(m_aAbsURIRef, aNewQuery, m_aPath.getEnd() + 1) + + 1; + } + m_aFragment += nDelta; + return true; +} + +bool INetURLObject::clearFragment() +{ + if (HasError()) + return false; + if (m_aFragment.isPresent()) + { + m_aAbsURIRef.setLength(m_aFragment.getBegin() - 1); + m_aFragment.clear(); + } + return true; +} + +bool INetURLObject::setFragment(std::u16string_view rTheFragment, + EncodeMechanism eMechanism, + rtl_TextEncoding eCharset) +{ + if (HasError()) + return false; + OUStringBuffer aNewFragment; + encodeText(aNewFragment, rTheFragment, PART_URIC, + eMechanism, eCharset, true); + if (m_aFragment.isPresent()) + m_aFragment.set(m_aAbsURIRef, aNewFragment); + else + { + m_aAbsURIRef.append('#'); + m_aFragment.set(m_aAbsURIRef, aNewFragment, m_aAbsURIRef.getLength()); + } + return true; +} + +bool INetURLObject::hasDosVolume(FSysStyle eStyle) const +{ + sal_Unicode const * p = m_aAbsURIRef.getStr() + m_aPath.getBegin(); + return (eStyle & FSysStyle::Dos) + && m_aPath.getLength() >= 3 + && p[0] == '/' + && rtl::isAsciiAlpha(p[1]) + && p[2] == ':' + && (m_aPath.getLength() == 3 || p[3] == '/'); +} + +// static +void INetURLObject::encodeText( OUStringBuffer& rOutputBuffer, + sal_Unicode const * pBegin, + sal_Unicode const * pEnd, + Part ePart, EncodeMechanism eMechanism, + rtl_TextEncoding eCharset, + bool bKeepVisibleEscapes) +{ + while (pBegin < pEnd) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 = getUTF32(pBegin, pEnd, + eMechanism, eCharset, eEscapeType); + appendUCS4(rOutputBuffer, nUTF32, eEscapeType, ePart, + eCharset, bKeepVisibleEscapes); + } +} + +// static +OUString INetURLObject::decode(sal_Unicode const * pBegin, + sal_Unicode const * pEnd, + DecodeMechanism eMechanism, + rtl_TextEncoding eCharset) +{ + switch (eMechanism) + { + case DecodeMechanism::NONE: + return OUString(pBegin, pEnd - pBegin); + + case DecodeMechanism::ToIUri: + eCharset = RTL_TEXTENCODING_UTF8; + break; + + default: + break; + } + OUStringBuffer aResult(static_cast<int>(pEnd-pBegin)); + while (pBegin < pEnd) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 = getUTF32(pBegin, pEnd, + EncodeMechanism::WasEncoded, eCharset, eEscapeType); + switch (eEscapeType) + { + case EscapeType::NONE: + aResult.appendUtf32(nUTF32); + break; + + case EscapeType::Octet: + appendEscape(aResult, nUTF32); + break; + + case EscapeType::Utf32: + if ( + rtl::isAscii(nUTF32) && + ( + eMechanism == DecodeMechanism::ToIUri || + ( + eMechanism == DecodeMechanism::Unambiguous && + mustEncode(nUTF32, PART_UNAMBIGUOUS) + ) + ) + ) + { + appendEscape(aResult, nUTF32); + } + else + aResult.appendUtf32(nUTF32); + break; + } + } + return aResult.makeStringAndClear(); +} + +OUString INetURLObject::GetURLNoPass(DecodeMechanism eMechanism, + rtl_TextEncoding eCharset) const +{ + INetURLObject aTemp(*this); + aTemp.clearPassword(); + return aTemp.GetMainURL(eMechanism, eCharset); +} + +OUString INetURLObject::GetURLNoMark(DecodeMechanism eMechanism, + rtl_TextEncoding eCharset) const +{ + INetURLObject aTemp(*this); + aTemp.clearFragment(); + return aTemp.GetMainURL(eMechanism, eCharset); +} + +OUString +INetURLObject::getAbbreviated( + uno::Reference< util::XStringWidth > const & rStringWidth, + sal_Int32 nWidth, + DecodeMechanism eMechanism, + rtl_TextEncoding eCharset) + const +{ + OSL_ENSURE(rStringWidth.is(), "specification violation"); + OUStringBuffer aBuffer; + // make sure that the scheme is copied for generic schemes: getSchemeInfo().m_pScheme + // is empty ("") in that case, so take the scheme from m_aAbsURIRef + if (m_eScheme != INetProtocol::Generic) + { + aBuffer.append(getSchemeInfo().m_sScheme); + } + else + { + if (!m_aAbsURIRef.isEmpty()) + { + sal_Unicode const * pSchemeBegin + = m_aAbsURIRef.getStr(); + sal_Unicode const * pSchemeEnd = pSchemeBegin; + + while (pSchemeEnd[0] != ':') + { + ++pSchemeEnd; + } + aBuffer.append(pSchemeBegin, pSchemeEnd - pSchemeBegin); + } + } + aBuffer.append(':'); + bool bAuthority = getSchemeInfo().m_bAuthority; + sal_Unicode const * pCoreBegin + = m_aAbsURIRef.getStr() + (bAuthority ? getAuthorityBegin() : + m_aPath.getBegin()); + sal_Unicode const * pCoreEnd + = m_aAbsURIRef.getStr() + m_aPath.getBegin() + m_aPath.getLength(); + bool bSegment = false; + if (getSchemeInfo().m_bHierarchical) + { + OUString aRest; + if (m_aQuery.isPresent()) + aRest = "?..."; + else if (m_aFragment.isPresent()) + aRest = "#..."; + OUStringBuffer aTrailer; + sal_Unicode const * pBegin = pCoreBegin; + sal_Unicode const * pEnd = pCoreEnd; + sal_Unicode const * pPrefixBegin = pBegin; + sal_Unicode const * pSuffixEnd = pEnd; + bool bPrefix = true; + bool bSuffix = true; + do + { + if (bSuffix) + { + sal_Unicode const * p = pSuffixEnd - 1; + if (pSuffixEnd == pCoreEnd && *p == '/') + --p; + while (*p != '/') + --p; + if (bAuthority && p == pCoreBegin + 1) + --p; + OUString + aSegment(decode(p + (p == pBegin && pBegin != pCoreBegin ? + 1 : 0), + pSuffixEnd, + eMechanism, + eCharset)); + pSuffixEnd = p; + OUStringBuffer aResult(aBuffer); + if (pSuffixEnd != pBegin) + aResult.append("..."); + aResult.append(aSegment + aTrailer + aRest); + if (rStringWidth-> + queryStringWidth(aResult.makeStringAndClear()) + <= nWidth) + { + aTrailer.insert(0, aSegment); + bSegment = true; + pEnd = pSuffixEnd; + } + else + bSuffix = false; + if (pPrefixBegin > pSuffixEnd) + pPrefixBegin = pSuffixEnd; + if (pBegin == pEnd) + break; + } + if (bPrefix) + { + sal_Unicode const * p + = pPrefixBegin + + (bAuthority && pPrefixBegin == pCoreBegin ? 2 : + 1); + OSL_ASSERT(p <= pEnd); + while (p < pEnd && *p != '/') + ++p; + if (p == pCoreEnd - 1 && *p == '/') + ++p; + OUString + aSegment(decode(pPrefixBegin + + (pPrefixBegin == pCoreBegin ? 0 : + 1), + p == pEnd ? p : p + 1, + eMechanism, + eCharset)); + pPrefixBegin = p; + OUStringBuffer aResult(aBuffer + aSegment); + if (pPrefixBegin != pEnd) + aResult.append("..."); + aResult.append(aTrailer + aRest); + if (rStringWidth-> + queryStringWidth(aResult.makeStringAndClear()) + <= nWidth) + { + aBuffer.append(aSegment); + bSegment = true; + pBegin = pPrefixBegin; + } + else + bPrefix = false; + if (pPrefixBegin > pSuffixEnd) + pSuffixEnd = pPrefixBegin; + if (pBegin == pEnd) + break; + } + } + while (bPrefix || bSuffix); + if (bSegment) + { + if (pPrefixBegin != pBegin || pSuffixEnd != pEnd) + aBuffer.append("..."); + aBuffer.append(aTrailer); + } + } + if (!bSegment) + aBuffer.append(decode(pCoreBegin, + pCoreEnd, + eMechanism, + eCharset)); + if (m_aQuery.isPresent()) + { + aBuffer.append("?" + decode(m_aQuery, eMechanism, eCharset)); + } + if (m_aFragment.isPresent()) + { + aBuffer.append("#" + decode(m_aFragment, eMechanism, eCharset)); + } + if (!aBuffer.isEmpty()) + { + OUStringBuffer aResult(aBuffer); + if (rStringWidth->queryStringWidth(aResult.makeStringAndClear()) + > nWidth) + for (sal_Int32 i = aBuffer.getLength();;) + { + if (i == 0) + { + aBuffer.setLength(aBuffer.getLength() - 1); + if (aBuffer.isEmpty()) + break; + } + else + { + aBuffer.setLength(--i); + aBuffer.append("..."); + } + aResult = aBuffer; + if (rStringWidth-> + queryStringWidth(aResult.makeStringAndClear()) + <= nWidth) + break; + } + } + return aBuffer.makeStringAndClear(); +} + +bool INetURLObject::operator ==(INetURLObject const & rObject) const +{ + if (m_eScheme != rObject.m_eScheme) + return false; + if (m_eScheme == INetProtocol::NotValid) + return std::u16string_view(m_aAbsURIRef) == std::u16string_view(rObject.m_aAbsURIRef); + if ((m_aScheme.compare( + rObject.m_aScheme, m_aAbsURIRef, rObject.m_aAbsURIRef) + != 0) + || GetUser(DecodeMechanism::NONE) != rObject.GetUser(DecodeMechanism::NONE) + || GetPass(DecodeMechanism::NONE) != rObject.GetPass(DecodeMechanism::NONE) + || !GetHost(DecodeMechanism::NONE).equalsIgnoreAsciiCase( + rObject.GetHost(DecodeMechanism::NONE)) + || GetPort() != rObject.GetPort() + || HasParam() != rObject.HasParam() + || GetParam() != rObject.GetParam()) + return false; + OUString aPath1(GetURLPath(DecodeMechanism::NONE)); + OUString aPath2(rObject.GetURLPath(DecodeMechanism::NONE)); + switch (m_eScheme) + { + case INetProtocol::File: + { + // If the URL paths of two file URLs only differ in that one has a + // final '/' and the other has not, take the two paths as + // equivalent (this could be useful for other schemes, too): + sal_Int32 nLength = aPath1.getLength(); + switch (nLength - aPath2.getLength()) + { + case -1: + if (aPath2[nLength] != '/') + return false; + break; + + case 0: + break; + + case 1: + if (aPath1[--nLength] != '/') + return false; + break; + + default: + return false; + } + return aPath1.compareTo(aPath2, nLength) == 0; + } + + default: + return aPath1 == aPath2; + } +} + +bool INetURLObject::ConcatData(INetProtocol eTheScheme, + std::u16string_view rTheUser, + std::u16string_view rThePassword, + std::u16string_view rTheHost, + sal_uInt32 nThePort, + std::u16string_view rThePath) +{ + setInvalid(); + m_eScheme = eTheScheme; + if (HasError() || m_eScheme == INetProtocol::Generic) + return false; + m_aAbsURIRef.setLength(0); + m_aAbsURIRef.append(getSchemeInfo().m_sScheme); + m_aAbsURIRef.append(':'); + if (getSchemeInfo().m_bAuthority) + { + m_aAbsURIRef.append("//"); + bool bUserInfo = false; + if (getSchemeInfo().m_bUser) + { + if (!rTheUser.empty()) + { + OUStringBuffer aNewUser; + encodeText(aNewUser, rTheUser, PART_USER_PASSWORD, + EncodeMechanism::WasEncoded, RTL_TEXTENCODING_UTF8, false); + m_aUser.set(m_aAbsURIRef, aNewUser, m_aAbsURIRef.getLength()); + bUserInfo = true; + } + } + else if (!rTheUser.empty()) + { + setInvalid(); + return false; + } + if (!rThePassword.empty()) + { + if (getSchemeInfo().m_bPassword) + { + m_aAbsURIRef.append(':'); + OUStringBuffer aNewAuth; + encodeText(aNewAuth, rThePassword, PART_USER_PASSWORD, + EncodeMechanism::WasEncoded, RTL_TEXTENCODING_UTF8, false); + m_aAuth.set(m_aAbsURIRef, aNewAuth, m_aAbsURIRef.getLength()); + bUserInfo = true; + } + else + { + setInvalid(); + return false; + } + } + if (bUserInfo && getSchemeInfo().m_bHost) + m_aAbsURIRef.append('@'); + if (getSchemeInfo().m_bHost) + { + OUStringBuffer aSynHost(rTheHost); + bool bNetBiosName = false; + switch (m_eScheme) + { + case INetProtocol::File: + { + if (OUString::unacquired(aSynHost).equalsIgnoreAsciiCase( "localhost" )) + { + aSynHost.setLength(0); + } + bNetBiosName = true; + } + break; + + case INetProtocol::Ldap: + if (aSynHost.isEmpty() && nThePort != 0) + { + setInvalid(); + return false; + } + break; + + default: + if (aSynHost.isEmpty()) + { + setInvalid(); + return false; + } + break; + } + if (!parseHostOrNetBiosName( + aSynHost.getStr(), aSynHost.getStr() + aSynHost.getLength(), + EncodeMechanism::WasEncoded, RTL_TEXTENCODING_UTF8, bNetBiosName, &aSynHost)) + { + setInvalid(); + return false; + } + m_aHost.set(m_aAbsURIRef, aSynHost, m_aAbsURIRef.getLength()); + if (nThePort != 0) + { + if (getSchemeInfo().m_bPort) + { + m_aAbsURIRef.append(':'); + m_aPort.set(m_aAbsURIRef, + OUString::number(nThePort), + m_aAbsURIRef.getLength()); + } + else + { + setInvalid(); + return false; + } + } + } + else if (!rTheHost.empty() || nThePort != 0) + { + setInvalid(); + return false; + } + } + OUStringBuffer aSynPath(256); + sal_Unicode const * p = rThePath.data(); + sal_Unicode const * pEnd = p + rThePath.size(); + if (!parsePath(m_eScheme, &p, pEnd, EncodeMechanism::WasEncoded, RTL_TEXTENCODING_UTF8, false, '/', + 0x80000000, 0x80000000, 0x80000000, aSynPath) + || p != pEnd) + { + setInvalid(); + return false; + } + m_aPath.set(m_aAbsURIRef, aSynPath, m_aAbsURIRef.getLength()); + return true; +} + +// static +OUString INetURLObject::GetAbsURL(std::u16string_view rTheBaseURIRef, + OUString const & rTheRelURIRef, + EncodeMechanism eEncodeMechanism, + DecodeMechanism eDecodeMechanism, + rtl_TextEncoding eCharset) +{ + // Backwards compatibility: + if (rTheRelURIRef.isEmpty() || rTheRelURIRef[0] == '#') + return rTheRelURIRef; + + INetURLObject aTheAbsURIRef; + bool bWasAbsolute; + return INetURLObject(rTheBaseURIRef, eEncodeMechanism, eCharset). + convertRelToAbs(rTheRelURIRef, aTheAbsURIRef, + bWasAbsolute, eEncodeMechanism, + eCharset, false, false, + false, FSysStyle::Detect) + || eEncodeMechanism != EncodeMechanism::WasEncoded + || eDecodeMechanism != DecodeMechanism::ToIUri + || eCharset != RTL_TEXTENCODING_UTF8 ? + aTheAbsURIRef.GetMainURL(eDecodeMechanism, eCharset) : + rTheRelURIRef; +} + +OUString INetURLObject::getExternalURL() const +{ + OUString aTheExtURIRef; + translateToExternal( + m_aAbsURIRef, aTheExtURIRef); + return aTheExtURIRef; +} + +bool INetURLObject::isSchemeEqualTo(std::u16string_view scheme) const { + return m_aScheme.isPresent() + && (rtl_ustr_compareIgnoreAsciiCase_WithLength( + scheme.data(), scheme.size(), + m_aAbsURIRef.getStr() + m_aScheme.getBegin(), + m_aScheme.getLength()) + == 0); +} + +bool INetURLObject::isAnyKnownWebDAVScheme() const { + return ( isSchemeEqualTo( INetProtocol::Http ) || + isSchemeEqualTo( INetProtocol::Https ) || + isSchemeEqualTo( INetProtocol::VndSunStarWebdav ) || + isSchemeEqualTo( u"vnd.sun.star.webdavs" ) || + isSchemeEqualTo( u"webdav" ) || + isSchemeEqualTo( u"webdavs" )); +} + +// static +OUString INetURLObject::GetScheme(INetProtocol eTheScheme) +{ + return OUString::createFromAscii(getSchemeInfo(eTheScheme).m_pPrefix); +} + +// static +const OUString & INetURLObject::GetSchemeName(INetProtocol eTheScheme) +{ + return getSchemeInfo(eTheScheme).m_sScheme; +} + +// static +INetProtocol INetURLObject::CompareProtocolScheme(std::u16string_view aTheAbsURIRef) +{ + sal_Unicode const * p = aTheAbsURIRef.data(); + PrefixInfo const * pPrefix = getPrefix(p, p + aTheAbsURIRef.size()); + return pPrefix ? pPrefix->m_eScheme : INetProtocol::NotValid; +} + +OUString INetURLObject::GetHostPort(DecodeMechanism eMechanism, + rtl_TextEncoding eCharset) const +{ + // Check because PROT_VND_SUN_STAR_HELP, PROT_VND_SUN_STAR_HIER, and + // PROT_VND_SUN_STAR_PKG misuse m_aHost: + if (!getSchemeInfo().m_bHost) + return OUString(); + OUStringBuffer aHostPort(decode(m_aHost, eMechanism, eCharset)); + if (m_aPort.isPresent()) + { + aHostPort.append(":" + decode(m_aPort, eMechanism, eCharset)); + } + return aHostPort.makeStringAndClear(); +} + +sal_uInt32 INetURLObject::GetPort() const +{ + if (m_aPort.isPresent()) + { + sal_Unicode const * p = m_aAbsURIRef.getStr() + m_aPort.getBegin(); + sal_Unicode const * pEnd = p + m_aPort.getLength(); + sal_uInt32 nThePort; + if (INetMIME::scanUnsigned(p, pEnd, true, nThePort) && p == pEnd) + return nThePort; + } + return 0; +} + +bool INetURLObject::SetPort(sal_uInt32 nThePort) +{ + if (getSchemeInfo().m_bPort && m_aHost.isPresent()) + { + sal_Int32 nDelta; + if (m_aPort.isPresent()) + nDelta = m_aPort.set(m_aAbsURIRef, OUString::number(nThePort)); + else + { + m_aAbsURIRef.insert(m_aHost.getEnd(), u':'); + nDelta = m_aPort.set(m_aAbsURIRef, OUString::number(nThePort), m_aHost.getEnd() + 1) + + 1; + } + m_aPath += nDelta; + m_aQuery += nDelta; + m_aFragment += nDelta; + return true; + } + return false; +} + +sal_Int32 INetURLObject::getSegmentCount(bool bIgnoreFinalSlash) const +{ + if (!checkHierarchical()) + return 0; + + sal_Unicode const * p = m_aAbsURIRef.getStr() + m_aPath.getBegin(); + sal_Unicode const * pEnd = p + m_aPath.getLength(); + if (bIgnoreFinalSlash && pEnd > p && pEnd[-1] == '/') + --pEnd; + sal_Int32 n = p == pEnd || *p == '/' ? 0 : 1; + while (p != pEnd) + if (*p++ == '/') + ++n; + return n; +} + +bool INetURLObject::removeSegment(sal_Int32 nIndex, bool bIgnoreFinalSlash) +{ + SubString aSegment(getSegment(nIndex, bIgnoreFinalSlash)); + if (!aSegment.isPresent()) + return false; + + OUStringBuffer aNewPath(m_aPath.getLength()); + aNewPath.append(m_aAbsURIRef.getStr() + m_aPath.getBegin(), + aSegment.getBegin() - m_aPath.getBegin()); + if (bIgnoreFinalSlash && aSegment.getEnd() == m_aPath.getEnd()) + aNewPath.append('/'); + else + aNewPath.append(m_aAbsURIRef.getStr() + aSegment.getEnd(), + m_aPath.getEnd() - aSegment.getEnd()); + if (aNewPath.isEmpty() && !aSegment.isEmpty() && + m_aAbsURIRef[aSegment.getBegin()] == '/') + { + aNewPath.append('/'); + } + + return setPath(aNewPath, EncodeMechanism::NotCanonical, + RTL_TEXTENCODING_UTF8); +} + +OUString INetURLObject::getName(sal_Int32 nIndex, bool bIgnoreFinalSlash, + DecodeMechanism eMechanism, + rtl_TextEncoding eCharset) const +{ + SubString aSegment(getSegment(nIndex, bIgnoreFinalSlash)); + if (!aSegment.isPresent()) + return OUString(); + + sal_Unicode const * pSegBegin + = m_aAbsURIRef.getStr() + aSegment.getBegin(); + sal_Unicode const * pSegEnd = pSegBegin + aSegment.getLength(); + + if (pSegBegin < pSegEnd && *pSegBegin == '/') + ++pSegBegin; + sal_Unicode const * p = pSegBegin; + while (p != pSegEnd && *p != ';') + ++p; + + return decode(pSegBegin, p, eMechanism, eCharset); +} + +bool INetURLObject::setName(std::u16string_view rTheName, EncodeMechanism eMechanism, + rtl_TextEncoding eCharset) +{ + SubString aSegment(getSegment(LAST_SEGMENT, true)); + if (!aSegment.isPresent()) + return false; + + sal_Unicode const * pPathBegin + = m_aAbsURIRef.getStr() + m_aPath.getBegin(); + sal_Unicode const * pPathEnd = pPathBegin + m_aPath.getLength(); + sal_Unicode const * pSegBegin + = m_aAbsURIRef.getStr() + aSegment.getBegin(); + sal_Unicode const * pSegEnd = pSegBegin + aSegment.getLength(); + + if (pSegBegin < pSegEnd && *pSegBegin == '/') + ++pSegBegin; + sal_Unicode const * p = pSegBegin; + while (p != pSegEnd && *p != ';') + ++p; + + OUStringBuffer aNewPath(256); + aNewPath.append(std::u16string_view(pPathBegin, pSegBegin - pPathBegin)); + encodeText(aNewPath, rTheName, PART_PCHAR, eMechanism, eCharset, true); + aNewPath.append(std::u16string_view(p, pPathEnd - p)); + return setPath(aNewPath, EncodeMechanism::NotCanonical, RTL_TEXTENCODING_UTF8); +} + +bool INetURLObject::hasExtension() + const +{ + SubString aSegment(getSegment(LAST_SEGMENT, true/*bIgnoreFinalSlash*/)); + if (!aSegment.isPresent()) + return false; + + sal_Unicode const * pSegBegin + = m_aAbsURIRef.getStr() + aSegment.getBegin(); + sal_Unicode const * pSegEnd = pSegBegin + aSegment.getLength(); + + if (pSegBegin < pSegEnd && *pSegBegin == '/') + ++pSegBegin; + for (sal_Unicode const * p = pSegBegin; p != pSegEnd && *p != ';'; ++p) + if (*p == '.' && p != pSegBegin) + return true; + return false; +} + +OUString INetURLObject::getBase(sal_Int32 nIndex, bool bIgnoreFinalSlash, + DecodeMechanism eMechanism, + rtl_TextEncoding eCharset) const +{ + SubString aSegment(getSegment(nIndex, bIgnoreFinalSlash)); + if (!aSegment.isPresent()) + return OUString(); + + sal_Unicode const * pSegBegin + = m_aAbsURIRef.getStr() + aSegment.getBegin(); + sal_Unicode const * pSegEnd = pSegBegin + aSegment.getLength(); + + if (pSegBegin < pSegEnd && *pSegBegin == '/') + ++pSegBegin; + sal_Unicode const * pExtension = nullptr; + sal_Unicode const * p = pSegBegin; + for (; p != pSegEnd && *p != ';'; ++p) + if (*p == '.' && p != pSegBegin) + pExtension = p; + if (!pExtension) + pExtension = p; + + return decode(pSegBegin, pExtension, eMechanism, eCharset); +} + +bool INetURLObject::setBase(std::u16string_view rTheBase, sal_Int32 nIndex, + EncodeMechanism eMechanism, + rtl_TextEncoding eCharset) +{ + SubString aSegment(getSegment(nIndex, true/*bIgnoreFinalSlash*/)); + if (!aSegment.isPresent()) + return false; + + sal_Unicode const * pPathBegin + = m_aAbsURIRef.getStr() + m_aPath.getBegin(); + sal_Unicode const * pPathEnd = pPathBegin + m_aPath.getLength(); + sal_Unicode const * pSegBegin + = m_aAbsURIRef.getStr() + aSegment.getBegin(); + sal_Unicode const * pSegEnd = pSegBegin + aSegment.getLength(); + + if (pSegBegin < pSegEnd && *pSegBegin == '/') + ++pSegBegin; + sal_Unicode const * pExtension = nullptr; + sal_Unicode const * p = pSegBegin; + for (; p != pSegEnd && *p != ';'; ++p) + if (*p == '.' && p != pSegBegin) + pExtension = p; + if (!pExtension) + pExtension = p; + + OUStringBuffer aNewPath(256); + aNewPath.append(std::u16string_view(pPathBegin, pSegBegin - pPathBegin)); + encodeText(aNewPath, rTheBase, PART_PCHAR, eMechanism, eCharset, true); + aNewPath.append(std::u16string_view(pExtension, pPathEnd - pExtension)); + return setPath(aNewPath, EncodeMechanism::NotCanonical, RTL_TEXTENCODING_UTF8); +} + +OUString INetURLObject::getExtension(sal_Int32 nIndex, + bool bIgnoreFinalSlash, + DecodeMechanism eMechanism, + rtl_TextEncoding eCharset) const +{ + SubString aSegment(getSegment(nIndex, bIgnoreFinalSlash)); + if (!aSegment.isPresent()) + return OUString(); + + sal_Unicode const * pSegBegin + = m_aAbsURIRef.getStr() + aSegment.getBegin(); + sal_Unicode const * pSegEnd = pSegBegin + aSegment.getLength(); + + if (pSegBegin < pSegEnd && *pSegBegin == '/') + ++pSegBegin; + sal_Unicode const * pExtension = nullptr; + sal_Unicode const * p = pSegBegin; + for (; p != pSegEnd && *p != ';'; ++p) + if (*p == '.' && p != pSegBegin) + pExtension = p; + + if (!pExtension) + return OUString(); + + return decode(pExtension + 1, p, eMechanism, eCharset); +} + +bool INetURLObject::setExtension(std::u16string_view rTheExtension, + sal_Int32 nIndex, bool bIgnoreFinalSlash, + rtl_TextEncoding eCharset) +{ + SubString aSegment(getSegment(nIndex, bIgnoreFinalSlash)); + if (!aSegment.isPresent()) + return false; + + sal_Unicode const * pPathBegin + = m_aAbsURIRef.getStr() + m_aPath.getBegin(); + sal_Unicode const * pPathEnd = pPathBegin + m_aPath.getLength(); + sal_Unicode const * pSegBegin + = m_aAbsURIRef.getStr() + aSegment.getBegin(); + sal_Unicode const * pSegEnd = pSegBegin + aSegment.getLength(); + + if (pSegBegin < pSegEnd && *pSegBegin == '/') + ++pSegBegin; + sal_Unicode const * pExtension = nullptr; + sal_Unicode const * p = pSegBegin; + for (; p != pSegEnd && *p != ';'; ++p) + if (*p == '.' && p != pSegBegin) + pExtension = p; + if (!pExtension) + pExtension = p; + + OUStringBuffer aNewPath(256); + aNewPath.append(OUString::Concat(std::u16string_view(pPathBegin, pExtension - pPathBegin)) + "."); + encodeText(aNewPath, rTheExtension, PART_PCHAR, EncodeMechanism::WasEncoded, eCharset, true); + aNewPath.append(std::u16string_view(p, pPathEnd - p)); + return setPath(aNewPath, EncodeMechanism::NotCanonical, RTL_TEXTENCODING_UTF8); +} + +bool INetURLObject::removeExtension(sal_Int32 nIndex, bool bIgnoreFinalSlash) +{ + SubString aSegment(getSegment(nIndex, bIgnoreFinalSlash)); + if (!aSegment.isPresent()) + return false; + + sal_Unicode const * pPathBegin + = m_aAbsURIRef.getStr() + m_aPath.getBegin(); + sal_Unicode const * pPathEnd = pPathBegin + m_aPath.getLength(); + sal_Unicode const * pSegBegin + = m_aAbsURIRef.getStr() + aSegment.getBegin(); + sal_Unicode const * pSegEnd = pSegBegin + aSegment.getLength(); + + if (pSegBegin < pSegEnd && *pSegBegin == '/') + ++pSegBegin; + sal_Unicode const * pExtension = nullptr; + sal_Unicode const * p = pSegBegin; + for (; p != pSegEnd && *p != ';'; ++p) + if (*p == '.' && p != pSegBegin) + pExtension = p; + if (!pExtension) + return true; + + OUString aNewPath = + OUString::Concat(std::u16string_view(pPathBegin, pExtension - pPathBegin)) + + std::u16string_view(p, pPathEnd - p); + + return setPath(aNewPath, EncodeMechanism::NotCanonical, RTL_TEXTENCODING_UTF8); +} + +bool INetURLObject::hasFinalSlash() const +{ + if (!checkHierarchical()) + return false; + + sal_Unicode const * pPathBegin + = m_aAbsURIRef.getStr() + m_aPath.getBegin(); + sal_Unicode const * pPathEnd = pPathBegin + m_aPath.getLength(); + return pPathEnd > pPathBegin && pPathEnd[-1] == '/'; +} + +bool INetURLObject::setFinalSlash() +{ + if (!checkHierarchical()) + return false; + + sal_Unicode const * pPathBegin + = m_aAbsURIRef.getStr() + m_aPath.getBegin(); + sal_Unicode const * pPathEnd = pPathBegin + m_aPath.getLength(); + if (pPathEnd > pPathBegin && pPathEnd[-1] == '/') + return true; + + OUString aNewPath + = OUString::Concat(std::u16string_view(pPathBegin, pPathEnd - pPathBegin)) + "/"; + + return setPath(aNewPath, EncodeMechanism::NotCanonical, RTL_TEXTENCODING_UTF8); +} + +bool INetURLObject::removeFinalSlash() +{ + if (!checkHierarchical()) + return false; + + sal_Unicode const * pPathBegin + = m_aAbsURIRef.getStr() + m_aPath.getBegin(); + sal_Unicode const * pPathEnd = pPathBegin + m_aPath.getLength(); + if (pPathEnd <= pPathBegin || pPathEnd[-1] != '/') + return true; + + --pPathEnd; + if (pPathEnd == pPathBegin && *pPathBegin == '/') + return false; + OUString aNewPath(pPathBegin, pPathEnd - pPathBegin); + + return setPath(aNewPath, EncodeMechanism::NotCanonical, RTL_TEXTENCODING_UTF8); +} + +OUString INetURLObject::getFSysPath(FSysStyle eStyle, + sal_Unicode * pDelimiter) const +{ + if (m_eScheme != INetProtocol::File) + return OUString(); + + if (((eStyle & FSysStyle::Vos) ? 1 : 0) + + ((eStyle & FSysStyle::Unix) ? 1 : 0) + + ((eStyle & FSysStyle::Dos) ? 1 : 0) + > 1) + { + if(eStyle & FSysStyle::Vos && m_aHost.isPresent() && m_aHost.getLength() > 0) + { + eStyle= FSysStyle::Vos; + } + else + { + if(hasDosVolume(eStyle) || ((eStyle & FSysStyle::Dos) && m_aHost.isPresent() && m_aHost.getLength() > 0)) + { + eStyle = FSysStyle::Dos; + } + else + { + if(eStyle & FSysStyle::Unix && (!m_aHost.isPresent() || m_aHost.getLength() == 0)) + { + eStyle = FSysStyle::Unix; + } + else + { + eStyle= FSysStyle(0); + } + } + } + } + + switch (eStyle) + { + case FSysStyle::Vos: + { + if (pDelimiter) + *pDelimiter = '/'; + + OUStringBuffer aSynFSysPath("//"); + if (m_aHost.isPresent() && m_aHost.getLength() > 0) + aSynFSysPath.append(decode(m_aHost, DecodeMechanism::WithCharset, + RTL_TEXTENCODING_UTF8)); + else + aSynFSysPath.append('.'); + aSynFSysPath.append(decode(m_aPath, DecodeMechanism::WithCharset, + RTL_TEXTENCODING_UTF8)); + return aSynFSysPath.makeStringAndClear(); + } + + case FSysStyle::Unix: + { + if (m_aHost.isPresent() && m_aHost.getLength() > 0) + return OUString(); + + if (pDelimiter) + *pDelimiter = '/'; + + return decode(m_aPath, DecodeMechanism::WithCharset, RTL_TEXTENCODING_UTF8); + } + + case FSysStyle::Dos: + { + if (pDelimiter) + *pDelimiter = '\\'; + + OUStringBuffer aSynFSysPath(64); + if (m_aHost.isPresent() && m_aHost.getLength() > 0) + { + aSynFSysPath.append("\\\\" + + decode(m_aHost, DecodeMechanism::WithCharset, RTL_TEXTENCODING_UTF8) + + "\\"); + } + sal_Unicode const * p + = m_aAbsURIRef.getStr() + m_aPath.getBegin(); + sal_Unicode const * pEnd = p + m_aPath.getLength(); + DBG_ASSERT(p < pEnd && *p == '/', + "INetURLObject::getFSysPath(): Bad path"); + ++p; + while (p < pEnd) + { + EscapeType eEscapeType; + sal_uInt32 nUTF32 = getUTF32(p, pEnd, EncodeMechanism::WasEncoded, + RTL_TEXTENCODING_UTF8, + eEscapeType); + if (eEscapeType == EscapeType::NONE && nUTF32 == '/') + aSynFSysPath.append('\\'); + else + aSynFSysPath.appendUtf32(nUTF32); + } + return aSynFSysPath.makeStringAndClear(); + } + + default: + return OUString(); + } +} + +// static +void INetURLObject::appendUCS4Escape(OUStringBuffer & rTheText, + sal_uInt32 nUCS4) +{ + DBG_ASSERT(nUCS4 < 0x80000000, + "INetURLObject::appendUCS4Escape(): Bad char"); + if (nUCS4 < 0x80) + appendEscape(rTheText, nUCS4); + else if (nUCS4 < 0x800) + { + appendEscape(rTheText, nUCS4 >> 6 | 0xC0); + appendEscape(rTheText, (nUCS4 & 0x3F) | 0x80); + } + else if (nUCS4 < 0x10000) + { + appendEscape(rTheText, nUCS4 >> 12 | 0xE0); + appendEscape(rTheText, (nUCS4 >> 6 & 0x3F) | 0x80); + appendEscape(rTheText, (nUCS4 & 0x3F) | 0x80); + } + else if (nUCS4 < 0x200000) + { + appendEscape(rTheText, nUCS4 >> 18 | 0xF0); + appendEscape(rTheText, (nUCS4 >> 12 & 0x3F) | 0x80); + appendEscape(rTheText, (nUCS4 >> 6 & 0x3F) | 0x80); + appendEscape(rTheText, (nUCS4 & 0x3F) | 0x80); + } + else if (nUCS4 < 0x4000000) + { + appendEscape(rTheText, nUCS4 >> 24 | 0xF8); + appendEscape(rTheText, (nUCS4 >> 18 & 0x3F) | 0x80); + appendEscape(rTheText, (nUCS4 >> 12 & 0x3F) | 0x80); + appendEscape(rTheText, (nUCS4 >> 6 & 0x3F) | 0x80); + appendEscape(rTheText, (nUCS4 & 0x3F) | 0x80); + } + else + { + appendEscape(rTheText, nUCS4 >> 30 | 0xFC); + appendEscape(rTheText, (nUCS4 >> 24 & 0x3F) | 0x80); + appendEscape(rTheText, (nUCS4 >> 18 & 0x3F) | 0x80); + appendEscape(rTheText, (nUCS4 >> 12 & 0x3F) | 0x80); + appendEscape(rTheText, (nUCS4 >> 6 & 0x3F) | 0x80); + appendEscape(rTheText, (nUCS4 & 0x3F) | 0x80); + } +} + +// static +void INetURLObject::appendUCS4(OUStringBuffer& rTheText, sal_uInt32 nUCS4, + EscapeType eEscapeType, + Part ePart, rtl_TextEncoding eCharset, + bool bKeepVisibleEscapes) +{ + bool bEscape; + rtl_TextEncoding eTargetCharset = RTL_TEXTENCODING_DONTKNOW; + switch (eEscapeType) + { + case EscapeType::NONE: + if (mustEncode(nUCS4, ePart)) + { + bEscape = true; + eTargetCharset = RTL_TEXTENCODING_UTF8; + } + else + bEscape = false; + break; + + case EscapeType::Octet: + bEscape = true; + eTargetCharset = RTL_TEXTENCODING_ISO_8859_1; + break; + + case EscapeType::Utf32: + if (mustEncode(nUCS4, ePart)) + { + bEscape = true; + eTargetCharset = eCharset; + } + else if (bKeepVisibleEscapes && INetMIME::isVisible(nUCS4)) + { + bEscape = true; + eTargetCharset = RTL_TEXTENCODING_ASCII_US; + } + else + bEscape = false; + break; + default: + bEscape = false; + } + + if (bEscape) + { + switch (eTargetCharset) + { + default: + OSL_FAIL("INetURLObject::appendUCS4(): Unsupported charset"); + [[fallthrough]]; + case RTL_TEXTENCODING_ASCII_US: + case RTL_TEXTENCODING_ISO_8859_1: + appendEscape(rTheText, nUCS4); + break; + case RTL_TEXTENCODING_UTF8: + appendUCS4Escape(rTheText, nUCS4); + break; + } + } + else + rTheText.append(sal_Unicode(nUCS4)); +} + +// static +sal_uInt32 INetURLObject::getUTF32(sal_Unicode const *& rBegin, + sal_Unicode const * pEnd, + EncodeMechanism eMechanism, + rtl_TextEncoding eCharset, + EscapeType & rEscapeType) +{ + DBG_ASSERT(rBegin < pEnd, "INetURLObject::getUTF32(): Bad sequence"); + sal_uInt32 nUTF32 = INetMIME::getUTF32Character(rBegin, pEnd); + switch (eMechanism) + { + case EncodeMechanism::All: + rEscapeType = EscapeType::NONE; + break; + + case EncodeMechanism::WasEncoded: + { + int nWeight1; + int nWeight2; + if (nUTF32 == static_cast<unsigned char>('%') && rBegin + 1 < pEnd + && (nWeight1 = INetMIME::getHexWeight(rBegin[0])) >= 0 + && (nWeight2 = INetMIME::getHexWeight(rBegin[1])) >= 0) + { + rBegin += 2; + nUTF32 = nWeight1 << 4 | nWeight2; + switch (eCharset) + { + default: + OSL_FAIL( + "INetURLObject::getUTF32(): Unsupported charset"); + [[fallthrough]]; + case RTL_TEXTENCODING_ASCII_US: + rEscapeType = rtl::isAscii(nUTF32) ? + EscapeType::Utf32 : EscapeType::Octet; + break; + + case RTL_TEXTENCODING_ISO_8859_1: + rEscapeType = EscapeType::Utf32; + break; + + case RTL_TEXTENCODING_UTF8: + if (rtl::isAscii(nUTF32)) + rEscapeType = EscapeType::Utf32; + else + { + if (nUTF32 >= 0xC0 && nUTF32 <= 0xF4) + { + sal_uInt32 nEncoded; + int nShift; + sal_uInt32 nMin; + if (nUTF32 <= 0xDF) + { + nEncoded = (nUTF32 & 0x1F) << 6; + nShift = 0; + nMin = 0x80; + } + else if (nUTF32 <= 0xEF) + { + nEncoded = (nUTF32 & 0x0F) << 12; + nShift = 6; + nMin = 0x800; + } + else + { + nEncoded = (nUTF32 & 0x07) << 18; + nShift = 12; + nMin = 0x10000; + } + sal_Unicode const * p = rBegin; + bool bUTF8 = true; + for (;;) + { + if (pEnd - p < 3 + || p[0] != '%' + || (nWeight1 + = INetMIME::getHexWeight(p[1])) + < 8 + || nWeight1 > 11 + || (nWeight2 + = INetMIME::getHexWeight(p[2])) + < 0) + { + bUTF8 = false; + break; + } + p += 3; + nEncoded + |= ((nWeight1 & 3) << 4 | nWeight2) + << nShift; + if (nShift == 0) + break; + nShift -= 6; + } + if (bUTF8 && rtl::isUnicodeScalarValue(nEncoded) + && nEncoded >= nMin) + { + rBegin = p; + nUTF32 = nEncoded; + rEscapeType = EscapeType::Utf32; + break; + } + } + rEscapeType = EscapeType::Octet; + } + break; + } + } + else + rEscapeType = EscapeType::NONE; + break; + } + + case EncodeMechanism::NotCanonical: + { + int nWeight1; + int nWeight2; + if (nUTF32 == static_cast<unsigned char>('%') && rBegin + 1 < pEnd + && ((nWeight1 = INetMIME::getHexWeight(rBegin[0])) >= 0) + && ((nWeight2 = INetMIME::getHexWeight(rBegin[1])) >= 0)) + { + rBegin += 2; + nUTF32 = nWeight1 << 4 | nWeight2; + rEscapeType = EscapeType::Octet; + } + else + rEscapeType = EscapeType::NONE; + break; + } + } + return nUTF32; +} + +// static +sal_uInt32 INetURLObject::scanDomain(sal_Unicode const *& rBegin, + sal_Unicode const * pEnd, + bool bEager) +{ + enum State { STATE_DOT, STATE_LABEL, STATE_HYPHEN }; + State eState = STATE_DOT; + sal_Int32 nLabels = 0; + sal_Unicode const * pLastAlphanumeric = nullptr; + for (sal_Unicode const * p = rBegin;; ++p) + switch (eState) + { + case STATE_DOT: + if (p != pEnd && (rtl::isAsciiAlphanumeric(*p) || *p == '_')) + { + ++nLabels; + eState = STATE_LABEL; + break; + } + if (bEager || nLabels == 0) + return 0; + rBegin = p - 1; + return nLabels; + + case STATE_LABEL: + if (p != pEnd) + { + if (rtl::isAsciiAlphanumeric(*p) || *p == '_') + break; + else if (*p == '.') + { + eState = STATE_DOT; + break; + } + else if (*p == '-') + { + pLastAlphanumeric = p; + eState = STATE_HYPHEN; + break; + } + } + rBegin = p; + return nLabels; + + case STATE_HYPHEN: + if (p != pEnd) + { + if (rtl::isAsciiAlphanumeric(*p) || *p == '_') + { + eState = STATE_LABEL; + break; + } + else if (*p == '-') + break; + } + if (bEager) + return 0; + rBegin = pLastAlphanumeric; + return nLabels; + } +} + +// static +bool INetURLObject::scanIPv6reference(sal_Unicode const *& rBegin, + sal_Unicode const * pEnd) +{ + if (rBegin != pEnd && *rBegin == '[') { + sal_Unicode const * p = rBegin + 1; + //TODO: check for valid IPv6address (RFC 2373): + while (p != pEnd && (rtl::isAsciiHexDigit(*p) || *p == ':' || *p == '.')) + { + ++p; + } + if (p != pEnd && *p == ']') { + rBegin = p + 1; + return true; + } + } + return false; +} + +OUString INetURLObject::GetPartBeforeLastName() + const +{ + if (!checkHierarchical()) + return OUString(); + INetURLObject aTemp(*this); + aTemp.clearFragment(); + aTemp.clearQuery(); + aTemp.removeSegment(LAST_SEGMENT, false); + aTemp.setFinalSlash(); + return aTemp.GetMainURL(DecodeMechanism::ToIUri); +} + +OUString INetURLObject::GetLastName(DecodeMechanism eMechanism, + rtl_TextEncoding eCharset) const +{ + return getName(LAST_SEGMENT, true, eMechanism, eCharset); +} + +OUString INetURLObject::GetFileExtension() const +{ + return getExtension(LAST_SEGMENT, false); +} + +void INetURLObject::CutLastName() +{ + INetURLObject aTemp(*this); + aTemp.clearFragment(); + aTemp.clearQuery(); + if (!aTemp.removeSegment(LAST_SEGMENT, false)) + return; + *this = aTemp; +} + +OUString INetURLObject::PathToFileName() const +{ + if (m_eScheme != INetProtocol::File) + return OUString(); + OUString aSystemPath; + if (osl::FileBase::getSystemPathFromFileURL( + decode(m_aAbsURIRef.getStr(), + m_aAbsURIRef.getStr() + m_aPath.getEnd(), + DecodeMechanism::NONE, RTL_TEXTENCODING_UTF8), + aSystemPath) + != osl::FileBase::E_None) + return OUString(); + return aSystemPath; +} + +OUString INetURLObject::GetFull() const +{ + INetURLObject aTemp(*this); + aTemp.removeFinalSlash(); + return aTemp.PathToFileName(); +} + +OUString INetURLObject::GetPath() const +{ + INetURLObject aTemp(*this); + aTemp.removeSegment(); + aTemp.removeFinalSlash(); + return aTemp.PathToFileName(); +} + +void INetURLObject::SetBase(std::u16string_view rTheBase) +{ + setBase(rTheBase, LAST_SEGMENT, EncodeMechanism::All); +} + +OUString INetURLObject::GetBase() const +{ + return getBase(LAST_SEGMENT, true, DecodeMechanism::WithCharset); +} + +void INetURLObject::SetExtension(std::u16string_view rTheExtension) +{ + setExtension(rTheExtension, LAST_SEGMENT, false); +} + +OUString INetURLObject::CutExtension() +{ + OUString aTheExtension(getExtension(LAST_SEGMENT, false)); + return removeExtension(LAST_SEGMENT, false) + ? aTheExtension : OUString(); +} + +bool INetURLObject::IsExoticProtocol() const +{ + return m_eScheme == INetProtocol::Slot || + m_eScheme == INetProtocol::Macro || + m_eScheme == INetProtocol::Uno || + isSchemeEqualTo(u"vnd.sun.star.script") || + isSchemeEqualTo(u"service"); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/fsys/wldcrd.cxx b/tools/source/fsys/wldcrd.cxx new file mode 100644 index 0000000000..e8199975c6 --- /dev/null +++ b/tools/source/fsys/wldcrd.cxx @@ -0,0 +1,120 @@ +/* -*- 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 <tools/wldcrd.hxx> + +/** Tests, whether a wildcard in pWild will match for pStr. + * + * '*' in pWild means n chars for n > 0. + * '?' in pWild mean match exactly one character. + * + */ +bool WildCard::ImpMatch( std::u16string_view aWild, std::u16string_view aStr ) +{ + const sal_Unicode* pPosAfterAsterisk = nullptr; + const sal_Unicode* pWild = aWild.data(); + const sal_Unicode* pWildEnd = aWild.data() + aWild.size(); + const sal_Unicode* pStr = aStr.data(); + const sal_Unicode* pStrEnd = aStr.data() + aStr.size(); + + while (pWild != pWildEnd) + { + switch (*pWild) + { + case '?': + if ( pStr == pStrEnd ) + return false; + break; // Match -> proceed to the next character + case '\\': // Escaping '?' and '*'; don't we need to escape '\\'? + if ((pWild + 1 != pWildEnd) && ((*(pWild + 1) == '?') || (*(pWild + 1) == '*'))) + pWild++; + [[fallthrough]]; + default: // No wildcard, literal match + if (pStr == pStrEnd) + return false; + if (*pWild == *pStr) + break; // Match -> proceed to the next character + if (!pPosAfterAsterisk) + return false; + pWild = pPosAfterAsterisk; + [[fallthrough]]; + case '*': + while ( pWild != pWildEnd && *pWild == '*' ) + pWild++; + if ( pWild == pWildEnd ) + return true; + // Consider strange things like "**?*?*" + while (*pWild == '?') + { + if (pStr == pStrEnd) + return false; + pWild++; + pStr++; + while (pWild != pWildEnd && *pWild == '*') + pWild++; + if (pWild == pWildEnd) + return true; + } + // At this point, we are past wildcards, and a literal match must follow + if ( pStr == pStrEnd ) + return false; + pPosAfterAsterisk = pWild; + if ((*pWild == '\\') && (pWild + 1 != pWildEnd) && ((*(pWild + 1) == '?') || (*(pWild + 1) == '*'))) + pWild++; + while (*pStr != *pWild) + { + pStr++; + if ( pStr == pStrEnd ) + return false; + } + break; // Match -> proceed to the next character + } + // We arrive here when the current characters in pWild and pStr match + assert(pWild != pWildEnd); + pWild++; + assert(pStr != pStrEnd); + pStr++; + if (pWild == pWildEnd && pPosAfterAsterisk && pStr != pStrEnd) + pWild = pPosAfterAsterisk; // Try again on the rest of pStr + } + assert(pWild == pWildEnd); + return pStr == pStrEnd; +} + +bool WildCard::Matches( std::u16string_view rString ) const +{ + std::u16string_view aTmpWild = aWildString; + + size_t nSepPos; + + if ( cSepSymbol != '\0' ) + { + while ( (nSepPos = aTmpWild.find(cSepSymbol)) != std::u16string_view::npos ) + { + // Check all split wildcards + if ( ImpMatch( aTmpWild.substr( 0, nSepPos ), rString ) ) + return true; + aTmpWild = aTmpWild.substr(nSepPos + 1); // remove separator + } + } + + return ImpMatch( aTmpWild, rString ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/generic/b3dtrans.cxx b/tools/source/generic/b3dtrans.cxx new file mode 100644 index 0000000000..0cae750da0 --- /dev/null +++ b/tools/source/generic/b3dtrans.cxx @@ -0,0 +1,456 @@ +/* -*- 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 <tools/b3dtrans.hxx> + +#include <osl/diagnose.h> + + // Near and far clipping planes +constexpr double gfNearBound = 0.001; +constexpr double gfFarBound = 1.001; + + +// B3dTransformationSet -------------------------------------------------------- +// Transformations for all 3D output + +B3dTransformationSet::B3dTransformationSet() +{ + Reset(); +} + +B3dTransformationSet::~B3dTransformationSet() +{ +} + +void B3dTransformationSet::Orientation(basegfx::B3DHomMatrix& rTarget, const basegfx::B3DPoint& aVRP, basegfx::B3DVector aVPN, basegfx::B3DVector aVUP) +{ + rTarget.translate( -aVRP.getX(), -aVRP.getY(), -aVRP.getZ()); + aVUP.normalize(); + aVPN.normalize(); + basegfx::B3DVector aRx(aVUP); + basegfx::B3DVector aRy(aVPN); + aRx = aRx.getPerpendicular(aRy); + aRx.normalize(); + aRy = aRy.getPerpendicular(aRx); + aRy.normalize(); + basegfx::B3DHomMatrix aTemp; + aTemp.set(0, 0, aRx.getX()); + aTemp.set(0, 1, aRx.getY()); + aTemp.set(0, 2, aRx.getZ()); + aTemp.set(1, 0, aRy.getX()); + aTemp.set(1, 1, aRy.getY()); + aTemp.set(1, 2, aRy.getZ()); + aTemp.set(2, 0, aVPN.getX()); + aTemp.set(2, 1, aVPN.getY()); + aTemp.set(2, 2, aVPN.getZ()); + rTarget *= aTemp; +} + +void B3dTransformationSet::Frustum(basegfx::B3DHomMatrix& rTarget, double fLeft, double fRight, double fBottom, double fTop, double fNear, double fFar) +{ + if(!(fNear > 0.0)) + { + fNear = 0.001; + } + if(!(fFar > 0.0)) + { + fFar = 1.0; + } + if(fNear == fFar) + { + fFar = fNear + 1.0; + } + if(fLeft == fRight) + { + fLeft -= 1.0; + fRight += 1.0; + } + if(fTop == fBottom) + { + fBottom -= 1.0; + fTop += 1.0; + } + basegfx::B3DHomMatrix aTemp; + + aTemp.set(0, 0, 2.0 * fNear / (fRight - fLeft)); + aTemp.set(1, 1, 2.0 * fNear / (fTop - fBottom)); + aTemp.set(0, 2, (fRight + fLeft) / (fRight - fLeft)); + aTemp.set(1, 2, (fTop + fBottom) / (fTop - fBottom)); + aTemp.set(2, 2, -1.0 * ((fFar + fNear) / (fFar - fNear))); + aTemp.set(3, 2, -1.0); + aTemp.set(2, 3, -1.0 * ((2.0 * fFar * fNear) / (fFar - fNear))); + aTemp.set(3, 3, 0.0); + + rTarget *= aTemp; +} + +void B3dTransformationSet::Ortho(basegfx::B3DHomMatrix& rTarget, + double fLeft, double fRight, double fBottom, double fTop, + double fNear, double fFar) +{ + if(fNear == fFar) + { + OSL_FAIL("Near and far clipping plane in Ortho definition are identical"); + fFar = fNear + 1.0; + } + if(fLeft == fRight) + { + OSL_FAIL("Left and right in Ortho definition are identical"); + fLeft -= 1.0; + fRight += 1.0; + } + if(fTop == fBottom) + { + OSL_FAIL("Top and bottom in Ortho definition are identical"); + fBottom -= 1.0; + fTop += 1.0; + } + basegfx::B3DHomMatrix aTemp; + + aTemp.set(0, 0, 2.0 / (fRight - fLeft)); + aTemp.set(1, 1, 2.0 / (fTop - fBottom)); + aTemp.set(2, 2, -1.0 * (2.0 / (fFar - fNear))); + aTemp.set(0, 3, -1.0 * ((fRight + fLeft) / (fRight - fLeft))); + aTemp.set(1, 3, -1.0 * ((fTop + fBottom) / (fTop - fBottom))); + aTemp.set(2, 3, -1.0 * ((fFar + fNear) / (fFar - fNear))); + + rTarget *= aTemp; +} + +/// reset values +void B3dTransformationSet::Reset() +{ + // Reset matrices to identity matrices + maObjectTrans.identity(); + PostSetObjectTrans(); + + Orientation(maOrientation); + PostSetOrientation(); + + maTexture.identity(); + + mfLeftBound = mfBottomBound = -1.0; + mfRightBound = mfTopBound = 1.0; + + mfRatio = 0.0; + + maViewportRectangle = tools::Rectangle(-1, -1, 2, 2); + maVisibleRectangle = maViewportRectangle; + + mbPerspective = true; + + mbProjectionValid = false; + + CalcViewport(); +} + +/// Object transformation +void B3dTransformationSet::PostSetObjectTrans() +{ + // Assign and compute inverse + maInvObjectTrans = maObjectTrans; + maInvObjectTrans.invert(); +} + +void B3dTransformationSet::SetOrientation(const basegfx::B3DPoint& rVRP, const basegfx::B3DVector& rVPN, const basegfx::B3DVector& rVUP) +{ + maOrientation.identity(); + Orientation(maOrientation, rVRP, rVPN, rVUP); + + PostSetOrientation(); +} + +void B3dTransformationSet::PostSetOrientation() +{ + // Assign and compute inverse + maInvOrientation = maOrientation; + maInvOrientation.invert(); +} + +/// Projections for transformations +void B3dTransformationSet::SetProjection(const basegfx::B3DHomMatrix& mProject) +{ + maProjection = mProject; + PostSetProjection(); +} + +const basegfx::B3DHomMatrix& B3dTransformationSet::GetProjection() +{ + if(!mbProjectionValid) + CalcViewport(); + return maProjection; +} + +void B3dTransformationSet::PostSetProjection() +{ + // Assign and compute inverse + maInvProjection = GetProjection(); + maInvProjection.invert(); +} + +/// Transformations for viewport +void B3dTransformationSet::CalcViewport() +{ + // Parameters for projection + double fLeft(mfLeftBound); + double fRight(mfRightBound); + double fBottom(mfBottomBound); + double fTop(mfTopBound); + + // Adjust projection to aspect ratio, if set + if(GetRatio() != 0.0) + { + // Compute current aspect ratio of boundaries + double fBoundWidth = static_cast<double>(maViewportRectangle.GetWidth() + 1); + double fBoundHeight = static_cast<double>(maViewportRectangle.GetHeight() + 1); + double fActRatio = 1; + double fFactor; + + if(fBoundWidth != 0.0) + fActRatio = fBoundHeight / fBoundWidth; + // FIXME else in this case has a lot of problems, should this return. + + // scale down larger part + if(fActRatio > mfRatio) + { + // scale down Y + fFactor = fActRatio; + fTop *= fFactor; + fBottom *= fFactor; + } + else + { + // scale down X + fFactor = 1.0 / fActRatio; + fRight *= fFactor; + fLeft *= fFactor; + } + } + + // Do projection and object areas overlap? + maSetBound = maViewportRectangle; + + // Reset projection with new values + basegfx::B3DHomMatrix aNewProjection; + + // #i36281# + // OpenGL needs a little more rough additional size to not let + // the front face vanish. Changed from SMALL_DVALUE to 0.000001, + // which is 1/10000th, compared with 1/tenth of a million from SMALL_DVALUE. + const double fDistPart((gfFarBound - gfNearBound) * 0.0001); + + // To avoid critical clipping, set Near & Far generously + if(mbPerspective) + { + Frustum(aNewProjection, fLeft, fRight, fBottom, fTop, gfNearBound - fDistPart, gfFarBound + fDistPart); + } + else + { + Ortho(aNewProjection, fLeft, fRight, fBottom, fTop, gfNearBound - fDistPart, gfFarBound + fDistPart); + } + + // Set to true to guarantee loop termination + mbProjectionValid = true; + + // set new projection + SetProjection(aNewProjection); + + // fill parameters for ViewportTransformation + // Translation + maTranslate.setX(static_cast<double>(maSetBound.Left()) + ((maSetBound.GetWidth() - 1) / 2.0)); + maTranslate.setY(static_cast<double>(maSetBound.Top()) + ((maSetBound.GetHeight() - 1) / 2.0)); + maTranslate.setZ(ZBUFFER_DEPTH_RANGE / 2.0); + + // Scaling + maScale.setX((maSetBound.GetWidth() - 1) / 2.0); + maScale.setY((maSetBound.GetHeight() - 1) / -2.0); + maScale.setZ(ZBUFFER_DEPTH_RANGE / 2.0); +} + +void B3dTransformationSet::SetRatio(double fNew) +{ + if(mfRatio != fNew) + { + mfRatio = fNew; + mbProjectionValid = false; + } +} + +void B3dTransformationSet::SetDeviceRectangle(double fL, double fR, double fB, double fT) +{ + if(fL != mfLeftBound || fR != mfRightBound || fB != mfBottomBound || fT != mfTopBound) + { + mfLeftBound = fL; + mfRightBound = fR; + mfBottomBound = fB; + mfTopBound = fT; + + mbProjectionValid = false; + + // Broadcast changes + DeviceRectangleChange(); + } +} + +void B3dTransformationSet::DeviceRectangleChange() +{ +} + +void B3dTransformationSet::SetPerspective(bool bNew) +{ + if(mbPerspective != bNew) + { + mbPerspective = bNew; + mbProjectionValid = false; + } +} + +void B3dTransformationSet::SetViewportRectangle(tools::Rectangle const & rRect, tools::Rectangle const & rVisible) +{ + if(rRect != maViewportRectangle || rVisible != maVisibleRectangle) + { + maViewportRectangle = rRect; + maVisibleRectangle = rVisible; + + mbProjectionValid = false; + } +} + +// direct access to various transformations + +basegfx::B3DPoint B3dTransformationSet::WorldToEyeCoor(const basegfx::B3DPoint& rVec) +{ + basegfx::B3DPoint aVec(rVec); + aVec *= maOrientation; + return aVec; +} + +basegfx::B3DPoint B3dTransformationSet::EyeToWorldCoor(const basegfx::B3DPoint& rVec) +{ + basegfx::B3DPoint aVec(rVec); + aVec *= maInvOrientation; + return aVec; +} + +// B3dViewport ----------------------------------------------------------------- + +B3dViewport::B3dViewport() +: aVRP(0, 0, 0), + aVPN(0, 0, 1), + aVUV(0, 1, 0) +{ + CalcOrientation(); +} + +B3dViewport::~B3dViewport() +{ +} + +void B3dViewport::SetVUV(const basegfx::B3DVector& rNewVUV) +{ + aVUV = rNewVUV; + CalcOrientation(); +} + +void B3dViewport::SetViewportValues( + const basegfx::B3DPoint& rNewVRP, + const basegfx::B3DVector& rNewVPN, + const basegfx::B3DVector& rNewVUV) +{ + aVRP = rNewVRP; + aVPN = rNewVPN; + aVUV = rNewVUV; + CalcOrientation(); +} + +void B3dViewport::CalcOrientation() +{ + SetOrientation(aVRP, aVPN, aVUV); +} + +// B3dCamera ------------------------------------------------------------------- + +B3dCamera::B3dCamera( + const basegfx::B3DPoint& rPos, const basegfx::B3DVector& rLkAt, + double fFocLen, double fBnkAng) +: aPosition(rPos), + aLookAt(rLkAt), + fFocalLength(fFocLen), + fBankAngle(fBnkAng) +{ + CalcNewViewportValues(); +} + +B3dCamera::~B3dCamera() +{ +} + +void B3dCamera::DeviceRectangleChange() +{ + // call parent + B3dViewport::DeviceRectangleChange(); + + // react to changes + CalcNewViewportValues(); +} + +void B3dCamera::CalcNewViewportValues() +{ + basegfx::B3DVector aNewVPN(aPosition - aLookAt); + + basegfx::B3DVector aNewVUV(0.0, 1.0, 0.0); + if(aNewVPN.getLength() < aNewVPN.getY()) + aNewVUV.setX(0.5); + + aNewVUV.normalize(); + aNewVPN.normalize(); + + basegfx::B3DVector aNewToTheRight = aNewVPN.getPerpendicular(aNewVUV); + aNewToTheRight.normalize(); + aNewVUV = aNewToTheRight.getPerpendicular(aNewVPN); + aNewVUV.normalize(); + + SetViewportValues(aPosition, aNewVPN, aNewVUV); + CalcFocalLength(); + + if(fBankAngle != 0.0) + { + basegfx::B3DHomMatrix aRotMat; + aRotMat.rotate(0.0, 0.0, fBankAngle); + basegfx::B3DVector aUp(0.0, 1.0, 0.0); + aUp *= aRotMat; + aUp = EyeToWorldCoor(aUp); + aUp.normalize(); + SetVUV(aUp); + } +} + +void B3dCamera::CalcFocalLength() +{ + double fWidth = GetDeviceRectangleWidth(); + + // Adjust focal length based on given position + basegfx::B3DPoint aOldPosition = WorldToEyeCoor({}); + if(fWidth != 0.0) + fFocalLength = aOldPosition.getZ() / fWidth * 35.0; + if(fFocalLength < 5.0) + fFocalLength = 5.0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/generic/bigint.cxx b/tools/source/generic/bigint.cxx new file mode 100644 index 0000000000..51810cab17 --- /dev/null +++ b/tools/source/generic/bigint.cxx @@ -0,0 +1,852 @@ +/* -*- 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 <math.h> + +#include <osl/diagnose.h> +#include <tools/bigint.hxx> + +#include <algorithm> +#include <string.h> + +/** + * The range in which we can perform add/sub without fear of overflow + */ +const sal_Int32 MY_MAXLONG = 0x3fffffff; +const sal_Int32 MY_MINLONG = -MY_MAXLONG; + +/* + * The algorithms for Addition, Subtraction, Multiplication and Division + * of large numbers originate from SEMINUMERICAL ALGORITHMS by + * DONALD E. KNUTH in the series The Art of Computer Programming: + * chapter 4.3.1. The Classical Algorithms. + */ + +// TODO: Needs conversion to sal_uInt16/INT16/sal_uInt32/sal_Int32 +void BigInt::MakeBigInt( const BigInt& rVal ) +{ + if ( rVal.nLen != 0 ) + { + memcpy( static_cast<void*>(this), static_cast<const void*>(&rVal), sizeof( BigInt ) ); + while ( nLen > 1 && nNum[nLen-1] == 0 ) + nLen--; + } + else + { + nVal = rVal.nVal; + sal_uInt32 nTmp; + if (nVal < 0) + { + bIsNeg = true; + nTmp = -static_cast<sal_Int64>(nVal); + } + else + { + bIsNeg = false; + nTmp = nVal; + } + + nNum[0] = static_cast<sal_uInt16>(nTmp & 0xffffL); + nNum[1] = static_cast<sal_uInt16>(nTmp >> 16); + if ( nTmp & 0xffff0000L ) + nLen = 2; + else + nLen = 1; + } +} + +void BigInt::Normalize() +{ + if ( nLen != 0 ) + { + while ( nLen > 1 && nNum[nLen-1] == 0 ) + nLen--; + + if ( nLen < 3 ) + { + sal_Int32 newVal; + if ( nLen < 2 ) + newVal = nNum[0]; + else if ( nNum[1] & 0x8000 ) + return; + else + newVal = (static_cast<sal_Int32>(nNum[1]) << 16) + nNum[0]; + + nLen = 0; + nVal = newVal; + + if ( bIsNeg ) + nVal = -nVal; + } + // else nVal is undefined !!! W.P. + } + // why? nVal is undefined ??? W.P. + else if ( nVal & 0xFFFF0000L ) + nLen = 2; + else + nLen = 1; +} + +void BigInt::Mult( const BigInt &rVal, sal_uInt16 nMul ) +{ + sal_uInt16 nK = 0; + for ( int i = 0; i < rVal.nLen; i++ ) + { + sal_uInt32 nTmp = static_cast<sal_uInt32>(rVal.nNum[i]) * static_cast<sal_uInt32>(nMul) + nK; + nK = static_cast<sal_uInt16>(nTmp >> 16); + nNum[i] = static_cast<sal_uInt16>(nTmp); + } + + if ( nK ) + { + nNum[rVal.nLen] = nK; + nLen = rVal.nLen + 1; + } + else + nLen = rVal.nLen; + + bIsNeg = rVal.bIsNeg; +} + +void BigInt::Div( sal_uInt16 nDiv, sal_uInt16& rRem ) +{ + sal_uInt32 nK = 0; + for ( int i = nLen - 1; i >= 0; i-- ) + { + sal_uInt32 nTmp = static_cast<sal_uInt32>(nNum[i]) + (nK << 16); + nNum[i] = static_cast<sal_uInt16>(nTmp / nDiv); + nK = nTmp % nDiv; + } + rRem = static_cast<sal_uInt16>(nK); + + if ( nNum[nLen-1] == 0 ) + nLen -= 1; +} + +bool BigInt::IsLess( const BigInt& rVal ) const +{ + if ( rVal.nLen < nLen) + return true; + if ( rVal.nLen > nLen ) + return false; + + int i; + for ( i = nLen - 1; i > 0 && nNum[i] == rVal.nNum[i]; i-- ) + { + } + return rVal.nNum[i] < nNum[i]; +} + +void BigInt::AddLong( BigInt& rB, BigInt& rErg ) +{ + if ( bIsNeg == rB.bIsNeg ) + { + int i; + char len; + + // if length of the two values differ, fill remaining positions + // of the smaller value with zeros. + if (nLen >= rB.nLen) + { + len = nLen; + for (i = rB.nLen; i < len; i++) + rB.nNum[i] = 0; + } + else + { + len = rB.nLen; + for (i = nLen; i < len; i++) + nNum[i] = 0; + } + + // Add numerals, starting from the back + sal_Int32 k; + sal_Int32 nZ = 0; + for (i = 0, k = 0; i < len; i++) { + nZ = static_cast<sal_Int32>(nNum[i]) + static_cast<sal_Int32>(rB.nNum[i]) + k; + if (nZ & 0xff0000L) + k = 1; + else + k = 0; + rErg.nNum[i] = static_cast<sal_uInt16>(nZ & 0xffffL); + } + // If an overflow occurred, add to solution + if (nZ & 0xff0000L) // or if(k) + { + rErg.nNum[i] = 1; + len++; + } + // Set length and sign + rErg.nLen = len; + rErg.bIsNeg = bIsNeg && rB.bIsNeg; + } + // If one of the values is negative, perform subtraction instead + else if (bIsNeg) + { + bIsNeg = false; + rB.SubLong(*this, rErg); + bIsNeg = true; + } + else + { + rB.bIsNeg = false; + SubLong(rB, rErg); + rB.bIsNeg = true; + } +} + +void BigInt::SubLong( BigInt& rB, BigInt& rErg ) +{ + if ( bIsNeg == rB.bIsNeg ) + { + int i; + char len; + sal_Int32 nZ, k; + + // if length of the two values differ, fill remaining positions + // of the smaller value with zeros. + if (nLen >= rB.nLen) + { + len = nLen; + for (i = rB.nLen; i < len; i++) + rB.nNum[i] = 0; + } + else + { + len = rB.nLen; + for (i = nLen; i < len; i++) + nNum[i] = 0; + } + + if ( IsLess(rB) ) + { + for (i = 0, k = 0; i < len; i++) + { + nZ = static_cast<sal_Int32>(nNum[i]) - static_cast<sal_Int32>(rB.nNum[i]) + k; + if (nZ < 0) + k = -1; + else + k = 0; + rErg.nNum[i] = static_cast<sal_uInt16>(nZ & 0xffffL); + } + rErg.bIsNeg = bIsNeg; + } + else + { + for (i = 0, k = 0; i < len; i++) + { + nZ = static_cast<sal_Int32>(rB.nNum[i]) - static_cast<sal_Int32>(nNum[i]) + k; + if (nZ < 0) + k = -1; + else + k = 0; + rErg.nNum[i] = static_cast<sal_uInt16>(nZ & 0xffffL); + } + // if a < b, revert sign + rErg.bIsNeg = !bIsNeg; + } + rErg.nLen = len; + } + // If one of the values is negative, perform addition instead + else if (bIsNeg) + { + bIsNeg = false; + AddLong(rB, rErg); + bIsNeg = true; + rErg.bIsNeg = true; + } + else + { + rB.bIsNeg = false; + AddLong(rB, rErg); + rB.bIsNeg = true; + rErg.bIsNeg = false; + } +} + +void BigInt::MultLong( const BigInt& rB, BigInt& rErg ) const +{ + int i, j; + sal_uInt32 nZ, k; + + rErg.bIsNeg = bIsNeg != rB.bIsNeg; + rErg.nLen = nLen + rB.nLen; + + for (i = 0; i < rErg.nLen; i++) + rErg.nNum[i] = 0; + + for (j = 0; j < rB.nLen; j++) + { + for (i = 0, k = 0; i < nLen; i++) + { + nZ = static_cast<sal_uInt32>(nNum[i]) * static_cast<sal_uInt32>(rB.nNum[j]) + + static_cast<sal_uInt32>(rErg.nNum[i + j]) + k; + rErg.nNum[i + j] = static_cast<sal_uInt16>(nZ & 0xffffU); + k = nZ >> 16; + } + rErg.nNum[i + j] = static_cast<sal_uInt16>(k); + } +} + +void BigInt::DivLong( const BigInt& rB, BigInt& rErg ) const +{ + int i, j; + sal_uInt16 nK, nQ, nMult; + sal_uInt16 nLenB = rB.nLen; + sal_uInt16 nLenB1 = rB.nLen - 1; + BigInt aTmpA, aTmpB; + + nMult = static_cast<sal_uInt16>(0x10000L / (static_cast<sal_Int32>(rB.nNum[nLenB1]) + 1)); + + aTmpA.Mult( *this, nMult ); + if ( aTmpA.nLen == nLen ) + { + aTmpA.nNum[aTmpA.nLen] = 0; + aTmpA.nLen++; + } + + aTmpB.Mult( rB, nMult ); + + for (j = aTmpA.nLen - 1; j >= nLenB; j--) + { // guess divisor + sal_uInt32 nTmp = ( static_cast<sal_uInt32>(aTmpA.nNum[j]) << 16 ) + aTmpA.nNum[j - 1]; + if (aTmpA.nNum[j] == aTmpB.nNum[nLenB1]) + nQ = 0xFFFF; + else + nQ = static_cast<sal_uInt16>(nTmp / aTmpB.nNum[nLenB1]); + + if ( (static_cast<sal_uInt32>(aTmpB.nNum[nLenB1 - 1]) * nQ) > + ((nTmp - static_cast<sal_uInt32>(aTmpB.nNum[nLenB1]) * nQ) << 16) + aTmpA.nNum[j - 2]) + nQ--; + // Start division + nK = 0; + for (i = 0; i < nLenB; i++) + { + nTmp = static_cast<sal_uInt32>(aTmpA.nNum[j - nLenB + i]) + - (static_cast<sal_uInt32>(aTmpB.nNum[i]) * nQ) + - nK; + aTmpA.nNum[j - nLenB + i] = static_cast<sal_uInt16>(nTmp); + nK = static_cast<sal_uInt16>(nTmp >> 16); + if ( nK ) + nK = static_cast<sal_uInt16>(0x10000U - nK); + } + sal_uInt16& rNum( aTmpA.nNum[j - nLenB + i] ); + rNum -= nK; + if (aTmpA.nNum[j - nLenB + i] == 0) + rErg.nNum[j - nLenB] = nQ; + else + { + rErg.nNum[j - nLenB] = nQ - 1; + nK = 0; + for (i = 0; i < nLenB; i++) + { + nTmp = aTmpA.nNum[j - nLenB + i] + aTmpB.nNum[i] + nK; + aTmpA.nNum[j - nLenB + i] = static_cast<sal_uInt16>(nTmp & 0xFFFFL); + if (nTmp & 0xFFFF0000L) + nK = 1; + else + nK = 0; + } + } + } + + rErg.bIsNeg = bIsNeg != rB.bIsNeg; + rErg.nLen = nLen - rB.nLen + 1; +} + +void BigInt::ModLong( const BigInt& rB, BigInt& rErg ) const +{ + sal_uInt16 i, j; + sal_uInt16 nK, nQ, nMult; + sal_Int16 nLenB = rB.nLen; + sal_Int16 nLenB1 = rB.nLen - 1; + BigInt aTmpA, aTmpB; + + nMult = static_cast<sal_uInt16>(0x10000L / (static_cast<sal_Int32>(rB.nNum[nLenB1]) + 1)); + + aTmpA.Mult( *this, nMult); + if ( aTmpA.nLen == nLen ) + { + aTmpA.nNum[aTmpA.nLen] = 0; + aTmpA.nLen++; + } + + aTmpB.Mult( rB, nMult); + + for (j = aTmpA.nLen - 1; j >= nLenB; j--) + { // Guess divisor + sal_uInt32 nTmp = ( static_cast<sal_uInt32>(aTmpA.nNum[j]) << 16 ) + aTmpA.nNum[j - 1]; + if (aTmpA.nNum[j] == aTmpB.nNum[nLenB1]) + nQ = 0xFFFF; + else + nQ = static_cast<sal_uInt16>(nTmp / aTmpB.nNum[nLenB1]); + + if ( (static_cast<sal_uInt32>(aTmpB.nNum[nLenB1 - 1]) * nQ) > + ((nTmp - aTmpB.nNum[nLenB1] * nQ) << 16) + aTmpA.nNum[j - 2]) + nQ--; + // Start division + nK = 0; + for (i = 0; i < nLenB; i++) + { + nTmp = static_cast<sal_uInt32>(aTmpA.nNum[j - nLenB + i]) + - (static_cast<sal_uInt32>(aTmpB.nNum[i]) * nQ) + - nK; + aTmpA.nNum[j - nLenB + i] = static_cast<sal_uInt16>(nTmp); + nK = static_cast<sal_uInt16>(nTmp >> 16); + if ( nK ) + nK = static_cast<sal_uInt16>(0x10000U - nK); + } + sal_uInt16& rNum( aTmpA.nNum[j - nLenB + i] ); + rNum = rNum - nK; + if (aTmpA.nNum[j - nLenB + i] == 0) + rErg.nNum[j - nLenB] = nQ; + else + { + rErg.nNum[j - nLenB] = nQ - 1; + nK = 0; + for (i = 0; i < nLenB; i++) { + nTmp = aTmpA.nNum[j - nLenB + i] + aTmpB.nNum[i] + nK; + aTmpA.nNum[j - nLenB + i] = static_cast<sal_uInt16>(nTmp & 0xFFFFL); + if (nTmp & 0xFFFF0000L) + nK = 1; + else + nK = 0; + } + } + } + + rErg = aTmpA; + rErg.Div( nMult, nQ ); +} + +bool BigInt::ABS_IsLess( const BigInt& rB ) const +{ + if (nLen != 0 || rB.nLen != 0) + { + BigInt nA, nB; + nA.MakeBigInt( *this ); + nB.MakeBigInt( rB ); + if (nA.nLen == nB.nLen) + { + int i; + for (i = nA.nLen - 1; i > 0 && nA.nNum[i] == nB.nNum[i]; i--) + { + } + return nA.nNum[i] < nB.nNum[i]; + } + else + return nA.nLen < nB.nLen; + } + if ( nVal < 0 ) + if ( rB.nVal < 0 ) + return nVal > rB.nVal; + else + return nVal > -rB.nVal; + else + if ( rB.nVal < 0 ) + return nVal < -rB.nVal; + else + return nVal < rB.nVal; +} + +BigInt::BigInt( const BigInt& rBigInt ) + : nLen(0) + , bIsNeg(false) +{ + if ( rBigInt.nLen != 0 ) + memcpy( static_cast<void*>(this), static_cast<const void*>(&rBigInt), sizeof( BigInt ) ); + else + nVal = rBigInt.nVal; +} + +BigInt::BigInt( std::u16string_view rString ) + : nLen(0) +{ + bIsNeg = false; + nVal = 0; + + bool bNeg = false; + auto p = rString.begin(); + auto pEnd = rString.end(); + if (p == pEnd) + return; + if ( *p == '-' ) + { + bNeg = true; + p++; + } + if (p == pEnd) + return; + while( p != pEnd && *p >= '0' && *p <= '9' ) + { + *this *= 10; + *this += *p - '0'; + p++; + } + if ( nLen != 0 ) + bIsNeg = bNeg; + else if( bNeg ) + nVal = -nVal; +} + +BigInt::BigInt( double nValue ) + : nVal(0) +{ + if ( nValue < 0 ) + { + nValue *= -1; + bIsNeg = true; + } + else + { + bIsNeg = false; + } + + if ( nValue < 1 ) + { + nVal = 0; + nLen = 0; + } + else + { + int i=0; + + while ( ( nValue > 65536.0 ) && ( i < MAX_DIGITS ) ) + { + nNum[i] = static_cast<sal_uInt16>(fmod( nValue, 65536.0 )); + nValue -= nNum[i]; + nValue /= 65536.0; + i++; + } + if ( i < MAX_DIGITS ) + nNum[i++] = static_cast<sal_uInt16>(nValue); + + nLen = i; + + if ( i < 3 ) + Normalize(); + } +} + +BigInt::BigInt( sal_uInt32 nValue ) + : nVal(0) +{ + if ( nValue & 0x80000000U ) + { + bIsNeg = false; + nNum[0] = static_cast<sal_uInt16>(nValue & 0xffffU); + nNum[1] = static_cast<sal_uInt16>(nValue >> 16); + nLen = 2; + } + else + { + bIsNeg = false; + nVal = nValue; + nLen = 0; + } +} + +BigInt::BigInt( sal_Int64 nValue ) + : nVal(0) +{ + bIsNeg = nValue < 0; + nLen = 0; + + if ((nValue >= SAL_MIN_INT32) && (nValue <= SAL_MAX_INT32)) + { + nVal = static_cast<sal_Int32>(nValue); + } + else + { + sal_uInt64 nUValue = static_cast<sal_uInt64>(bIsNeg ? -nValue : nValue); + for (int i = 0; (i != sizeof(sal_uInt64) / 2) && (nUValue != 0); ++i) + { + nNum[i] = static_cast<sal_uInt16>(nUValue & 0xffffUL); + nUValue = nUValue >> 16; + ++nLen; + } + } +} + +BigInt::operator double() const +{ + if ( nLen == 0 ) + return static_cast<double>(nVal); + else + { + int i = nLen-1; + double nRet = static_cast<double>(static_cast<sal_uInt32>(nNum[i])); + + while ( i ) + { + nRet *= 65536.0; + i--; + nRet += static_cast<double>(static_cast<sal_uInt32>(nNum[i])); + } + + if ( bIsNeg ) + nRet *= -1; + + return nRet; + } +} + +BigInt& BigInt::operator=( const BigInt& rBigInt ) +{ + if (this == &rBigInt) + return *this; + + if ( rBigInt.nLen != 0 ) + memcpy( static_cast<void*>(this), static_cast<const void*>(&rBigInt), sizeof( BigInt ) ); + else + { + nLen = 0; + nVal = rBigInt.nVal; + } + return *this; +} + +BigInt& BigInt::operator+=( const BigInt& rVal ) +{ + if ( nLen == 0 && rVal.nLen == 0 ) + { + if( nVal <= MY_MAXLONG && rVal.nVal <= MY_MAXLONG + && nVal >= MY_MINLONG && rVal.nVal >= MY_MINLONG ) + { // No overflows may occur here + nVal += rVal.nVal; + return *this; + } + + if( (nVal < 0) != (rVal.nVal < 0) ) + { // No overflows may occur here + nVal += rVal.nVal; + return *this; + } + } + + BigInt aTmp1, aTmp2; + aTmp1.MakeBigInt( *this ); + aTmp2.MakeBigInt( rVal ); + aTmp1.AddLong( aTmp2, *this ); + Normalize(); + return *this; +} + +BigInt& BigInt::operator-=( const BigInt& rVal ) +{ + if ( nLen == 0 && rVal.nLen == 0 ) + { + if ( nVal <= MY_MAXLONG && rVal.nVal <= MY_MAXLONG && + nVal >= MY_MINLONG && rVal.nVal >= MY_MINLONG ) + { // No overflows may occur here + nVal -= rVal.nVal; + return *this; + } + + if ( (nVal < 0) == (rVal.nVal < 0) ) + { // No overflows may occur here + nVal -= rVal.nVal; + return *this; + } + } + + BigInt aTmp1, aTmp2; + aTmp1.MakeBigInt( *this ); + aTmp2.MakeBigInt( rVal ); + aTmp1.SubLong( aTmp2, *this ); + Normalize(); + return *this; +} + +BigInt& BigInt::operator*=( const BigInt& rVal ) +{ + static const sal_Int32 MY_MAXSHORT = 0x00007fff; + static const sal_Int32 MY_MINSHORT = -MY_MAXSHORT; + + if ( nLen == 0 && rVal.nLen == 0 + && nVal <= MY_MAXSHORT && rVal.nVal <= MY_MAXSHORT + && nVal >= MY_MINSHORT && rVal.nVal >= MY_MINSHORT ) + // TODO: not optimal !!! W.P. + { // No overflows may occur here + nVal *= rVal.nVal; + } + else + { + BigInt aTmp1, aTmp2; + aTmp1.MakeBigInt( rVal ); + aTmp2.MakeBigInt( *this ); + aTmp1.MultLong(aTmp2, *this); + Normalize(); + } + return *this; +} + +BigInt& BigInt::operator/=( const BigInt& rVal ) +{ + if ( rVal.nLen == 0 ) + { + if ( rVal.nVal == 0 ) + { + OSL_FAIL( "BigInt::operator/ --> divide by zero" ); + return *this; + } + + if ( nLen == 0 ) + { + // No overflows may occur here + nVal /= rVal.nVal; + return *this; + } + + if ( rVal.nVal == 1 ) + return *this; + + if ( rVal.nVal == -1 ) + { + bIsNeg = !bIsNeg; + return *this; + } + + if ( rVal.nVal <= 0xFFFF && rVal.nVal >= -0xFFFF ) + { + // Divide BigInt with an sal_uInt16 + sal_uInt16 nTmp; + if ( rVal.nVal < 0 ) + { + nTmp = static_cast<sal_uInt16>(-rVal.nVal); + bIsNeg = !bIsNeg; + } + else + nTmp = static_cast<sal_uInt16>(rVal.nVal); + + Div( nTmp, nTmp ); + Normalize(); + return *this; + } + } + + if ( ABS_IsLess( rVal ) ) + { + *this = BigInt( 0 ); + return *this; + } + + // Divide BigInt with BigInt + BigInt aTmp1, aTmp2; + aTmp1.MakeBigInt( *this ); + aTmp2.MakeBigInt( rVal ); + aTmp1.DivLong(aTmp2, *this); + Normalize(); + return *this; +} + +BigInt& BigInt::operator%=( const BigInt& rVal ) +{ + if ( rVal.nLen == 0 ) + { + if ( rVal.nVal == 0 ) + { + OSL_FAIL( "BigInt::operator/ --> divide by zero" ); + return *this; + } + + if ( nLen == 0 ) + { + // No overflows may occur here + nVal %= rVal.nVal; + return *this; + } + + if ( rVal.nVal <= 0xFFFF && rVal.nVal >= -0xFFFF ) + { + // Divide Bigint by int16 + sal_uInt16 nTmp; + if ( rVal.nVal < 0 ) + { + nTmp = static_cast<sal_uInt16>(-rVal.nVal); + bIsNeg = !bIsNeg; + } + else + nTmp = static_cast<sal_uInt16>(rVal.nVal); + + Div( nTmp, nTmp ); + *this = BigInt( nTmp ); + return *this; + } + } + + if ( ABS_IsLess( rVal ) ) + return *this; + + // Divide BigInt with BigInt + BigInt aTmp1, aTmp2; + aTmp1.MakeBigInt( *this ); + aTmp2.MakeBigInt( rVal ); + aTmp1.ModLong(aTmp2, *this); + Normalize(); + return *this; +} + +bool operator==( const BigInt& rVal1, const BigInt& rVal2 ) +{ + if (rVal1.nLen == 0 && rVal2.nLen == 0) + return rVal1.nVal == rVal2.nVal; + + BigInt nA, nB; + nA.MakeBigInt(rVal1); + nB.MakeBigInt(rVal2); + return nA.bIsNeg == nB.bIsNeg && nA.nLen == nB.nLen + && std::equal(nA.nNum, nA.nNum + nA.nLen, nB.nNum); +} + +bool operator<( const BigInt& rVal1, const BigInt& rVal2 ) +{ + if (rVal1.nLen == 0 && rVal2.nLen == 0) + return rVal1.nVal < rVal2.nVal; + + BigInt nA, nB; + nA.MakeBigInt(rVal1); + nB.MakeBigInt(rVal2); + if (nA.bIsNeg != nB.bIsNeg) + return !nB.bIsNeg; + if (nA.nLen != nB.nLen) + return nA.bIsNeg ? (nA.nLen > nB.nLen) : (nA.nLen < nB.nLen); + int i = nA.nLen - 1; + while (i > 0 && nA.nNum[i] == nB.nNum[i]) + --i; + return nA.bIsNeg ? (nA.nNum[i] > nB.nNum[i]) : (nA.nNum[i] < nB.nNum[i]); +} + +tools::Long BigInt::Scale( tools::Long nVal, tools::Long nMul, tools::Long nDiv ) +{ + BigInt aVal( nVal ); + + aVal *= nMul; + + if ( aVal.IsNeg() != ( nDiv < 0 ) ) + aVal -= nDiv / 2; // for correct rounding + else + aVal += nDiv / 2; // for correct rounding + + aVal /= nDiv; + + return tools::Long( aVal ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/generic/color.cxx b/tools/source/generic/color.cxx new file mode 100644 index 0000000000..421045b588 --- /dev/null +++ b/tools/source/generic/color.cxx @@ -0,0 +1,266 @@ +/* -*- 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 <algorithm> +#include <iomanip> +#include <sstream> +#include <stdlib.h> + +#include <tools/color.hxx> +#include <tools/helpers.hxx> +#include <tools/long.hxx> +#include <o3tl/string_view.hxx> +#include <basegfx/color/bcolortools.hxx> + +void Color::IncreaseLuminance(sal_uInt8 cLumInc) +{ + R = sal_uInt8(std::clamp(R + cLumInc, 0, 255)); + G = sal_uInt8(std::clamp(G + cLumInc, 0, 255)); + B = sal_uInt8(std::clamp(B + cLumInc, 0, 255)); +} + +void Color::DecreaseLuminance(sal_uInt8 cLumDec) +{ + R = sal_uInt8(std::clamp(R - cLumDec, 0, 255)); + G = sal_uInt8(std::clamp(G - cLumDec, 0, 255)); + B = sal_uInt8(std::clamp(B - cLumDec, 0, 255)); +} + +void Color::DecreaseContrast(sal_uInt8 nContDec) +{ + if (nContDec) + { + const double fM = (128.0 - 0.4985 * nContDec) / 128.0; + const double fOff = 128.0 - fM * 128.0; + + R = sal_uInt8(std::clamp(FRound(R * fM + fOff), tools::Long(0), tools::Long(255))); + G = sal_uInt8(std::clamp(FRound(G * fM + fOff), tools::Long(0), tools::Long(255))); + B = sal_uInt8(std::clamp(FRound(B * fM + fOff), tools::Long(0), tools::Long(255))); + } +} + +// color space conversion + +void Color::RGBtoHSB( sal_uInt16& nHue, sal_uInt16& nSat, sal_uInt16& nBri ) const +{ + sal_uInt8 c[3]; + sal_uInt8 cMax, cMin; + + c[0] = R; + c[1] = G; + c[2] = B; + + cMax = c[0]; + if( c[1] > cMax ) + cMax = c[1]; + if( c[2] > cMax ) + cMax = c[2]; + + // Brightness = max(R, G, B); + nBri = cMax * 100 / 255; + + cMin = c[0]; + if( c[1] < cMin ) + cMin = c[1]; + if( c[2] < cMin ) + cMin = c[2]; + + sal_uInt8 cDelta = cMax - cMin; + + // Saturation = max - min / max + if( nBri > 0 ) + nSat = cDelta * 100 / cMax; + else + nSat = 0; + + if( nSat == 0 ) + nHue = 0; // Default = undefined + else + { + double dHue = 0.0; + + if( c[0] == cMax ) + { + dHue = static_cast<double>( c[1] - c[2] ) / static_cast<double>(cDelta); + } + else if( c[1] == cMax ) + { + dHue = 2.0 + static_cast<double>( c[2] - c[0] ) / static_cast<double>(cDelta); + } + else if ( c[2] == cMax ) + { + dHue = 4.0 + static_cast<double>( c[0] - c[1] ) / static_cast<double>(cDelta); + } + dHue *= 60.0; + + if( dHue < 0.0 ) + dHue += 360.0; + + nHue = static_cast<sal_uInt16>(dHue); + } +} + +Color Color::HSBtoRGB( sal_uInt16 nHue, sal_uInt16 nSat, sal_uInt16 nBri ) +{ + sal_uInt8 cR=0,cG=0,cB=0; + sal_uInt8 nB = static_cast<sal_uInt8>( nBri * 255 / 100 ); + + if( nSat == 0 ) + { + cR = nB; + cG = nB; + cB = nB; + } + else + { + double dH = nHue; + double f; + sal_uInt16 n; + if( dH == 360.0 ) + dH = 0.0; + + dH /= 60.0; + n = static_cast<sal_uInt16>(dH); + f = dH - n; + + sal_uInt8 a = static_cast<sal_uInt8>( nB * ( 100 - nSat ) / 100 ); + sal_uInt8 b = static_cast<sal_uInt8>( nB * ( 100 - ( static_cast<double>(nSat) * f ) ) / 100 ); + sal_uInt8 c = static_cast<sal_uInt8>( nB * ( 100 - ( static_cast<double>(nSat) * ( 1.0 - f ) ) ) / 100 ); + + switch( n ) + { + case 0: cR = nB; cG = c; cB = a; break; + case 1: cR = b; cG = nB; cB = a; break; + case 2: cR = a; cG = nB; cB = c; break; + case 3: cR = a; cG = b; cB = nB; break; + case 4: cR = c; cG = a; cB = nB; break; + case 5: cR = nB; cG = a; cB = b; break; + } + } + + return Color( cR, cG, cB ); +} + +Color Color::STRtoRGB(std::u16string_view colorname) +{ + Color col; + if(colorname.empty()) return col; + + switch(colorname.size()){ + case 7: + col.mValue = o3tl::toUInt32(colorname.substr(1,6), 16); + break; + case 6: + col.mValue = o3tl::toUInt32(colorname, 16); + break; + case 4: + { + sal_Unicode data[6] = { colorname[1], colorname[1], colorname[2], + colorname[2], colorname[3], colorname[3] }; + col.mValue = o3tl::toUInt32(std::u16string_view(data,6), 16); + break; + } + case 3: + { + sal_Unicode data[6] = { colorname[0], colorname[0], colorname[1], + colorname[1], colorname[2], colorname[2] }; + col.mValue = o3tl::toUInt32(std::u16string_view(data,6), 16); + break; + } + default: + break; + } + return col; +} + +OUString Color::AsRGBHexString() const +{ + std::stringstream ss; + ss << std::hex << std::setfill ('0') << std::setw(6) << sal_uInt32(GetRGBColor()); + return OUString::createFromAscii(ss.str()); +} + +OUString Color::AsRGBHEXString() const +{ + std::stringstream ss; + ss << std::hex << std::uppercase << std::setfill ('0') << std::setw(6) << sal_uInt32(GetRGBColor()); + return OUString::createFromAscii(ss.str()); +} + +void Color::ApplyTintOrShade(sal_Int16 n100thPercent) +{ + if (n100thPercent == 0) + return; + + basegfx::BColor aBColor = basegfx::utils::rgb2hsl(getBColor()); + double fFactor = 1.0 - (std::abs(double(n100thPercent)) / 10000.0); + double fResult; + + if (n100thPercent > 0) // tint + { + fResult = aBColor.getBlue() * fFactor + (1.0 - fFactor); + } + else // shade + { + fResult = aBColor.getBlue() * fFactor; + } + + aBColor.setBlue(fResult); + aBColor = basegfx::utils::hsl2rgb(aBColor); + + R = sal_uInt8(std::lround(aBColor.getRed() * 255.0)); + G = sal_uInt8(std::lround(aBColor.getGreen() * 255.0)); + B = sal_uInt8(std::lround(aBColor.getBlue() * 255.0)); +} + +void Color::ApplyLumModOff(sal_Int16 nMod, sal_Int16 nOff) +{ + if (nMod == 10000 && nOff == 0) + { + return; + } + // Switch to HSL, where applying these transforms is easier. + basegfx::BColor aBColor = basegfx::utils::rgb2hsl(getBColor()); + + // 50% is half luminance, 200% is double luminance. Unit is 100th percent. + aBColor.setBlue(std::clamp(aBColor.getBlue() * nMod / 10000, 0.0, 1.0)); + // If color changes to black or white, it will stay gray if luminance changes again. + if ((aBColor.getBlue() == 0.0) || (aBColor.getBlue() == 1.0)) + { + aBColor.setGreen(0.0); + } + + // Luminance offset means hue and saturation is left unchanged. Unit is 100th percent. + aBColor.setBlue(std::clamp(aBColor.getBlue() + static_cast<double>(nOff) / 10000, 0.0, 1.0)); + // If color changes to black or white, it will stay gray if luminance changes again. + if ((aBColor.getBlue() == 0.0) || (aBColor.getBlue() == 1.0)) + { + aBColor.setGreen(0.0); + } + + // Switch back to RGB. + aBColor = basegfx::utils::hsl2rgb(aBColor); + R = sal_uInt8(std::lround(aBColor.getRed() * 255.0)); + G = sal_uInt8(std::lround(aBColor.getGreen() * 255.0)); + B = sal_uInt8(std::lround(aBColor.getBlue() * 255.0)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/generic/config.cxx b/tools/source/generic/config.cxx new file mode 100644 index 0000000000..cc5cce9c3b --- /dev/null +++ b/tools/source/generic/config.cxx @@ -0,0 +1,944 @@ +/* -*- 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 <cstddef> +#include <cstdlib> +#include <string.h> + +#ifdef _WIN32 +#include <stdlib.h> +#endif + +#include <osl/file.hxx> +#include <tools/config.hxx> +#include <sal/log.hxx> + +namespace { + +struct ImplKeyData +{ + ImplKeyData* mpNext; + OString maKey; + OString maValue; + bool mbIsComment; +}; + +} + +struct ImplGroupData +{ + ImplGroupData* mpNext; + ImplKeyData* mpFirstKey; + OString maGroupName; + sal_uInt16 mnEmptyLines; +}; + +struct ImplConfigData +{ + ImplGroupData* mpFirstGroup; + OUString maFileName; + sal_uInt32 mnDataUpdateId; + sal_uInt32 mnTimeStamp; + bool mbModified; + bool mbRead; + bool mbIsUTF8BOM; +}; + +static OUString toUncPath( const OUString& rPath ) +{ + OUString aFileURL; + + // check if rFileName is already a URL; if not make it so + if( rPath.startsWith( "file://")) + { + aFileURL = rPath; + } + else if( ::osl::FileBase::getFileURLFromSystemPath( rPath, aFileURL ) != ::osl::FileBase::E_None ) + { + aFileURL = rPath; + } + return aFileURL; +} + +static sal_uInt32 ImplSysGetConfigTimeStamp( const OUString& rFileName ) +{ + sal_uInt32 nTimeStamp = 0; + ::osl::DirectoryItem aItem; + ::osl::FileStatus aStatus( osl_FileStatus_Mask_ModifyTime ); + + if( ::osl::DirectoryItem::get( rFileName, aItem ) == ::osl::FileBase::E_None && + aItem.getFileStatus( aStatus ) == ::osl::FileBase::E_None ) + { + nTimeStamp = aStatus.getModifyTime().Seconds; + } + + return nTimeStamp; +} + +static std::unique_ptr<sal_uInt8[]> ImplSysReadConfig( const OUString& rFileName, + sal_uInt64& rRead, bool& rbRead, bool& rbIsUTF8BOM, sal_uInt32& rTimeStamp ) +{ + std::unique_ptr<sal_uInt8[]> pBuf; + ::osl::File aFile( rFileName ); + + if( aFile.open( osl_File_OpenFlag_Read ) == ::osl::FileBase::E_None ) + { + sal_uInt64 nPos = 0; + if( aFile.getSize( nPos ) == ::osl::FileBase::E_None ) + { + if (nPos > SAL_MAX_SIZE) { + aFile.close(); + return nullptr; + } + pBuf.reset(new sal_uInt8[static_cast< std::size_t >(nPos)]); + sal_uInt64 nRead = 0; + if( aFile.read( pBuf.get(), nPos, nRead ) == ::osl::FileBase::E_None && nRead == nPos ) + { + //skip the byte-order-mark 0xEF 0xBB 0xBF, if it was UTF8 files + unsigned char const BOM[3] = {0xEF, 0xBB, 0xBF}; + if (nRead > 2 && memcmp(pBuf.get(), BOM, 3) == 0) + { + nRead -= 3; + memmove(pBuf.get(), pBuf.get() + 3, sal::static_int_cast<std::size_t>(nRead * sizeof(sal_uInt8)) ); + rbIsUTF8BOM = true; + } + + rTimeStamp = ImplSysGetConfigTimeStamp( rFileName ); + rbRead = true; + rRead = nRead; + } + else + { + pBuf.reset(); + } + } + aFile.close(); + } + + return pBuf; +} + +static bool ImplSysWriteConfig( const OUString& rFileName, + const sal_uInt8* pBuf, sal_uInt32 nBufLen, bool rbIsUTF8BOM, sal_uInt32& rTimeStamp ) +{ + bool bSuccess = false; + bool bUTF8BOMSuccess = false; + + ::osl::File aFile( rFileName ); + ::osl::FileBase::RC eError = aFile.open( osl_File_OpenFlag_Write | osl_File_OpenFlag_Create ); + if( eError != ::osl::FileBase::E_None ) + eError = aFile.open( osl_File_OpenFlag_Write ); + if( eError == ::osl::FileBase::E_None ) + { + // truncate + aFile.setSize( 0 ); + sal_uInt64 nWritten; + + //write the byte-order-mark 0xEF 0xBB 0xBF first , if it was UTF8 files + if ( rbIsUTF8BOM ) + { + unsigned char const BOM[3] = {0xEF, 0xBB, 0xBF}; + sal_uInt64 nUTF8BOMWritten; + if( aFile.write( BOM, 3, nUTF8BOMWritten ) == ::osl::FileBase::E_None && 3 == nUTF8BOMWritten ) + { + bUTF8BOMSuccess = true; + } + } + + if( aFile.write( pBuf, nBufLen, nWritten ) == ::osl::FileBase::E_None && nWritten == nBufLen ) + { + bSuccess = true; + } + if ( rbIsUTF8BOM ? bSuccess && bUTF8BOMSuccess : bSuccess ) + { + rTimeStamp = ImplSysGetConfigTimeStamp( rFileName ); + } + } + + return rbIsUTF8BOM ? bSuccess && bUTF8BOMSuccess : bSuccess; +} + +namespace { +OString makeOString(const sal_uInt8* p, sal_uInt64 n) +{ + if (n > SAL_MAX_INT32) + { + #ifdef _WIN32 + abort(); + #else + ::std::abort(); //TODO: handle this gracefully + #endif + } + return OString( + reinterpret_cast< char const * >(p), + sal::static_int_cast< sal_Int32 >(n)); +} +} + +static void ImplMakeConfigList( ImplConfigData* pData, + const sal_uInt8* pBuf, sal_uInt64 nLen ) +{ + if ( !nLen ) + return; + + // Parse buffer and build config list + sal_uInt64 nStart; + sal_uInt64 nLineLen; + sal_uInt64 nNameLen; + sal_uInt64 nKeyLen; + sal_uInt64 i; + const sal_uInt8* pLine; + ImplKeyData* pPrevKey = nullptr; + ImplGroupData* pPrevGroup = nullptr; + ImplGroupData* pGroup = nullptr; + i = 0; + while ( i < nLen ) + { + // Ctrl+Z + if ( pBuf[i] == 0x1A ) + break; + + // Remove spaces and tabs + while ( (pBuf[i] == ' ') || (pBuf[i] == '\t') ) + i++; + + // remember line-starts + nStart = i; + pLine = pBuf+i; + + // search line-endings + while ( (i < nLen) && pBuf[i] && (pBuf[i] != '\r') && (pBuf[i] != '\n') && + (pBuf[i] != 0x1A) ) + i++; + + nLineLen = i-nStart; + + // if Line-ending is found, continue once + if ( (i+1 < nLen) && + (pBuf[i] != pBuf[i+1]) && + ((pBuf[i+1] == '\r') || (pBuf[i+1] == '\n')) ) + i++; + i++; + + // evaluate line + if ( *pLine == '[' ) + { + pGroup = new ImplGroupData; + pGroup->mpNext = nullptr; + pGroup->mpFirstKey = nullptr; + pGroup->mnEmptyLines = 0; + if ( pPrevGroup ) + pPrevGroup->mpNext = pGroup; + else + pData->mpFirstGroup = pGroup; + pPrevGroup = pGroup; + pPrevKey = nullptr; + + // filter group names + pLine++; + nLineLen--; + // remove spaces and tabs + while ( (*pLine == ' ') || (*pLine == '\t') ) + { + nLineLen--; + pLine++; + } + nNameLen = 0; + while ( (nNameLen < nLineLen) && (pLine[nNameLen] != ']') ) + nNameLen++; + if ( nNameLen ) + { + while ( (pLine[nNameLen-1] == ' ') || (pLine[nNameLen-1] == '\t') ) + nNameLen--; + } + pGroup->maGroupName = makeOString(pLine, nNameLen); + } + else + { + if ( nLineLen ) + { + // If no group exists yet, add to default + if ( !pGroup ) + { + pGroup = new ImplGroupData; + pGroup->mpNext = nullptr; + pGroup->mpFirstKey = nullptr; + pGroup->mnEmptyLines = 0; + pData->mpFirstGroup = pGroup; + pPrevGroup = pGroup; + pPrevKey = nullptr; + } + + // if empty line, append it + if ( pPrevKey ) + { + while ( pGroup->mnEmptyLines ) + { + ImplKeyData* pKey = new ImplKeyData; + pKey->mbIsComment = true; + pPrevKey->mpNext = pKey; + pPrevKey = pKey; + pGroup->mnEmptyLines--; + } + } + + // Generate new key + ImplKeyData* pKey = new ImplKeyData; + pKey->mpNext = nullptr; + if ( pPrevKey ) + pPrevKey->mpNext = pKey; + else + pGroup->mpFirstKey = pKey; + pPrevKey = pKey; + if ( pLine[0] == ';' ) + { + pKey->maValue = makeOString(pLine, nLineLen); + pKey->mbIsComment = true; + } + else + { + pKey->mbIsComment = false; + nNameLen = 0; + while ( (nNameLen < nLineLen) && (pLine[nNameLen] != '=') ) + nNameLen++; + nKeyLen = nNameLen; + // Remove spaces and tabs + if ( nNameLen ) + { + while ( (pLine[nNameLen-1] == ' ') || (pLine[nNameLen-1] == '\t') ) + nNameLen--; + } + pKey->maKey = makeOString(pLine, nNameLen); + nKeyLen++; + if ( nKeyLen < nLineLen ) + { + pLine += nKeyLen; + nLineLen -= nKeyLen; + // Remove spaces and tabs + while ( (*pLine == ' ') || (*pLine == '\t') ) + { + nLineLen--; + pLine++; + } + if ( nLineLen ) + { + while ( (pLine[nLineLen-1] == ' ') || (pLine[nLineLen-1] == '\t') ) + nLineLen--; + pKey->maValue = makeOString(pLine, nLineLen); + } + } + } + } + else + { + // Spaces are counted and appended only after key generation, + // as we want to store spaces even after adding new keys + if ( pGroup ) + pGroup->mnEmptyLines++; + } + } + } +} + +static std::unique_ptr<sal_uInt8[]> ImplGetConfigBuffer( const ImplConfigData* pData, sal_uInt32& rLen ) +{ + std::unique_ptr<sal_uInt8[]> pWriteBuf; + sal_uInt8* pBuf; + sal_uInt8 aLineEndBuf[2] = {0, 0}; + ImplKeyData* pKey; + ImplGroupData* pGroup; + sal_uInt32 nBufLen; + sal_uInt32 nValueLen; + sal_uInt32 nKeyLen; + sal_uInt32 nLineEndLen; + + aLineEndBuf[0] = '\r'; + aLineEndBuf[1] = '\n'; + nLineEndLen = 2; + + nBufLen = 0; + pGroup = pData->mpFirstGroup; + while ( pGroup ) + { + // Don't write empty groups + if ( pGroup->mpFirstKey ) + { + nBufLen += pGroup->maGroupName.getLength() + nLineEndLen + 2; + pKey = pGroup->mpFirstKey; + while ( pKey ) + { + nValueLen = pKey->maValue.getLength(); + if ( pKey->mbIsComment ) + nBufLen += nValueLen + nLineEndLen; + else + nBufLen += pKey->maKey.getLength() + nValueLen + nLineEndLen + 1; + + pKey = pKey->mpNext; + } + + // Write empty lines after each group + if ( !pGroup->mnEmptyLines ) + pGroup->mnEmptyLines = 1; + nBufLen += nLineEndLen * pGroup->mnEmptyLines; + } + + pGroup = pGroup->mpNext; + } + + // Output buffer length + rLen = nBufLen; + if ( !nBufLen ) + { + pWriteBuf.reset(new sal_uInt8[nLineEndLen]); + pWriteBuf[0] = aLineEndBuf[0]; + if ( nLineEndLen == 2 ) + pWriteBuf[1] = aLineEndBuf[1]; + return pWriteBuf; + } + + // Allocate new write buffer (caller frees it) + pWriteBuf.reset(new sal_uInt8[nBufLen]); + + // fill buffer + pBuf = pWriteBuf.get(); + pGroup = pData->mpFirstGroup; + while ( pGroup ) + { + // Don't write empty groups + if ( pGroup->mpFirstKey ) + { + *pBuf = '['; pBuf++; + memcpy( pBuf, pGroup->maGroupName.getStr(), pGroup->maGroupName.getLength() ); + pBuf += pGroup->maGroupName.getLength(); + *pBuf = ']'; pBuf++; + *pBuf = aLineEndBuf[0]; pBuf++; + if ( nLineEndLen == 2 ) + { + *pBuf = aLineEndBuf[1]; pBuf++; + } + pKey = pGroup->mpFirstKey; + while ( pKey ) + { + nValueLen = pKey->maValue.getLength(); + if ( pKey->mbIsComment ) + { + if ( nValueLen ) + { + memcpy( pBuf, pKey->maValue.getStr(), nValueLen ); + pBuf += nValueLen; + } + *pBuf = aLineEndBuf[0]; pBuf++; + if ( nLineEndLen == 2 ) + { + *pBuf = aLineEndBuf[1]; pBuf++; + } + } + else + { + nKeyLen = pKey->maKey.getLength(); + memcpy( pBuf, pKey->maKey.getStr(), nKeyLen ); + pBuf += nKeyLen; + *pBuf = '='; pBuf++; + memcpy( pBuf, pKey->maValue.getStr(), nValueLen ); + pBuf += nValueLen; + *pBuf = aLineEndBuf[0]; pBuf++; + if ( nLineEndLen == 2 ) + { + *pBuf = aLineEndBuf[1]; pBuf++; + } + } + + pKey = pKey->mpNext; + } + + // Store empty line after each group + sal_uInt16 nEmptyLines = pGroup->mnEmptyLines; + while ( nEmptyLines ) + { + *pBuf = aLineEndBuf[0]; pBuf++; + if ( nLineEndLen == 2 ) + { + *pBuf = aLineEndBuf[1]; pBuf++; + } + nEmptyLines--; + } + } + + pGroup = pGroup->mpNext; + } + + return pWriteBuf; +} + +static void ImplReadConfig( ImplConfigData* pData ) +{ + sal_uInt32 nTimeStamp = 0; + sal_uInt64 nRead = 0; + bool bRead = false; + bool bIsUTF8BOM = false; + std::unique_ptr<sal_uInt8[]> pBuf = ImplSysReadConfig( pData->maFileName, nRead, bRead, bIsUTF8BOM, nTimeStamp ); + + // Read config list from buffer + if ( pBuf ) + { + ImplMakeConfigList( pData, pBuf.get(), nRead ); + pBuf.reset(); + } + pData->mnTimeStamp = nTimeStamp; + pData->mbModified = false; + if ( bRead ) + pData->mbRead = true; + if ( bIsUTF8BOM ) + pData->mbIsUTF8BOM = true; +} + +static void ImplWriteConfig( ImplConfigData* pData ) +{ + SAL_WARN_IF( pData->mnTimeStamp != ImplSysGetConfigTimeStamp( pData->maFileName ), + "tools.generic", "Config overwrites modified configfile: " << pData->maFileName ); + + // Read config list from buffer + sal_uInt32 nBufLen; + std::unique_ptr<sal_uInt8[]> pBuf = ImplGetConfigBuffer( pData, nBufLen ); + if ( pBuf ) + { + if ( ImplSysWriteConfig( pData->maFileName, pBuf.get(), nBufLen, pData->mbIsUTF8BOM, pData->mnTimeStamp ) ) + pData->mbModified = false; + } + else + pData->mbModified = false; +} + +static void ImplDeleteConfigData( ImplConfigData* pData ) +{ + ImplKeyData* pTempKey; + ImplKeyData* pKey; + ImplGroupData* pTempGroup; + ImplGroupData* pGroup = pData->mpFirstGroup; + while ( pGroup ) + { + pTempGroup = pGroup->mpNext; + + // remove all keys + pKey = pGroup->mpFirstKey; + while ( pKey ) + { + pTempKey = pKey->mpNext; + delete pKey; + pKey = pTempKey; + } + + // remove group and continue + delete pGroup; + pGroup = pTempGroup; + } + + pData->mpFirstGroup = nullptr; +} + +static std::unique_ptr<ImplConfigData> ImplGetConfigData( const OUString& rFileName ) +{ + std::unique_ptr<ImplConfigData> pData(new ImplConfigData); + pData->maFileName = rFileName; + pData->mpFirstGroup = nullptr; + pData->mnDataUpdateId = 0; + pData->mbRead = false; + pData->mbIsUTF8BOM = false; + ImplReadConfig( pData.get() ); + + return pData; +} + +bool Config::ImplUpdateConfig() const +{ + // Re-read file if timestamp differs + if ( mpData->mnTimeStamp != ImplSysGetConfigTimeStamp( maFileName ) ) + { + ImplDeleteConfigData( mpData.get() ); + ImplReadConfig( mpData.get() ); + mpData->mnDataUpdateId++; + return true; + } + else + return false; +} + +ImplGroupData* Config::ImplGetGroup() const +{ + if ( !mpActGroup || (mnDataUpdateId != mpData->mnDataUpdateId) ) + { + ImplGroupData* pPrevGroup = nullptr; + ImplGroupData* pGroup = mpData->mpFirstGroup; + while ( pGroup ) + { + if ( pGroup->maGroupName.equalsIgnoreAsciiCase(maGroupName) ) + break; + + pPrevGroup = pGroup; + pGroup = pGroup->mpNext; + } + + // Add group if not exists + if ( !pGroup ) + { + pGroup = new ImplGroupData; + pGroup->mpNext = nullptr; + pGroup->mpFirstKey = nullptr; + pGroup->mnEmptyLines = 1; + if ( pPrevGroup ) + pPrevGroup->mpNext = pGroup; + else + mpData->mpFirstGroup = pGroup; + } + + // Always inherit group names and update cache members + pGroup->maGroupName = maGroupName; + const_cast<Config*>(this)->mnDataUpdateId = mpData->mnDataUpdateId; + const_cast<Config*>(this)->mpActGroup = pGroup; + } + + return mpActGroup; +} + +Config::Config( const OUString& rFileName ) +{ + // Initialize config data + maFileName = toUncPath( rFileName ); + mpData = ImplGetConfigData( maFileName ); + mpActGroup = nullptr; + mnDataUpdateId = 0; + + SAL_INFO("tools.generic", "Config::Config( " << maFileName << " )"); +} + +Config::~Config() +{ + SAL_INFO("tools.generic", "Config::~Config()" ); + + Flush(); + ImplDeleteConfigData( mpData.get() ); +} + +void Config::SetGroup(const OString& rGroup) +{ + // If group is to be reset, it needs to be updated on next call + if ( maGroupName != rGroup ) + { + maGroupName = rGroup; + mnDataUpdateId = mpData->mnDataUpdateId-1; + } +} + +void Config::DeleteGroup(std::string_view rGroup) +{ + // Update config data if necessary + if ( !mpData->mbRead ) + { + ImplUpdateConfig(); + mpData->mbRead = true; + } + + ImplGroupData* pPrevGroup = nullptr; + ImplGroupData* pGroup = mpData->mpFirstGroup; + while ( pGroup ) + { + if ( pGroup->maGroupName.equalsIgnoreAsciiCase(rGroup) ) + break; + + pPrevGroup = pGroup; + pGroup = pGroup->mpNext; + } + + if ( !pGroup ) + return; + + // Remove all keys + ImplKeyData* pTempKey; + ImplKeyData* pKey = pGroup->mpFirstKey; + while ( pKey ) + { + pTempKey = pKey->mpNext; + delete pKey; + pKey = pTempKey; + } + + // Rewire pointers and remove group + if ( pPrevGroup ) + pPrevGroup->mpNext = pGroup->mpNext; + else + mpData->mpFirstGroup = pGroup->mpNext; + delete pGroup; + + // Rewrite config data + mpData->mbModified = true; + + mnDataUpdateId = mpData->mnDataUpdateId; + mpData->mnDataUpdateId++; +} + +OString Config::GetGroupName(sal_uInt16 nGroup) const +{ + ImplGroupData* pGroup = mpData->mpFirstGroup; + sal_uInt16 nGroupCount = 0; + OString aGroupName; + while ( pGroup ) + { + if ( nGroup == nGroupCount ) + { + aGroupName = pGroup->maGroupName; + break; + } + + nGroupCount++; + pGroup = pGroup->mpNext; + } + + return aGroupName; +} + +sal_uInt16 Config::GetGroupCount() const +{ + ImplGroupData* pGroup = mpData->mpFirstGroup; + sal_uInt16 nGroupCount = 0; + while ( pGroup ) + { + nGroupCount++; + pGroup = pGroup->mpNext; + } + + return nGroupCount; +} + +bool Config::HasGroup(std::string_view rGroup) const +{ + ImplGroupData* pGroup = mpData->mpFirstGroup; + bool bRet = false; + + while( pGroup ) + { + if( pGroup->maGroupName.equalsIgnoreAsciiCase(rGroup) ) + { + bRet = true; + break; + } + + pGroup = pGroup->mpNext; + } + + return bRet; +} + +OString Config::ReadKey(const OString& rKey) const +{ + return ReadKey(rKey, OString()); +} + +OString Config::ReadKey(const OString& rKey, const OString& rDefault) const +{ + SAL_INFO("tools.generic", "Config::ReadKey( " << rKey << " ) from " << GetGroup() + << " in " << maFileName); + + // Search key, return value if found + ImplGroupData* pGroup = ImplGetGroup(); + if ( pGroup ) + { + ImplKeyData* pKey = pGroup->mpFirstKey; + while ( pKey ) + { + if ( !pKey->mbIsComment && pKey->maKey.equalsIgnoreAsciiCase(rKey) ) + return pKey->maValue; + + pKey = pKey->mpNext; + } + } + + return rDefault; +} + +void Config::WriteKey(const OString& rKey, const OString& rStr) +{ + SAL_INFO("tools.generic", "Config::WriteKey( " << rKey << ", " << rStr << " ) to " + << GetGroup() << " in " << maFileName); + + // Update config data if necessary + if ( !mpData->mbRead ) + { + ImplUpdateConfig(); + mpData->mbRead = true; + } + + // Search key and update value if found + ImplGroupData* pGroup = ImplGetGroup(); + if ( !pGroup ) + return; + + ImplKeyData* pPrevKey = nullptr; + ImplKeyData* pKey = pGroup->mpFirstKey; + while ( pKey ) + { + if ( !pKey->mbIsComment && pKey->maKey.equalsIgnoreAsciiCase(rKey) ) + break; + + pPrevKey = pKey; + pKey = pKey->mpNext; + } + + bool bNewValue; + if ( !pKey ) + { + pKey = new ImplKeyData; + pKey->mpNext = nullptr; + pKey->maKey = rKey; + pKey->mbIsComment = false; + if ( pPrevKey ) + pPrevKey->mpNext = pKey; + else + pGroup->mpFirstKey = pKey; + bNewValue = true; + } + else + bNewValue = pKey->maValue != rStr; + + if ( bNewValue ) + { + pKey->maValue = rStr; + + mpData->mbModified = true; + } +} + +void Config::DeleteKey(std::string_view rKey) +{ + // Update config data if necessary + if ( !mpData->mbRead ) + { + ImplUpdateConfig(); + mpData->mbRead = true; + } + + // Search key and update value + ImplGroupData* pGroup = ImplGetGroup(); + if ( !pGroup ) + return; + + ImplKeyData* pPrevKey = nullptr; + ImplKeyData* pKey = pGroup->mpFirstKey; + while ( pKey ) + { + if ( !pKey->mbIsComment && pKey->maKey.equalsIgnoreAsciiCase(rKey) ) + break; + + pPrevKey = pKey; + pKey = pKey->mpNext; + } + + if ( pKey ) + { + // Rewire group pointers and delete + if ( pPrevKey ) + pPrevKey->mpNext = pKey->mpNext; + else + pGroup->mpFirstKey = pKey->mpNext; + delete pKey; + + mpData->mbModified = true; + } +} + +sal_uInt16 Config::GetKeyCount() const +{ + SAL_INFO("tools.generic", "Config::GetKeyCount() from " << GetGroup() << " in " << maFileName); + + // Search key and update value + sal_uInt16 nCount = 0; + ImplGroupData* pGroup = ImplGetGroup(); + if ( pGroup ) + { + ImplKeyData* pKey = pGroup->mpFirstKey; + while ( pKey ) + { + if ( !pKey->mbIsComment ) + nCount++; + + pKey = pKey->mpNext; + } + } + + return nCount; +} + +OString Config::GetKeyName(sal_uInt16 nKey) const +{ + SAL_INFO("tools.generic", "Config::GetKeyName( " << OString::number(static_cast<sal_Int32>(nKey)) + << " ) from " << GetGroup() << " in " << maFileName); + + // search key and return name if found + ImplGroupData* pGroup = ImplGetGroup(); + if ( pGroup ) + { + ImplKeyData* pKey = pGroup->mpFirstKey; + while ( pKey ) + { + if ( !pKey->mbIsComment ) + { + if ( !nKey ) + return pKey->maKey; + nKey--; + } + + pKey = pKey->mpNext; + } + } + + return OString(); +} + +OString Config::ReadKey(sal_uInt16 nKey) const +{ + SAL_INFO("tools.generic", "Config::ReadKey( " << OString::number(static_cast<sal_Int32>(nKey)) + << " ) from " << GetGroup() << " in " << maFileName); + + // Search key and return value if found + ImplGroupData* pGroup = ImplGetGroup(); + if ( pGroup ) + { + ImplKeyData* pKey = pGroup->mpFirstKey; + while ( pKey ) + { + if ( !pKey->mbIsComment ) + { + if ( !nKey ) + return pKey->maValue; + nKey--; + } + + pKey = pKey->mpNext; + } + } + + return OString(); +} + +void Config::Flush() +{ + if ( mpData->mbModified ) + ImplWriteConfig( mpData.get() ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/generic/fract.cxx b/tools/source/generic/fract.cxx new file mode 100644 index 0000000000..abe8ee41b2 --- /dev/null +++ b/tools/source/generic/fract.cxx @@ -0,0 +1,537 @@ +/* -*- 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 <tools/fract.hxx> +#include <tools/debug.hxx> +#include <o3tl/hash_combine.hxx> +#include <o3tl/safeint.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +#include <algorithm> +#include <cmath> +#include <numeric> + +#include <boost/rational.hpp> + +#ifdef _MSC_VER +#include <intrin.h> +#endif + +static boost::rational<sal_Int32> rational_FromDouble(double dVal); +static void rational_ReduceInaccurate(boost::rational<sal_Int32>& rRational, unsigned nSignificantBits); +static int impl_NumberOfBits( sal_uInt32 nNum ); + +static boost::rational<sal_Int32> toRational(sal_Int32 n, sal_Int32 d) +{ + // https://github.com/boostorg/boost/issues/335 when these are std::numeric_limits<sal_Int32>::min + if (n == d) + return 1; + // tdf#144319 avoid boost::bad_rational e.g. if numerator=-476741369, denominator=-2147483648 + if (d < -std::numeric_limits<sal_Int32>::max()) + return 0; + return boost::rational<sal_Int32>(n, d); +} + +static constexpr bool isOutOfRange(sal_Int64 nNum) +{ + return nNum < std::numeric_limits<sal_Int32>::min() + || nNum > std::numeric_limits<sal_Int32>::max(); +} + +Fraction::Fraction( sal_Int64 nNum, sal_Int64 nDen ) : mnNumerator(nNum), mnDenominator(nDen) +{ + if ( isOutOfRange(nNum) || isOutOfRange(nDen) ) + { + // tdf#143200 + if (const auto gcd = std::gcd(nNum, nDen); gcd > 1) + { + nNum /= gcd; + nDen /= gcd; + } + SAL_WARN_IF(isOutOfRange(nNum) || isOutOfRange(nDen), + "tools.fraction", "values outside of range we can represent, doing reduction, which will reduce precision"); + while (isOutOfRange(nNum) || isOutOfRange(nDen)) + { + nNum /= 2; + nDen /= 2; + } + mnNumerator = nNum; + mnDenominator = nDen; + } + if ( mnDenominator == 0 ) + { + mbValid = false; + SAL_WARN( "tools.fraction", "'Fraction(" << nNum << ",0)' invalid fraction created" ); + return; + } + else if ((nDen == -1 && nNum == std::numeric_limits<sal_Int32>::min()) || + (nNum == -1 && nDen == std::numeric_limits<sal_Int32>::min())) + { + mbValid = false; + SAL_WARN("tools.fraction", "'Fraction(" << nNum << "," << nDen << ")' invalid fraction created"); + return; + } +} + +/** + * only here to prevent passing of NaN + */ +Fraction::Fraction( double nNum, double nDen ) + : Fraction(sal_Int64(nNum), sal_Int64(nDen)) +{} + +Fraction::Fraction( double dVal ) +{ + try + { + boost::rational<sal_Int32> v = rational_FromDouble( dVal ); + mnNumerator = v.numerator(); + mnDenominator = v.denominator(); + } + catch (const boost::bad_rational&) + { + mbValid = false; + SAL_WARN( "tools.fraction", "'Fraction(" << dVal << ")' invalid fraction created" ); + } +} + +Fraction::operator double() const +{ + if (!mbValid) + { + SAL_WARN( "tools.fraction", "'double()' on invalid fraction" ); + return 0.0; + } + + return boost::rational_cast<double>(toRational(mnNumerator, mnDenominator)); +} + +// This methods first validates both values. +// If one of the arguments is invalid, the whole operation is invalid. +// After computation detect if result overflows a sal_Int32 value +// which cause the operation to be marked as invalid +Fraction& Fraction::operator += ( const Fraction& rVal ) +{ + if ( !rVal.mbValid ) + mbValid = false; + + if ( !mbValid ) + { + SAL_WARN( "tools.fraction", "'operator +=' with invalid fraction" ); + return *this; + } + + boost::rational<sal_Int32> a = toRational(mnNumerator, mnDenominator); + a += toRational(rVal.mnNumerator, rVal.mnDenominator); + mnNumerator = a.numerator(); + mnDenominator = a.denominator(); + + return *this; +} + +Fraction& Fraction::operator -= ( const Fraction& rVal ) +{ + if ( !rVal.mbValid ) + mbValid = false; + + if ( !mbValid ) + { + SAL_WARN( "tools.fraction", "'operator -=' with invalid fraction" ); + return *this; + } + + boost::rational<sal_Int32> a = toRational(mnNumerator, mnDenominator); + a -= toRational(rVal.mnNumerator, rVal.mnDenominator); + mnNumerator = a.numerator(); + mnDenominator = a.denominator(); + + return *this; +} + +namespace +{ + bool checked_multiply_by(boost::rational<sal_Int32>& i, const boost::rational<sal_Int32>& r) + { + // Protect against self-modification + sal_Int32 num = r.numerator(); + sal_Int32 den = r.denominator(); + + // Fast-path if the number of bits in input is < the number of bits in the output, overflow cannot happen + // This is considerably faster than repeated std::gcd() operations + if ((impl_NumberOfBits(std::abs(i.numerator())) + impl_NumberOfBits(std::abs(r.numerator()))) < 32 && + (impl_NumberOfBits(std::abs(i.denominator())) + impl_NumberOfBits(std::abs(r.denominator()))) < 32) + { + i *= r; + return false; + } + + // Avoid overflow and preserve normalization + sal_Int32 gcd1 = std::gcd(i.numerator(), den); + sal_Int32 gcd2 = std::gcd(num, i.denominator()); + + if (!gcd1 || !gcd2) + return true; + + bool fail = false; + fail |= o3tl::checked_multiply(i.numerator() / gcd1, num / gcd2, num); + fail |= o3tl::checked_multiply(i.denominator() / gcd2, den / gcd1, den); + + if (!fail) + i.assign(num, den); + + return fail; + } +} + +Fraction& Fraction::operator *= ( const Fraction& rVal ) +{ + if ( !rVal.mbValid ) + mbValid = false; + + if ( !mbValid ) + { + SAL_WARN( "tools.fraction", "'operator *=' with invalid fraction" ); + return *this; + } + + boost::rational<sal_Int32> a = toRational(mnNumerator, mnDenominator); + boost::rational<sal_Int32> b = toRational(rVal.mnNumerator, rVal.mnDenominator); + bool bFail = checked_multiply_by(a, b); + mnNumerator = a.numerator(); + mnDenominator = a.denominator(); + + if (bFail) + { + mbValid = false; + } + + return *this; +} + +Fraction& Fraction::operator /= ( const Fraction& rVal ) +{ + if ( !rVal.mbValid ) + mbValid = false; + + if ( !mbValid ) + { + SAL_WARN( "tools.fraction", "'operator /=' with invalid fraction" ); + return *this; + } + + boost::rational<sal_Int32> a = toRational(mnNumerator, mnDenominator); + a /= toRational(rVal.mnNumerator, rVal.mnDenominator); + mnNumerator = a.numerator(); + mnDenominator = a.denominator(); + + return *this; +} + +/** Inaccurate cancellation for a fraction. + + Clip both nominator and denominator to said number of bits. If + either of those already have equal or less number of bits used, + this method does nothing. + + @param nSignificantBits denotes, how many significant binary + digits to maintain, in both nominator and denominator. + + @example ReduceInaccurate(8) has an error <1% [1/2^(8-1)] - the + largest error occurs with the following pair of values: + + binary 1000000011111111111111111111111b/1000000000000000000000000000000b + = 1082130431/1073741824 + = approx. 1.007812499 + + A ReduceInaccurate(8) yields 1/1. +*/ +void Fraction::ReduceInaccurate( unsigned nSignificantBits ) +{ + if ( !mbValid ) + { + SAL_WARN( "tools.fraction", "'ReduceInaccurate' on invalid fraction" ); + return; + } + + if ( !mnNumerator ) + return; + + auto a = toRational(mnNumerator, mnDenominator); + rational_ReduceInaccurate(a, nSignificantBits); + mnNumerator = a.numerator(); + mnDenominator = a.denominator(); +} + +sal_Int32 Fraction::GetNumerator() const +{ + if ( !mbValid ) + { + SAL_WARN( "tools.fraction", "'GetNumerator()' on invalid fraction" ); + return 0; + } + return mnNumerator; +} + +sal_Int32 Fraction::GetDenominator() const +{ + if ( !mbValid ) + { + SAL_WARN( "tools.fraction", "'GetDenominator()' on invalid fraction" ); + return -1; + } + return mnDenominator; +} + +Fraction::operator sal_Int32() const +{ + if ( !mbValid ) + { + SAL_WARN( "tools.fraction", "'operator sal_Int32()' on invalid fraction" ); + return 0; + } + return boost::rational_cast<sal_Int32>(toRational(mnNumerator, mnDenominator)); +} + +Fraction operator+( const Fraction& rVal1, const Fraction& rVal2 ) +{ + Fraction aErg( rVal1 ); + aErg += rVal2; + return aErg; +} + +Fraction operator-( const Fraction& rVal1, const Fraction& rVal2 ) +{ + Fraction aErg( rVal1 ); + aErg -= rVal2; + return aErg; +} + +Fraction operator*( const Fraction& rVal1, const Fraction& rVal2 ) +{ + Fraction aErg( rVal1 ); + aErg *= rVal2; + return aErg; +} + +Fraction operator/( const Fraction& rVal1, const Fraction& rVal2 ) +{ + Fraction aErg( rVal1 ); + aErg /= rVal2; + return aErg; +} + +bool operator !=( const Fraction& rVal1, const Fraction& rVal2 ) +{ + return !(rVal1 == rVal2); +} + +bool operator <=( const Fraction& rVal1, const Fraction& rVal2 ) +{ + return !(rVal1 > rVal2); +} + +bool operator >=( const Fraction& rVal1, const Fraction& rVal2 ) +{ + return !(rVal1 < rVal2); +} + +bool operator == ( const Fraction& rVal1, const Fraction& rVal2 ) +{ + if ( !rVal1.mbValid || !rVal2.mbValid ) + { + SAL_WARN( "tools.fraction", "'operator ==' with an invalid fraction" ); + return false; + } + + return toRational(rVal1.mnNumerator, rVal1.mnDenominator) == toRational(rVal2.mnNumerator, rVal2.mnDenominator); +} + +bool operator < ( const Fraction& rVal1, const Fraction& rVal2 ) +{ + if ( !rVal1.mbValid || !rVal2.mbValid ) + { + SAL_WARN( "tools.fraction", "'operator <' with an invalid fraction" ); + return false; + } + + return toRational(rVal1.mnNumerator, rVal1.mnDenominator) < toRational(rVal2.mnNumerator, rVal2.mnDenominator); +} + +bool operator > ( const Fraction& rVal1, const Fraction& rVal2 ) +{ + if ( !rVal1.mbValid || !rVal2.mbValid ) + { + SAL_WARN( "tools.fraction", "'operator >' with an invalid fraction" ); + return false; + } + + return toRational(rVal1.mnNumerator, rVal1.mnDenominator) > toRational(rVal2.mnNumerator, rVal2.mnDenominator); +} + +// If dVal > LONG_MAX or dVal < LONG_MIN, the rational throws a boost::bad_rational. +// Otherwise, dVal and denominator are multiplied by 10, until one of them +// is larger than (LONG_MAX / 10). +// +// NOTE: here we use 'sal_Int32' due that only values in sal_Int32 range are valid. +static boost::rational<sal_Int32> rational_FromDouble(double dVal) +{ + if ( dVal > std::numeric_limits<sal_Int32>::max() || + dVal < std::numeric_limits<sal_Int32>::min() || + std::isnan(dVal) ) + throw boost::bad_rational(); + + const sal_Int32 nMAX = std::numeric_limits<sal_Int32>::max() / 10; + sal_Int32 nDen = 1; + while ( std::abs( dVal ) < nMAX && nDen < nMAX ) { + dVal *= 10; + nDen *= 10; + } + return boost::rational<sal_Int32>( sal_Int32(dVal), nDen ); +} + +/** + * Find the number of bits required to represent this number, using the CLZ intrinsic + */ +static int impl_NumberOfBits( sal_uInt32 nNum ) +{ + if (nNum == 0) + return 0; +#ifdef _MSC_VER + unsigned long r = 0; + _BitScanReverse(&r, nNum); + return r + 1; +#else + return 32 - __builtin_clz(nNum); +#endif +} + +/** Inaccurate cancellation for a fraction. + + Clip both nominator and denominator to said number of bits. If + either of those already have equal or less number of bits used, + this method does nothing. + + @param nSignificantBits denotes, how many significant binary + digits to maintain, in both nominator and denominator. + + @example ReduceInaccurate(8) has an error <1% [1/2^(8-1)] - the + largest error occurs with the following pair of values: + + binary 1000000011111111111111111111111b/1000000000000000000000000000000b + = 1082130431/1073741824 + = approx. 1.007812499 + + A ReduceInaccurate(8) yields 1/1. +*/ +static void rational_ReduceInaccurate(boost::rational<sal_Int32>& rRational, unsigned nSignificantBits) +{ + if ( !rRational ) + return; + + // http://www.boost.org/doc/libs/release/libs/rational/rational.html#Internal%20representation + sal_Int32 nMul = rRational.numerator(); + if (nMul == std::numeric_limits<sal_Int32>::min()) + { + // ofz#32973 Integer-overflow + return; + } + const bool bNeg = nMul < 0; + if (bNeg) + nMul = -nMul; + sal_Int32 nDiv = rRational.denominator(); + + DBG_ASSERT(nSignificantBits<65, "More than 64 bit of significance is overkill!"); + + // How much bits can we lose? + const int nMulBitsToLose = std::max( ( impl_NumberOfBits( nMul ) - int( nSignificantBits ) ), 0 ); + const int nDivBitsToLose = std::max( ( impl_NumberOfBits( nDiv ) - int( nSignificantBits ) ), 0 ); + + const int nToLose = std::min( nMulBitsToLose, nDivBitsToLose ); + + // Remove the bits + nMul >>= nToLose; + nDiv >>= nToLose; + + if ( !nMul || !nDiv ) { + // Return without reduction + OSL_FAIL( "Oops, we reduced too much..." ); + return; + } + + rRational.assign( bNeg ? -nMul : nMul, nDiv ); +} + +size_t Fraction::GetHashValue() const +{ + size_t hash = 0; + o3tl::hash_combine( hash, mnNumerator ); + o3tl::hash_combine( hash, mnDenominator ); + o3tl::hash_combine( hash, mbValid ); + return hash; +} + +Fraction Fraction::MakeFraction( tools::Long nN1, tools::Long nN2, tools::Long nD1, tools::Long nD2 ) +{ + if( nD1 == 0 || nD2 == 0 ) //under these bad circumstances the following while loop will be endless + { + SAL_WARN("tools.fraction", "Invalid parameter for ImplMakeFraction"); + return Fraction( 1, 1 ); + } + + tools::Long i = 1; + + if ( nN1 < 0 ) { i = -i; nN1 = -nN1; } + if ( nN2 < 0 ) { i = -i; nN2 = -nN2; } + if ( nD1 < 0 ) { i = -i; nD1 = -nD1; } + if ( nD2 < 0 ) { i = -i; nD2 = -nD2; } + // all positive; i sign + + assert( nN1 >= std::numeric_limits<sal_Int32>::min() ); + assert( nN1 <= std::numeric_limits<sal_Int32>::max( )); + assert( nD1 >= std::numeric_limits<sal_Int32>::min() ); + assert( nD1 <= std::numeric_limits<sal_Int32>::max( )); + assert( nN2 >= std::numeric_limits<sal_Int32>::min() ); + assert( nN2 <= std::numeric_limits<sal_Int32>::max( )); + assert( nD2 >= std::numeric_limits<sal_Int32>::min() ); + assert( nD2 <= std::numeric_limits<sal_Int32>::max( )); + + boost::rational<sal_Int32> a = toRational(i*nN1, nD1); + boost::rational<sal_Int32> b = toRational(nN2, nD2); + bool bFail = checked_multiply_by(a, b); + + while ( bFail ) { + if ( nN1 > nN2 ) + nN1 = (nN1 + 1) / 2; + else + nN2 = (nN2 + 1) / 2; + if ( nD1 > nD2 ) + nD1 = (nD1 + 1) / 2; + else + nD2 = (nD2 + 1) / 2; + + a = toRational(i*nN1, nD1); + b = toRational(nN2, nD2); + bFail = checked_multiply_by(a, b); + } + + return Fraction(a.numerator(), a.denominator()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/generic/gen.cxx b/tools/source/generic/gen.cxx new file mode 100644 index 0000000000..012318c021 --- /dev/null +++ b/tools/source/generic/gen.cxx @@ -0,0 +1,225 @@ +/* -*- 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 <rtl/string.hxx> + +#include <algorithm> +#include <tuple> +#include <o3tl/hash_combine.hxx> +#include <o3tl/safeint.hxx> +#include <tools/gen.hxx> + +OString Pair::toString() const +{ + // Note that this is not just used for debugging output but the + // format is parsed by external code (passed in callbacks to + // LibreOfficeKit clients). So don't change. + return OString::number(A()) + ", " + OString::number(B()); +} + +size_t Pair::GetHashValue() const +{ + size_t hash = 0; + o3tl::hash_combine( hash, mnA ); + o3tl::hash_combine( hash, mnB ); + return hash; +} + +void RectangleTemplateBase::SaturatingSetSize(const SizeTemplateBase& rSize) +{ + if (rSize.Width() < 0) + mnRight = o3tl::saturating_add(mnLeft, (rSize.Width() + 1)); + else if ( rSize.Width() > 0 ) + mnRight = o3tl::saturating_add(mnLeft, (rSize.Width() - 1)); + else + SetWidthEmpty(); + + if ( rSize.Height() < 0 ) + mnBottom = o3tl::saturating_add(mnTop, (rSize.Height() + 1)); + else if ( rSize.Height() > 0 ) + mnBottom = o3tl::saturating_add(mnTop, (rSize.Height() - 1)); + else + SetHeightEmpty(); +} + +void RectangleTemplateBase::SaturatingSetPosX(tools::Long x) +{ + if (!IsWidthEmpty()) + mnRight = o3tl::saturating_add(mnRight, x - mnLeft); + mnLeft = x; +} + +void RectangleTemplateBase::SaturatingSetPosY(tools::Long y) +{ + if (!IsHeightEmpty()) + mnBottom = o3tl::saturating_add(mnBottom, y - mnTop); + mnTop = y; +} + +void RectangleTemplateBase::Union( const RectangleTemplateBase& rRect ) +{ + if ( rRect.IsEmpty() ) + return; + + if ( IsEmpty() ) + *this = rRect; + else + { + std::tie(mnLeft, mnRight) = std::minmax({ mnLeft, rRect.mnLeft, mnRight, rRect.mnRight }); + std::tie(mnTop, mnBottom) = std::minmax({ mnTop, rRect.mnTop, mnBottom, rRect.mnBottom }); + } +} + +void RectangleTemplateBase::Intersection( const RectangleTemplateBase& rRect ) +{ + if ( IsEmpty() ) + return; + if ( rRect.IsEmpty() ) + { + *this = tools::Rectangle(); + return; + } + + // Normalize rectangle + RectangleTemplateBase aTmpRect( rRect ); + Normalize(); + aTmpRect.Normalize(); + + // Perform intersection + mnLeft = std::max( mnLeft, aTmpRect.mnLeft ); + mnRight = std::min( mnRight, aTmpRect.mnRight ); + mnTop = std::max( mnTop, aTmpRect.mnTop ); + mnBottom= std::min( mnBottom, aTmpRect.mnBottom ); + + // Determine if intersection is empty + if ( mnRight < mnLeft || mnBottom < mnTop ) + *this = tools::Rectangle(); +} + +void RectangleTemplateBase::Normalize() +{ + if ((mnRight < mnLeft) && (!IsWidthEmpty())) + { + std::swap(mnLeft, mnRight); + } + + if ((mnBottom < mnTop) && (!IsHeightEmpty())) + { + std::swap(mnBottom, mnTop); + } +} + +bool RectangleTemplateBase::Contains( const PointTemplateBase& rPoint ) const +{ + if ( IsEmpty() ) + return false; + + if ( mnLeft <= mnRight ) + { + if ( (rPoint.X() < mnLeft) || (rPoint.X() > mnRight) ) + return false; + } + else + { + if ( (rPoint.X() > mnLeft) || (rPoint.X() < mnRight) ) + return false; + } + if ( mnTop <= mnBottom ) + { + if ( (rPoint.Y() < mnTop) || (rPoint.Y() > mnBottom) ) + return false; + } + else + { + if ( (rPoint.Y() > mnTop) || (rPoint.Y() < mnBottom) ) + return false; + } + return true; +} + +bool RectangleTemplateBase::Contains( const RectangleTemplateBase& rRect ) const +{ + return Contains( PointTemplateBase{ rRect.Left(), rRect.Top() } ) + && Contains( PointTemplateBase{ rRect.Right(), rRect.Bottom() } ); +} + +bool RectangleTemplateBase::Overlaps( const RectangleTemplateBase& rRect ) const +{ + // If there's no intersection, they don't overlap + RectangleTemplateBase aTmp(*this); + aTmp.Intersection(rRect); + return !aTmp.IsEmpty(); +} + +OString RectangleTemplateBase::toString() const +{ + // Note that this is not just used for debugging output but the + // format is parsed by external code (passed in callbacks to + // LibreOfficeKit clients). So don't change. + return OString::number(Left()) + ", " + + OString::number(Top()) + ", " + + OString::number(getOpenWidth()) + ", " + + OString::number(getOpenHeight()); +} + +void RectangleTemplateBase::expand(tools::Long nExpandBy) +{ + AdjustLeft(-nExpandBy); + AdjustTop(-nExpandBy); + AdjustRight(nExpandBy); + AdjustBottom(nExpandBy); +} + +void RectangleTemplateBase::shrink(tools::Long nShrinkBy) +{ + mnLeft += nShrinkBy; + mnTop += nShrinkBy; + if (!IsWidthEmpty()) + mnRight -= nShrinkBy; + if (!IsHeightEmpty()) + mnBottom -= nShrinkBy; +} + +tools::Long RectangleTemplateBase::AdjustRight(tools::Long nHorzMoveDelta) +{ + if (IsWidthEmpty()) + mnRight = mnLeft + nHorzMoveDelta - 1; + else + mnRight += nHorzMoveDelta; + return mnRight; +} + +tools::Long RectangleTemplateBase::AdjustBottom( tools::Long nVertMoveDelta ) +{ + if (IsHeightEmpty()) + mnBottom = mnTop + nVertMoveDelta - 1; + else + mnBottom += nVertMoveDelta; + return mnBottom; +} + +static_assert( std::is_trivially_copyable< Pair >::value ); +static_assert( std::is_trivially_copyable< Point >::value ); +static_assert( std::is_trivially_copyable< Size >::value ); +static_assert( std::is_trivially_copyable< Range >::value ); +static_assert( std::is_trivially_copyable< Selection >::value ); +static_assert( std::is_trivially_copyable< tools::Rectangle >::value ); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/generic/line.cxx b/tools/source/generic/line.cxx new file mode 100644 index 0000000000..ee9ad97979 --- /dev/null +++ b/tools/source/generic/line.cxx @@ -0,0 +1,140 @@ +/* -*- 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 <tools/line.hxx> +#include <tools/helpers.hxx> + +#include <cmath> + +namespace tools +{ + +double Line::GetLength() const +{ + return hypot( maStart.X() - maEnd.X(), maStart.Y() - maEnd.Y() ); +} + +bool Line::Intersection( const Line& rLine, Point& rIntersection ) const +{ + double fX, fY; + bool bRet; + + if( Intersection( rLine, fX, fY ) ) + { + rIntersection.setX( FRound( fX ) ); + rIntersection.setY( FRound( fY ) ); + bRet = true; + } + else + bRet = false; + + return bRet; +} + +bool Line::Intersection( const tools::Line& rLine, double& rIntersectionX, double& rIntersectionY ) const +{ + const double fAx = static_cast<double>(maEnd.X()) - maStart.X(); + const double fAy = static_cast<double>(maEnd.Y()) - maStart.Y(); + const double fBx = static_cast<double>(rLine.maStart.X()) - rLine.maEnd.X(); + const double fBy = static_cast<double>(rLine.maStart.Y()) - rLine.maEnd.Y(); + const double fDen = fAy * fBx - fAx * fBy; + bool bOk = false; + + if( fDen != 0. ) + { + const double fCx = static_cast<double>(maStart.X()) - rLine.maStart.X(); + const double fCy = static_cast<double>(maStart.Y()) - rLine.maStart.Y(); + const double fA = fBy * fCx - fBx * fCy; + const bool bGreater = ( fDen > 0. ); + + bOk = true; + + if ( bGreater ) + { + if ( ( fA < 0. ) || ( fA > fDen ) ) + bOk = false; + } + else if ( ( fA > 0. ) || ( fA < fDen ) ) + bOk = false; + + if ( bOk ) + { + const double fB = fAx * fCy - fAy * fCx; + + if ( bGreater ) + { + if ( ( fB < 0. ) || ( fB > fDen ) ) + bOk = false; + } + else if ( ( fB > 0. ) || ( fB < fDen ) ) + bOk = false; + + if( bOk ) + { + const double fAlpha = fA / fDen; + + rIntersectionX = ( maStart.X() + fAlpha * fAx ); + rIntersectionY = ( maStart.Y() + fAlpha * fAy ); + } + } + } + + return bOk; +} + +double Line::GetDistance( const double& rPtX, const double& rPtY ) const +{ + double fDist; + + if( maStart != maEnd ) + { + const double fDistX = static_cast<double>(maEnd.X()) - maStart.X(); + const double fDistY = static_cast<double>(maEnd.Y()) - maStart.Y(); + const double fACX = static_cast<double>(maStart.X()) - rPtX; + const double fACY = static_cast<double>(maStart.Y()) - rPtY; + const double fL2 = fDistX * fDistX + fDistY * fDistY; + const double fR = ( fACY * -fDistY - fACX * fDistX ) / fL2; + const double fS = ( fACY * fDistX - fACX * fDistY ) / fL2; + + if( fR < 0.0 ) + { + fDist = hypot( maStart.X() - rPtX, maStart.Y() - rPtY ); + + if( fS < 0.0 ) + fDist *= -1.0; + } + else if( fR <= 1.0 ) + fDist = fS * sqrt( fL2 ); + else + { + fDist = hypot( maEnd.X() - rPtX, maEnd.Y() - rPtY ); + + if( fS < 0.0 ) + fDist *= -1.0; + } + } + else + fDist = hypot( maStart.X() - rPtX, maStart.Y() - rPtY ); + + return fDist; +} + +} //namespace tools + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/generic/point.cxx b/tools/source/generic/point.cxx new file mode 100644 index 0000000000..e71f60411a --- /dev/null +++ b/tools/source/generic/point.cxx @@ -0,0 +1,86 @@ +/* -*- 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 <tools/gen.hxx> + +void PointTemplateBase::RotateAround( PointTemplateBase& rPoint, + Degree10 nOrientation ) const +{ + tools::Long nX = rPoint.mnA; + tools::Long nY = rPoint.mnB; + RotateAround(nX, nY, nOrientation); + rPoint.mnA = nX; + rPoint.mnB = nY; +} + +void PointTemplateBase::RotateAround( tools::Long& rX, tools::Long& rY, + Degree10 nOrientation ) const +{ + const tools::Long nOriginX = mnA; + const tools::Long nOriginY = mnB; + + if ( (nOrientation >= 0_deg10) && !(nOrientation % 900_deg10) ) + { + if ( nOrientation >= 3600_deg10 ) + nOrientation %= 3600_deg10; + + if ( nOrientation ) + { + rX -= nOriginX; + rY -= nOriginY; + + if ( nOrientation == 900_deg10 ) + { + tools::Long nTemp = rX; + rX = rY; + rY = -nTemp; + } + else if ( nOrientation == 1800_deg10 ) + { + rX = -rX; + rY = -rY; + } + else /* ( nOrientation == 2700 ) */ + { + tools::Long nTemp = rX; + rX = -rY; + rY = nTemp; + } + + rX += nOriginX; + rY += nOriginY; + } + } + else + { + double nRealOrientation = toRadians(nOrientation); + double nCos = cos( nRealOrientation ); + double nSin = sin( nRealOrientation ); + + // Translation... + tools::Long nX = rX-nOriginX; + tools::Long nY = rY-nOriginY; + + // Rotation... + rX = + static_cast<tools::Long>(nCos*nX + nSin*nY) + nOriginX; + rY = - static_cast<tools::Long>(nSin*nX - nCos*nY) + nOriginY; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/generic/poly.cxx b/tools/source/generic/poly.cxx new file mode 100644 index 0000000000..89405eea41 --- /dev/null +++ b/tools/source/generic/poly.cxx @@ -0,0 +1,1866 @@ +/* -*- 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/endian.h> +#include <osl/diagnose.h> +#include <sal/log.hxx> +#include <tools/bigint.hxx> +#include <tools/debug.hxx> +#include <tools/helpers.hxx> +#include <tools/stream.hxx> +#include <tools/vcompat.hxx> +#include <tools/gen.hxx> +#include <poly.h> +#include <o3tl/safeint.hxx> +#include <tools/line.hxx> +#include <tools/poly.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/curve/b2dcubicbezier.hxx> + +#include <memory> +#include <vector> +#include <algorithm> +#include <cassert> +#include <cstring> +#include <limits.h> +#include <cmath> + +constexpr int EDGE_LEFT = 1; +constexpr int EDGE_TOP = 2; +constexpr int EDGE_RIGHT = 4; +constexpr int EDGE_BOTTOM = 8; +constexpr int EDGE_HORZ = EDGE_RIGHT | EDGE_LEFT; +constexpr int EDGE_VERT = EDGE_TOP | EDGE_BOTTOM; +constexpr double SMALL_DVALUE = 0.0000001; +#define FSQRT2 1.4142135623730950488016887242097 + +static double ImplGetParameter( const Point& rCenter, const Point& rPt, double fWR, double fHR ) +{ + const double nDX = static_cast<double>(rPt.X()) - rCenter.X(); + const double nDY = static_cast<double>(rCenter.Y()) - rPt.Y(); + double fAngle = atan2(nDY, nDX); + + return atan2(fWR*sin(fAngle), fHR*cos(fAngle)); +} + +ImplPolygon::ImplPolygon( sal_uInt16 nInitSize ) +{ + ImplInitSize(nInitSize, false); +} + +ImplPolygon::ImplPolygon( const ImplPolygon& rImpPoly ) +{ + if ( rImpPoly.mnPoints ) + { + mxPointAry.reset(new Point[rImpPoly.mnPoints]); + memcpy(mxPointAry.get(), rImpPoly.mxPointAry.get(), rImpPoly.mnPoints * sizeof(Point)); + + if( rImpPoly.mxFlagAry ) + { + mxFlagAry.reset(new PolyFlags[rImpPoly.mnPoints]); + memcpy(mxFlagAry.get(), rImpPoly.mxFlagAry.get(), rImpPoly.mnPoints); + } + } + + mnPoints = rImpPoly.mnPoints; +} + +ImplPolygon::ImplPolygon( sal_uInt16 nInitSize, const Point* pInitAry, const PolyFlags* pInitFlags ) +{ + if ( nInitSize ) + { + mxPointAry.reset(new Point[nInitSize]); + memcpy(mxPointAry.get(), pInitAry, nInitSize * sizeof(Point)); + + if( pInitFlags ) + { + mxFlagAry.reset(new PolyFlags[nInitSize]); + memcpy(mxFlagAry.get(), pInitFlags, nInitSize); + } + } + + mnPoints = nInitSize; +} + +ImplPolygon::ImplPolygon( const tools::Rectangle& rRect ) +{ + if ( !rRect.IsEmpty() ) + { + ImplInitSize(5); + mxPointAry[0] = rRect.TopLeft(); + mxPointAry[1] = rRect.TopRight(); + mxPointAry[2] = rRect.BottomRight(); + mxPointAry[3] = rRect.BottomLeft(); + mxPointAry[4] = rRect.TopLeft(); + } + else + mnPoints = 0; +} + +ImplPolygon::ImplPolygon( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound ) +{ + if ( !rRect.IsEmpty() ) + { + tools::Rectangle aRect( rRect ); + aRect.Normalize(); // SJ: i9140 + + nHorzRound = std::min( nHorzRound, static_cast<sal_uInt32>(std::abs( aRect.GetWidth() >> 1 )) ); + nVertRound = std::min( nVertRound, static_cast<sal_uInt32>(std::abs( aRect.GetHeight() >> 1 )) ); + + if( !nHorzRound && !nVertRound ) + { + ImplInitSize(5); + mxPointAry[0] = aRect.TopLeft(); + mxPointAry[1] = aRect.TopRight(); + mxPointAry[2] = aRect.BottomRight(); + mxPointAry[3] = aRect.BottomLeft(); + mxPointAry[4] = aRect.TopLeft(); + } + else + { + const Point aTL( aRect.Left() + nHorzRound, aRect.Top() + nVertRound ); + const Point aTR( aRect.Right() - nHorzRound, aRect.Top() + nVertRound ); + const Point aBR( aRect.Right() - nHorzRound, aRect.Bottom() - nVertRound ); + const Point aBL( aRect.Left() + nHorzRound, aRect.Bottom() - nVertRound ); + tools::Polygon aEllipsePoly( Point(), nHorzRound, nVertRound ); + sal_uInt16 i, nEnd, nSize4 = aEllipsePoly.GetSize() >> 2; + + ImplInitSize(aEllipsePoly.GetSize() + 1); + + const Point* pSrcAry = aEllipsePoly.GetConstPointAry(); + Point* pDstAry = mxPointAry.get(); + + for( i = 0, nEnd = nSize4; i < nEnd; i++ ) + pDstAry[ i ] = pSrcAry[ i ] + aTR; + + for( nEnd = nEnd + nSize4; i < nEnd; i++ ) + pDstAry[ i ] = pSrcAry[ i ] + aTL; + + for( nEnd = nEnd + nSize4; i < nEnd; i++ ) + pDstAry[ i ] = pSrcAry[ i ] + aBL; + + for( nEnd = nEnd + nSize4; i < nEnd; i++ ) + pDstAry[ i ] = pSrcAry[ i ] + aBR; + + pDstAry[ nEnd ] = pDstAry[ 0 ]; + } + } + else + mnPoints = 0; +} + +ImplPolygon::ImplPolygon( const Point& rCenter, tools::Long nRadX, tools::Long nRadY ) +{ + if( nRadX && nRadY ) + { + sal_uInt16 nPoints; + // Compute default (depends on size) + tools::Long nRadXY; + const bool bOverflow = o3tl::checked_multiply(nRadX, nRadY, nRadXY); + if (!bOverflow) + { + nPoints = std::clamp( + ( M_PI * ( 1.5 * ( nRadX + nRadY ) - + sqrt( static_cast<double>(std::abs(nRadXY)) ) ) ), + 32.0, 256.0 ); + } + else + { + nPoints = 256; + } + + if( ( nRadX > 32 ) && ( nRadY > 32 ) && ( nRadX + nRadY ) < 8192 ) + nPoints >>= 1; + + // Ceil number of points until divisible by four + nPoints = (nPoints + 3) & ~3; + ImplInitSize(nPoints); + + sal_uInt16 i; + sal_uInt16 nPoints2 = nPoints >> 1; + sal_uInt16 nPoints4 = nPoints >> 2; + double nAngle; + double nAngleStep = M_PI_2 / ( nPoints4 - 1 ); + + for( i=0, nAngle = 0.0; i < nPoints4; i++, nAngle += nAngleStep ) + { + tools::Long nX = FRound( nRadX * cos( nAngle ) ); + tools::Long nY = FRound( -nRadY * sin( nAngle ) ); + + Point* pPt = &(mxPointAry[i]); + pPt->setX( nX + rCenter.X() ); + pPt->setY( nY + rCenter.Y() ); + pPt = &(mxPointAry[nPoints2-i-1]); + pPt->setX( -nX + rCenter.X() ); + pPt->setY( nY + rCenter.Y() ); + pPt = &(mxPointAry[i+nPoints2]); + pPt->setX( -nX + rCenter.X() ); + pPt->setY( -nY + rCenter.Y() ); + pPt = &(mxPointAry[nPoints-i-1]); + pPt->setX( nX + rCenter.X() ); + pPt->setY( -nY + rCenter.Y() ); + } + } + else + mnPoints = 0; +} + +ImplPolygon::ImplPolygon(const tools::Rectangle& rBound, const Point& rStart, const Point& rEnd, + PolyStyle eStyle, const bool bClockWiseArcDirection) +{ + const auto nWidth = rBound.GetWidth(); + const auto nHeight = rBound.GetHeight(); + + if ((nWidth != 0) && (nHeight != 0)) + { + const Point aCenter(rBound.Center()); + // tdf#142268 Get Top Left corner of rectangle (the rectangle is not always correctly created) + const auto aBoundLeft = rBound.Left() < aCenter.X() ? rBound.Left() : rBound.Right(); + const auto aBoundTop = rBound.Top() < aCenter.Y() ? rBound.Top() : rBound.Bottom(); + const auto nRadX = o3tl::saturating_sub(aCenter.X(), aBoundLeft); + const auto nRadY = o3tl::saturating_sub(aCenter.Y(), aBoundTop); + sal_uInt16 nPoints; + + tools::Long nRadXY; + const bool bOverflow = o3tl::checked_multiply(nRadX, nRadY, nRadXY); + if (!bOverflow) + { + nPoints = std::clamp( + ( M_PI * ( 1.5 * ( nRadX + nRadY ) - + sqrt( static_cast<double>(std::abs(nRadXY)) ) ) ), + 32.0, 256.0 ); + } + else + { + nPoints = 256; + } + + + if (nRadX > 32 && nRadY > 32 && o3tl::saturating_add(nRadX, nRadY) < 8192) + nPoints >>= 1; + + // compute threshold + const double fRadX = nRadX; + const double fRadY = nRadY; + const double fCenterX = aCenter.X(); + const double fCenterY = aCenter.Y(); + double fStart = ImplGetParameter( aCenter, rStart, fRadX, fRadY ); + double fEnd = ImplGetParameter( aCenter, rEnd, fRadX, fRadY ); + double fDiff = fEnd - fStart; + double fStep; + sal_uInt16 nStart; + sal_uInt16 nEnd; + + if (bClockWiseArcDirection == false) + { + // #i73608# If startPoint is equal to endPoint, then draw full circle instead of nothing (as Metafiles spec) + if (fDiff <= 0.) + fDiff += 2. * M_PI; + } + else + { + fDiff = (2. * M_PI) - fDiff; + if (fDiff > 2. * M_PI) + fDiff -= 2. * M_PI; + } + + // Proportionally shrink number of points( fDiff / (2PI) ); + nPoints = std::max(static_cast<sal_uInt16>((fDiff / (2. * M_PI)) * nPoints), sal_uInt16(16)); + fStep = fDiff / (nPoints - 1); + if (bClockWiseArcDirection == true) + fStep = -fStep; + + if (PolyStyle::Pie == eStyle) + { + const Point aCenter2(FRound(fCenterX), FRound(fCenterY)); + + nStart = 1; + nEnd = nPoints + 1; + ImplInitSize(nPoints + 2); + mxPointAry[0] = aCenter2; + mxPointAry[nEnd] = aCenter2; + } + else + { + ImplInitSize( ( PolyStyle::Chord == eStyle ) ? ( nPoints + 1 ) : nPoints ); + nStart = 0; + nEnd = nPoints; + } + + for(; nStart < nEnd; nStart++, fStart += fStep ) + { + Point& rPt = mxPointAry[nStart]; + + rPt.setX( FRound( fCenterX + fRadX * cos( fStart ) ) ); + rPt.setY( FRound( fCenterY - fRadY * sin( fStart ) ) ); + } + + if( PolyStyle::Chord == eStyle ) + mxPointAry[nPoints] = mxPointAry[0]; + } + else + mnPoints = 0; +} + +ImplPolygon::ImplPolygon( const Point& rBezPt1, const Point& rCtrlPt1, + const Point& rBezPt2, const Point& rCtrlPt2, sal_uInt16 nPoints ) +{ + nPoints = ( 0 == nPoints ) ? 25 : ( ( nPoints < 2 ) ? 2 : nPoints ); + + const double fInc = 1.0 / ( nPoints - 1 ); + double fK_1 = 0.0, fK1_1 = 1.0; + double fK_2, fK_3, fK1_2, fK1_3; + const double fX0 = rBezPt1.X(); + const double fY0 = rBezPt1.Y(); + const double fX1 = 3.0 * rCtrlPt1.X(); + const double fY1 = 3.0 * rCtrlPt1.Y(); + const double fX2 = 3.0 * rCtrlPt2.X(); + const double fY2 = 3.0 * rCtrlPt2.Y(); + const double fX3 = rBezPt2.X(); + const double fY3 = rBezPt2.Y(); + + ImplInitSize(nPoints); + + for( sal_uInt16 i = 0; i < nPoints; i++, fK_1 += fInc, fK1_1 -= fInc ) + { + Point& rPt = mxPointAry[i]; + + fK_2 = fK_1; + fK_2 *= fK_1; + fK_3 = fK_2; + fK_3 *= fK_1; + fK1_2 = fK1_1; + fK1_2 *= fK1_1; + fK1_3 = fK1_2; + fK1_3 *= fK1_1; + double fK12 = fK_1 * fK1_2; + double fK21 = fK_2 * fK1_1; + + rPt.setX( FRound( fK1_3 * fX0 + fK12 * fX1 + fK21 * fX2 + fK_3 * fX3 ) ); + rPt.setY( FRound( fK1_3 * fY0 + fK12 * fY1 + fK21 * fY2 + fK_3 * fY3 ) ); + } +} + +// constructor to convert from basegfx::B2DPolygon +// #i76891# Needed to change from adding all control points (even for unused +// edges) and creating a fixed-size Polygon in the first run to creating the +// minimal Polygon. This requires a temporary Point- and Flag-Array for curves +// and a memcopy at ImplPolygon creation, but contains no zero-controlpoints +// for straight edges. +ImplPolygon::ImplPolygon(const basegfx::B2DPolygon& rPolygon) + : mnPoints(0) +{ + const bool bCurve(rPolygon.areControlPointsUsed()); + const bool bClosed(rPolygon.isClosed()); + sal_uInt32 nB2DLocalCount(rPolygon.count()); + + if(bCurve) + { + // #127979# Reduce source point count hard to the limit of the tools Polygon + if(nB2DLocalCount > ((0x0000ffff / 3) - 1)) + { + OSL_FAIL("Polygon::Polygon: Too many points in given B2DPolygon, need to reduce hard to maximum of tools Polygon (!)"); + nB2DLocalCount = ((0x0000ffff / 3) - 1); + } + + // calculate target point count + const sal_uInt32 nLoopCount(bClosed ? nB2DLocalCount : (nB2DLocalCount ? nB2DLocalCount - 1 : 0 )); + + if(nLoopCount) + { + // calculate maximum array size and allocate; prepare insert index + const sal_uInt32 nMaxTargetCount((nLoopCount * 3) + 1); + ImplInitSize(static_cast< sal_uInt16 >(nMaxTargetCount), true); + + // prepare insert index and current point + sal_uInt32 nArrayInsert(0); + basegfx::B2DCubicBezier aBezier; + aBezier.setStartPoint(rPolygon.getB2DPoint(0)); + + for(sal_uInt32 a(0); a < nLoopCount; a++) + { + // add current point (always) and remember StartPointIndex for evtl. later corrections + const Point aStartPoint(FRound(aBezier.getStartPoint().getX()), FRound(aBezier.getStartPoint().getY())); + const sal_uInt32 nStartPointIndex(nArrayInsert); + mxPointAry[nStartPointIndex] = aStartPoint; + mxFlagAry[nStartPointIndex] = PolyFlags::Normal; + nArrayInsert++; + + // prepare next segment + const sal_uInt32 nNextIndex((a + 1) % nB2DLocalCount); + aBezier.setEndPoint(rPolygon.getB2DPoint(nNextIndex)); + aBezier.setControlPointA(rPolygon.getNextControlPoint(a)); + aBezier.setControlPointB(rPolygon.getPrevControlPoint(nNextIndex)); + + if(aBezier.isBezier()) + { + // if one is used, add always two control points due to the old schema + mxPointAry[nArrayInsert] = Point(FRound(aBezier.getControlPointA().getX()), FRound(aBezier.getControlPointA().getY())); + mxFlagAry[nArrayInsert] = PolyFlags::Control; + nArrayInsert++; + + mxPointAry[nArrayInsert] = Point(FRound(aBezier.getControlPointB().getX()), FRound(aBezier.getControlPointB().getY())); + mxFlagAry[nArrayInsert] = PolyFlags::Control; + nArrayInsert++; + } + + // test continuity with previous control point to set flag value + if(aBezier.getControlPointA() != aBezier.getStartPoint() && (bClosed || a)) + { + const basegfx::B2VectorContinuity eCont(rPolygon.getContinuityInPoint(a)); + + if(basegfx::B2VectorContinuity::C1 == eCont) + { + mxFlagAry[nStartPointIndex] = PolyFlags::Smooth; + } + else if(basegfx::B2VectorContinuity::C2 == eCont) + { + mxFlagAry[nStartPointIndex] = PolyFlags::Symmetric; + } + } + + // prepare next polygon step + aBezier.setStartPoint(aBezier.getEndPoint()); + } + + if(bClosed) + { + // add first point again as closing point due to old definition + mxPointAry[nArrayInsert] = mxPointAry[0]; + mxFlagAry[nArrayInsert] = PolyFlags::Normal; + nArrayInsert++; + } + else + { + // add last point as closing point + const basegfx::B2DPoint aClosingPoint(rPolygon.getB2DPoint(nB2DLocalCount - 1)); + const Point aEnd(FRound(aClosingPoint.getX()), FRound(aClosingPoint.getY())); + mxPointAry[nArrayInsert] = aEnd; + mxFlagAry[nArrayInsert] = PolyFlags::Normal; + nArrayInsert++; + } + + DBG_ASSERT(nArrayInsert <= nMaxTargetCount, "Polygon::Polygon from basegfx::B2DPolygon: wrong max point count estimation (!)"); + + if(nArrayInsert != nMaxTargetCount) + { + ImplSetSize(static_cast< sal_uInt16 >(nArrayInsert)); + } + } + } + else + { + // #127979# Reduce source point count hard to the limit of the tools Polygon + if(nB2DLocalCount > (0x0000ffff - 1)) + { + OSL_FAIL("Polygon::Polygon: Too many points in given B2DPolygon, need to reduce hard to maximum of tools Polygon (!)"); + nB2DLocalCount = (0x0000ffff - 1); + } + + if(nB2DLocalCount) + { + // point list creation + const sal_uInt32 nTargetCount(nB2DLocalCount + (bClosed ? 1 : 0)); + ImplInitSize(static_cast< sal_uInt16 >(nTargetCount)); + sal_uInt16 nIndex(0); + + for(sal_uInt32 a(0); a < nB2DLocalCount; a++) + { + basegfx::B2DPoint aB2DPoint(rPolygon.getB2DPoint(a)); + Point aPoint(FRound(aB2DPoint.getX()), FRound(aB2DPoint.getY())); + mxPointAry[nIndex++] = aPoint; + } + + if(bClosed) + { + // add first point as closing point + mxPointAry[nIndex] = mxPointAry[0]; + } + } + } +} + +bool ImplPolygon::operator==( const ImplPolygon& rCandidate) const +{ + return mnPoints == rCandidate.mnPoints && + mxFlagAry.get() == rCandidate.mxFlagAry.get() && + mxPointAry.get() == rCandidate.mxPointAry.get(); +} + +void ImplPolygon::ImplInitSize(sal_uInt16 nInitSize, bool bFlags) +{ + if (nInitSize) + { + mxPointAry.reset(new Point[nInitSize]); + } + + if (bFlags) + { + mxFlagAry.reset(new PolyFlags[nInitSize]); + memset(mxFlagAry.get(), 0, nInitSize); + } + + mnPoints = nInitSize; +} + +void ImplPolygon::ImplSetSize( sal_uInt16 nNewSize, bool bResize ) +{ + if( mnPoints == nNewSize ) + return; + + std::unique_ptr<Point[]> xNewAry; + + if (nNewSize) + { + const std::size_t nNewSz(static_cast<std::size_t>(nNewSize)*sizeof(Point)); + xNewAry.reset(new Point[nNewSize]); + + if ( bResize ) + { + // Copy the old points + if ( mnPoints < nNewSize ) + { + // New points are already implicitly initialized to zero + const std::size_t nOldSz(mnPoints * sizeof(Point)); + if (mxPointAry) + memcpy(xNewAry.get(), mxPointAry.get(), nOldSz); + } + else + { + if (mxPointAry) + memcpy(xNewAry.get(), mxPointAry.get(), nNewSz); + } + } + } + + mxPointAry = std::move(xNewAry); + + // take FlagArray into account, if applicable + if( mxFlagAry ) + { + std::unique_ptr<PolyFlags[]> xNewFlagAry; + + if( nNewSize ) + { + xNewFlagAry.reset(new PolyFlags[nNewSize]); + + if( bResize ) + { + // copy the old flags + if ( mnPoints < nNewSize ) + { + // initialize new flags to zero + memset(xNewFlagAry.get() + mnPoints, 0, nNewSize-mnPoints); + memcpy(xNewFlagAry.get(), mxFlagAry.get(), mnPoints); + } + else + memcpy(xNewFlagAry.get(), mxFlagAry.get(), nNewSize); + } + } + + mxFlagAry = std::move(xNewFlagAry); + } + + mnPoints = nNewSize; +} + +bool ImplPolygon::ImplSplit( sal_uInt16 nPos, sal_uInt16 nSpace, ImplPolygon const * pInitPoly ) +{ + //Can't fit this in :-(, throw ? + if (mnPoints + nSpace > USHRT_MAX) + { + SAL_WARN("tools", "Polygon needs " << mnPoints + nSpace << " points, but only " << USHRT_MAX << " possible"); + return false; + } + + const sal_uInt16 nNewSize = mnPoints + nSpace; + const std::size_t nSpaceSize = static_cast<std::size_t>(nSpace) * sizeof(Point); + + if( nPos >= mnPoints ) + { + // Append at the back + nPos = mnPoints; + ImplSetSize( nNewSize ); + + if( pInitPoly ) + { + memcpy(mxPointAry.get() + nPos, pInitPoly->mxPointAry.get(), nSpaceSize); + + if (pInitPoly->mxFlagAry) + memcpy(mxFlagAry.get() + nPos, pInitPoly->mxFlagAry.get(), nSpace); + } + } + else + { + const sal_uInt16 nSecPos = nPos + nSpace; + const sal_uInt16 nRest = mnPoints - nPos; + + std::unique_ptr<Point[]> xNewAry(new Point[nNewSize]); + memcpy(xNewAry.get(), mxPointAry.get(), nPos * sizeof(Point)); + + if( pInitPoly ) + memcpy(xNewAry.get() + nPos, pInitPoly->mxPointAry.get(), nSpaceSize); + + memcpy(xNewAry.get() + nSecPos, mxPointAry.get() + nPos, nRest * sizeof(Point)); + mxPointAry = std::move(xNewAry); + + // consider FlagArray + if (mxFlagAry) + { + std::unique_ptr<PolyFlags[]> xNewFlagAry(new PolyFlags[nNewSize]); + + memcpy(xNewFlagAry.get(), mxFlagAry.get(), nPos); + + if (pInitPoly && pInitPoly->mxFlagAry) + memcpy(xNewFlagAry.get() + nPos, pInitPoly->mxFlagAry.get(), nSpace); + else + memset(xNewFlagAry.get() + nPos, 0, nSpace); + + memcpy(xNewFlagAry.get() + nSecPos, mxFlagAry.get() + nPos, nRest); + mxFlagAry = std::move(xNewFlagAry); + } + + mnPoints = nNewSize; + } + + return true; +} + +void ImplPolygon::ImplCreateFlagArray() +{ + if (!mxFlagAry) + { + mxFlagAry.reset(new PolyFlags[mnPoints]); + memset(mxFlagAry.get(), 0, mnPoints); + } +} + +namespace { + +class ImplPointFilter +{ +public: + virtual void LastPoint() = 0; + virtual void Input( const Point& rPoint ) = 0; + +protected: + ~ImplPointFilter() {} +}; + +class ImplPolygonPointFilter : public ImplPointFilter +{ + ImplPolygon maPoly; + sal_uInt16 mnSize; +public: + explicit ImplPolygonPointFilter(sal_uInt16 nDestSize) + : maPoly(nDestSize) + , mnSize(0) + { + } + + virtual ~ImplPolygonPointFilter() + { + } + + virtual void LastPoint() override; + virtual void Input( const Point& rPoint ) override; + + ImplPolygon& get() { return maPoly; } +}; + +} + +void ImplPolygonPointFilter::Input( const Point& rPoint ) +{ + if ( !mnSize || (rPoint != maPoly.mxPointAry[mnSize-1]) ) + { + mnSize++; + if ( mnSize > maPoly.mnPoints ) + maPoly.ImplSetSize( mnSize ); + maPoly.mxPointAry[mnSize-1] = rPoint; + } +} + +void ImplPolygonPointFilter::LastPoint() +{ + if ( mnSize < maPoly.mnPoints ) + maPoly.ImplSetSize( mnSize ); +}; + +namespace { + +class ImplEdgePointFilter : public ImplPointFilter +{ + Point maFirstPoint; + Point maLastPoint; + ImplPointFilter& mrNextFilter; + const tools::Long mnLow; + const tools::Long mnHigh; + const int mnEdge; + int mnLastOutside; + bool mbFirst; + +public: + ImplEdgePointFilter( int nEdge, tools::Long nLow, tools::Long nHigh, + ImplPointFilter& rNextFilter ) : + mrNextFilter( rNextFilter ), + mnLow( nLow ), + mnHigh( nHigh ), + mnEdge( nEdge ), + mnLastOutside( 0 ), + mbFirst( true ) + { + } + + virtual ~ImplEdgePointFilter() {} + + Point EdgeSection( const Point& rPoint, int nEdge ) const; + int VisibleSide( const Point& rPoint ) const; + bool IsPolygon() const + { return maFirstPoint == maLastPoint; } + + virtual void Input( const Point& rPoint ) override; + virtual void LastPoint() override; +}; + +} + +inline int ImplEdgePointFilter::VisibleSide( const Point& rPoint ) const +{ + if ( mnEdge & EDGE_HORZ ) + { + return rPoint.X() < mnLow ? EDGE_LEFT : + rPoint.X() > mnHigh ? EDGE_RIGHT : 0; + } + else + { + return rPoint.Y() < mnLow ? EDGE_TOP : + rPoint.Y() > mnHigh ? EDGE_BOTTOM : 0; + } +} + +Point ImplEdgePointFilter::EdgeSection( const Point& rPoint, int nEdge ) const +{ + tools::Long lx = maLastPoint.X(); + tools::Long ly = maLastPoint.Y(); + tools::Long md = rPoint.X() - lx; + tools::Long mn = rPoint.Y() - ly; + tools::Long nNewX; + tools::Long nNewY; + + if ( nEdge & EDGE_VERT ) + { + nNewY = (nEdge == EDGE_TOP) ? mnLow : mnHigh; + tools::Long dy = nNewY - ly; + if ( !md ) + nNewX = lx; + else if ( (LONG_MAX / std::abs(md)) >= std::abs(dy) ) + nNewX = (dy * md) / mn + lx; + else + { + BigInt ady = dy; + ady *= md; + if( ady.IsNeg() ) + if( mn < 0 ) + ady += mn/2; + else + ady -= (mn-1)/2; + else + if( mn < 0 ) + ady -= (mn+1)/2; + else + ady += mn/2; + ady /= mn; + nNewX = static_cast<tools::Long>(ady) + lx; + } + } + else + { + nNewX = (nEdge == EDGE_LEFT) ? mnLow : mnHigh; + tools::Long dx = nNewX - lx; + if ( !mn ) + nNewY = ly; + else if ( (LONG_MAX / std::abs(mn)) >= std::abs(dx) ) + nNewY = (dx * mn) / md + ly; + else + { + BigInt adx = dx; + adx *= mn; + if( adx.IsNeg() ) + if( md < 0 ) + adx += md/2; + else + adx -= (md-1)/2; + else + if( md < 0 ) + adx -= (md+1)/2; + else + adx += md/2; + adx /= md; + nNewY = static_cast<tools::Long>(adx) + ly; + } + } + + return Point( nNewX, nNewY ); +} + +void ImplEdgePointFilter::Input( const Point& rPoint ) +{ + int nOutside = VisibleSide( rPoint ); + + if ( mbFirst ) + { + maFirstPoint = rPoint; + mbFirst = false; + if ( !nOutside ) + mrNextFilter.Input( rPoint ); + } + else if ( rPoint == maLastPoint ) + return; + else if ( !nOutside ) + { + if ( mnLastOutside ) + mrNextFilter.Input( EdgeSection( rPoint, mnLastOutside ) ); + mrNextFilter.Input( rPoint ); + } + else if ( !mnLastOutside ) + mrNextFilter.Input( EdgeSection( rPoint, nOutside ) ); + else if ( nOutside != mnLastOutside ) + { + mrNextFilter.Input( EdgeSection( rPoint, mnLastOutside ) ); + mrNextFilter.Input( EdgeSection( rPoint, nOutside ) ); + } + + maLastPoint = rPoint; + mnLastOutside = nOutside; +} + +void ImplEdgePointFilter::LastPoint() +{ + if ( !mbFirst ) + { + int nOutside = VisibleSide( maFirstPoint ); + + if ( nOutside != mnLastOutside ) + Input( maFirstPoint ); + mrNextFilter.LastPoint(); + } +} + +namespace tools +{ + +tools::Polygon Polygon::SubdivideBezier( const tools::Polygon& rPoly ) +{ + tools::Polygon aPoly; + + // #100127# Use adaptive subdivide instead of fixed 25 segments + rPoly.AdaptiveSubdivide( aPoly ); + + return aPoly; +} + +Polygon::Polygon() : mpImplPolygon(ImplPolygon()) +{ +} + +Polygon::Polygon( sal_uInt16 nSize ) : mpImplPolygon(ImplPolygon(nSize)) +{ +} + +Polygon::Polygon( sal_uInt16 nPoints, const Point* pPtAry, const PolyFlags* pFlagAry ) : mpImplPolygon(ImplPolygon(nPoints, pPtAry, pFlagAry)) +{ +} + +Polygon::Polygon( const tools::Polygon& rPoly ) : mpImplPolygon(rPoly.mpImplPolygon) +{ +} + +Polygon::Polygon( tools::Polygon&& rPoly) noexcept + : mpImplPolygon(std::move(rPoly.mpImplPolygon)) +{ +} + +Polygon::Polygon( const tools::Rectangle& rRect ) : mpImplPolygon(ImplPolygon(rRect)) +{ +} + +Polygon::Polygon( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound ) + : mpImplPolygon(ImplPolygon(rRect, nHorzRound, nVertRound)) +{ +} + +Polygon::Polygon( const Point& rCenter, tools::Long nRadX, tools::Long nRadY ) + : mpImplPolygon(ImplPolygon(rCenter, nRadX, nRadY)) +{ +} + +Polygon::Polygon(const tools::Rectangle& rBound, const Point& rStart, const Point& rEnd, + PolyStyle eStyle, const bool bClockWiseArcDirection) + : mpImplPolygon(ImplPolygon(rBound, rStart, rEnd, eStyle, bClockWiseArcDirection)) +{ +} + +Polygon::Polygon( const Point& rBezPt1, const Point& rCtrlPt1, + const Point& rBezPt2, const Point& rCtrlPt2, + sal_uInt16 nPoints ) : mpImplPolygon(ImplPolygon(rBezPt1, rCtrlPt1, rBezPt2, rCtrlPt2, nPoints)) +{ +} + +Polygon::~Polygon() +{ +} + +Point * Polygon::GetPointAry() +{ + return mpImplPolygon->mxPointAry.get(); +} + +const Point* Polygon::GetConstPointAry() const +{ + return mpImplPolygon->mxPointAry.get(); +} + +const PolyFlags* Polygon::GetConstFlagAry() const +{ + return mpImplPolygon->mxFlagAry.get(); +} + +void Polygon::SetPoint( const Point& rPt, sal_uInt16 nPos ) +{ + DBG_ASSERT( nPos < mpImplPolygon->mnPoints, + "Polygon::SetPoint(): nPos >= nPoints" ); + + mpImplPolygon->mxPointAry[nPos] = rPt; +} + +void Polygon::SetFlags( sal_uInt16 nPos, PolyFlags eFlags ) +{ + DBG_ASSERT( nPos < mpImplPolygon->mnPoints, + "Polygon::SetFlags(): nPos >= nPoints" ); + + // we do only want to create the flag array if there + // is at least one flag different to PolyFlags::Normal + if ( eFlags != PolyFlags::Normal ) + { + mpImplPolygon->ImplCreateFlagArray(); + mpImplPolygon->mxFlagAry[ nPos ] = eFlags; + } +} + +const Point& Polygon::GetPoint( sal_uInt16 nPos ) const +{ + DBG_ASSERT( nPos < mpImplPolygon->mnPoints, + "Polygon::GetPoint(): nPos >= nPoints" ); + + return mpImplPolygon->mxPointAry[nPos]; +} + +PolyFlags Polygon::GetFlags( sal_uInt16 nPos ) const +{ + DBG_ASSERT( nPos < mpImplPolygon->mnPoints, + "Polygon::GetFlags(): nPos >= nPoints" ); + return mpImplPolygon->mxFlagAry + ? mpImplPolygon->mxFlagAry[ nPos ] + : PolyFlags::Normal; +} + +bool Polygon::HasFlags() const +{ + return bool(mpImplPolygon->mxFlagAry); +} + +bool Polygon::IsRect() const +{ + bool bIsRect = false; + if (!mpImplPolygon->mxFlagAry) + { + if ( ( ( mpImplPolygon->mnPoints == 5 ) && ( mpImplPolygon->mxPointAry[ 0 ] == mpImplPolygon->mxPointAry[ 4 ] ) ) || + ( mpImplPolygon->mnPoints == 4 ) ) + { + if ( ( mpImplPolygon->mxPointAry[ 0 ].X() == mpImplPolygon->mxPointAry[ 3 ].X() ) && + ( mpImplPolygon->mxPointAry[ 0 ].Y() == mpImplPolygon->mxPointAry[ 1 ].Y() ) && + ( mpImplPolygon->mxPointAry[ 1 ].X() == mpImplPolygon->mxPointAry[ 2 ].X() ) && + ( mpImplPolygon->mxPointAry[ 2 ].Y() == mpImplPolygon->mxPointAry[ 3 ].Y() ) ) + bIsRect = true; + } + } + return bIsRect; +} + +void Polygon::SetSize( sal_uInt16 nNewSize ) +{ + if( nNewSize != mpImplPolygon->mnPoints ) + { + mpImplPolygon->ImplSetSize( nNewSize ); + } +} + +sal_uInt16 Polygon::GetSize() const +{ + return mpImplPolygon->mnPoints; +} + +void Polygon::Clear() +{ + mpImplPolygon = ImplType(ImplPolygon()); +} + +double Polygon::CalcDistance( sal_uInt16 nP1, sal_uInt16 nP2 ) const +{ + DBG_ASSERT( nP1 < mpImplPolygon->mnPoints, + "Polygon::CalcDistance(): nPos1 >= nPoints" ); + DBG_ASSERT( nP2 < mpImplPolygon->mnPoints, + "Polygon::CalcDistance(): nPos2 >= nPoints" ); + + const Point& rP1 = mpImplPolygon->mxPointAry[ nP1 ]; + const Point& rP2 = mpImplPolygon->mxPointAry[ nP2 ]; + const double fDx = rP2.X() - rP1.X(); + const double fDy = rP2.Y() - rP1.Y(); + + return std::hypot( fDx, fDy ); +} + +void Polygon::Optimize( PolyOptimizeFlags nOptimizeFlags ) +{ + sal_uInt16 nSize = mpImplPolygon->mnPoints; + + if( !(bool(nOptimizeFlags) && nSize) ) + return; + + if( nOptimizeFlags & PolyOptimizeFlags::EDGES ) + { + const tools::Rectangle aBound( GetBoundRect() ); + const double fArea = ( aBound.GetWidth() + aBound.GetHeight() ) * 0.5; + const sal_uInt16 nPercent = 50; + + Optimize( PolyOptimizeFlags::NO_SAME ); + ImplReduceEdges( *this, fArea, nPercent ); + } + else if( nOptimizeFlags & PolyOptimizeFlags::NO_SAME ) + { + tools::Polygon aNewPoly; + const Point& rFirst = mpImplPolygon->mxPointAry[ 0 ]; + + while( nSize && ( mpImplPolygon->mxPointAry[ nSize - 1 ] == rFirst ) ) + nSize--; + + if( nSize > 1 ) + { + sal_uInt16 nLast = 0, nNewCount = 1; + + aNewPoly.SetSize( nSize ); + aNewPoly[ 0 ] = rFirst; + + for( sal_uInt16 i = 1; i < nSize; i++ ) + { + if( mpImplPolygon->mxPointAry[ i ] != mpImplPolygon->mxPointAry[ nLast ]) + { + nLast = i; + aNewPoly[ nNewCount++ ] = mpImplPolygon->mxPointAry[ i ]; + } + } + + if( nNewCount == 1 ) + aNewPoly.Clear(); + else + aNewPoly.SetSize( nNewCount ); + } + + *this = aNewPoly; + } + + nSize = mpImplPolygon->mnPoints; + + if( nSize > 1 ) + { + if( ( nOptimizeFlags & PolyOptimizeFlags::CLOSE ) && + ( mpImplPolygon->mxPointAry[ 0 ] != mpImplPolygon->mxPointAry[ nSize - 1 ] ) ) + { + SetSize( mpImplPolygon->mnPoints + 1 ); + mpImplPolygon->mxPointAry[ mpImplPolygon->mnPoints - 1 ] = mpImplPolygon->mxPointAry[ 0 ]; + } + } +} + + +/** Recursively subdivide cubic bezier curve via deCasteljau. + + @param rPoints + Output vector, where the subdivided polylines are written to. + + @param d + Squared difference of curve to a straight line + + @param P* + Exactly four points, interpreted as support and control points of + a cubic bezier curve. Must be in device coordinates, since stop + criterion is based on the following assumption: the device has a + finite resolution, it is thus sufficient to stop subdivision if the + curve does not deviate more than one pixel from a straight line. + +*/ +static void ImplAdaptiveSubdivide( std::vector<Point>& rPoints, + const double old_d2, + int recursionDepth, + const double d2, + const double P1x, const double P1y, + const double P2x, const double P2y, + const double P3x, const double P3y, + const double P4x, const double P4y ) +{ + // Hard limit on recursion depth, empiric number. + enum {maxRecursionDepth=128}; + + // Perform bezier flatness test (lecture notes from R. Schaback, + // Mathematics of Computer-Aided Design, Uni Goettingen, 2000) + + // ||P(t) - L(t)|| <= max ||b_j - b_0 - j/n(b_n - b_0)|| + // 0<=j<=n + + // What is calculated here is an upper bound to the distance from + // a line through b_0 and b_3 (P1 and P4 in our notation) and the + // curve. We can drop 0 and n from the running indices, since the + // argument of max becomes zero for those cases. + const double fJ1x( P2x - P1x - 1.0/3.0*(P4x - P1x) ); + const double fJ1y( P2y - P1y - 1.0/3.0*(P4y - P1y) ); + const double fJ2x( P3x - P1x - 2.0/3.0*(P4x - P1x) ); + const double fJ2y( P3y - P1y - 2.0/3.0*(P4y - P1y) ); + const double distance2( ::std::max( fJ1x*fJ1x + fJ1y*fJ1y, + fJ2x*fJ2x + fJ2y*fJ2y) ); + + // stop if error measure does not improve anymore. This is a + // safety guard against floating point inaccuracies. + // stop at recursion level 128. This is a safety guard against + // floating point inaccuracies. + // stop if distance from line is guaranteed to be bounded by d + if( old_d2 > d2 && + recursionDepth < maxRecursionDepth && + distance2 >= d2 && + rPoints.size() < SAL_MAX_UINT16 ) + { + // deCasteljau bezier arc, split at t=0.5 + // Foley/vanDam, p. 508 + const double L1x( P1x ), L1y( P1y ); + const double L2x( (P1x + P2x)*0.5 ), L2y( (P1y + P2y)*0.5 ); + const double Hx ( (P2x + P3x)*0.5 ), Hy ( (P2y + P3y)*0.5 ); + const double L3x( (L2x + Hx)*0.5 ), L3y( (L2y + Hy)*0.5 ); + const double R4x( P4x ), R4y( P4y ); + const double R3x( (P3x + P4x)*0.5 ), R3y( (P3y + P4y)*0.5 ); + const double R2x( (Hx + R3x)*0.5 ), R2y( (Hy + R3y)*0.5 ); + const double R1x( (L3x + R2x)*0.5 ), R1y( (L3y + R2y)*0.5 ); + const double L4x( R1x ), L4y( R1y ); + + // subdivide further + ++recursionDepth; + ImplAdaptiveSubdivide(rPoints, distance2, recursionDepth, d2, L1x, L1y, L2x, L2y, L3x, L3y, L4x, L4y); + ImplAdaptiveSubdivide(rPoints, distance2, recursionDepth, d2, R1x, R1y, R2x, R2y, R3x, R3y, R4x, R4y); + } + else + { + // requested resolution reached. + // Add end points to output iterator. + // order is preserved, since this is so to say depth first traversal. + rPoints.push_back(Point(FRound(P1x), FRound(P1y))); + } +} + +void Polygon::AdaptiveSubdivide( Polygon& rResult, const double d ) const +{ + if (!mpImplPolygon->mxFlagAry) + { + rResult = *this; + } + else + { + sal_uInt16 i; + sal_uInt16 nPts( GetSize() ); + ::std::vector< Point > aPoints; + aPoints.reserve( nPts ); + + for(i=0; i<nPts;) + { + if( ( i + 3 ) < nPts ) + { + PolyFlags P1( mpImplPolygon->mxFlagAry[ i ] ); + PolyFlags P4( mpImplPolygon->mxFlagAry[ i + 3 ] ); + + if( ( PolyFlags::Normal == P1 || PolyFlags::Smooth == P1 || PolyFlags::Symmetric == P1 ) && + ( PolyFlags::Control == mpImplPolygon->mxFlagAry[ i + 1 ] ) && + ( PolyFlags::Control == mpImplPolygon->mxFlagAry[ i + 2 ] ) && + ( PolyFlags::Normal == P4 || PolyFlags::Smooth == P4 || PolyFlags::Symmetric == P4 ) ) + { + ImplAdaptiveSubdivide( aPoints, d*d+1.0, 0, d*d, + mpImplPolygon->mxPointAry[ i ].X(), mpImplPolygon->mxPointAry[ i ].Y(), + mpImplPolygon->mxPointAry[ i+1 ].X(), mpImplPolygon->mxPointAry[ i+1 ].Y(), + mpImplPolygon->mxPointAry[ i+2 ].X(), mpImplPolygon->mxPointAry[ i+2 ].Y(), + mpImplPolygon->mxPointAry[ i+3 ].X(), mpImplPolygon->mxPointAry[ i+3 ].Y() ); + i += 3; + continue; + } + } + + aPoints.push_back(mpImplPolygon->mxPointAry[i++]); + + if (aPoints.size() >= SAL_MAX_UINT16) + { + OSL_ENSURE(aPoints.size() < SAL_MAX_UINT16, + "Polygon::AdaptiveSubdivision created polygon too many points;" + " using original polygon instead"); + + // The resulting polygon can not hold all the points + // that we have created so far. Stop the subdivision + // and return a copy of the unmodified polygon. + rResult = *this; + return; + } + } + + // fill result polygon + rResult = tools::Polygon( static_cast<sal_uInt16>(aPoints.size()) ); // ensure sufficient size for copy + ::std::copy(aPoints.begin(), aPoints.end(), rResult.mpImplPolygon->mxPointAry.get()); + } +} + +namespace { + +class Vector2D +{ +private: + double mfX; + double mfY; +public: + explicit Vector2D( const Point& rPoint ) : mfX( rPoint.X() ), mfY( rPoint.Y() ) {}; + double GetLength() const { return hypot( mfX, mfY ); } + Vector2D& operator-=( const Vector2D& rVec ) { mfX -= rVec.mfX; mfY -= rVec.mfY; return *this; } + double Scalar( const Vector2D& rVec ) const { return mfX * rVec.mfX + mfY * rVec.mfY ; } + Vector2D& Normalize(); + bool IsPositive( Vector2D const & rVec ) const { return ( mfX * rVec.mfY - mfY * rVec.mfX ) >= 0.0; } + bool IsNegative( Vector2D const & rVec ) const { return !IsPositive( rVec ); } +}; + +} + +Vector2D& Vector2D::Normalize() +{ + double fLen = Scalar( *this ); + + if( ( fLen != 0.0 ) && ( fLen != 1.0 ) ) + { + fLen = sqrt( fLen ); + if( fLen != 0.0 ) + { + mfX /= fLen; + mfY /= fLen; + } + } + + return *this; +} + +void Polygon::ImplReduceEdges( tools::Polygon& rPoly, const double& rArea, sal_uInt16 nPercent ) +{ + const double fBound = 2000.0 * ( 100 - nPercent ) * 0.01; + sal_uInt16 nNumNoChange = 0, + nNumRuns = 0; + + while( nNumNoChange < 2 ) + { + sal_uInt16 nPntCnt = rPoly.GetSize(), nNewPos = 0; + tools::Polygon aNewPoly( nPntCnt ); + bool bChangeInThisRun = false; + + for( sal_uInt16 n = 0; n < nPntCnt; n++ ) + { + bool bDeletePoint = false; + + if( ( n + nNumRuns ) % 2 ) + { + sal_uInt16 nIndPrev = !n ? nPntCnt - 1 : n - 1; + sal_uInt16 nIndPrevPrev = !nIndPrev ? nPntCnt - 1 : nIndPrev - 1; + sal_uInt16 nIndNext = ( n == nPntCnt-1 ) ? 0 : n + 1; + sal_uInt16 nIndNextNext = ( nIndNext == nPntCnt - 1 ) ? 0 : nIndNext + 1; + Vector2D aVec1( rPoly[ nIndPrev ] ); aVec1 -= Vector2D(rPoly[ nIndPrevPrev ]); + Vector2D aVec2( rPoly[ n ] ); aVec2 -= Vector2D(rPoly[ nIndPrev ]); + Vector2D aVec3( rPoly[ nIndNext ] ); aVec3 -= Vector2D(rPoly[ n ]); + Vector2D aVec4( rPoly[ nIndNextNext ] ); aVec4 -= Vector2D(rPoly[ nIndNext ]); + double fDist1 = aVec1.GetLength(), fDist2 = aVec2.GetLength(); + double fDist3 = aVec3.GetLength(), fDist4 = aVec4.GetLength(); + double fTurnB = aVec2.Normalize().Scalar( aVec3.Normalize() ); + + if( fabs( fTurnB ) < ( 1.0 + SMALL_DVALUE ) && fabs( fTurnB ) > ( 1.0 - SMALL_DVALUE ) ) + bDeletePoint = true; + else + { + Vector2D aVecB( rPoly[ nIndNext ] ); + aVecB -= Vector2D(rPoly[ nIndPrev ] ); + double fDistB = aVecB.GetLength(); + double fLenWithB = fDist2 + fDist3; + double fLenFact = ( fDistB != 0.0 ) ? fLenWithB / fDistB : 1.0; + double fTurnPrev = aVec1.Normalize().Scalar( aVec2 ); + double fTurnNext = aVec3.Scalar( aVec4.Normalize() ); + double fGradPrev, fGradB, fGradNext; + + if( fabs( fTurnPrev ) < ( 1.0 + SMALL_DVALUE ) && fabs( fTurnPrev ) > ( 1.0 - SMALL_DVALUE ) ) + fGradPrev = 0.0; + else + fGradPrev = basegfx::rad2deg(acos(fTurnPrev)) * (aVec1.IsNegative(aVec2) ? -1 : 1); + + fGradB = basegfx::rad2deg(acos(fTurnB)) * (aVec2.IsNegative(aVec3) ? -1 : 1); + + if( fabs( fTurnNext ) < ( 1.0 + SMALL_DVALUE ) && fabs( fTurnNext ) > ( 1.0 - SMALL_DVALUE ) ) + fGradNext = 0.0; + else + fGradNext = basegfx::rad2deg(acos(fTurnNext)) * (aVec3.IsNegative(aVec4) ? -1 : 1); + + if( ( fGradPrev > 0.0 && fGradB < 0.0 && fGradNext > 0.0 ) || + ( fGradPrev < 0.0 && fGradB > 0.0 && fGradNext < 0.0 ) ) + { + if( ( fLenFact < ( FSQRT2 + SMALL_DVALUE ) ) && + ( ( ( fDist1 + fDist4 ) / ( fDist2 + fDist3 ) ) * 2000.0 ) > fBound ) + { + bDeletePoint = true; + } + } + else + { + double fRelLen = 1.0 - sqrt( fDistB / rArea ); + + if( fRelLen < 0.0 ) + fRelLen = 0.0; + else if( fRelLen > 1.0 ) + fRelLen = 1.0; + + if( ( std::round( ( fLenFact - 1.0 ) * 1000000.0 ) < fBound ) && + ( fabs( fGradB ) <= ( fRelLen * fBound * 0.01 ) ) ) + { + bDeletePoint = true; + } + } + } + } + + if( !bDeletePoint ) + aNewPoly[ nNewPos++ ] = rPoly[ n ]; + else + bChangeInThisRun = true; + } + + if( bChangeInThisRun && nNewPos ) + { + aNewPoly.SetSize( nNewPos ); + rPoly = aNewPoly; + nNumNoChange = 0; + } + else + nNumNoChange++; + + nNumRuns++; + } +} + +void Polygon::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + // This check is required for DrawEngine + if ( !nHorzMove && !nVertMove ) + return; + + // Move points + sal_uInt16 nCount = mpImplPolygon->mnPoints; + for ( sal_uInt16 i = 0; i < nCount; i++ ) + { + Point& rPt = mpImplPolygon->mxPointAry[i]; + rPt.AdjustX(nHorzMove ); + rPt.AdjustY(nVertMove ); + } +} + +void Polygon::Translate(const Point& rTrans) +{ + for ( sal_uInt16 i = 0, nCount = mpImplPolygon->mnPoints; i < nCount; i++ ) + mpImplPolygon->mxPointAry[ i ] += rTrans; +} + +void Polygon::Scale( double fScaleX, double fScaleY ) +{ + for ( sal_uInt16 i = 0, nCount = mpImplPolygon->mnPoints; i < nCount; i++ ) + { + Point& rPnt = mpImplPolygon->mxPointAry[i]; + rPnt.setX( static_cast<tools::Long>( fScaleX * rPnt.X() ) ); + rPnt.setY( static_cast<tools::Long>( fScaleY * rPnt.Y() ) ); + } +} + +void Polygon::Rotate( const Point& rCenter, Degree10 nAngle10 ) +{ + nAngle10 %= 3600_deg10; + + if( nAngle10 ) + { + const double fAngle = toRadians(nAngle10); + Rotate( rCenter, sin( fAngle ), cos( fAngle ) ); + } +} + +void Polygon::Rotate( const Point& rCenter, double fSin, double fCos ) +{ + tools::Long nCenterX = rCenter.X(); + tools::Long nCenterY = rCenter.Y(); + + for( sal_uInt16 i = 0, nCount = mpImplPolygon->mnPoints; i < nCount; i++ ) + { + Point& rPt = mpImplPolygon->mxPointAry[ i ]; + + const tools::Long nX = rPt.X() - nCenterX; + const tools::Long nY = rPt.Y() - nCenterY; + rPt.setX( FRound(fCos * nX + fSin * nY + nCenterX) ); + rPt.setY( FRound(-(fSin * nX - fCos * nY - nCenterY)) ); + } +} + +void Polygon::Clip( const tools::Rectangle& rRect ) +{ + // This algorithm is broken for bezier curves, they would get converted to lines. + // Use PolyPolygon::Clip(). + assert( !HasFlags()); + + // #105251# Normalize rect before edge filtering + tools::Rectangle aJustifiedRect( rRect ); + aJustifiedRect.Normalize(); + + sal_uInt16 nSourceSize = mpImplPolygon->mnPoints; + ImplPolygonPointFilter aPolygon( nSourceSize ); + ImplEdgePointFilter aHorzFilter( EDGE_HORZ, aJustifiedRect.Left(), aJustifiedRect.Right(), + aPolygon ); + ImplEdgePointFilter aVertFilter( EDGE_VERT, aJustifiedRect.Top(), aJustifiedRect.Bottom(), + aHorzFilter ); + + for ( sal_uInt16 i = 0; i < nSourceSize; i++ ) + aVertFilter.Input( mpImplPolygon->mxPointAry[i] ); + if ( aVertFilter.IsPolygon() ) + aVertFilter.LastPoint(); + else + aPolygon.LastPoint(); + + mpImplPolygon = ImplType(aPolygon.get()); +} + +tools::Rectangle Polygon::GetBoundRect() const +{ + // Removing the assert. Bezier curves have the attribute that each single + // curve segment defined by four points can not exit the four-point polygon + // defined by that points. This allows to say that the curve segment can also + // never leave the Range of its defining points. + // The result is that Polygon::GetBoundRect() may not create the minimal + // BoundRect of the Polygon (to get that, use basegfx::B2DPolygon classes), + // but will always create a valid BoundRect, at least as long as this method + // 'blindly' travels over all points, including control points. + + // DBG_ASSERT( !mpImplPolygon->mxFlagAry.get(), "GetBoundRect could fail with beziers!" ); + + sal_uInt16 nCount = mpImplPolygon->mnPoints; + if( ! nCount ) + return tools::Rectangle(); + + tools::Long nXMin, nXMax, nYMin, nYMax; + + const Point& pFirstPt = mpImplPolygon->mxPointAry[0]; + nXMin = nXMax = pFirstPt.X(); + nYMin = nYMax = pFirstPt.Y(); + + for ( sal_uInt16 i = 0; i < nCount; i++ ) + { + const Point& rPt = mpImplPolygon->mxPointAry[i]; + + if (rPt.X() < nXMin) + nXMin = rPt.X(); + if (rPt.X() > nXMax) + nXMax = rPt.X(); + if (rPt.Y() < nYMin) + nYMin = rPt.Y(); + if (rPt.Y() > nYMax) + nYMax = rPt.Y(); + } + + return tools::Rectangle( nXMin, nYMin, nXMax, nYMax ); +} + +bool Polygon::Contains( const Point& rPoint ) const +{ + DBG_ASSERT( !mpImplPolygon->mxFlagAry, "IsInside could fail with beziers!" ); + + const tools::Rectangle aBound( GetBoundRect() ); + const Line aLine( rPoint, Point( aBound.Right() + 100, rPoint.Y() ) ); + sal_uInt16 nCount = mpImplPolygon->mnPoints; + sal_uInt16 nPCounter = 0; + + if ( ( nCount > 2 ) && aBound.Contains( rPoint ) ) + { + Point aPt1( mpImplPolygon->mxPointAry[ 0 ] ); + Point aIntersection; + Point aLastIntersection; + + while ( ( aPt1 == mpImplPolygon->mxPointAry[ nCount - 1 ] ) && ( nCount > 3 ) ) + nCount--; + + for ( sal_uInt16 i = 1; i <= nCount; i++ ) + { + const Point& rPt2 = mpImplPolygon->mxPointAry[ ( i < nCount ) ? i : 0 ]; + + if ( aLine.Intersection( Line( aPt1, rPt2 ), aIntersection ) ) + { + // This avoids insertion of double intersections + if ( nPCounter ) + { + if ( aIntersection != aLastIntersection ) + { + aLastIntersection = aIntersection; + nPCounter++; + } + } + else + { + aLastIntersection = aIntersection; + nPCounter++; + } + } + + aPt1 = rPt2; + } + } + + // is inside, if number of intersection points is odd + return ( ( nPCounter & 1 ) == 1 ); +} + +void Polygon::Insert( sal_uInt16 nPos, const Point& rPt ) +{ + if( nPos >= mpImplPolygon->mnPoints ) + nPos = mpImplPolygon->mnPoints; + + if (mpImplPolygon->ImplSplit(nPos, 1)) + mpImplPolygon->mxPointAry[ nPos ] = rPt; +} + +void Polygon::Insert( sal_uInt16 nPos, const tools::Polygon& rPoly ) +{ + const sal_uInt16 nInsertCount = rPoly.mpImplPolygon->mnPoints; + + if( nInsertCount ) + { + if( nPos >= mpImplPolygon->mnPoints ) + nPos = mpImplPolygon->mnPoints; + + if (rPoly.mpImplPolygon->mxFlagAry) + mpImplPolygon->ImplCreateFlagArray(); + + mpImplPolygon->ImplSplit( nPos, nInsertCount, rPoly.mpImplPolygon.get() ); + } +} + +Point& Polygon::operator[]( sal_uInt16 nPos ) +{ + assert( nPos < mpImplPolygon->mnPoints ); + + return mpImplPolygon->mxPointAry[nPos]; +} + +tools::Polygon& Polygon::operator=( const tools::Polygon& rPoly ) +{ + mpImplPolygon = rPoly.mpImplPolygon; + return *this; +} + +tools::Polygon& Polygon::operator=( tools::Polygon&& rPoly ) noexcept +{ + mpImplPolygon = std::move(rPoly.mpImplPolygon); + return *this; +} + +bool Polygon::operator==( const tools::Polygon& rPoly ) const +{ + return (mpImplPolygon == rPoly.mpImplPolygon); +} + +bool Polygon::IsEqual( const tools::Polygon& rPoly ) const +{ + bool bIsEqual = true; + sal_uInt16 i; + if ( GetSize() != rPoly.GetSize() ) + bIsEqual = false; + else + { + for ( i = 0; i < GetSize(); i++ ) + { + if ( ( GetPoint( i ) != rPoly.GetPoint( i ) ) || + ( GetFlags( i ) != rPoly.GetFlags( i ) ) ) + { + bIsEqual = false; + break; + } + } + } + return bIsEqual; +} + +SvStream& ReadPolygon( SvStream& rIStream, tools::Polygon& rPoly ) +{ + sal_uInt16 nPoints(0); + + // read all points and create array + rIStream.ReadUInt16( nPoints ); + + const size_t nMaxRecordsPossible = rIStream.remainingSize() / (2 * sizeof(sal_Int32)); + if (nPoints > nMaxRecordsPossible) + { + SAL_WARN("tools", "Polygon claims " << nPoints << " records, but only " << nMaxRecordsPossible << " possible"); + nPoints = nMaxRecordsPossible; + } + + rPoly.mpImplPolygon->ImplSetSize( nPoints, false ); + + for (sal_uInt16 i = 0; i < nPoints; i++) + { + sal_Int32 nTmpX(0), nTmpY(0); + rIStream.ReadInt32(nTmpX).ReadInt32(nTmpY); + rPoly.mpImplPolygon->mxPointAry[i].setX(nTmpX); + rPoly.mpImplPolygon->mxPointAry[i].setY(nTmpY); + } + + return rIStream; +} + +SvStream& WritePolygon( SvStream& rOStream, const tools::Polygon& rPoly ) +{ + sal_uInt16 nPoints = rPoly.GetSize(); + + // Write number of points + rOStream.WriteUInt16( nPoints ); + + for (sal_uInt16 i = 0; i < nPoints; i++) + { + rOStream.WriteInt32(rPoly.mpImplPolygon->mxPointAry[i].X()) + .WriteInt32(rPoly.mpImplPolygon->mxPointAry[i].Y()); + } + + return rOStream; +} + +void Polygon::ImplRead( SvStream& rIStream ) +{ + sal_uInt8 bHasPolyFlags(0); + + ReadPolygon( rIStream, *this ); + rIStream.ReadUChar( bHasPolyFlags ); + + if ( bHasPolyFlags ) + { + mpImplPolygon->mxFlagAry.reset(new PolyFlags[mpImplPolygon->mnPoints]); + auto nRead = rIStream.ReadBytes(mpImplPolygon->mxFlagAry.get(), mpImplPolygon->mnPoints); + if (nRead != mpImplPolygon->mnPoints) + { + SAL_WARN("tools", "Short read"); + memset(mpImplPolygon->mxFlagAry.get() + nRead, 0, mpImplPolygon->mnPoints - nRead); + } + } +} + +void Polygon::Read( SvStream& rIStream ) +{ + VersionCompatRead aCompat(rIStream); + + ImplRead( rIStream ); +} + +void Polygon::ImplWrite( SvStream& rOStream ) const +{ + bool bHasPolyFlags(mpImplPolygon->mxFlagAry); + WritePolygon( rOStream, *this ); + rOStream.WriteBool(bHasPolyFlags); + + if ( bHasPolyFlags ) + rOStream.WriteBytes(mpImplPolygon->mxFlagAry.get(), mpImplPolygon->mnPoints); +} + +void Polygon::Write( SvStream& rOStream ) const +{ + VersionCompatWrite aCompat(rOStream, 1); + + ImplWrite( rOStream ); +} + +// #i74631#/#i115917# numerical correction method for B2DPolygon +static void impCorrectContinuity(basegfx::B2DPolygon& roPolygon, sal_uInt32 nIndex, PolyFlags nCFlag) +{ + const sal_uInt32 nPointCount(roPolygon.count()); + OSL_ENSURE(nIndex < nPointCount, "impCorrectContinuity: index access out of range (!)"); + + if(nIndex >= nPointCount || (PolyFlags::Smooth != nCFlag && PolyFlags::Symmetric != nCFlag)) + return; + + if(!roPolygon.isPrevControlPointUsed(nIndex) || !roPolygon.isNextControlPointUsed(nIndex)) + return; + + // #i115917# Patch from osnola (modified, thanks for showing the problem) + + // The correction is needed because an integer polygon with control points + // is converted to double precision. When C1 or C2 is used the involved vectors + // may not have the same directions/lengths since these come from integer coordinates + // and may have been snapped to different nearest integer coordinates. The snap error + // is in the range of +-1 in y and y, thus 0.0 <= error <= sqrt(2.0). Nonetheless, + // it needs to be corrected to be able to detect the continuity in this points + // correctly. + + // We only have the integer data here (already in double precision form, but no mantissa + // used), so the best correction is to use: + + // for C1: The longest vector since it potentially has best preserved the original vector. + // Even better the sum of the vectors, weighted by their length. This gives the + // normal vector addition to get the vector itself, lengths need to be preserved. + // for C2: The mediated vector(s) since both should be the same, but mirrored + + // extract the point and vectors + const basegfx::B2DPoint aPoint(roPolygon.getB2DPoint(nIndex)); + const basegfx::B2DVector aNext(roPolygon.getNextControlPoint(nIndex) - aPoint); + const basegfx::B2DVector aPrev(aPoint - roPolygon.getPrevControlPoint(nIndex)); + + // calculate common direction vector, normalize + const basegfx::B2DVector aDirection(aNext + aPrev); + const double fDirectionLen = aDirection.getLength(); + if (fDirectionLen == 0.0) + return; + + if (PolyFlags::Smooth == nCFlag) + { + // C1: apply common direction vector, preserve individual lengths + const double fInvDirectionLen(1.0 / fDirectionLen); + roPolygon.setNextControlPoint(nIndex, basegfx::B2DPoint(aPoint + (aDirection * (aNext.getLength() * fInvDirectionLen)))); + roPolygon.setPrevControlPoint(nIndex, basegfx::B2DPoint(aPoint - (aDirection * (aPrev.getLength() * fInvDirectionLen)))); + } + else // PolyFlags::Symmetric + { + // C2: get mediated length. Taking half of the unnormalized direction would be + // an approximation, but not correct. + const double fMedLength((aNext.getLength() + aPrev.getLength()) * (0.5 / fDirectionLen)); + const basegfx::B2DVector aScaledDirection(aDirection * fMedLength); + + // Bring Direction to correct length and apply + roPolygon.setNextControlPoint(nIndex, basegfx::B2DPoint(aPoint + aScaledDirection)); + roPolygon.setPrevControlPoint(nIndex, basegfx::B2DPoint(aPoint - aScaledDirection)); + } +} + +// convert to basegfx::B2DPolygon and return +basegfx::B2DPolygon Polygon::getB2DPolygon() const +{ + basegfx::B2DPolygon aRetval; + const sal_uInt16 nCount(mpImplPolygon->mnPoints); + + if (nCount) + { + if (mpImplPolygon->mxFlagAry) + { + // handling for curves. Add start point + const Point aStartPoint(mpImplPolygon->mxPointAry[0]); + PolyFlags nPointFlag(mpImplPolygon->mxFlagAry[0]); + aRetval.append(basegfx::B2DPoint(aStartPoint.X(), aStartPoint.Y())); + Point aControlA, aControlB; + + for(sal_uInt16 a(1); a < nCount;) + { + bool bControlA(false); + bool bControlB(false); + + if(PolyFlags::Control == mpImplPolygon->mxFlagAry[a]) + { + aControlA = mpImplPolygon->mxPointAry[a++]; + bControlA = true; + } + + if(a < nCount && PolyFlags::Control == mpImplPolygon->mxFlagAry[a]) + { + aControlB = mpImplPolygon->mxPointAry[a++]; + bControlB = true; + } + + // assert invalid polygons + OSL_ENSURE(bControlA == bControlB, "Polygon::getB2DPolygon: Invalid source polygon (!)"); + + if(a < nCount) + { + const Point aEndPoint(mpImplPolygon->mxPointAry[a]); + + if(bControlA) + { + // bezier edge, add + aRetval.appendBezierSegment( + basegfx::B2DPoint(aControlA.X(), aControlA.Y()), + basegfx::B2DPoint(aControlB.X(), aControlB.Y()), + basegfx::B2DPoint(aEndPoint.X(), aEndPoint.Y())); + + impCorrectContinuity(aRetval, aRetval.count() - 2, nPointFlag); + } + else + { + // no bezier edge, add end point + aRetval.append(basegfx::B2DPoint(aEndPoint.X(), aEndPoint.Y())); + } + + nPointFlag = mpImplPolygon->mxFlagAry[a++]; + } + } + + // if exist, remove double first/last points, set closed and correct control points + basegfx::utils::checkClosed(aRetval); + + if(aRetval.isClosed()) + { + // closeWithGeometryChange did really close, so last point(s) were removed. + // Correct the continuity in the changed point + impCorrectContinuity(aRetval, 0, mpImplPolygon->mxFlagAry[0]); + } + } + else + { + // extra handling for non-curves (most-used case) for speedup + for(sal_uInt16 a(0); a < nCount; a++) + { + // get point and add + const Point aPoint(mpImplPolygon->mxPointAry[a]); + aRetval.append(basegfx::B2DPoint(aPoint.X(), aPoint.Y())); + } + + // set closed flag + basegfx::utils::checkClosed(aRetval); + } + } + + return aRetval; +} + +Polygon::Polygon(const basegfx::B2DPolygon& rPolygon) : mpImplPolygon(ImplPolygon(rPolygon)) +{ +} + +} // namespace tools + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/generic/poly2.cxx b/tools/source/generic/poly2.cxx new file mode 100644 index 0000000000..a00f64f41b --- /dev/null +++ b/tools/source/generic/poly2.cxx @@ -0,0 +1,520 @@ +/* -*- 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/log.hxx> +#include <osl/diagnose.h> +#include <poly.h> +#include <tools/poly.hxx> +#include <tools/debug.hxx> +#include <tools/stream.hxx> +#include <tools/vcompat.hxx> +#include <tools/gen.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/polygon/b2dpolypolygoncutter.hxx> +#include <basegfx/polygon/b2dpolygonclipper.hxx> + +namespace tools { + +PolyPolygon::PolyPolygon( sal_uInt16 nInitSize ) + : mpImplPolyPolygon( ImplPolyPolygon( nInitSize ) ) +{ +} + +PolyPolygon::PolyPolygon( const tools::Polygon& rPoly ) + : mpImplPolyPolygon( rPoly ) +{ +} +PolyPolygon::PolyPolygon( const tools::Rectangle& rRect ) + : mpImplPolyPolygon( tools::Polygon(rRect) ) +{ +} + +PolyPolygon::PolyPolygon( const tools::PolyPolygon& rPolyPoly ) + : mpImplPolyPolygon( rPolyPoly.mpImplPolyPolygon ) +{ +} + +PolyPolygon::PolyPolygon( tools::PolyPolygon&& rPolyPoly ) noexcept + : mpImplPolyPolygon( std::move(rPolyPoly.mpImplPolyPolygon) ) +{ +} + +PolyPolygon::~PolyPolygon() +{ +} + +void PolyPolygon::Insert( const tools::Polygon& rPoly, sal_uInt16 nPos ) +{ + assert ( mpImplPolyPolygon->mvPolyAry.size() < MAX_POLYGONS ); + + if ( nPos > mpImplPolyPolygon->mvPolyAry.size() ) + nPos = mpImplPolyPolygon->mvPolyAry.size(); + + mpImplPolyPolygon->mvPolyAry.insert(mpImplPolyPolygon->mvPolyAry.begin() + nPos, rPoly); +} + +void PolyPolygon::Remove( sal_uInt16 nPos ) +{ + assert(nPos < Count() && "PolyPolygon::Remove(): nPos >= nSize"); + + mpImplPolyPolygon->mvPolyAry.erase(mpImplPolyPolygon->mvPolyAry.begin() + nPos); +} + +void PolyPolygon::Replace( const tools::Polygon& rPoly, sal_uInt16 nPos ) +{ + assert(nPos < Count() && "PolyPolygon::Replace(): nPos >= nSize"); + + mpImplPolyPolygon->mvPolyAry[nPos] = rPoly; +} + +const tools::Polygon& PolyPolygon::GetObject( sal_uInt16 nPos ) const +{ + assert(nPos < Count() && "PolyPolygon::GetObject(): nPos >= nSize"); + + return mpImplPolyPolygon->mvPolyAry[nPos]; +} + +bool PolyPolygon::IsRect() const +{ + bool bIsRect = false; + if ( Count() == 1 ) + bIsRect = mpImplPolyPolygon->mvPolyAry[ 0 ].IsRect(); + return bIsRect; +} + +void PolyPolygon::Clear() +{ + mpImplPolyPolygon->mvPolyAry.clear(); +} + +void PolyPolygon::Optimize( PolyOptimizeFlags nOptimizeFlags ) +{ + if(!(bool(nOptimizeFlags) && Count())) + return; + + // #115630# ImplDrawHatch does not work with beziers included in the polypolygon, take care of that + bool bIsCurve(false); + + for(sal_uInt16 a(0); !bIsCurve && a < Count(); a++) + { + if((*this)[a].HasFlags()) + { + bIsCurve = true; + } + } + + if(bIsCurve) + { + OSL_ENSURE(false, "Optimize does *not* support curves, falling back to AdaptiveSubdivide()..."); + tools::PolyPolygon aPolyPoly; + + AdaptiveSubdivide(aPolyPoly); + aPolyPoly.Optimize(nOptimizeFlags); + *this = aPolyPoly; + } + else + { + double fArea; + const bool bEdges = ( nOptimizeFlags & PolyOptimizeFlags::EDGES ) == PolyOptimizeFlags::EDGES; + sal_uInt16 nPercent = 0; + + if( bEdges ) + { + const tools::Rectangle aBound( GetBoundRect() ); + + fArea = ( aBound.GetWidth() + aBound.GetHeight() ) * 0.5; + nPercent = 50; + nOptimizeFlags &= ~PolyOptimizeFlags::EDGES; + } + + // Optimize polygons + for( sal_uInt16 i = 0, nPolyCount = mpImplPolyPolygon->mvPolyAry.size(); i < nPolyCount; i++ ) + { + if( bEdges ) + { + mpImplPolyPolygon->mvPolyAry[ i ].Optimize( PolyOptimizeFlags::NO_SAME ); + tools::Polygon::ImplReduceEdges( mpImplPolyPolygon->mvPolyAry[ i ], fArea, nPercent ); + } + + if( bool(nOptimizeFlags) ) + mpImplPolyPolygon->mvPolyAry[ i ].Optimize( nOptimizeFlags ); + } + } +} + +void PolyPolygon::AdaptiveSubdivide( tools::PolyPolygon& rResult ) const +{ + rResult.Clear(); + + tools::Polygon aPolygon; + + for( size_t i = 0; i < mpImplPolyPolygon->mvPolyAry.size(); i++ ) + { + mpImplPolyPolygon->mvPolyAry[ i ].AdaptiveSubdivide( aPolygon, 1.0 ); + rResult.Insert( aPolygon ); + } +} + +tools::PolyPolygon PolyPolygon::SubdivideBezier( const tools::PolyPolygon& rPolyPoly ) +{ + sal_uInt16 i, nPolys = rPolyPoly.Count(); + tools::PolyPolygon aPolyPoly( nPolys ); + for( i=0; i<nPolys; ++i ) + aPolyPoly.Insert( tools::Polygon::SubdivideBezier( rPolyPoly.GetObject(i) ) ); + + return aPolyPoly; +} + + +void PolyPolygon::GetIntersection( const tools::PolyPolygon& rPolyPoly, tools::PolyPolygon& rResult ) const +{ + ImplDoOperation( rPolyPoly, rResult, PolyClipOp::INTERSECT ); +} + +void PolyPolygon::GetUnion( const tools::PolyPolygon& rPolyPoly, tools::PolyPolygon& rResult ) const +{ + ImplDoOperation( rPolyPoly, rResult, PolyClipOp::UNION ); +} + +void PolyPolygon::ImplDoOperation( const tools::PolyPolygon& rPolyPoly, tools::PolyPolygon& rResult, PolyClipOp nOperation ) const +{ + // Convert to B2DPolyPolygon, temporarily. It might be + // advantageous in the future, to have a tools::PolyPolygon adaptor that + // just simulates a B2DPolyPolygon here... + basegfx::B2DPolyPolygon aMergePolyPolygonA( getB2DPolyPolygon() ); + basegfx::B2DPolyPolygon aMergePolyPolygonB( rPolyPoly.getB2DPolyPolygon() ); + + // normalize the two polypolygons before. Force properly oriented + // polygons. + aMergePolyPolygonA = basegfx::utils::prepareForPolygonOperation( aMergePolyPolygonA ); + aMergePolyPolygonB = basegfx::utils::prepareForPolygonOperation( aMergePolyPolygonB ); + + switch( nOperation ) + { + // All code extracted from svx/source/svdraw/svedtv2.cxx + + case PolyClipOp::UNION: + { + // merge A and B (OR) + aMergePolyPolygonA = basegfx::utils::solvePolygonOperationOr(aMergePolyPolygonA, aMergePolyPolygonB); + break; + } + + default: + case PolyClipOp::INTERSECT: + { + // cut poly 1 against polys 2..n (AND) + aMergePolyPolygonA = basegfx::utils::solvePolygonOperationAnd(aMergePolyPolygonA, aMergePolyPolygonB); + break; + } + } + + rResult = tools::PolyPolygon( aMergePolyPolygonA ); +} + +sal_uInt16 PolyPolygon::Count() const +{ + return mpImplPolyPolygon->mvPolyAry.size(); +} + +void PolyPolygon::Move( tools::Long nHorzMove, tools::Long nVertMove ) +{ + // Required for DrawEngine + if( nHorzMove || nVertMove ) + { + // move points + sal_uInt16 nPolyCount = mpImplPolyPolygon->mvPolyAry.size(); + for ( sal_uInt16 i = 0; i < nPolyCount; i++ ) + mpImplPolyPolygon->mvPolyAry[i].Move( nHorzMove, nVertMove ); + } +} + +void PolyPolygon::Translate( const Point& rTrans ) +{ + // move points + for ( sal_uInt16 i = 0, nCount = mpImplPolyPolygon->mvPolyAry.size(); i < nCount; i++ ) + mpImplPolyPolygon->mvPolyAry[ i ].Translate( rTrans ); +} + +void PolyPolygon::Scale( double fScaleX, double fScaleY ) +{ + // Move points + for ( sal_uInt16 i = 0, nCount = mpImplPolyPolygon->mvPolyAry.size(); i < nCount; i++ ) + mpImplPolyPolygon->mvPolyAry[ i ].Scale( fScaleX, fScaleY ); +} + +void PolyPolygon::Rotate( const Point& rCenter, Degree10 nAngle10 ) +{ + nAngle10 %= 3600_deg10; + + if( nAngle10 ) + { + const double fAngle = toRadians(nAngle10); + Rotate( rCenter, sin( fAngle ), cos( fAngle ) ); + } +} + +void PolyPolygon::Rotate( const Point& rCenter, double fSin, double fCos ) +{ + // move points + for ( sal_uInt16 i = 0, nCount = mpImplPolyPolygon->mvPolyAry.size(); i < nCount; i++ ) + mpImplPolyPolygon->mvPolyAry[ i ].Rotate( rCenter, fSin, fCos ); +} + +void PolyPolygon::Clip( const tools::Rectangle& rRect ) +{ + sal_uInt16 nPolyCount = mpImplPolyPolygon->mvPolyAry.size(); + sal_uInt16 i; + + if ( !nPolyCount ) + return; + + // If there are bezier curves involved, Polygon::Clip() is broken. + // Use a temporary B2DPolyPolygon for the clipping. + for ( i = 0; i < nPolyCount; i++ ) + { + if(mpImplPolyPolygon->mvPolyAry[i].HasFlags()) + { + const basegfx::B2DPolyPolygon aPoly( + basegfx::utils::clipPolyPolygonOnRange( + getB2DPolyPolygon(), + basegfx::B2DRange( + rRect.Left(), + rRect.Top(), + rRect.Right() + 1, + rRect.Bottom() + 1), + true, + false)); + *this = PolyPolygon( aPoly ); + return; + } + } + + // Clip every polygon, deleting the empty ones + for ( i = 0; i < nPolyCount; i++ ) + mpImplPolyPolygon->mvPolyAry[i].Clip( rRect ); + while ( nPolyCount ) + { + if ( GetObject( nPolyCount-1 ).GetSize() <= 2 ) + Remove( nPolyCount-1 ); + nPolyCount--; + } +} + +tools::Rectangle PolyPolygon::GetBoundRect() const +{ + tools::Long nXMin=0, nXMax=0, nYMin=0, nYMax=0; + bool bFirst = true; + sal_uInt16 nPolyCount = mpImplPolyPolygon->mvPolyAry.size(); + + for ( sal_uInt16 n = 0; n < nPolyCount; n++ ) + { + const tools::Polygon* pPoly = &mpImplPolyPolygon->mvPolyAry[n]; + const Point* pAry = pPoly->GetConstPointAry(); + sal_uInt16 nPointCount = pPoly->GetSize(); + + for ( sal_uInt16 i = 0; i < nPointCount; i++ ) + { + const Point* pPt = &pAry[ i ]; + + if ( bFirst ) + { + nXMin = nXMax = pPt->X(); + nYMin = nYMax = pPt->Y(); + bFirst = false; + } + else + { + if ( pPt->X() < nXMin ) + nXMin = pPt->X(); + if ( pPt->X() > nXMax ) + nXMax = pPt->X(); + if ( pPt->Y() < nYMin ) + nYMin = pPt->Y(); + if ( pPt->Y() > nYMax ) + nYMax = pPt->Y(); + } + } + } + + if ( !bFirst ) + return tools::Rectangle( nXMin, nYMin, nXMax, nYMax ); + else + return tools::Rectangle(); +} + +Polygon& PolyPolygon::operator[]( sal_uInt16 nPos ) +{ + assert(nPos < Count() && "PolyPolygon::[](): nPos >= nSize"); + + return mpImplPolyPolygon->mvPolyAry[nPos]; +} + +PolyPolygon& PolyPolygon::operator=( const tools::PolyPolygon& rPolyPoly ) +{ + mpImplPolyPolygon = rPolyPoly.mpImplPolyPolygon; + return *this; +} + +PolyPolygon& PolyPolygon::operator=( tools::PolyPolygon&& rPolyPoly ) noexcept +{ + mpImplPolyPolygon = std::move(rPolyPoly.mpImplPolyPolygon); + return *this; +} + +bool PolyPolygon::operator==( const tools::PolyPolygon& rPolyPoly ) const +{ + return rPolyPoly.mpImplPolyPolygon == mpImplPolyPolygon; +} + +SvStream& ReadPolyPolygon( SvStream& rIStream, tools::PolyPolygon& rPolyPoly ) +{ + sal_uInt16 nPolyCount(0); + + // Read number of polygons + rIStream.ReadUInt16( nPolyCount ); + + const size_t nMinRecordSize = sizeof(sal_uInt16); + const size_t nMaxRecords = rIStream.remainingSize() / nMinRecordSize; + if (nPolyCount > nMaxRecords) + { + SAL_WARN("tools", "Parsing error: " << nMaxRecords << + " max possible entries, but " << nPolyCount << " claimed, truncating"); + nPolyCount = nMaxRecords; + } + + if( nPolyCount ) + { + rPolyPoly.mpImplPolyPolygon->mvPolyAry.resize(nPolyCount); + + tools::Polygon aTempPoly; + for ( sal_uInt16 i = 0; i < nPolyCount; i++ ) + { + ReadPolygon( rIStream, aTempPoly ); + rPolyPoly.mpImplPolyPolygon->mvPolyAry[i] = aTempPoly; + } + } + else + rPolyPoly = tools::PolyPolygon(); + + return rIStream; +} + +SvStream& WritePolyPolygon( SvStream& rOStream, const tools::PolyPolygon& rPolyPoly ) +{ + // Write number of polygons + sal_uInt16 nPolyCount = rPolyPoly.mpImplPolyPolygon->mvPolyAry.size(); + rOStream.WriteUInt16( nPolyCount ); + + // output polygons + for ( sal_uInt16 i = 0; i < nPolyCount; i++ ) + WritePolygon( rOStream, rPolyPoly.mpImplPolyPolygon->mvPolyAry[i] ); + + return rOStream; +} + +void PolyPolygon::Read( SvStream& rIStream ) +{ + VersionCompatRead aCompat(rIStream); + + sal_uInt16 nPolyCount(0); + + // Read number of polygons + rIStream.ReadUInt16( nPolyCount ); + + const size_t nMinRecordSize = sizeof(sal_uInt16); + const size_t nMaxRecords = rIStream.remainingSize() / nMinRecordSize; + if (nPolyCount > nMaxRecords) + { + SAL_WARN("tools", "Parsing error: " << nMaxRecords << + " max possible entries, but " << nPolyCount << " claimed, truncating"); + nPolyCount = nMaxRecords; + } + + if( nPolyCount ) + { + mpImplPolyPolygon->mvPolyAry.clear(); + + for ( sal_uInt16 i = 0; i < nPolyCount; i++ ) + { + tools::Polygon aTempPoly; + aTempPoly.ImplRead( rIStream ); + mpImplPolyPolygon->mvPolyAry.emplace_back( aTempPoly ); + } + } + else + *this = tools::PolyPolygon(); +} + +void PolyPolygon::Write( SvStream& rOStream ) const +{ + VersionCompatWrite aCompat(rOStream, 1); + + // Write number of polygons + sal_uInt16 nPolyCount = mpImplPolyPolygon->mvPolyAry.size(); + rOStream.WriteUInt16( nPolyCount ); + + // Output polygons + for ( sal_uInt16 i = 0; i < nPolyCount; i++ ) + mpImplPolyPolygon->mvPolyAry[i].ImplWrite( rOStream ); +} + +// convert to basegfx::B2DPolyPolygon and return +basegfx::B2DPolyPolygon PolyPolygon::getB2DPolyPolygon() const +{ + basegfx::B2DPolyPolygon aRetval; + + for(size_t a(0); a < mpImplPolyPolygon->mvPolyAry.size(); a++) + { + tools::Polygon const & rCandidate = mpImplPolyPolygon->mvPolyAry[a]; + aRetval.append(rCandidate.getB2DPolygon()); + } + + return aRetval; +} + +// constructor to convert from basegfx::B2DPolyPolygon +PolyPolygon::PolyPolygon(const basegfx::B2DPolyPolygon& rPolyPolygon) + : mpImplPolyPolygon(rPolyPolygon) +{ +} + +} /* namespace tools */ + +ImplPolyPolygon::ImplPolyPolygon(const basegfx::B2DPolyPolygon& rPolyPolygon) +{ + const sal_uInt16 nCount(sal_uInt16(rPolyPolygon.count())); + DBG_ASSERT(sal_uInt32(nCount) == rPolyPolygon.count(), + "PolyPolygon::PolyPolygon: Too many sub-polygons in given basegfx::B2DPolyPolygon (!)"); + + if ( nCount ) + { + mvPolyAry.resize( nCount ); + + for(sal_uInt16 a(0); a < nCount; a++) + { + const basegfx::B2DPolygon& aCandidate(rPolyPolygon.getB2DPolygon(sal_uInt32(a))); + mvPolyAry[a] = tools::Polygon( aCandidate ); + } + } + else + mvPolyAry.reserve(16); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/generic/svborder.cxx b/tools/source/generic/svborder.cxx new file mode 100644 index 0000000000..a61c79b0e4 --- /dev/null +++ b/tools/source/generic/svborder.cxx @@ -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 . + */ +#include <tools/svborder.hxx> +#include <tools/gen.hxx> + +tools::Rectangle& operator+=(tools::Rectangle& rRect, const SvBorder& rBorder) +{ + // call GetSize first due to Empty-Rect + Size aS(rRect.GetSize()); + aS.AdjustWidth(rBorder.Left() + rBorder.Right()); + aS.AdjustHeight(rBorder.Top() + rBorder.Bottom()); + + rRect.AdjustLeft(-(rBorder.Left())); + rRect.AdjustTop(-(rBorder.Top())); + rRect.SetSize(aS); + return rRect; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/inet/inetmime.cxx b/tools/source/inet/inetmime.cxx new file mode 100644 index 0000000000..6694dc3986 --- /dev/null +++ b/tools/source/inet/inetmime.cxx @@ -0,0 +1,1387 @@ +/* -*- 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 <algorithm> +#include <limits> +#include <forward_list> +#include <memory> + +#include <sal/log.hxx> +#include <rtl/ustring.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/tencinfo.h> +#include <tools/debug.hxx> +#include <tools/inetmime.hxx> +#include <rtl/character.hxx> + +namespace { + +rtl_TextEncoding getCharsetEncoding(const char * pBegin, + const char * pEnd); + +/** Check for US-ASCII white space character. + + @param nChar Some UCS-4 character. + + @return True if nChar is a US-ASCII white space character (US-ASCII + 0x09 or 0x20). + */ +bool isWhiteSpace(sal_uInt32 nChar) +{ + return nChar == '\t' || nChar == ' '; +} + +/** Get the Base 64 digit weight of a US-ASCII character. + + @param nChar Some UCS-4 character. + + @return If nChar is a US-ASCII Base 64 digit character (US-ASCII + 'A'--'F', or 'a'--'f', '0'--'9', '+', or '/'), return the + corresponding weight (0--63); if nChar is the US-ASCII Base 64 padding + character (US-ASCII '='), return -1; otherwise, return -2. + */ +int getBase64Weight(sal_uInt32 nChar) +{ + return rtl::isAsciiUpperCase(nChar) ? int(nChar - 'A') : + rtl::isAsciiLowerCase(nChar) ? int(nChar - 'a' + 26) : + rtl::isAsciiDigit(nChar) ? int(nChar - '0' + 52) : + nChar == '+' ? 62 : + nChar == '/' ? 63 : + nChar == '=' ? -1 : -2; +} + +bool startsWithLineFolding(const sal_Unicode * pBegin, + const sal_Unicode * pEnd) +{ + DBG_ASSERT(pBegin && pBegin <= pEnd, + "startsWithLineFolding(): Bad sequence"); + + return pEnd - pBegin >= 3 && pBegin[0] == 0x0D && pBegin[1] == 0x0A + && isWhiteSpace(pBegin[2]); // CR, LF +} + +rtl_TextEncoding translateFromMIME(rtl_TextEncoding + eEncoding) +{ +#if defined(_WIN32) + return eEncoding == RTL_TEXTENCODING_ISO_8859_1 ? + RTL_TEXTENCODING_MS_1252 : eEncoding; +#else + return eEncoding; +#endif +} + +bool isMIMECharsetEncoding(rtl_TextEncoding eEncoding) +{ + return rtl_isOctetTextEncoding(eEncoding); +} + +std::unique_ptr<sal_Unicode[]> convertToUnicode(const char * pBegin, + const char * pEnd, + rtl_TextEncoding eEncoding, + sal_Size & rSize) +{ + if (eEncoding == RTL_TEXTENCODING_DONTKNOW) + return nullptr; + rtl_TextToUnicodeConverter hConverter + = rtl_createTextToUnicodeConverter(eEncoding); + rtl_TextToUnicodeContext hContext + = rtl_createTextToUnicodeContext(hConverter); + std::unique_ptr<sal_Unicode[]> pBuffer; + sal_uInt32 nInfo; + for (sal_Size nBufferSize = pEnd - pBegin;; + nBufferSize += nBufferSize / 3 + 1) + { + pBuffer.reset(new sal_Unicode[nBufferSize]); + sal_Size nSrcCvtBytes; + rSize = rtl_convertTextToUnicode( + hConverter, hContext, pBegin, pEnd - pBegin, pBuffer.get(), + nBufferSize, + RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR + | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR + | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR, + &nInfo, &nSrcCvtBytes); + if (nInfo != RTL_TEXTTOUNICODE_INFO_DESTBUFFERTOOSMALL) + break; + pBuffer.reset(); + rtl_resetTextToUnicodeContext(hConverter, hContext); + } + rtl_destroyTextToUnicodeContext(hConverter, hContext); + rtl_destroyTextToUnicodeConverter(hConverter); + if (nInfo != 0) + { + pBuffer.reset(); + } + return pBuffer; +} + +void writeUTF8(OStringBuffer & rSink, sal_uInt32 nChar) +{ + // See RFC 2279 for a discussion of UTF-8. + DBG_ASSERT(nChar < 0x80000000, "writeUTF8(): Bad char"); + + if (nChar < 0x80) + rSink.append(char(nChar)); + else if (nChar < 0x800) + rSink.append(OStringChar(char(nChar >> 6 | 0xC0)) + + OStringChar(char((nChar & 0x3F) | 0x80))); + else if (nChar < 0x10000) + rSink.append( + OStringChar(char(nChar >> 12 | 0xE0)) + + OStringChar(char((nChar >> 6 & 0x3F) | 0x80)) + + OStringChar(char((nChar & 0x3F) | 0x80))); + else if (nChar < 0x200000) + rSink.append( + OStringChar(char(nChar >> 18 | 0xF0)) + + OStringChar(char((nChar >> 12 & 0x3F) | 0x80)) + + OStringChar(char((nChar >> 6 & 0x3F) | 0x80)) + + OStringChar(char((nChar & 0x3F) | 0x80))); + else if (nChar < 0x4000000) + rSink.append( + OStringChar(char(nChar >> 24 | 0xF8)) + + OStringChar(char((nChar >> 18 & 0x3F) | 0x80)) + + OStringChar(char((nChar >> 12 & 0x3F) | 0x80)) + + OStringChar(char((nChar >> 6 & 0x3F) | 0x80)) + + OStringChar(char((nChar & 0x3F) | 0x80))); + else + rSink.append( + OStringChar(char(nChar >> 30 | 0xFC)) + + OStringChar(char((nChar >> 24 & 0x3F) | 0x80)) + + OStringChar(char((nChar >> 18 & 0x3F) | 0x80)) + + OStringChar(char((nChar >> 12 & 0x3F) | 0x80)) + + OStringChar(char((nChar >> 6 & 0x3F) | 0x80)) + + OStringChar(char((nChar & 0x3F) | 0x80))); +} + +bool translateUTF8Char(const char *& rBegin, + const char * pEnd, + sal_uInt32 & rCharacter) +{ + if (rBegin == pEnd || static_cast< unsigned char >(*rBegin) < 0x80 + || static_cast< unsigned char >(*rBegin) >= 0xFE) + return false; + + int nCount; + sal_uInt32 nMin; + sal_uInt32 nUCS4; + const char * p = rBegin; + if (static_cast< unsigned char >(*p) < 0xE0) + { + nCount = 1; + nMin = 0x80; + nUCS4 = static_cast< unsigned char >(*p) & 0x1F; + } + else if (static_cast< unsigned char >(*p) < 0xF0) + { + nCount = 2; + nMin = 0x800; + nUCS4 = static_cast< unsigned char >(*p) & 0xF; + } + else if (static_cast< unsigned char >(*p) < 0xF8) + { + nCount = 3; + nMin = 0x10000; + nUCS4 = static_cast< unsigned char >(*p) & 7; + } + else if (static_cast< unsigned char >(*p) < 0xFC) + { + nCount = 4; + nMin = 0x200000; + nUCS4 = static_cast< unsigned char >(*p) & 3; + } + else + { + nCount = 5; + nMin = 0x4000000; + nUCS4 = static_cast< unsigned char >(*p) & 1; + } + ++p; + + for (; nCount-- > 0; ++p) + if ((static_cast< unsigned char >(*p) & 0xC0) == 0x80) + nUCS4 = (nUCS4 << 6) | (static_cast< unsigned char >(*p) & 0x3F); + else + return false; + + if (!rtl::isUnicodeCodePoint(nUCS4) || nUCS4 < nMin) + return false; + + rCharacter = nUCS4; + rBegin = p; + return true; +} + +void appendISO88591(OUStringBuffer & rText, char const * pBegin, + char const * pEnd); + +struct Parameter +{ + OString m_aAttribute; + OString m_aCharset; + OString m_aLanguage; + OString m_aValue; + sal_uInt32 m_nSection; + bool m_bExtended; + + bool operator<(const Parameter& rhs) const // is used by std::list<Parameter>::sort + { + int nComp = m_aAttribute.compareTo(rhs.m_aAttribute); + return nComp < 0 || + (nComp == 0 && m_nSection < rhs.m_nSection); + } + struct IsSameSection // is used to check container for duplicates with std::any_of + { + const OString& rAttribute; + const sal_uInt32 nSection; + bool operator()(const Parameter& r) const + { return r.m_aAttribute == rAttribute && r.m_nSection == nSection; } + }; +}; + +typedef std::forward_list<Parameter> ParameterList; + +bool parseParameters(ParameterList const & rInput, + INetContentTypeParameterList * pOutput); + +// appendISO88591 + +void appendISO88591(OUStringBuffer & rText, char const * pBegin, + char const * pEnd) +{ + sal_Int32 nLength = pEnd - pBegin; + std::unique_ptr<sal_Unicode[]> pBuffer(new sal_Unicode[nLength]); + for (sal_Unicode * p = pBuffer.get(); pBegin != pEnd;) + *p++ = static_cast<unsigned char>(*pBegin++); + rText.append(pBuffer.get(), nLength); +} + +// parseParameters + +bool parseParameters(ParameterList const & rInput, + INetContentTypeParameterList * pOutput) +{ + if (pOutput) + pOutput->clear(); + + for (auto it = rInput.begin(), itPrev = rInput.end(); it != rInput.end() ; itPrev = it++) + { + if (it->m_nSection > 0 + && (itPrev == rInput.end() + || itPrev->m_nSection != it->m_nSection - 1 + || itPrev->m_aAttribute != it->m_aAttribute)) + return false; + } + + if (pOutput) + for (auto it = rInput.begin(), itNext = rInput.begin(); it != rInput.end(); it = itNext) + { + bool bCharset = !it->m_aCharset.isEmpty(); + rtl_TextEncoding eEncoding = RTL_TEXTENCODING_DONTKNOW; + if (bCharset) + eEncoding + = getCharsetEncoding(it->m_aCharset.getStr(), + it->m_aCharset.getStr() + + it->m_aCharset.getLength()); + OUStringBuffer aValue(64); + bool bBadEncoding = false; + itNext = it; + do + { + sal_Size nSize; + std::unique_ptr<sal_Unicode[]> pUnicode + = convertToUnicode(itNext->m_aValue.getStr(), + itNext->m_aValue.getStr() + + itNext->m_aValue.getLength(), + bCharset && it->m_bExtended ? + eEncoding : + RTL_TEXTENCODING_UTF8, + nSize); + if (!pUnicode && !(bCharset && it->m_bExtended)) + pUnicode = convertToUnicode( + itNext->m_aValue.getStr(), + itNext->m_aValue.getStr() + + itNext->m_aValue.getLength(), + RTL_TEXTENCODING_ISO_8859_1, nSize); + if (!pUnicode) + { + bBadEncoding = true; + break; + } + aValue.append(pUnicode.get(), static_cast<sal_Int32>(nSize)); + ++itNext; + } + while (itNext != rInput.end() && itNext->m_nSection != 0); + + if (bBadEncoding) + { + aValue.setLength(0); + itNext = it; + do + { + if (itNext->m_bExtended) + { + for (sal_Int32 i = 0; i < itNext->m_aValue.getLength(); ++i) + aValue.append( + static_cast<sal_Unicode>( + static_cast<unsigned char>(itNext->m_aValue[i]) + | 0xF800)); // map to unicode corporate use sub area + } + else + { + for (sal_Int32 i = 0; i < itNext->m_aValue.getLength(); ++i) + aValue.append( itNext->m_aValue[i] ); + } + ++itNext; + } + while (itNext != rInput.end() && itNext->m_nSection != 0); + } + auto const ret = pOutput->insert( + {it->m_aAttribute, + {it->m_aCharset, it->m_aLanguage, aValue.makeStringAndClear(), !bBadEncoding}}); + SAL_INFO_IF(!ret.second, "tools", + "INetMIME: dropping duplicate parameter: " << it->m_aAttribute); + } + return true; +} + +/** Check whether some character is valid within an RFC 2045 <token>. + + @param nChar Some UCS-4 character. + + @return True if nChar is valid within an RFC 2047 <token> (US-ASCII + 'A'--'Z', 'a'--'z', '0'--'9', '!', '#', '$', '%', '&', ''', '*', '+', + '-', '.', '^', '_', '`', '{', '|', '}', or '~'). + */ +bool isTokenChar(sal_uInt32 nChar) +{ + static const bool aMap[128] + = { false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, true, false, true, true, true, true, true, // !"#$%&' + false, false, true, true, false, true, true, false, //()*+,-./ + true, true, true, true, true, true, true, true, //01234567 + true, true, false, false, false, false, false, false, //89:;<=>? + false, true, true, true, true, true, true, true, //@ABCDEFG + true, true, true, true, true, true, true, true, //HIJKLMNO + true, true, true, true, true, true, true, true, //PQRSTUVW + true, true, true, false, false, false, true, true, //XYZ[\]^_ + true, true, true, true, true, true, true, true, //`abcdefg + true, true, true, true, true, true, true, true, //hijklmno + true, true, true, true, true, true, true, true, //pqrstuvw + true, true, true, true, true, true, true, false //xyz{|}~ + }; + return rtl::isAscii(nChar) && aMap[nChar]; +} + +const sal_Unicode * skipComment(const sal_Unicode * pBegin, + const sal_Unicode * pEnd) +{ + DBG_ASSERT(pBegin && pBegin <= pEnd, + "skipComment(): Bad sequence"); + + if (pBegin != pEnd && *pBegin == '(') + { + sal_uInt32 nLevel = 0; + for (const sal_Unicode * p = pBegin; p != pEnd;) + switch (*p++) + { + case '(': + ++nLevel; + break; + + case ')': + if (--nLevel == 0) + return p; + break; + + case '\\': + if (p != pEnd) + ++p; + break; + } + } + return pBegin; +} + +const sal_Unicode * skipLinearWhiteSpaceComment(const sal_Unicode * + pBegin, + const sal_Unicode * + pEnd) +{ + DBG_ASSERT(pBegin && pBegin <= pEnd, + "skipLinearWhiteSpaceComment(): Bad sequence"); + + while (pBegin != pEnd) + switch (*pBegin) + { + case '\t': + case ' ': + ++pBegin; + break; + + case 0x0D: // CR + if (startsWithLineFolding(pBegin, pEnd)) + pBegin += 3; + else + return pBegin; + break; + + case '(': + { + const sal_Unicode * p = skipComment(pBegin, pEnd); + if (p == pBegin) + return pBegin; + pBegin = p; + break; + } + + default: + return pBegin; + } + return pBegin; +} + +const sal_Unicode * skipQuotedString(const sal_Unicode * pBegin, + const sal_Unicode * pEnd) +{ + DBG_ASSERT(pBegin && pBegin <= pEnd, + "skipQuotedString(): Bad sequence"); + + if (pBegin != pEnd && *pBegin == '"') + for (const sal_Unicode * p = pBegin + 1; p != pEnd;) + switch (*p++) + { + case 0x0D: // CR + if (pEnd - p < 2 || *p++ != 0x0A // LF + || !isWhiteSpace(*p++)) + return pBegin; + break; + + case '"': + return p; + + case '\\': + if (p != pEnd) + ++p; + break; + } + return pBegin; +} + +sal_Unicode const * scanParameters(sal_Unicode const * pBegin, + sal_Unicode const * pEnd, + INetContentTypeParameterList * + pParameters) +{ + ParameterList aList; + sal_Unicode const * pParameterBegin = pBegin; + for (sal_Unicode const * p = pParameterBegin;;) + { + pParameterBegin = skipLinearWhiteSpaceComment(p, pEnd); + if (pParameterBegin == pEnd || *pParameterBegin != ';') + break; + p = pParameterBegin + 1; + + sal_Unicode const * pAttributeBegin + = skipLinearWhiteSpaceComment(p, pEnd); + p = pAttributeBegin; + bool bDowncaseAttribute = false; + while (p != pEnd && isTokenChar(*p) && *p != '*') + { + bDowncaseAttribute = bDowncaseAttribute || rtl::isAsciiUpperCase(*p); + ++p; + } + if (p == pAttributeBegin) + break; + OString aAttribute(pAttributeBegin, p - pAttributeBegin, RTL_TEXTENCODING_ASCII_US); + if (bDowncaseAttribute) + aAttribute = aAttribute.toAsciiLowerCase(); + + sal_uInt32 nSection = 0; + if (p != pEnd && *p == '*') + { + ++p; + if (p != pEnd && rtl::isAsciiDigit(*p) + && !INetMIME::scanUnsigned(p, pEnd, false, nSection)) + break; + } + + bool bPresent = std::any_of(aList.begin(), aList.end(), + Parameter::IsSameSection{aAttribute, nSection}); + if (bPresent) + break; + + bool bExtended = false; + if (p != pEnd && *p == '*') + { + ++p; + bExtended = true; + } + + p = skipLinearWhiteSpaceComment(p, pEnd); + + if (p == pEnd || *p != '=') + break; + + p = skipLinearWhiteSpaceComment(p + 1, pEnd); + + OString aCharset; + OString aLanguage; + OString aValue; + if (bExtended) + { + if (nSection == 0) + { + sal_Unicode const * pCharsetBegin = p; + bool bDowncaseCharset = false; + while (p != pEnd && isTokenChar(*p) && *p != '\'') + { + bDowncaseCharset = bDowncaseCharset || rtl::isAsciiUpperCase(*p); + ++p; + } + if (p == pCharsetBegin) + break; + if (pParameters) + { + aCharset = OString( + pCharsetBegin, + p - pCharsetBegin, + RTL_TEXTENCODING_ASCII_US); + if (bDowncaseCharset) + aCharset = aCharset.toAsciiLowerCase(); + } + + if (p == pEnd || *p != '\'') + break; + ++p; + + sal_Unicode const * pLanguageBegin = p; + bool bDowncaseLanguage = false; + int nLetters = 0; + for (; p != pEnd; ++p) + if (rtl::isAsciiAlpha(*p)) + { + if (++nLetters > 8) + break; + bDowncaseLanguage = bDowncaseLanguage + || rtl::isAsciiUpperCase(*p); + } + else if (*p == '-') + { + if (nLetters == 0) + break; + nLetters = 0; + } + else + break; + if (nLetters == 0 || nLetters > 8) + break; + if (pParameters) + { + aLanguage = OString( + pLanguageBegin, + p - pLanguageBegin, + RTL_TEXTENCODING_ASCII_US); + if (bDowncaseLanguage) + aLanguage = aLanguage.toAsciiLowerCase(); + } + + if (p == pEnd || *p != '\'') + break; + ++p; + } + if (pParameters) + { + OStringBuffer aSink; + while (p != pEnd) + { + auto q = p; + sal_uInt32 nChar = INetMIME::getUTF32Character(q, pEnd); + if (rtl::isAscii(nChar) && !isTokenChar(nChar)) + break; + p = q; + if (nChar == '%' && p + 1 < pEnd) + { + int nWeight1 = INetMIME::getHexWeight(p[0]); + int nWeight2 = INetMIME::getHexWeight(p[1]); + if (nWeight1 >= 0 && nWeight2 >= 0) + { + aSink.append(char(nWeight1 << 4 | nWeight2)); + p += 2; + continue; + } + } + writeUTF8(aSink, nChar); + } + aValue = aSink.makeStringAndClear(); + } + else + while (p != pEnd && (isTokenChar(*p) || !rtl::isAscii(*p))) + ++p; + } + else if (p != pEnd && *p == '"') + if (pParameters) + { + OStringBuffer aSink(256); + bool bInvalid = false; + for (++p;;) + { + if (p == pEnd) + { + bInvalid = true; + break; + } + sal_uInt32 nChar = INetMIME::getUTF32Character(p, pEnd); + if (nChar == '"') + break; + else if (nChar == 0x0D) // CR + { + if (pEnd - p < 2 || *p++ != 0x0A // LF + || !isWhiteSpace(*p)) + { + bInvalid = true; + break; + } + nChar = static_cast<unsigned char>(*p++); + } + else if (nChar == '\\') + { + if (p == pEnd) + { + bInvalid = true; + break; + } + nChar = INetMIME::getUTF32Character(p, pEnd); + } + writeUTF8(aSink, nChar); + } + if (bInvalid) + break; + aValue = aSink.makeStringAndClear(); + } + else + { + sal_Unicode const * pStringEnd = skipQuotedString(p, pEnd); + if (p == pStringEnd) + break; + p = pStringEnd; + } + else + { + sal_Unicode const * pTokenBegin = p; + while (p != pEnd && (isTokenChar(*p) || !rtl::isAscii(*p))) + ++p; + if (p == pTokenBegin) + break; + if (pParameters) + aValue = OString( + pTokenBegin, p - pTokenBegin, + RTL_TEXTENCODING_UTF8); + } + aList.emplace_front(Parameter{aAttribute, aCharset, aLanguage, aValue, nSection, bExtended}); + } + aList.sort(); + return parseParameters(aList, pParameters) ? pParameterBegin : pBegin; +} + +bool equalIgnoreCase(const char * pBegin1, + const char * pEnd1, + const char * pString2) +{ + DBG_ASSERT(pBegin1 && pBegin1 <= pEnd1 && pString2, + "equalIgnoreCase(): Bad sequences"); + + while (*pString2 != 0) + if (pBegin1 == pEnd1 + || (rtl::toAsciiUpperCase(static_cast<unsigned char>(*pBegin1++)) + != rtl::toAsciiUpperCase( + static_cast<unsigned char>(*pString2++)))) + return false; + return pBegin1 == pEnd1; +} + +struct EncodingEntry +{ + char const * m_aName; + rtl_TextEncoding m_eEncoding; +}; + +// The source for the following table is <ftp://ftp.iana.org/in-notes/iana/ +// assignments/character-sets> as of Jan, 21 2000 12:46:00, unless otherwise +// noted: +EncodingEntry const aEncodingMap[] + = { { "US-ASCII", RTL_TEXTENCODING_ASCII_US }, + { "ANSI_X3.4-1968", RTL_TEXTENCODING_ASCII_US }, + { "ISO-IR-6", RTL_TEXTENCODING_ASCII_US }, + { "ANSI_X3.4-1986", RTL_TEXTENCODING_ASCII_US }, + { "ISO_646.IRV:1991", RTL_TEXTENCODING_ASCII_US }, + { "ASCII", RTL_TEXTENCODING_ASCII_US }, + { "ISO646-US", RTL_TEXTENCODING_ASCII_US }, + { "US", RTL_TEXTENCODING_ASCII_US }, + { "IBM367", RTL_TEXTENCODING_ASCII_US }, + { "CP367", RTL_TEXTENCODING_ASCII_US }, + { "CSASCII", RTL_TEXTENCODING_ASCII_US }, + { "ISO-8859-1", RTL_TEXTENCODING_ISO_8859_1 }, + { "ISO_8859-1:1987", RTL_TEXTENCODING_ISO_8859_1 }, + { "ISO-IR-100", RTL_TEXTENCODING_ISO_8859_1 }, + { "ISO_8859-1", RTL_TEXTENCODING_ISO_8859_1 }, + { "LATIN1", RTL_TEXTENCODING_ISO_8859_1 }, + { "L1", RTL_TEXTENCODING_ISO_8859_1 }, + { "IBM819", RTL_TEXTENCODING_ISO_8859_1 }, + { "CP819", RTL_TEXTENCODING_ISO_8859_1 }, + { "CSISOLATIN1", RTL_TEXTENCODING_ISO_8859_1 }, + { "ISO-8859-2", RTL_TEXTENCODING_ISO_8859_2 }, + { "ISO_8859-2:1987", RTL_TEXTENCODING_ISO_8859_2 }, + { "ISO-IR-101", RTL_TEXTENCODING_ISO_8859_2 }, + { "ISO_8859-2", RTL_TEXTENCODING_ISO_8859_2 }, + { "LATIN2", RTL_TEXTENCODING_ISO_8859_2 }, + { "L2", RTL_TEXTENCODING_ISO_8859_2 }, + { "CSISOLATIN2", RTL_TEXTENCODING_ISO_8859_2 }, + { "ISO-8859-3", RTL_TEXTENCODING_ISO_8859_3 }, + { "ISO_8859-3:1988", RTL_TEXTENCODING_ISO_8859_3 }, + { "ISO-IR-109", RTL_TEXTENCODING_ISO_8859_3 }, + { "ISO_8859-3", RTL_TEXTENCODING_ISO_8859_3 }, + { "LATIN3", RTL_TEXTENCODING_ISO_8859_3 }, + { "L3", RTL_TEXTENCODING_ISO_8859_3 }, + { "CSISOLATIN3", RTL_TEXTENCODING_ISO_8859_3 }, + { "ISO-8859-4", RTL_TEXTENCODING_ISO_8859_4 }, + { "ISO_8859-4:1988", RTL_TEXTENCODING_ISO_8859_4 }, + { "ISO-IR-110", RTL_TEXTENCODING_ISO_8859_4 }, + { "ISO_8859-4", RTL_TEXTENCODING_ISO_8859_4 }, + { "LATIN4", RTL_TEXTENCODING_ISO_8859_4 }, + { "L4", RTL_TEXTENCODING_ISO_8859_4 }, + { "CSISOLATIN4", RTL_TEXTENCODING_ISO_8859_4 }, + { "ISO-8859-5", RTL_TEXTENCODING_ISO_8859_5 }, + { "ISO_8859-5:1988", RTL_TEXTENCODING_ISO_8859_5 }, + { "ISO-IR-144", RTL_TEXTENCODING_ISO_8859_5 }, + { "ISO_8859-5", RTL_TEXTENCODING_ISO_8859_5 }, + { "CYRILLIC", RTL_TEXTENCODING_ISO_8859_5 }, + { "CSISOLATINCYRILLIC", RTL_TEXTENCODING_ISO_8859_5 }, + { "ISO-8859-6", RTL_TEXTENCODING_ISO_8859_6 }, + { "ISO_8859-6:1987", RTL_TEXTENCODING_ISO_8859_6 }, + { "ISO-IR-127", RTL_TEXTENCODING_ISO_8859_6 }, + { "ISO_8859-6", RTL_TEXTENCODING_ISO_8859_6 }, + { "ECMA-114", RTL_TEXTENCODING_ISO_8859_6 }, + { "ASMO-708", RTL_TEXTENCODING_ISO_8859_6 }, + { "ARABIC", RTL_TEXTENCODING_ISO_8859_6 }, + { "CSISOLATINARABIC", RTL_TEXTENCODING_ISO_8859_6 }, + { "ISO-8859-7", RTL_TEXTENCODING_ISO_8859_7 }, + { "ISO_8859-7:1987", RTL_TEXTENCODING_ISO_8859_7 }, + { "ISO-IR-126", RTL_TEXTENCODING_ISO_8859_7 }, + { "ISO_8859-7", RTL_TEXTENCODING_ISO_8859_7 }, + { "ELOT_928", RTL_TEXTENCODING_ISO_8859_7 }, + { "ECMA-118", RTL_TEXTENCODING_ISO_8859_7 }, + { "GREEK", RTL_TEXTENCODING_ISO_8859_7 }, + { "GREEK8", RTL_TEXTENCODING_ISO_8859_7 }, + { "CSISOLATINGREEK", RTL_TEXTENCODING_ISO_8859_7 }, + { "ISO-8859-8", RTL_TEXTENCODING_ISO_8859_8 }, + { "ISO_8859-8:1988", RTL_TEXTENCODING_ISO_8859_8 }, + { "ISO-IR-138", RTL_TEXTENCODING_ISO_8859_8 }, + { "ISO_8859-8", RTL_TEXTENCODING_ISO_8859_8 }, + { "HEBREW", RTL_TEXTENCODING_ISO_8859_8 }, + { "CSISOLATINHEBREW", RTL_TEXTENCODING_ISO_8859_8 }, + { "ISO-8859-9", RTL_TEXTENCODING_ISO_8859_9 }, + { "ISO_8859-9:1989", RTL_TEXTENCODING_ISO_8859_9 }, + { "ISO-IR-148", RTL_TEXTENCODING_ISO_8859_9 }, + { "ISO_8859-9", RTL_TEXTENCODING_ISO_8859_9 }, + { "LATIN5", RTL_TEXTENCODING_ISO_8859_9 }, + { "L5", RTL_TEXTENCODING_ISO_8859_9 }, + { "CSISOLATIN5", RTL_TEXTENCODING_ISO_8859_9 }, + { "ISO-8859-14", RTL_TEXTENCODING_ISO_8859_14 }, // RFC 2047 + { "ISO_8859-15", RTL_TEXTENCODING_ISO_8859_15 }, + { "ISO-8859-15", RTL_TEXTENCODING_ISO_8859_15 }, // RFC 2047 + { "MACINTOSH", RTL_TEXTENCODING_APPLE_ROMAN }, + { "MAC", RTL_TEXTENCODING_APPLE_ROMAN }, + { "CSMACINTOSH", RTL_TEXTENCODING_APPLE_ROMAN }, + { "IBM437", RTL_TEXTENCODING_IBM_437 }, + { "CP437", RTL_TEXTENCODING_IBM_437 }, + { "437", RTL_TEXTENCODING_IBM_437 }, + { "CSPC8CODEPAGE437", RTL_TEXTENCODING_IBM_437 }, + { "IBM850", RTL_TEXTENCODING_IBM_850 }, + { "CP850", RTL_TEXTENCODING_IBM_850 }, + { "850", RTL_TEXTENCODING_IBM_850 }, + { "CSPC850MULTILINGUAL", RTL_TEXTENCODING_IBM_850 }, + { "IBM860", RTL_TEXTENCODING_IBM_860 }, + { "CP860", RTL_TEXTENCODING_IBM_860 }, + { "860", RTL_TEXTENCODING_IBM_860 }, + { "CSIBM860", RTL_TEXTENCODING_IBM_860 }, + { "IBM861", RTL_TEXTENCODING_IBM_861 }, + { "CP861", RTL_TEXTENCODING_IBM_861 }, + { "861", RTL_TEXTENCODING_IBM_861 }, + { "CP-IS", RTL_TEXTENCODING_IBM_861 }, + { "CSIBM861", RTL_TEXTENCODING_IBM_861 }, + { "IBM863", RTL_TEXTENCODING_IBM_863 }, + { "CP863", RTL_TEXTENCODING_IBM_863 }, + { "863", RTL_TEXTENCODING_IBM_863 }, + { "CSIBM863", RTL_TEXTENCODING_IBM_863 }, + { "IBM865", RTL_TEXTENCODING_IBM_865 }, + { "CP865", RTL_TEXTENCODING_IBM_865 }, + { "865", RTL_TEXTENCODING_IBM_865 }, + { "CSIBM865", RTL_TEXTENCODING_IBM_865 }, + { "IBM775", RTL_TEXTENCODING_IBM_775 }, + { "CP775", RTL_TEXTENCODING_IBM_775 }, + { "CSPC775BALTIC", RTL_TEXTENCODING_IBM_775 }, + { "IBM852", RTL_TEXTENCODING_IBM_852 }, + { "CP852", RTL_TEXTENCODING_IBM_852 }, + { "852", RTL_TEXTENCODING_IBM_852 }, + { "CSPCP852", RTL_TEXTENCODING_IBM_852 }, + { "IBM855", RTL_TEXTENCODING_IBM_855 }, + { "CP855", RTL_TEXTENCODING_IBM_855 }, + { "855", RTL_TEXTENCODING_IBM_855 }, + { "CSIBM855", RTL_TEXTENCODING_IBM_855 }, + { "IBM857", RTL_TEXTENCODING_IBM_857 }, + { "CP857", RTL_TEXTENCODING_IBM_857 }, + { "857", RTL_TEXTENCODING_IBM_857 }, + { "CSIBM857", RTL_TEXTENCODING_IBM_857 }, + { "IBM862", RTL_TEXTENCODING_IBM_862 }, + { "CP862", RTL_TEXTENCODING_IBM_862 }, + { "862", RTL_TEXTENCODING_IBM_862 }, + { "CSPC862LATINHEBREW", RTL_TEXTENCODING_IBM_862 }, + { "IBM864", RTL_TEXTENCODING_IBM_864 }, + { "CP864", RTL_TEXTENCODING_IBM_864 }, + { "CSIBM864", RTL_TEXTENCODING_IBM_864 }, + { "IBM866", RTL_TEXTENCODING_IBM_866 }, + { "CP866", RTL_TEXTENCODING_IBM_866 }, + { "866", RTL_TEXTENCODING_IBM_866 }, + { "CSIBM866", RTL_TEXTENCODING_IBM_866 }, + { "IBM869", RTL_TEXTENCODING_IBM_869 }, + { "CP869", RTL_TEXTENCODING_IBM_869 }, + { "869", RTL_TEXTENCODING_IBM_869 }, + { "CP-GR", RTL_TEXTENCODING_IBM_869 }, + { "CSIBM869", RTL_TEXTENCODING_IBM_869 }, + { "WINDOWS-1250", RTL_TEXTENCODING_MS_1250 }, + { "WINDOWS-1251", RTL_TEXTENCODING_MS_1251 }, + { "WINDOWS-1253", RTL_TEXTENCODING_MS_1253 }, + { "WINDOWS-1254", RTL_TEXTENCODING_MS_1254 }, + { "WINDOWS-1255", RTL_TEXTENCODING_MS_1255 }, + { "WINDOWS-1256", RTL_TEXTENCODING_MS_1256 }, + { "WINDOWS-1257", RTL_TEXTENCODING_MS_1257 }, + { "WINDOWS-1258", RTL_TEXTENCODING_MS_1258 }, + { "SHIFT_JIS", RTL_TEXTENCODING_SHIFT_JIS }, + { "MS_KANJI", RTL_TEXTENCODING_SHIFT_JIS }, + { "CSSHIFTJIS", RTL_TEXTENCODING_SHIFT_JIS }, + { "GB2312", RTL_TEXTENCODING_GB_2312 }, + { "CSGB2312", RTL_TEXTENCODING_GB_2312 }, + { "BIG5", RTL_TEXTENCODING_BIG5 }, + { "CSBIG5", RTL_TEXTENCODING_BIG5 }, + { "EUC-JP", RTL_TEXTENCODING_EUC_JP }, + { "EXTENDED_UNIX_CODE_PACKED_FORMAT_FOR_JAPANESE", + RTL_TEXTENCODING_EUC_JP }, + { "CSEUCPKDFMTJAPANESE", RTL_TEXTENCODING_EUC_JP }, + { "ISO-2022-JP", RTL_TEXTENCODING_ISO_2022_JP }, + { "CSISO2022JP", RTL_TEXTENCODING_ISO_2022_JP }, + { "ISO-2022-CN", RTL_TEXTENCODING_ISO_2022_CN }, + { "KOI8-R", RTL_TEXTENCODING_KOI8_R }, + { "CSKOI8R", RTL_TEXTENCODING_KOI8_R }, + { "UTF-7", RTL_TEXTENCODING_UTF7 }, + { "UTF-8", RTL_TEXTENCODING_UTF8 }, + { "ISO-8859-10", RTL_TEXTENCODING_ISO_8859_10 }, // RFC 2047 + { "ISO-8859-13", RTL_TEXTENCODING_ISO_8859_13 }, // RFC 2047 + { "EUC-KR", RTL_TEXTENCODING_EUC_KR }, + { "CSEUCKR", RTL_TEXTENCODING_EUC_KR }, + { "ISO-2022-KR", RTL_TEXTENCODING_ISO_2022_KR }, + { "CSISO2022KR", RTL_TEXTENCODING_ISO_2022_KR }, + { "ISO-10646-UCS-4", RTL_TEXTENCODING_UCS4 }, + { "CSUCS4", RTL_TEXTENCODING_UCS4 }, + { "ISO-10646-UCS-2", RTL_TEXTENCODING_UCS2 }, + { "CSUNICODE", RTL_TEXTENCODING_UCS2 } }; + +rtl_TextEncoding getCharsetEncoding(char const * pBegin, + char const * pEnd) +{ + for (const EncodingEntry& i : aEncodingMap) + if (equalIgnoreCase(pBegin, pEnd, i.m_aName)) + return i.m_eEncoding; + return RTL_TEXTENCODING_DONTKNOW; +} + +} + +// INetMIME + +// static +bool INetMIME::isAtomChar(sal_uInt32 nChar) +{ + static const bool aMap[128] + = { false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, true, false, true, true, true, true, true, // !"#$%&' + false, false, true, true, false, true, false, true, //()*+,-./ + true, true, true, true, true, true, true, true, //01234567 + true, true, false, false, false, true, false, true, //89:;<=>? + false, true, true, true, true, true, true, true, //@ABCDEFG + true, true, true, true, true, true, true, true, //HIJKLMNO + true, true, true, true, true, true, true, true, //PQRSTUVW + true, true, true, false, false, false, true, true, //XYZ[\]^_ + true, true, true, true, true, true, true, true, //`abcdefg + true, true, true, true, true, true, true, true, //hijklmno + true, true, true, true, true, true, true, true, //pqrstuvw + true, true, true, true, true, true, true, false //xyz{|}~ + }; + return rtl::isAscii(nChar) && aMap[nChar]; +} + +// static +bool INetMIME::isIMAPAtomChar(sal_uInt32 nChar) +{ + static const bool aMap[128] + = { false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, true, false, true, true, false, true, true, // !"#$%&' + false, false, false, true, true, true, true, true, //()*+,-./ + true, true, true, true, true, true, true, true, //01234567 + true, true, true, true, true, true, true, true, //89:;<=>? + true, true, true, true, true, true, true, true, //@ABCDEFG + true, true, true, true, true, true, true, true, //HIJKLMNO + true, true, true, true, true, true, true, true, //PQRSTUVW + true, true, true, true, false, true, true, true, //XYZ[\]^_ + true, true, true, true, true, true, true, true, //`abcdefg + true, true, true, true, true, true, true, true, //hijklmno + true, true, true, true, true, true, true, true, //pqrstuvw + true, true, true, false, true, true, true, false //xyz{|}~ + }; + return rtl::isAscii(nChar) && aMap[nChar]; +} + +// static +bool INetMIME::equalIgnoreCase(const sal_Unicode * pBegin1, + const sal_Unicode * pEnd1, + const char * pString2) +{ + DBG_ASSERT(pBegin1 && pBegin1 <= pEnd1 && pString2, + "INetMIME::equalIgnoreCase(): Bad sequences"); + + while (*pString2 != 0) + if (pBegin1 == pEnd1 + || (rtl::toAsciiUpperCase(*pBegin1++) + != rtl::toAsciiUpperCase( + static_cast<unsigned char>(*pString2++)))) + return false; + return pBegin1 == pEnd1; +} + +// static +bool INetMIME::scanUnsigned(const sal_Unicode *& rBegin, + const sal_Unicode * pEnd, bool bLeadingZeroes, + sal_uInt32 & rValue) +{ + sal_uInt64 nTheValue = 0; + const sal_Unicode * p = rBegin; + for ( ; p != pEnd; ++p) + { + int nWeight = getWeight(*p); + if (nWeight < 0) + break; + nTheValue = 10 * nTheValue + nWeight; + if (nTheValue > std::numeric_limits< sal_uInt32 >::max()) + return false; + } + if (nTheValue == 0 && (p == rBegin || (!bLeadingZeroes && p - rBegin != 1))) + return false; + rBegin = p; + rValue = sal_uInt32(nTheValue); + return true; +} + +// static +sal_Unicode const * INetMIME::scanContentType( + std::u16string_view rStr, OUString * pType, + OUString * pSubType, INetContentTypeParameterList * pParameters) +{ + sal_Unicode const * pBegin = rStr.data(); + sal_Unicode const * pEnd = pBegin + rStr.size(); + sal_Unicode const * p = skipLinearWhiteSpaceComment(pBegin, pEnd); + sal_Unicode const * pTypeBegin = p; + while (p != pEnd && isTokenChar(*p)) + { + ++p; + } + if (p == pTypeBegin) + return nullptr; + sal_Unicode const * pTypeEnd = p; + + p = skipLinearWhiteSpaceComment(p, pEnd); + if (p == pEnd || *p++ != '/') + return nullptr; + + p = skipLinearWhiteSpaceComment(p, pEnd); + sal_Unicode const * pSubTypeBegin = p; + while (p != pEnd && isTokenChar(*p)) + { + ++p; + } + if (p == pSubTypeBegin) + return nullptr; + sal_Unicode const * pSubTypeEnd = p; + + if (pType != nullptr) + { + *pType = OUString(pTypeBegin, pTypeEnd - pTypeBegin).toAsciiLowerCase(); + } + if (pSubType != nullptr) + { + *pSubType = OUString(pSubTypeBegin, pSubTypeEnd - pSubTypeBegin) + .toAsciiLowerCase(); + } + + return scanParameters(p, pEnd, pParameters); +} + +// static +OUString INetMIME::decodeHeaderFieldBody(const OString& rBody) +{ + // Due to a bug in INetCoreRFC822MessageStream::ConvertTo7Bit(), old + // versions of StarOffice send mails with header fields where encoded + // words can be preceded by '=', ',', '.', '"', or '(', and followed by + // '=', ',', '.', '"', ')', without any required white space in between. + // And there appear to exist some broken mailers that only encode single + // letters within words, like "Appel + // =?iso-8859-1?Q?=E0?=t=?iso-8859-1?Q?=E9?=moin", so it seems best to + // detect encoded words even when not properly surrounded by white space. + + // Non US-ASCII characters in rBody are treated as ISO-8859-1. + + // encoded-word = "=?" + // 1*(%x21 / %x23-27 / %x2A-2B / %x2D / %30-39 / %x41-5A / %x5E-7E) + // ["*" 1*8ALPHA *("-" 1*8ALPHA)] "?" + // ("B?" *(4base64) (4base64 / 3base64 "=" / 2base64 "==") + // / "Q?" 1*(%x21-3C / %x3E / %x40-7E / "=" 2HEXDIG)) + // "?=" + + // base64 = ALPHA / DIGIT / "+" / "/" + + const char * pBegin = rBody.getStr(); + const char * pEnd = pBegin + rBody.getLength(); + + OUStringBuffer sDecoded; + const char * pCopyBegin = pBegin; + + /* bool bStartEncodedWord = true; */ + const char * pWSPBegin = pBegin; + + for (const char * p = pBegin; p != pEnd;) + { + if (*p == '=' /* && bStartEncodedWord */) + { + const char * q = p + 1; + bool bEncodedWord = q != pEnd && *q++ == '?'; + + rtl_TextEncoding eCharsetEncoding = RTL_TEXTENCODING_DONTKNOW; + if (bEncodedWord) + { + const char * pCharsetBegin = q; + const char * pLanguageBegin = nullptr; + int nAlphaCount = 0; + for (bool bDone = false; !bDone;) + if (q == pEnd) + { + bEncodedWord = false; + bDone = true; + } + else + { + char cChar = *q++; + switch (cChar) + { + case '*': + pLanguageBegin = q - 1; + nAlphaCount = 0; + break; + + case '-': + if (pLanguageBegin != nullptr) + { + if (nAlphaCount == 0) + pLanguageBegin = nullptr; + else + nAlphaCount = 0; + } + break; + + case '?': + if (pCharsetBegin == q - 1) + bEncodedWord = false; + else + { + eCharsetEncoding + = getCharsetEncoding( + pCharsetBegin, + pLanguageBegin == nullptr + || nAlphaCount == 0 ? + q - 1 : pLanguageBegin); + bEncodedWord = isMIMECharsetEncoding( + eCharsetEncoding); + eCharsetEncoding + = translateFromMIME(eCharsetEncoding); + } + bDone = true; + break; + + default: + if (pLanguageBegin != nullptr + && (!rtl::isAsciiAlpha( + static_cast<unsigned char>(cChar)) + || ++nAlphaCount > 8)) + pLanguageBegin = nullptr; + break; + } + } + } + + bool bEncodingB = false; + if (bEncodedWord) + { + if (q == pEnd) + bEncodedWord = false; + else + { + switch (*q++) + { + case 'B': + case 'b': + bEncodingB = true; + break; + + case 'Q': + case 'q': + bEncodingB = false; + break; + + default: + bEncodedWord = false; + break; + } + } + } + + bEncodedWord = bEncodedWord && q != pEnd && *q++ == '?'; + + OStringBuffer sText; + if (bEncodedWord) + { + if (bEncodingB) + { + for (bool bDone = false; !bDone;) + { + if (pEnd - q < 4) + { + bEncodedWord = false; + bDone = true; + } + else + { + bool bFinal = false; + int nCount = 3; + sal_uInt32 nValue = 0; + for (int nShift = 18; nShift >= 0; nShift -= 6) + { + int nWeight = getBase64Weight(*q++); + if (nWeight == -2) + { + bEncodedWord = false; + bDone = true; + break; + } + if (nWeight == -1) + { + if (!bFinal) + { + if (nShift >= 12) + { + bEncodedWord = false; + bDone = true; + break; + } + bFinal = true; + nCount = nShift == 6 ? 1 : 2; + } + } + else + nValue |= nWeight << nShift; + } + if (bEncodedWord) + { + for (int nShift = 16; nCount-- > 0; nShift -= 8) + sText.append(char(nValue >> nShift & 0xFF)); + if (*q == '?') + { + ++q; + bDone = true; + } + if (bFinal && !bDone) + { + bEncodedWord = false; + bDone = true; + } + } + } + } + } + else + { + const char * pEncodedTextBegin = q; + const char * pEncodedTextCopyBegin = q; + for (bool bDone = false; !bDone;) + if (q == pEnd) + { + bEncodedWord = false; + bDone = true; + } + else + { + sal_uInt32 nChar = static_cast<unsigned char>(*q++); + switch (nChar) + { + case '=': + { + if (pEnd - q < 2) + { + bEncodedWord = false; + bDone = true; + break; + } + int nDigit1 = getHexWeight(q[0]); + int nDigit2 = getHexWeight(q[1]); + if (nDigit1 < 0 || nDigit2 < 0) + { + bEncodedWord = false; + bDone = true; + break; + } + sText.append( + rBody.subView( + (pEncodedTextCopyBegin - pBegin), + (q - 1 - pEncodedTextCopyBegin)) + + OStringChar(char(nDigit1 << 4 | nDigit2))); + q += 2; + pEncodedTextCopyBegin = q; + break; + } + + case '?': + if (q - pEncodedTextBegin > 1) + sText.append(rBody.subView( + (pEncodedTextCopyBegin - pBegin), + (q - 1 - pEncodedTextCopyBegin))); + else + bEncodedWord = false; + bDone = true; + break; + + case '_': + sText.append( + rBody.subView( + (pEncodedTextCopyBegin - pBegin), + (q - 1 - pEncodedTextCopyBegin)) + + OString::Concat(" ")); + pEncodedTextCopyBegin = q; + break; + + default: + if (!isVisible(nChar)) + { + bEncodedWord = false; + bDone = true; + } + break; + } + } + } + } + + bEncodedWord = bEncodedWord && q != pEnd && *q++ == '='; + + std::unique_ptr<sal_Unicode[]> pUnicodeBuffer; + sal_Size nUnicodeSize = 0; + if (bEncodedWord) + { + pUnicodeBuffer + = convertToUnicode(sText.getStr(), + sText.getStr() + sText.getLength(), + eCharsetEncoding, nUnicodeSize); + if (!pUnicodeBuffer) + bEncodedWord = false; + } + + if (bEncodedWord) + { + appendISO88591(sDecoded, pCopyBegin, pWSPBegin); + sDecoded.append( + pUnicodeBuffer.get(), + static_cast< sal_Int32 >(nUnicodeSize)); + pUnicodeBuffer.reset(); + p = q; + pCopyBegin = p; + + pWSPBegin = p; + while (p != pEnd && isWhiteSpace(*p)) + ++p; + /* bStartEncodedWord = p != pWSPBegin; */ + continue; + } + } + + if (p == pEnd) + break; + + switch (*p++) + { + case '"': + /* bStartEncodedWord = true; */ + break; + + case '(': + /* bStartEncodedWord = true; */ + break; + + case ')': + /* bStartEncodedWord = false; */ + break; + + default: + { + const char * pUTF8Begin = p - 1; + const char * pUTF8End = pUTF8Begin; + sal_uInt32 nCharacter = 0; + if (translateUTF8Char(pUTF8End, pEnd, nCharacter)) + { + appendISO88591(sDecoded, pCopyBegin, p - 1); + sDecoded.appendUtf32(nCharacter); + p = pUTF8End; + pCopyBegin = p; + } + /* bStartEncodedWord = false; */ + break; + } + } + pWSPBegin = p; + } + + appendISO88591(sDecoded, pCopyBegin, pEnd); + return sDecoded.makeStringAndClear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/inet/inetmsg.cxx b/tools/source/inet/inetmsg.cxx new file mode 100644 index 0000000000..a5c6838176 --- /dev/null +++ b/tools/source/inet/inetmsg.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/types.h> +#include <tools/datetime.hxx> +#include <tools/inetmsg.hxx> +#include <comphelper/string.hxx> +#include <rtl/character.hxx> +#include <o3tl/safeint.hxx> +#include <o3tl/sprintf.hxx> +#include <o3tl/string_view.hxx> + +#include <map> + +void INetMIMEMessage::SetHeaderField_Impl ( + const OString &rName, + const OUString &rValue, + sal_uInt32 &rnIndex) +{ + SetHeaderField_Impl ( + INetMessageHeader (rName, rValue.toUtf8()), rnIndex); +} + +/* ParseDateField and local helper functions. + * + * Parses a String in (implied) GMT format into class Date and tools::Time objects. + * Four formats are accepted: + * + * [Wkd,] 1*2DIGIT Mon 2*4DIGIT 00:00:00 [GMT] (rfc1123) + * [Wkd,] 00 Mon 0000 00:00:00 [GMT]) (rfc822, rfc1123) + * Weekday, 00-Mon-00 00:00:00 [GMT] (rfc850, rfc1036) + * Wkd Mon 00 00:00:00 0000 [GMT] (ctime) + * 1*DIGIT (delta seconds) + */ + +static const char *months[12] = +{ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +static sal_uInt16 ParseNumber(std::string_view rStr, size_t& nIndex) +{ + size_t n = nIndex; + while ((n < rStr.size()) + && rtl::isAsciiDigit(static_cast<unsigned char>(rStr[n]))) + n++; + + std::string_view aNum(rStr.substr(nIndex, (n - nIndex))); + nIndex = n; + + return static_cast<sal_uInt16>(o3tl::toInt32(aNum)); +} + +static sal_uInt16 ParseMonth(std::string_view rStr, size_t& nIndex) +{ + size_t n = nIndex; + while ((n < rStr.size()) + && rtl::isAsciiAlpha(static_cast<unsigned char>(rStr[n]))) + n++; + + std::string_view aMonth(rStr.substr(nIndex, 3)); + nIndex = n; + + sal_uInt16 i; + for (i = 0; i < 12; i++) + if (o3tl::equalsIgnoreAsciiCase(aMonth, months[i])) break; + return (i + 1); +} + +bool INetMIMEMessage::ParseDateField ( + std::u16string_view rDateFieldW, DateTime& rDateTime) +{ + OString aDateField(OUStringToOString(rDateFieldW, + RTL_TEXTENCODING_ASCII_US)); + + if (aDateField.isEmpty()) return false; + + if (aDateField.indexOf(':') != -1) + { + // Some DateTime format. + size_t nIndex = 0; + + // Skip over <Wkd> or <Weekday>, leading and trailing space. + while ((nIndex < o3tl::make_unsigned(aDateField.getLength())) && + (aDateField[nIndex] == ' ')) + nIndex++; + + while ( + (nIndex < o3tl::make_unsigned(aDateField.getLength())) && + (rtl::isAsciiAlpha (static_cast<unsigned char>(aDateField[nIndex])) || + (aDateField[nIndex] == ',') )) + nIndex++; + + while ((nIndex < o3tl::make_unsigned(aDateField.getLength())) && + (aDateField[nIndex] == ' ')) + nIndex++; + + if (rtl::isAsciiAlpha (static_cast<unsigned char>(aDateField[nIndex]))) + { + // Format: ctime(). + if ((aDateField.getLength() - nIndex) < 20) return false; + + rDateTime.SetMonth (ParseMonth (aDateField, nIndex)); nIndex++; + rDateTime.SetDay (ParseNumber (aDateField, nIndex)); nIndex++; + + rDateTime.SetHour (ParseNumber (aDateField, nIndex)); nIndex++; + rDateTime.SetMin (ParseNumber (aDateField, nIndex)); nIndex++; + rDateTime.SetSec (ParseNumber (aDateField, nIndex)); nIndex++; + rDateTime.SetNanoSec (0); + + sal_uInt16 nYear = ParseNumber (aDateField, nIndex); + if (nYear < 100) nYear += 1900; + rDateTime.SetYear (nYear); + } + else + { + // Format: RFC1036 or RFC1123. + if ((aDateField.getLength() - nIndex) < 17) return false; + + rDateTime.SetDay (ParseNumber (aDateField, nIndex)); nIndex++; + rDateTime.SetMonth (ParseMonth (aDateField, nIndex)); nIndex++; + + sal_uInt16 nYear = ParseNumber (aDateField, nIndex); nIndex++; + if (nYear < 100) nYear += 1900; + rDateTime.SetYear (nYear); + + rDateTime.SetHour (ParseNumber (aDateField, nIndex)); nIndex++; + rDateTime.SetMin (ParseNumber (aDateField, nIndex)); nIndex++; + rDateTime.SetSec (ParseNumber (aDateField, nIndex)); nIndex++; + rDateTime.SetNanoSec (0); + + const char cPossiblePlusMinus = nIndex < o3tl::make_unsigned(aDateField.getLength()) ? aDateField[nIndex] : 0; + if (cPossiblePlusMinus == '+' || cPossiblePlusMinus == '-') + { + // Offset from GMT: "(+|-)HHMM". + bool bEast = (aDateField[nIndex++] == '+'); + sal_uInt16 nOffset = ParseNumber (aDateField, nIndex); + if (nOffset > 0) + { + tools::Time aDiff( tools::Time::EMPTY ); + aDiff.SetHour (nOffset / 100); + aDiff.SetMin (nOffset % 100); + aDiff.SetSec (0); + aDiff.SetNanoSec (0); + + if (bEast) + rDateTime -= aDiff; + else + rDateTime += aDiff; + } + } + } + } + else if (comphelper::string::isdigitAsciiString(aDateField)) + { + // Format: delta seconds. + tools::Time aDelta (0); + aDelta.SetTime (aDateField.toInt32() * 100); + + DateTime aNow( DateTime::SYSTEM ); + aNow += aDelta; + aNow.ConvertToUTC(); + + rDateTime.SetDate (aNow.GetDate()); + rDateTime.SetTime (aNow.GetTime()); + } + else + { + // Junk. + return false; + } + + return (rDateTime.IsValidAndGregorian() && + !((rDateTime.GetSec() > 59) || + (rDateTime.GetMin() > 59) || + (rDateTime.GetHour() > 23) )); +} + +const std::map<InetMessageMime, const char*> ImplINetMIMEMessageHeaderData = +{ + { InetMessageMime::VERSION, "MIME-Version"}, + { InetMessageMime::CONTENT_DISPOSITION, "Content-Disposition"}, + { InetMessageMime::CONTENT_TYPE, "Content-Type"}, + { InetMessageMime::CONTENT_TRANSFER_ENCODING, "Content-Transfer-Encoding"} +}; + +INetMIMEMessage::INetMIMEMessage() + : pParent(nullptr) +{ + for (sal_uInt16 i = 0; i < static_cast<int>(InetMessageMime::NUMHDR); i++) + m_nMIMEIndex[static_cast<InetMessageMime>(i)] = SAL_MAX_UINT32; +} + +INetMIMEMessage::~INetMIMEMessage() +{ +} + +void INetMIMEMessage::SetMIMEVersion (const OUString& rVersion) +{ + SetHeaderField_Impl ( + ImplINetMIMEMessageHeaderData.at(InetMessageMime::VERSION), rVersion, + m_nMIMEIndex[InetMessageMime::VERSION]); +} + +void INetMIMEMessage::SetContentDisposition (const OUString& rDisposition) +{ + SetHeaderField_Impl ( + ImplINetMIMEMessageHeaderData.at(InetMessageMime::CONTENT_DISPOSITION), rDisposition, + m_nMIMEIndex[InetMessageMime::CONTENT_DISPOSITION]); +} + +void INetMIMEMessage::SetContentType (const OUString& rType) +{ + SetHeaderField_Impl ( + ImplINetMIMEMessageHeaderData.at(InetMessageMime::CONTENT_TYPE), rType, + m_nMIMEIndex[InetMessageMime::CONTENT_TYPE]); +} + +void INetMIMEMessage::SetContentTransferEncoding ( + const OUString& rEncoding) +{ + SetHeaderField_Impl ( + ImplINetMIMEMessageHeaderData.at(InetMessageMime::CONTENT_TRANSFER_ENCODING), rEncoding, + m_nMIMEIndex[InetMessageMime::CONTENT_TRANSFER_ENCODING]); +} + +OUString INetMIMEMessage::GetDefaultContentType() +{ + if (pParent != nullptr) + { + OUString aParentCT (pParent->GetContentType()); + if (aParentCT.isEmpty()) + aParentCT = pParent->GetDefaultContentType(); + + if (aParentCT.equalsIgnoreAsciiCase("multipart/digest")) + return "message/rfc822"; + } + return "text/plain; charset=us-ascii"; +} + +void INetMIMEMessage::EnableAttachMultipartFormDataChild() +{ + // Check context. + if (IsContainer()) + return; + + // Generate a unique boundary from current time. + char sTail[16 + 1]; + tools::Time aCurTime( tools::Time::SYSTEM ); + sal_uInt64 nThis = reinterpret_cast< sal_uIntPtr >( this ); // we can be on a 64bit architecture + nThis = ( ( nThis >> 32 ) ^ nThis ) & SAL_MAX_UINT32; + o3tl::sprintf (sTail, "%08X%08X", + static_cast< unsigned int >(aCurTime.GetTime()), + static_cast< unsigned int >(nThis)); + m_aBoundary = "------------_4D48"_ostr; + m_aBoundary += sTail; + + // Set header fields. + SetMIMEVersion("1.0"); + SetContentType( + "multipart/form-data; boundary=" + OUString::fromUtf8(m_aBoundary)); + SetContentTransferEncoding("7bit"); +} + +void INetMIMEMessage::AttachChild(std::unique_ptr<INetMIMEMessage> pChildMsg) +{ + assert(IsContainer()); + if (IsContainer()) + { + pChildMsg->pParent = this; + aChildren.push_back( std::move(pChildMsg) ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/inet/inetstrm.cxx b/tools/source/inet/inetstrm.cxx new file mode 100644 index 0000000000..ac33f5eda4 --- /dev/null +++ b/tools/source/inet/inetstrm.cxx @@ -0,0 +1,296 @@ +/* -*- 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 <sal/types.h> +#include <tools/inetmsg.hxx> +#include <tools/inetstrm.hxx> + +int INetMIMEMessageStream::GetHeaderLine(char* pData, sal_uInt32 nSize) +{ + char* pWBuf = pData; + + sal_uInt32 i, n; + + if (maMsgBuffer.Tell() == 0) + { + // Insert formatted header into buffer. + n = pSourceMsg->GetHeaderCount(); + for (i = 0; i < n; i++) + { + INetMessageHeader aHeader (pSourceMsg->GetHeaderField(i)); + if (aHeader.GetValue().getLength()) + { + // NYI: Folding long lines. + maMsgBuffer.WriteOString( aHeader.GetName() ); + maMsgBuffer.WriteOString( ": " ); + maMsgBuffer.WriteOString( aHeader.GetValue() ); + maMsgBuffer.WriteOString( "\r\n" ); + } + } + + pMsgWrite = const_cast<char *>(static_cast<char const *>(maMsgBuffer.GetData())); + pMsgRead = pMsgWrite + maMsgBuffer.Tell(); + } + + n = pMsgRead - pMsgWrite; + if (n > 0) + { + // Move to caller. + if (nSize < n) n = nSize; + for (i = 0; i < n; i++) *pWBuf++ = *pMsgWrite++; + } + else + { + // Reset buffer. + maMsgBuffer.Seek(STREAM_SEEK_TO_BEGIN); + } + + return (pWBuf - pData); +} + +int INetMIMEMessageStream::GetBodyLine(char* pData, sal_uInt32 nSize) +{ + char* pWBuf = pData; + char* pWEnd = pData + nSize; + + if (pSourceMsg->GetDocumentLB()) + { + if (pMsgStrm == nullptr) + pMsgStrm.reset(new SvStream (pSourceMsg->GetDocumentLB())); + + sal_uInt32 nRead = pMsgStrm->ReadBytes(pWBuf, (pWEnd - pWBuf)); + pWBuf += nRead; + } + + return (pWBuf - pData); +} + +int INetMIMEMessageStream::GetMsgLine(char* pData, sal_uInt32 nSize) +{ + // Check for header or body. + if (!bHeaderGenerated) + { + if (!done) + { + // Prepare special header fields. + if (pSourceMsg->GetParent()) + { + OUString aPCT(pSourceMsg->GetParent()->GetContentType()); + if (aPCT.startsWithIgnoreAsciiCase("message/rfc822")) + pSourceMsg->SetMIMEVersion("1.0"); + else + pSourceMsg->SetMIMEVersion(OUString()); + } + else + { + pSourceMsg->SetMIMEVersion("1.0"); + } + + // Check ContentType. + OUString aContentType(pSourceMsg->GetContentType()); + if (!aContentType.isEmpty()) + { + // Determine default Content-Type. + OUString aDefaultType = pSourceMsg->GetDefaultContentType(); + + if (aDefaultType.equalsIgnoreAsciiCase(aContentType)) + { + // No need to specify default. + pSourceMsg->SetContentType(OUString()); + } + } + + // No need to specify default. + pSourceMsg->SetContentTransferEncoding(OUString()); + + // Mark we're done. + done = true; + } + + // Generate the message header. + int nRead = GetHeaderLine(pData, nSize); + if (nRead <= 0) + { + // Reset state. + done = false; + } + return nRead; + } + else + { + // Generate the message body. + if (pSourceMsg->IsContainer()) + { + // Encapsulated message body. + while (!done) + { + if (pChildStrm == nullptr) + { + INetMIMEMessage *pChild = pSourceMsg->GetChild(nChildIndex); + if (pChild) + { + // Increment child index. + nChildIndex++; + + // Create child stream. + pChildStrm.reset(new INetMIMEMessageStream(pChild, false)); + + if (pSourceMsg->IsMultipart()) + { + // Insert multipart delimiter. + OString aDelim = "--" + + pSourceMsg->GetMultipartBoundary() + + "\r\n"; + + memcpy(pData, aDelim.getStr(), + aDelim.getLength()); + return aDelim.getLength(); + } + } + else + { + // No more parts. Mark we're done. + done = true; + nChildIndex = 0; + + if (pSourceMsg->IsMultipart()) + { + // Insert close delimiter. + OString aDelim = "--" + + pSourceMsg->GetMultipartBoundary() + + "--\r\n"; + + memcpy(pData, aDelim.getStr(), + aDelim.getLength()); + return aDelim.getLength(); + } + } + } + else + { + // Read current child stream. + int nRead = pChildStrm->Read(pData, nSize); + if (nRead > 0) + { + return nRead; + } + else + { + // Cleanup exhausted child stream. + pChildStrm.reset(); + } + } + } + return 0; + } + else + { + // Single part message body. + if (pSourceMsg->GetDocumentLB() == nullptr) + { + // Empty message body. + return 0; + } + + // No Encoding. + return GetBodyLine(pData, nSize); + } + } +} + +namespace +{ + +const int BUFFER_SIZE = 2048; + +} + +INetMIMEMessageStream::INetMIMEMessageStream( + INetMIMEMessage *pMsg, bool headerGenerated): + pSourceMsg(pMsg), + bHeaderGenerated(headerGenerated), + mvBuffer(BUFFER_SIZE), + pMsgRead(nullptr), + pMsgWrite(nullptr), + done(false), + nChildIndex(0) +{ + assert(pMsg != nullptr); + maMsgBuffer.SetStreamCharSet(RTL_TEXTENCODING_ASCII_US); + pRead = pWrite = mvBuffer.data(); +} + +INetMIMEMessageStream::~INetMIMEMessageStream() +{ + pChildStrm.reset(); +} + +int INetMIMEMessageStream::Read(char* pData, sal_uInt32 nSize) +{ + char* pWBuf = pData; + char* pWEnd = pData + nSize; + + while (pWBuf < pWEnd) + { + // Caller's buffer not yet filled. + sal_uInt32 n = pRead - pWrite; + if (n > 0) + { + // Bytes still in buffer. + sal_uInt32 m = pWEnd - pWBuf; + if (m < n) n = m; + for (sal_uInt32 i = 0; i < n; i++) *pWBuf++ = *pWrite++; + } + else + { + // Buffer empty. Reset to <Begin-of-Buffer>. + pRead = pWrite = mvBuffer.data(); + + // Read next message line. + int nRead = GetMsgLine(mvBuffer.data(), mvBuffer.size()); + if (nRead > 0) + { + // Set read pointer. + pRead = mvBuffer.data() + nRead; + } + else + { + if (!bHeaderGenerated) + { + // Header generated. Insert empty line. + bHeaderGenerated = true; + *pRead++ = '\r'; + *pRead++ = '\n'; + } + else + { + // Body generated. + return (pWBuf - pData); + } + } + } + } + return (pWBuf - pData); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/memtools/multisel.cxx b/tools/source/memtools/multisel.cxx new file mode 100644 index 0000000000..739d287487 --- /dev/null +++ b/tools/source/memtools/multisel.cxx @@ -0,0 +1,744 @@ +/* -*- 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 <cstddef> + +#include <o3tl/string_view.hxx> +#include <tools/debug.hxx> +#include <tools/multisel.hxx> + +#include <rtl/ustrbuf.hxx> + +void MultiSelection::ImplClear() +{ + // no selected indexes + nSelCount = 0; + aSels.clear(); +} + +std::size_t MultiSelection::ImplFindSubSelection( sal_Int32 nIndex ) const +{ + // iterate through the sub selections + std::size_t n = 0; + for ( ; + n < aSels.size() && nIndex > aSels[ n ].Max(); + ++n ) {} /* empty loop */ + return n; +} + +void MultiSelection::ImplMergeSubSelections( sal_Int32 nPos1, std::size_t nPos2 ) +{ + // didn't a sub selection at nPos2 exist? + if ( nPos2 >= aSels.size() ) + return; + + // did the sub selections touch each other? + if ( (aSels[ nPos1 ].Max() + 1) == aSels[ nPos2 ].Min() ) + { + // merge them + aSels[ nPos1 ].Max() = aSels[ nPos2 ].Max(); + aSels.erase( aSels.begin() + nPos2 ); + } +} + +MultiSelection::MultiSelection(): + aTotRange( 0, -1 ), + nCurSubSel(0), + nCurIndex(0), + nSelCount(0), + bCurValid(false) +{ +} + +void MultiSelection::Reset() +{ + aTotRange = Range(0, -1); + bCurValid = false; + // clear the old sub selections + ImplClear(); +} + +MultiSelection::MultiSelection( const MultiSelection& rOrig ) : + aTotRange(rOrig.aTotRange), + nSelCount(rOrig.nSelCount), + bCurValid(rOrig.bCurValid) +{ + if ( bCurValid ) + { + nCurSubSel = rOrig.nCurSubSel; + nCurIndex = rOrig.nCurIndex; + } + else + { + nCurSubSel = 0; + nCurIndex = 0; + } + + // copy the sub selections + aSels.insert( aSels.end(), rOrig.aSels.begin(), rOrig.aSels.end() ); +} + +MultiSelection::MultiSelection( const Range& rRange ): + aTotRange(rRange), + nCurSubSel(0), + nCurIndex(0), + nSelCount(0), + bCurValid(false) +{ +} + +MultiSelection::~MultiSelection() +{ +} + +MultiSelection& MultiSelection::operator= ( const MultiSelection& rOrig ) +{ + aTotRange = rOrig.aTotRange; + bCurValid = rOrig.bCurValid; + if ( bCurValid ) + { + nCurSubSel = rOrig.nCurSubSel; + nCurIndex = rOrig.nCurIndex; + } + + // clear the old and copy the sub selections + ImplClear(); + aSels.insert( aSels.end(), rOrig.aSels.begin(), rOrig.aSels.end() ); + nSelCount = rOrig.nSelCount; + + return *this; +} + +void MultiSelection::SelectAll( bool bSelect ) +{ + ImplClear(); + if ( bSelect ) + { + aSels.push_back( aTotRange ); + nSelCount = aTotRange.Len(); + } +} + +bool MultiSelection::Select( sal_Int32 nIndex, bool bSelect ) +{ + DBG_ASSERT( aTotRange.Contains(nIndex), "selected index out of range" ); + + // out of range? + if ( !aTotRange.Contains(nIndex) ) + return false; + + // find the virtual target position + std::size_t nSubSelPos = ImplFindSubSelection( nIndex ); + + if ( bSelect ) + { + // is it included in the found sub selection? + if ( nSubSelPos < aSels.size() && aSels[ nSubSelPos ].Contains( nIndex ) ) + // already selected, nothing to do + return false; + + // it will become selected + ++nSelCount; + + // is it at the end of the previous sub selection + if ( nSubSelPos > 0 && + aSels[ nSubSelPos-1 ].Max() == (nIndex-1) ) + { + // expand the previous sub selection + aSels[ nSubSelPos-1 ].Max() = nIndex; + + // try to merge the previous sub selection + ImplMergeSubSelections( nSubSelPos-1, nSubSelPos ); + } + // is it at the beginning of the found sub selection + else if ( nSubSelPos < aSels.size() + && aSels[ nSubSelPos ].Min() == (nIndex+1) + ) + // expand the found sub selection + aSels[ nSubSelPos ].Min() = nIndex; + else + { + // create a new sub selection + if ( nSubSelPos < aSels.size() ) { + aSels.insert( aSels.begin() + nSubSelPos, Range( nIndex, nIndex ) ); + } else { + aSels.push_back( Range( nIndex, nIndex ) ); + } + if ( bCurValid && nCurSubSel >= nSubSelPos ) + ++nCurSubSel; + } + } + else + { + // is it excluded from the found sub selection? + if ( nSubSelPos >= aSels.size() + || !aSels[ nSubSelPos ].Contains( nIndex ) + ) { + // not selected, nothing to do + return false; + } + + // it will become deselected + --nSelCount; + + // is it the only index in the found sub selection? + if ( aSels[ nSubSelPos ].Len() == 1 ) + { + // remove the complete sub selection + aSels.erase( aSels.begin() + nSubSelPos ); + return true; + } + + // is it at the beginning of the found sub selection? + if ( aSels[ nSubSelPos ].Min() == nIndex ) + ++aSels[ nSubSelPos ].Min(); + // is it at the end of the found sub selection? + else if ( aSels[ nSubSelPos ].Max() == nIndex ) + --aSels[ nSubSelPos ].Max(); + // it is in the middle of the found sub selection? + else + { + // split the sub selection + if ( nSubSelPos < aSels.size() ) { + aSels.insert( aSels.begin() + nSubSelPos, Range( aSels[ nSubSelPos ].Min(), nIndex-1 ) ); + } else { + aSels.push_back( Range( aSels[ nSubSelPos ].Min(), nIndex-1 ) ); + } + aSels[ nSubSelPos+1 ].Min() = nIndex + 1; + } + } + + return true; +} + +void MultiSelection::Select( const Range& rIndexRange, bool bSelect ) +{ + sal_Int32 nOld; + + sal_Int32 nTmpMin = rIndexRange.Min(); + sal_Int32 nTmpMax = rIndexRange.Max(); + sal_Int32 nCurMin = FirstSelected(); + sal_Int32 nCurMax = LastSelected(); + DBG_ASSERT(aTotRange.Contains(nTmpMax), "selected index out of range" ); + DBG_ASSERT(aTotRange.Contains(nTmpMin), "selected index out of range" ); + + // replace whole selection? + if( aSels.empty() || (nTmpMin <= nCurMin && nTmpMax >= nCurMax ) ) + { + ImplClear(); + if ( bSelect ) + { + aSels.push_back( rIndexRange ); + nSelCount = rIndexRange.Len(); + } + return; + } + // expand on left side? + if( nTmpMax < nCurMin ) + { + if( bSelect ) + { + // extend first range? + if( nCurMin > (nTmpMax+1) ) + { + aSels.insert( aSels.begin(), rIndexRange ); + nSelCount += rIndexRange.Len(); + } + else + { + auto & rRange = aSels.front(); + nOld = rRange.Min(); + rRange.Min() = nTmpMin; + nSelCount += ( nOld - nTmpMin ); + } + bCurValid = false; + } + return; + } + // expand on right side? + else if( nTmpMin > nCurMax ) + { + if( bSelect ) + { + // extend last range? + if( nTmpMin > (nCurMax+1) ) + { + aSels.push_back( rIndexRange ); + nSelCount += rIndexRange.Len(); + } + else + { + auto & rRange = aSels.back(); + nOld = rRange.Max(); + rRange.Max() = nTmpMax; + nSelCount += ( nTmpMax - nOld ); + } + bCurValid = false; + } + return; + } + + // TODO here is potential for optimization + while( nTmpMin <= nTmpMax ) + { + Select( nTmpMin, bSelect ); + nTmpMin++; + } +} + +bool MultiSelection::IsSelected( sal_Int32 nIndex ) const +{ + // find the virtual target position + std::size_t nSubSelPos = ImplFindSubSelection( nIndex ); + + return nSubSelPos < aSels.size() && aSels[ nSubSelPos ].Contains(nIndex); +} + +void MultiSelection::Insert( sal_Int32 nIndex, sal_Int32 nCount ) +{ + // find the virtual target position + std::size_t nSubSelPos = ImplFindSubSelection( nIndex ); + + // did we need to shift the sub selections? + if ( nSubSelPos < aSels.size() ) + { // did we insert an unselected into an existing sub selection? + if ( aSels[ nSubSelPos ].Min() != nIndex + && aSels[ nSubSelPos ].Contains(nIndex) + ) { // split the sub selection + if ( nSubSelPos < aSels.size() ) { + aSels.insert( aSels.begin() + nSubSelPos, Range( aSels[ nSubSelPos ].Min(), nIndex-1 ) ); + } else { + aSels.push_back( Range( aSels[ nSubSelPos ].Min(), nIndex-1 ) ); + } + ++nSubSelPos; + aSels[ nSubSelPos ].Min() = nIndex; + } + + // shift the sub selections behind the inserting position + for ( std::size_t nPos = nSubSelPos; nPos < aSels.size(); ++nPos ) + { + aSels[ nPos ].Min() += nCount; + aSels[ nPos ].Max() += nCount; + } + } + + bCurValid = false; + aTotRange.Max() += nCount; +} + +void MultiSelection::Remove( sal_Int32 nIndex ) +{ + // find the virtual target position + std::size_t nSubSelPos = ImplFindSubSelection( nIndex ); + + // did we remove from an existing sub selection? + if ( nSubSelPos < aSels.size() + && aSels[ nSubSelPos ].Contains(nIndex) + ) { + // does this sub selection only contain the index to be deleted + if ( aSels[ nSubSelPos ].Len() == 1 ) { + // completely remove the sub selection + aSels.erase( aSels.begin() + nSubSelPos ); + } else { + // shorten this sub selection + --( aSels[ nSubSelPos++ ].Max() ); + } + + // adjust the selected counter + --nSelCount; + } + + // shift the sub selections behind the removed index + for ( std::size_t nPos = nSubSelPos; nPos < aSels.size(); ++nPos ) + { + --( aSels[ nPos ].Min() ); + --( aSels[ nPos ].Max() ); + } + + bCurValid = false; + aTotRange.Max() -= 1; +} + +sal_Int32 MultiSelection::FirstSelected() +{ + nCurSubSel = 0; + + bCurValid = !aSels.empty(); + if ( !bCurValid ) + return SFX_ENDOFSELECTION; + + nCurIndex = aSels[ 0 ].Min(); + return nCurIndex; +} + +sal_Int32 MultiSelection::LastSelected() +{ + bCurValid = !aSels.empty(); + + if ( !bCurValid ) + return SFX_ENDOFSELECTION; + + nCurSubSel = aSels.size() - 1; + nCurIndex = aSels[ nCurSubSel ].Max(); + return nCurIndex; +} + +sal_Int32 MultiSelection::NextSelected() +{ + if ( !bCurValid ) + return SFX_ENDOFSELECTION; + + // is the next index in the current sub selection too? + if ( nCurIndex < aSels[ nCurSubSel ].Max() ) + return ++nCurIndex; + + // are there further sub selections? + if ( ++nCurSubSel >= aSels.size() ) + // we are at the end! + return SFX_ENDOFSELECTION; + + nCurIndex = aSels[ nCurSubSel ].Min(); + return nCurIndex; +} + +void MultiSelection::SetTotalRange( const Range& rTotRange ) +{ + aTotRange = rTotRange; + + // adjust lower boundary + Range* pRange = aSels.empty() ? nullptr : &aSels.front(); + while( pRange ) + { + if( pRange->Max() < aTotRange.Min() ) + { + aSels.erase( aSels.begin() ); + } + else if( pRange->Min() < aTotRange.Min() ) + { + pRange->Min() = aTotRange.Min(); + break; + } + else + break; + + pRange = aSels.empty() ? nullptr : &aSels.front(); + } + + // adjust upper boundary + sal_Int32 nCount = aSels.size(); + while( nCount ) + { + pRange = &aSels[ nCount - 1 ]; + if( pRange->Min() > aTotRange.Max() ) + { + aSels.pop_back(); + } + else if( pRange->Max() > aTotRange.Max() ) + { + pRange->Max() = aTotRange.Max(); + break; + } + else + break; + + nCount = aSels.size(); + } + + // re-calculate selection count + nSelCount = 0; + for (Range const & rSel : aSels) + nSelCount += rSel.Len(); + + bCurValid = false; + nCurIndex = 0; +} + +// StringRangeEnumerator + +StringRangeEnumerator::StringRangeEnumerator( std::u16string_view i_rInput, + sal_Int32 i_nMinNumber, + sal_Int32 i_nMaxNumber, + sal_Int32 i_nLogicalOffset + ) + : mnCount( 0 ) + , mnMin( i_nMinNumber ) + , mnMax( i_nMaxNumber ) + , mnOffset( i_nLogicalOffset ) + , mbValidInput( false ) +{ + // Parse string only if boundaries are valid. + if( mnMin >= 0 && mnMax >= 0 && mnMin <= mnMax ) + mbValidInput = setRange( i_rInput ); +} + +bool StringRangeEnumerator::checkValue( sal_Int32 i_nValue, const o3tl::sorted_vector< sal_Int32 >* i_pPossibleValues ) const +{ + if( i_nValue < 0 || i_nValue < mnMin || i_nValue > mnMax ) + return false; + if( i_pPossibleValues && i_pPossibleValues->find( i_nValue ) == i_pPossibleValues->end() ) + return false; + return true; +} + +bool StringRangeEnumerator::insertRange( sal_Int32 i_nFirst, sal_Int32 i_nLast, bool bSequence ) +{ + bool bSuccess = true; + if( bSequence ) + { + // Check if the range is completely outside of possible pages range + if ((i_nFirst < mnMin && i_nLast < mnMin) || + (i_nFirst > mnMax && i_nLast > mnMax)) + return false; + if( i_nFirst < mnMin ) + i_nFirst = mnMin; + if( i_nFirst > mnMax ) + i_nFirst = mnMax; + if( i_nLast < mnMin ) + i_nLast = mnMin; + if( i_nLast > mnMax ) + i_nLast = mnMax; + if( checkValue( i_nFirst ) && checkValue( i_nLast ) ) + { + maSequence.push_back( Range( i_nFirst, i_nLast ) ); + sal_Int32 nNumber = i_nLast - i_nFirst; + nNumber = nNumber < 0 ? -nNumber : nNumber; + mnCount += nNumber + 1; + } + else + bSuccess = false; + } + else + { + if( checkValue( i_nFirst ) ) + { + maSequence.push_back( Range( i_nFirst, i_nFirst ) ); + mnCount++; + } + else if( checkValue( i_nLast ) ) + { + maSequence.push_back( Range( i_nLast, i_nLast ) ); + mnCount++; + } + else + bSuccess = false; + } + + return bSuccess; +} + +void StringRangeEnumerator::insertJoinedRanges( + const std::vector< sal_Int32 >& rNumbers ) +{ + size_t nCount = rNumbers.size(); + if( nCount == 0 ) + return; + + if( nCount == 1 ) + { + insertRange( rNumbers[0], -1, false ); + return; + } + + for( size_t i = 0; i < nCount - 1; i++ ) + { + sal_Int32 nFirst = rNumbers[i]; + sal_Int32 nLast = rNumbers[i + 1]; + if( i > 0 ) + { + if ( nFirst > nLast ) nFirst--; + else if( nFirst < nLast ) nFirst++; + } + + insertRange( nFirst, nLast, nFirst != nLast ); + } +} + +bool StringRangeEnumerator::setRange( std::u16string_view aNewRange ) +{ + mnCount = 0; + maSequence.clear(); + + auto pInput = aNewRange.begin(); + auto pInputEnd = aNewRange.end(); + OUStringBuffer aNumberBuf( 16 ); + std::vector< sal_Int32 > aNumbers; + bool bSequence = false; + while( pInput != pInputEnd ) + { + while( pInput != pInputEnd && *pInput >= '0' && *pInput <= '9' ) + aNumberBuf.append( *pInput++ ); + if( !aNumberBuf.isEmpty() ) + { + sal_Int32 nNumber = o3tl::toInt32(aNumberBuf) + mnOffset; + aNumberBuf.setLength(0); + aNumbers.push_back( nNumber ); + bSequence = false; + } + if (pInput == pInputEnd) + break; + if( *pInput == '-' ) + { + bSequence = true; + if( aNumbers.empty() ) + { + // push out-of-range small value, to exclude ranges totally outside of possible range + aNumbers.push_back( mnMin-1 ); + } + } + else if( *pInput == ',' || *pInput == ';' ) + { + if( bSequence && !aNumbers.empty() ) + { + // push out-of-range large value, to exclude ranges totally outside of possible range + aNumbers.push_back( mnMax+1 ); + } + insertJoinedRanges( aNumbers ); + + aNumbers.clear(); + bSequence = false; + } + else if( *pInput != ' ' ) + return false; // parse error + + pInput++; + } + // insert last entries + if( bSequence && !aNumbers.empty() ) + { + // push out-of-range large value, to exclude ranges totally outside of possible range + aNumbers.push_back( mnMax+1 ); + } + insertJoinedRanges( aNumbers ); + + return true; +} + +bool StringRangeEnumerator::hasValue( sal_Int32 i_nValue, const o3tl::sorted_vector< sal_Int32 >* i_pPossibleValues ) const +{ + if( i_pPossibleValues && i_pPossibleValues->find( i_nValue ) == i_pPossibleValues->end() ) + return false; + size_t n = maSequence.size(); + for( size_t i= 0; i < n; ++i ) + { + const StringRangeEnumerator::Range rRange( maSequence[i] ); + if( rRange.nFirst < rRange.nLast ) + { + if( i_nValue >= rRange.nFirst && i_nValue <= rRange.nLast ) + return true; + } + else + { + if( i_nValue >= rRange.nLast && i_nValue <= rRange.nFirst ) + return true; + } + } + return false; +} + +StringRangeEnumerator::Iterator& StringRangeEnumerator::Iterator::operator++() +{ + if( nRangeIndex >= 0 && nCurrent >= 0 && pEnumerator ) + { + const StringRangeEnumerator::Range& rRange( pEnumerator->maSequence[nRangeIndex] ); + bool bRangeChange = false; + if( rRange.nLast < rRange.nFirst ) + { + // backward range + if( nCurrent > rRange.nLast ) + nCurrent--; + else + bRangeChange = true; + } + else + { + // forward range + if( nCurrent < rRange.nLast ) + nCurrent++; + else + bRangeChange = true; + } + if( bRangeChange ) + { + nRangeIndex++; + if( size_t(nRangeIndex) == pEnumerator->maSequence.size() ) + { + // reached the end + nRangeIndex = nCurrent = -1; + } + else + nCurrent = pEnumerator->maSequence[nRangeIndex].nFirst; + } + if( nRangeIndex != -1 && nCurrent != -1 ) + { + if( ! pEnumerator->checkValue( nCurrent, pPossibleValues ) ) + return ++(*this); + } + } + return *this; +} + + +bool StringRangeEnumerator::Iterator::operator==( const Iterator& i_rCompare ) const +{ + return i_rCompare.pEnumerator == pEnumerator && i_rCompare.nRangeIndex == nRangeIndex && i_rCompare.nCurrent == nCurrent; +} + +StringRangeEnumerator::Iterator StringRangeEnumerator::begin( const o3tl::sorted_vector< sal_Int32 >* i_pPossibleValues ) const +{ + StringRangeEnumerator::Iterator it( this, + i_pPossibleValues, + maSequence.empty() ? -1 : 0, + maSequence.empty() ? -1 : maSequence[0].nFirst ); + if( ! checkValue(*it, i_pPossibleValues ) ) + ++it; + return it; +} + +StringRangeEnumerator::Iterator StringRangeEnumerator::end( const o3tl::sorted_vector< sal_Int32 >* i_pPossibleValues ) const +{ + return StringRangeEnumerator::Iterator( this, i_pPossibleValues, -1, -1 ); +} + +bool StringRangeEnumerator::getRangesFromString( std::u16string_view i_rPageRange, + std::vector< sal_Int32 >& o_rPageVector, + sal_Int32 i_nMinNumber, + sal_Int32 i_nMaxNumber, + sal_Int32 i_nLogicalOffset, + o3tl::sorted_vector< sal_Int32 > const * i_pPossibleValues + ) +{ + o_rPageVector.clear(); + + StringRangeEnumerator aEnum( i_rPageRange, i_nMinNumber, i_nMaxNumber, i_nLogicalOffset ) ; + + //Even if the input range wasn't completely valid, return what ranges could + //be extracted from the input. + o_rPageVector.reserve( static_cast< size_t >( aEnum.size() ) ); + for( StringRangeEnumerator::Iterator it = aEnum.begin( i_pPossibleValues ); + it != aEnum.end( i_pPossibleValues ); ++it ) + { + o_rPageVector.push_back( *it ); + } + + return aEnum.mbValidInput; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/misc/cpuid.cxx b/tools/source/misc/cpuid.cxx new file mode 100644 index 0000000000..855b87e6da --- /dev/null +++ b/tools/source/misc/cpuid.cxx @@ -0,0 +1,140 @@ +/* -*- 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 <tools/cpuid.hxx> +#include <cstdint> + +namespace cpuid +{ +namespace +{ +#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64)) +#include <intrin.h> +void getCpuId(uint32_t array[4], uint32_t nInfoType) +{ + __cpuid(reinterpret_cast<int*>(array), nInfoType); +} +#elif (defined(__i386__) || defined(__x86_64__)) +#include <cpuid.h> +void getCpuId(uint32_t array[4], uint32_t nInfoType) +{ + __cpuid_count(nInfoType, 0, *(array + 0), *(array + 1), *(array + 2), *(array + 3)); +} +#else +void getCpuId(uint32_t array[4], uint32_t /*nInfoType*/) +{ + array[0] = array[1] = array[2] = array[3] = 0; +} +#endif + +// For AVX we need to check if OS has support for ymm registers +bool checkAVXSupportInOS() +{ + uint32_t xcr0 = 0; +#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64)) + xcr0 = uint32_t(_xgetbv(0)); +#elif (defined(__i386__) || defined(__x86_64__)) + __asm__("xgetbv" : "=a"(xcr0) : "c"(0) : "%edx"); +#endif + return ((xcr0 & 6) == 6); /* checking if xmm and ymm state are enabled in XCR0 */ +} + +} // end anonymous namespace + +#define HYPER_bit (1 << 28) +#define SSE2_bit (1 << 26) +#define SSSE3_bit (1 << 9) +#define SSE41_bit (1 << 19) +#define SSE42_bit (1 << 20) +#define XSAVE_bit (1 << 27) +#define AVX_bit (1 << 28) +#define AVX2_bit (1 << 5) +#define AVX512F_bit (1 << 16) + +InstructionSetFlags getCpuInstructionSetFlags() +{ + InstructionSetFlags eInstructions = InstructionSetFlags::NONE; + + uint32_t info[] = { 0, 0, 0, 0 }; + getCpuId(info, 0); + int nLevel = info[0]; + + if (nLevel >= 1) + { + uint32_t aCpuInfoArray[] = { 0, 0, 0, 0 }; + getCpuId(aCpuInfoArray, 1); + + if ((aCpuInfoArray[3] & HYPER_bit) != 0) + eInstructions |= InstructionSetFlags::HYPER; + + if ((aCpuInfoArray[3] & SSE2_bit) != 0) + eInstructions |= InstructionSetFlags::SSE2; + + if ((aCpuInfoArray[2] & SSSE3_bit) != 0) + eInstructions |= InstructionSetFlags::SSSE3; + + if ((aCpuInfoArray[2] & SSE41_bit) != 0) + eInstructions |= InstructionSetFlags::SSE41; + + if ((aCpuInfoArray[2] & SSE42_bit) != 0) + eInstructions |= InstructionSetFlags::SSE42; + + if (((aCpuInfoArray[2] & AVX_bit) != 0) && ((aCpuInfoArray[2] & XSAVE_bit) != 0)) + { + if (checkAVXSupportInOS()) + { + eInstructions |= InstructionSetFlags::AVX; + + if (nLevel >= 7) + { + uint32_t aExtendedInfo[] = { 0, 0, 0, 0 }; + getCpuId(aExtendedInfo, 7); + + if ((aExtendedInfo[1] & AVX2_bit) != 0) + eInstructions |= InstructionSetFlags::AVX2; + if ((aExtendedInfo[1] & AVX512F_bit) != 0) + eInstructions |= InstructionSetFlags::AVX512F; + } + } + } + } + + return eInstructions; +} + +bool isCpuInstructionSetSupported(InstructionSetFlags eInstructions) +{ + static InstructionSetFlags eCPUFlags = getCpuInstructionSetFlags(); + return (eCPUFlags & eInstructions) == eInstructions; +} + +OUString instructionSetSupportedString() +{ + OUString aString; + if (isCpuInstructionSetSupported(InstructionSetFlags::SSE2)) + aString += "SSE2 "; + if (isCpuInstructionSetSupported(InstructionSetFlags::SSSE3)) + aString += "SSSE3 "; + if (isCpuInstructionSetSupported(InstructionSetFlags::SSE41)) + aString += "SSE4.1 "; + if (isCpuInstructionSetSupported(InstructionSetFlags::SSE42)) + aString += "SSE4.2 "; + if (isCpuInstructionSetSupported(InstructionSetFlags::AVX)) + aString += "AVX "; + if (isCpuInstructionSetSupported(InstructionSetFlags::AVX2)) + aString += "AVX2 "; + if (isCpuInstructionSetSupported(InstructionSetFlags::AVX512F)) + aString += "AVX512F "; + return aString; +} + +} // end cpuid + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/misc/extendapplicationenvironment.cxx b/tools/source/misc/extendapplicationenvironment.cxx new file mode 100644 index 0000000000..07f9779ccc --- /dev/null +++ b/tools/source/misc/extendapplicationenvironment.cxx @@ -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 . + */ + +#include <config_folders.h> + +#include <sal/config.h> + +#include <stdlib.h> + +#if defined UNX +#include <sys/resource.h> +#endif + +#include <osl/process.h> +#include <rtl/bootstrap.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/ustring.hxx> +#include <sal/types.h> +#include <tools/extendapplicationenvironment.hxx> + +namespace tools +{ +void extendApplicationEnvironment() +{ +#if defined UNX && !defined EMSCRIPTEN + // Try to set RLIMIT_NOFILE as large as possible (failure is harmless): + rlimit lim; + if (getrlimit(RLIMIT_NOFILE, &lim) == 0) + { + lim.rlim_cur = lim.rlim_max; + setrlimit(RLIMIT_NOFILE, &lim); + } +#endif + + // Make sure URE_BOOTSTRAP environment variable is set (failure is fatal): + OUStringBuffer env(512); + OUString envVar("URE_BOOTSTRAP"); + OUString uri; + if (rtl::Bootstrap::get(envVar, uri)) + { + if (!uri.matchIgnoreAsciiCase("vnd.sun.star.pathname:")) + { + uri = rtl::Bootstrap::encode(uri); + } + env.append(uri); + } + else + { + if (osl_getExecutableFile(&uri.pData) != osl_Process_E_None) + { + abort(); + } + sal_Int32 lastDirSeparatorPos = uri.lastIndexOf('/'); + if (lastDirSeparatorPos >= 0) + { + uri = uri.copy(0, lastDirSeparatorPos + 1); + } + env.append(rtl::Bootstrap::encode(uri)); +#ifdef MACOSX + env.append("../" LIBO_SHARE_FOLDER "/"); +#endif + env.append(SAL_CONFIGFILE("fundamental")); + } + OUString envValue(env.makeStringAndClear()); + if (osl_setEnvironment(envVar.pData, envValue.pData) != osl_Process_E_None) + { + abort(); + } +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/misc/fix16.cxx b/tools/source/misc/fix16.cxx new file mode 100644 index 0000000000..978f77291d --- /dev/null +++ b/tools/source/misc/fix16.cxx @@ -0,0 +1,172 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * libfixmath is Copyright (c) 2011-2021 Flatmush <Flatmush@gmail.com>, + * Petteri Aimonen <Petteri.Aimonen@gmail.com>, & libfixmath AUTHORS + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <tools/fix16.hxx> + +const fix16_t fix16_minimum = 0x80000000; /*!< the minimum value of fix16_t */ +const fix16_t fix16_overflow = 0x80000000; /*!< the value used to indicate overflows */ + +static inline uint32_t fix_abs(fix16_t in) +{ + if (in == fix16_minimum) + { + // minimum negative number has same representation as + // its absolute value in unsigned + return 0x80000000; + } + else + { + return (in >= 0) ? in : -in; + } +} + +/* 64-bit implementation for fix16_mul. Fastest version for e.g. ARM Cortex M3. + * Performs a 32*32 -> 64bit multiplication. The middle 32 bits are the result, + * bottom 16 bits are used for rounding, and upper 16 bits are used for overflow + * detection. + */ + +fix16_t fix16_mul(fix16_t inArg0, fix16_t inArg1) +{ + int64_t product = static_cast<int64_t>(inArg0) * inArg1; + + // The upper 17 bits should all be the same (the sign). + uint32_t upper = (product >> 47); + + if (product < 0) + { + if (~upper) + return fix16_overflow; + + // This adjustment is required in order to round -1/2 correctly + product--; + } + else + { + if (upper) + return fix16_overflow; + } + + fix16_t result = product >> 16; + result += (product & 0x8000) >> 15; + + return result; +} + +/* 32-bit implementation of fix16_div. Fastest version for e.g. ARM Cortex M3. + * Performs 32-bit divisions repeatedly to reduce the remainder. For this to + * be efficient, the processor has to have 32-bit hardware division. + */ +#ifdef __GNUC__ +// Count leading zeros, using processor-specific instruction if available. +#define clz(x) (__builtin_clzl(x) - (8 * sizeof(long) - 32)) +#else +static uint8_t clz(uint32_t x) +{ + uint8_t result = 0; + if (x == 0) + return 32; + while (!(x & 0xF0000000)) + { + result += 4; + x <<= 4; + } + while (!(x & 0x80000000)) + { + result += 1; + x <<= 1; + } + return result; +} +#endif + +fix16_t fix16_div(fix16_t a, fix16_t b) +{ + // This uses a hardware 32/32 bit division multiple times, until we have + // computed all the bits in (a<<17)/b. Usually this takes 1-3 iterations. + + if (b == 0) + return fix16_minimum; + + uint32_t remainder = fix_abs(a); + uint32_t divider = fix_abs(b); + uint64_t quotient = 0; + int bit_pos = 17; + + // Kick-start the division a bit. + // This improves speed in the worst-case scenarios where N and D are large + // It gets a lower estimate for the result by N/(D >> 17 + 1). + if (divider & 0xFFF00000) + { + uint32_t shifted_div = (divider >> 17) + 1; + quotient = remainder / shifted_div; + uint64_t tmp = (quotient * static_cast<uint64_t>(divider)) >> 17; + remainder -= static_cast<uint32_t>(tmp); + } + + // If the divider is divisible by 2^n, take advantage of it. + while (!(divider & 0xF) && bit_pos >= 4) + { + divider >>= 4; + bit_pos -= 4; + } + + while (remainder && bit_pos >= 0) + { + // Shift remainder as much as we can without overflowing + int shift = clz(remainder); + if (shift > bit_pos) + shift = bit_pos; + remainder <<= shift; + bit_pos -= shift; + + uint32_t div = remainder / divider; + remainder = remainder % divider; + quotient += static_cast<uint64_t>(div) << bit_pos; + + if (div & ~(0xFFFFFFFF >> bit_pos)) + return fix16_overflow; + + remainder <<= 1; + bit_pos--; + } + + // Quotient is always positive so rounding is easy + quotient++; + + fix16_t result = quotient >> 1; + + // Figure out the sign of the result + if ((a ^ b) & 0x80000000) + { + if (result == fix16_minimum) + return fix16_overflow; + + result = -result; + } + + return result; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/tools/source/misc/json_writer.cxx b/tools/source/misc/json_writer.cxx new file mode 100644 index 0000000000..e3e27bf756 --- /dev/null +++ b/tools/source/misc/json_writer.cxx @@ -0,0 +1,405 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 <tools/json_writer.hxx> +#include <stdio.h> +#include <cstring> +#include <rtl/math.hxx> + +namespace tools +{ +/** These buffers are short-lived, so rather waste some space and avoid the cost of + * repeated calls into the allocator */ +constexpr int DEFAULT_BUFFER_SIZE = 2048; + +JsonWriter::JsonWriter() + : mpBuffer(static_cast<char*>(malloc(DEFAULT_BUFFER_SIZE))) + , mPos(mpBuffer) + , mSpaceAllocated(DEFAULT_BUFFER_SIZE) + , mStartNodeCount(0) + , mbFirstFieldInNode(true) + , mbClosed(false) +{ + *mPos = '{'; + ++mPos; + *mPos = ' '; + ++mPos; + + addValidationMark(); +} + +JsonWriter::~JsonWriter() +{ + assert(mbClosed && "forgot to extract data?"); + free(mpBuffer); +} + +ScopedJsonWriterNode JsonWriter::startNode(std::string_view pNodeName) +{ + putLiteral(pNodeName, "{ "); + + mStartNodeCount++; + mbFirstFieldInNode = true; + + return ScopedJsonWriterNode(*this); +} + +void JsonWriter::endNode() +{ + assert(mStartNodeCount && "mismatched StartNode/EndNode somewhere"); + --mStartNodeCount; + ensureSpace(1); + *mPos = '}'; + ++mPos; + mbFirstFieldInNode = false; + + validate(); +} + +ScopedJsonWriterArray JsonWriter::startArray(std::string_view pNodeName) +{ + putLiteral(pNodeName, "[ "); + + mStartNodeCount++; + mbFirstFieldInNode = true; + + return ScopedJsonWriterArray(*this); +} + +void JsonWriter::endArray() +{ + assert(mStartNodeCount && "mismatched StartNode/EndNode somewhere"); + --mStartNodeCount; + ensureSpace(1); + *mPos = ']'; + ++mPos; + mbFirstFieldInNode = false; + + validate(); +} + +ScopedJsonWriterStruct JsonWriter::startStruct() +{ + ensureSpace(6); + + addCommaBeforeField(); + + *mPos = '{'; + ++mPos; + *mPos = ' '; + ++mPos; + mStartNodeCount++; + mbFirstFieldInNode = true; + + validate(); + + return ScopedJsonWriterStruct(*this); +} + +void JsonWriter::endStruct() +{ + assert(mStartNodeCount && "mismatched StartNode/EndNode somewhere"); + --mStartNodeCount; + ensureSpace(1); + *mPos = '}'; + ++mPos; + mbFirstFieldInNode = false; + + validate(); +} + +static char getEscapementChar(char ch) +{ + switch (ch) + { + case '\b': + return 'b'; + case '\t': + return 't'; + case '\n': + return 'n'; + case '\f': + return 'f'; + case '\r': + return 'r'; + default: + return ch; + } +} + +static bool writeEscapedSequence(sal_uInt32 ch, char*& pos) +{ + // control characters + if (ch <= 0x1f) + { + int written = snprintf(pos, 7, "\\u%.4x", static_cast<unsigned int>(ch)); + if (written > 0) + pos += written; + return true; + } + + switch (ch) + { + case '"': + case '/': + case '\\': + *pos++ = '\\'; + *pos++ = getEscapementChar(ch); + return true; + // Special processing of U+2028 and U+2029, which are valid JSON, but invalid JavaScript + // Write them in escaped '\u2028' or '\u2029' form + case 0x2028: + case 0x2029: + *pos++ = '\\'; + *pos++ = 'u'; + *pos++ = '2'; + *pos++ = '0'; + *pos++ = '2'; + *pos++ = ch == 0x2028 ? '8' : '9'; + return true; + default: + return false; + } +} + +void JsonWriter::writeEscapedOUString(const OUString& rPropVal) +{ + *mPos = '"'; + ++mPos; + + // Convert from UTF-16 to UTF-8 and perform escaping + sal_Int32 i = 0; + while (i < rPropVal.getLength()) + { + sal_uInt32 ch = rPropVal.iterateCodePoints(&i); + if (writeEscapedSequence(ch, mPos)) + continue; + if (ch <= 0x7F) + { + *mPos = static_cast<char>(ch); + ++mPos; + } + else if (ch <= 0x7FF) + { + *mPos = 0xC0 | (ch >> 6); /* 110xxxxx */ + ++mPos; + *mPos = 0x80 | (ch & 0x3F); /* 10xxxxxx */ + ++mPos; + } + else if (ch <= 0xFFFF) + { + *mPos = 0xE0 | (ch >> 12); /* 1110xxxx */ + ++mPos; + *mPos = 0x80 | ((ch >> 6) & 0x3F); /* 10xxxxxx */ + ++mPos; + *mPos = 0x80 | (ch & 0x3F); /* 10xxxxxx */ + ++mPos; + } + else + { + *mPos = 0xF0 | (ch >> 18); /* 11110xxx */ + ++mPos; + *mPos = 0x80 | ((ch >> 12) & 0x3F); /* 10xxxxxx */ + ++mPos; + *mPos = 0x80 | ((ch >> 6) & 0x3F); /* 10xxxxxx */ + ++mPos; + *mPos = 0x80 | (ch & 0x3F); /* 10xxxxxx */ + ++mPos; + } + } + + *mPos = '"'; + ++mPos; + + validate(); +} + +void JsonWriter::put(std::u16string_view pPropName, const OUString& rPropVal) +{ + auto nPropNameLength = pPropName.length(); + // But values can be any UTF-8, + // if the string only contains of 0x2028, it will be expanded 6 times (see writeEscapedSequence) + auto nWorstCasePropValLength = rPropVal.getLength() * 6; + ensureSpace(nPropNameLength + nWorstCasePropValLength + 8); + + addCommaBeforeField(); + + writeEscapedOUString(OUString(pPropName)); + + memcpy(mPos, ": ", 2); + mPos += 2; + + writeEscapedOUString(rPropVal); + + validate(); +} + +void JsonWriter::put(std::string_view pPropName, const OUString& rPropVal) +{ + // Values can be any UTF-8, + // if the string only contains of 0x2028, it will be expanded 6 times (see writeEscapedSequence) + auto nWorstCasePropValLength = rPropVal.getLength() * 6 + 2; + ensureSpaceAndWriteNameColon(pPropName, nWorstCasePropValLength); + + writeEscapedOUString(rPropVal); +} + +void JsonWriter::put(std::string_view pPropName, std::string_view rPropVal) +{ + // escaping can double the length, plus quotes + auto nWorstCasePropValLength = rPropVal.size() * 2 + 2; + ensureSpaceAndWriteNameColon(pPropName, nWorstCasePropValLength); + + *mPos = '"'; + ++mPos; + + // copy and perform escaping + bool bReachedEnd = false; + for (size_t i = 0; i < rPropVal.size() && !bReachedEnd; ++i) + { + char ch = rPropVal[i]; + switch (ch) + { + case '\b': + case '\t': + case '\n': + case '\f': + case '\r': + case '"': + case '/': + case '\\': + writeEscapedSequence(ch, mPos); + break; + case 0: + bReachedEnd = true; + break; + case '\xE2': // Special processing of U+2028 and U+2029 + if (i + 2 < rPropVal.size() && rPropVal[i + 1] == '\x80' + && (rPropVal[i + 2] == '\xA8' || rPropVal[i + 2] == '\xA9')) + { + writeEscapedSequence(rPropVal[i + 2] == '\xA8' ? 0x2028 : 0x2029, mPos); + i += 2; + break; + } + [[fallthrough]]; + default: + *mPos = ch; + ++mPos; + break; + } + } + + *mPos = '"'; + ++mPos; + + validate(); +} + +void JsonWriter::put(std::string_view pPropName, bool nPropVal) +{ + putLiteral(pPropName, nPropVal ? std::string_view("true") : std::string_view("false")); +} + +void JsonWriter::putSimpleValue(const OUString& rPropVal) +{ + auto nWorstCasePropValLength = rPropVal.getLength() * 6; + ensureSpace(nWorstCasePropValLength + 4); + + addCommaBeforeField(); + + writeEscapedOUString(rPropVal); +} + +void JsonWriter::putRaw(std::string_view rRawBuf) +{ + ensureSpace(rRawBuf.size() + 2); + + addCommaBeforeField(); + + memcpy(mPos, rRawBuf.data(), rRawBuf.size()); + mPos += rRawBuf.size(); + + validate(); +} + +void JsonWriter::addCommaBeforeField() +{ + if (mbFirstFieldInNode) + mbFirstFieldInNode = false; + else + { + *mPos = ','; + ++mPos; + *mPos = ' '; + ++mPos; + } +} + +void JsonWriter::ensureSpace(int noMoreBytesRequired) +{ + assert(!mbClosed && "already extracted data"); + int currentUsed = mPos - mpBuffer; + if (currentUsed + noMoreBytesRequired >= mSpaceAllocated) + { + auto newSize = (currentUsed + noMoreBytesRequired) * 2; + mpBuffer = static_cast<char*>(realloc(mpBuffer, newSize)); + mPos = mpBuffer + currentUsed; + mSpaceAllocated = newSize; + + addValidationMark(); + } +} + +void JsonWriter::ensureSpaceAndWriteNameColon(std::string_view name, int valSize) +{ + // we assume property names are ascii + ensureSpace(name.size() + valSize + 6); + + addCommaBeforeField(); + + *mPos = '"'; + ++mPos; + memcpy(mPos, name.data(), name.size()); + mPos += name.size(); + memcpy(mPos, "\": ", 3); + mPos += 3; +} + +void JsonWriter::putLiteral(std::string_view propName, std::string_view propValue) +{ + ensureSpaceAndWriteNameColon(propName, propValue.size()); + memcpy(mPos, propValue.data(), propValue.size()); + mPos += propValue.size(); + + validate(); +} + +OString JsonWriter::finishAndGetAsOString() +{ + assert(mStartNodeCount == 0 && "did not close all nodes"); + assert(!mbClosed && "data already extracted"); + ensureSpace(2); + // add closing brace + *mPos = '}'; + ++mPos; + // null-terminate + *mPos = 0; + mbClosed = true; + + OString ret(mpBuffer, mPos - mpBuffer); + return ret; +} + +bool JsonWriter::isDataEquals(std::string_view s) const +{ + return std::string_view(mpBuffer, static_cast<size_t>(mPos - mpBuffer)) == s; +} + +} // namespace tools +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/tools/source/misc/pathutils.cxx b/tools/source/misc/pathutils.cxx new file mode 100644 index 0000000000..706740a320 --- /dev/null +++ b/tools/source/misc/pathutils.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> + +#if defined(_WIN32) + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +#include <o3tl/safeint.hxx> +#include <sal/types.h> +#include <tools/pathutils.hxx> + +namespace tools { + +WCHAR * filename(WCHAR * path) { + WCHAR * f = path; + for (WCHAR * p = path;;) { + switch (*p++) { + case L'\0': + return f; + case L'\\': + f = p; + break; + } + } +} + +WCHAR * buildPath( + WCHAR * path, WCHAR const * frontBegin, WCHAR const * frontEnd, + WCHAR const * backBegin, std::size_t backLength) +{ + // Remove leading ".." segments in the second path together with matching + // segments in the first path that are neither empty nor "." nor ".." nor + // end in ":" (which is not foolproof, as it can erroneously erase the start + // of a UNC path, but only if the input is bad data): + while (backLength >= 2 && backBegin[0] == L'.' && backBegin[1] == L'.' && + (backLength == 2 || backBegin[2] == L'\\')) + { + if (frontEnd - frontBegin < 2 || frontEnd[-1] != L'\\' || + frontEnd[-2] == L'\\' || frontEnd[-2] == L':' || + (frontEnd[-2] == L'.' && + (frontEnd - frontBegin < 3 || frontEnd[-3] == L'\\' || + (frontEnd[-3] == L'.' && + (frontEnd - frontBegin < 4 || frontEnd[-4] == L'\\'))))) + { + break; + } + WCHAR const * p = frontEnd - 1; + while (p != frontBegin && p[-1] != L'\\') { + --p; + } + if (p == frontBegin) { + break; + } + frontEnd = p; + if (backLength == 2) { + backBegin += 2; + backLength -= 2; + } else { + backBegin += 3; + backLength -= 3; + } + } + if (backLength < + o3tl::make_unsigned(MAX_PATH - (frontEnd - frontBegin))) + { + WCHAR * p; + if (frontBegin == path) { + p = const_cast< WCHAR * >(frontEnd); + } else { + p = path; + while (frontBegin != frontEnd) { + *p++ = *frontBegin++; + } + } + for (; backLength > 0; --backLength) { + *p++ = *backBegin++; + } + *p = L'\0'; + return p; + } else { + SetLastError(ERROR_FILENAME_EXCED_RANGE); + return nullptr; + } +} + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/ref/globname.cxx b/tools/source/ref/globname.cxx new file mode 100644 index 0000000000..df8ff10943 --- /dev/null +++ b/tools/source/ref/globname.cxx @@ -0,0 +1,175 @@ +/* -*- 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 <string.h> + +#include <comphelper/mimeconfighelper.hxx> +#include <o3tl/sprintf.hxx> +#include <rtl/character.hxx> + +#include <tools/stream.hxx> +#include <tools/globname.hxx> + +// SvGlobalName ---------------------------------------------------------------- +SvGlobalName::SvGlobalName( const SvGUID & rId ) : + m_aData( rId ) +{ +} + +SvGlobalName::SvGlobalName( sal_uInt32 n1, sal_uInt16 n2, sal_uInt16 n3, + sal_uInt8 b8, sal_uInt8 b9, sal_uInt8 b10, sal_uInt8 b11, + sal_uInt8 b12, sal_uInt8 b13, sal_uInt8 b14, sal_uInt8 b15 ) : + m_aData{ n1, n2, n3, { b8, b9, b10, b11, b12, b13, b14, b15 } } +{ +} + +SvGlobalName::SvGlobalName( const css::uno::Sequence < sal_Int8 >& aSeq ) +{ + // create SvGlobalName from a platform independent representation + if ( aSeq.getLength() == 16 ) + { + m_aData.Data1 = ( ( ( ( ( static_cast<sal_uInt8>(aSeq[0]) << 8 ) + static_cast<sal_uInt8>(aSeq[1]) ) << 8 ) + static_cast<sal_uInt8>(aSeq[2]) ) << 8 ) + static_cast<sal_uInt8>(aSeq[3]); + m_aData.Data2 = ( static_cast<sal_uInt8>(aSeq[4]) << 8 ) + static_cast<sal_uInt8>(aSeq[5]); + m_aData.Data3 = ( static_cast<sal_uInt8>(aSeq[6]) << 8 ) + static_cast<sal_uInt8>(aSeq[7]); + for( int nInd = 0; nInd < 8; nInd++ ) + m_aData.Data4[nInd] = static_cast<sal_uInt8>(aSeq[nInd+8]); + } +} + +SvStream& WriteSvGlobalName( SvStream& rOStr, const SvGlobalName & rObj ) +{ + rOStr.WriteUInt32( rObj.m_aData.Data1 ); + rOStr.WriteUInt16( rObj.m_aData.Data2 ); + rOStr.WriteUInt16( rObj.m_aData.Data3 ); + rOStr.WriteBytes( &rObj.m_aData.Data4, 8 ); + return rOStr; +} + +SvStream& operator >> ( SvStream& rStr, SvGlobalName & rObj ) +{ + rStr.ReadUInt32( rObj.m_aData.Data1 ); + rStr.ReadUInt16( rObj.m_aData.Data2 ); + rStr.ReadUInt16( rObj.m_aData.Data3 ); + rStr.ReadBytes( &rObj.m_aData.Data4, 8 ); + return rStr; +} + + +bool SvGlobalName::operator < ( const SvGlobalName & rObj ) const +{ + if( m_aData.Data3 < rObj.m_aData.Data3 ) + return true; + else if( m_aData.Data3 > rObj.m_aData.Data3 ) + return false; + + if( m_aData.Data2 < rObj.m_aData.Data2 ) + return true; + else if( m_aData.Data2 > rObj.m_aData.Data2 ) + return false; + + return m_aData.Data1 < rObj.m_aData.Data1; +} + +bool SvGlobalName::operator == ( const SvGlobalName & rObj ) const +{ + return memcmp(&m_aData, &rObj.m_aData, sizeof(m_aData)) == 0; +} + +void SvGlobalName::MakeFromMemory( void const * pData ) +{ + memcpy( &m_aData, pData, sizeof( m_aData ) ); +} + +bool SvGlobalName::MakeId( std::u16string_view rIdStr ) +{ + const sal_Unicode *pStr = rIdStr.data(); + if( rIdStr.size() != 36 + || '-' != pStr[ 8 ] || '-' != pStr[ 13 ] + || '-' != pStr[ 18 ] || '-' != pStr[ 23 ] ) + return false; + + SvGUID aGuid = {}; + auto asciiHexDigitToNumber = [](sal_Unicode c) -> sal_uInt8 + { + if (rtl::isAsciiDigit(c)) + return c - '0'; + else + return rtl::toAsciiUpperCase(c) - 'A' + 10; + }; + + for( int i = 0; i < 8; i++ ) + { + if( !rtl::isAsciiHexDigit( *pStr ) ) + return false; + aGuid.Data1 = aGuid.Data1 * 16 + asciiHexDigitToNumber( *pStr++ ); + } + + pStr++; + for( int i = 0; i < 4; i++ ) + { + if( !rtl::isAsciiHexDigit( *pStr ) ) + return false; + aGuid.Data2 = aGuid.Data2 * 16 + asciiHexDigitToNumber( *pStr++ ); + } + + pStr++; + for( int i = 0; i < 4; i++ ) + { + if( !rtl::isAsciiHexDigit( *pStr ) ) + return false; + aGuid.Data3 = aGuid.Data3 * 16 + asciiHexDigitToNumber( *pStr++ ); + } + + pStr++; + for( int i = 0; i < 16; i++ ) + { + if( !rtl::isAsciiHexDigit( *pStr ) ) + return false; + aGuid.Data4[i/2] = aGuid.Data4[i/2] * 16 + asciiHexDigitToNumber( *pStr++ ); + if( i == 3 ) + pStr++; + } + + m_aData = aGuid; + return true; +} + +OUString SvGlobalName::GetHexName() const +{ + char buf[ 37 ]; + int n = o3tl::sprintf(buf, + "%8.8" SAL_PRIXUINT32 "-%4.4X-%4.4X-%2.2x%2.2x-%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x", + m_aData.Data1, m_aData.Data2, m_aData.Data3, + m_aData.Data4[0], m_aData.Data4[1], m_aData.Data4[2], m_aData.Data4[3], + m_aData.Data4[4], m_aData.Data4[5], m_aData.Data4[6], m_aData.Data4[7]); + assert(n == 36); + return OUString::createFromAscii(std::string_view(buf, n)); +} + +css::uno::Sequence < sal_Int8 > SvGlobalName::GetByteSequence() const +{ + // platform independent representation of a "GlobalName" + // maybe transported remotely + return comphelper::MimeConfigurationHelper::GetSequenceClassID( + m_aData.Data1, m_aData.Data2, m_aData.Data3, + m_aData.Data4[0], m_aData.Data4[1], m_aData.Data4[2], m_aData.Data4[3], + m_aData.Data4[4], m_aData.Data4[5], m_aData.Data4[6], m_aData.Data4[7]); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/ref/ref.cxx b/tools/source/ref/ref.cxx new file mode 100644 index 0000000000..a84ae45e69 --- /dev/null +++ b/tools/source/ref/ref.cxx @@ -0,0 +1,31 @@ +/* -*- 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 <tools/ref.hxx> +#include <tools/weakbase.hxx> + +SvRefBase::~SvRefBase() COVERITY_NOEXCEPT_FALSE {} + +tools::WeakBase::~WeakBase() +{ + if (mpWeakConnection.is()) + mpWeakConnection->mpReference = nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/reversemap/bestreversemap.cxx b/tools/source/reversemap/bestreversemap.cxx new file mode 100644 index 0000000000..f124140c19 --- /dev/null +++ b/tools/source/reversemap/bestreversemap.cxx @@ -0,0 +1,154 @@ +/* -*- 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 <sal/config.h> +#include <rtl/textcvt.h> + +#include <cstdlib> +#include <iterator> +#include <stdio.h> + +namespace { + +struct Encoder +{ + rtl_UnicodeToTextConverter m_aConverter; + bool m_bCapable; + const char *m_pEncoding; + Encoder(rtl_TextEncoding nEncoding, const char *pEncoding) + : m_aConverter(rtl_createUnicodeToTextConverter(nEncoding)) + , m_bCapable(true) + , m_pEncoding(pEncoding) + { + } + ~Encoder() + { + rtl_destroyUnicodeToTextConverter(m_aConverter); + } + void checkSupports(sal_Unicode c) + { + char aTempArray[8]; + sal_Size nTempSize; + sal_uInt32 nCvtInfo; + + sal_Size nChars = rtl_convertUnicodeToText(m_aConverter, + nullptr, &c, 1, aTempArray, sizeof(aTempArray), + RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR | RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR, + &nCvtInfo, &nTempSize); + m_bCapable = nChars > 0; + } + void reset() + { + m_bCapable = true; + } + bool isOK() const + { + return m_bCapable; + } + const char* getName() const + { + return m_pEncoding; + } + +}; + +} + +int main() +{ +# define EXP(x) Encoder(x, #x) + + Encoder aConverters[15] = + { + EXP(RTL_TEXTENCODING_MS_1361), + EXP(RTL_TEXTENCODING_MS_950), + EXP(RTL_TEXTENCODING_MS_949), + EXP(RTL_TEXTENCODING_MS_936), + EXP(RTL_TEXTENCODING_MS_932), + EXP(RTL_TEXTENCODING_MS_874), + EXP(RTL_TEXTENCODING_MS_1258), + EXP(RTL_TEXTENCODING_MS_1257), + EXP(RTL_TEXTENCODING_MS_1256), + EXP(RTL_TEXTENCODING_MS_1255), + EXP(RTL_TEXTENCODING_MS_1254), + EXP(RTL_TEXTENCODING_MS_1253), + EXP(RTL_TEXTENCODING_MS_1251), + EXP(RTL_TEXTENCODING_MS_1250), + EXP(RTL_TEXTENCODING_MS_1252) + }; + + printf("//Do not edit manually, generated from bestreversemap.cxx\n"); + printf("#include <rtl/textenc.h>\n"); + printf("#include <tools/tenccvt.hxx>\n"); + printf("rtl_TextEncoding getBestMSEncodingByChar(sal_Unicode c)\n"); + printf("{\n"); + + sal_Unicode c = 0; + while (c < 0xFFFF) + { + for (size_t i = 0; i < std::size(aConverters); ++i) + aConverters[i].reset(); + + int nMostCapable = -1; + + while(c < 0xFFFF) + { + bool bSomethingCapable = false; + for (size_t i = 0; i < std::size(aConverters); ++i) + { + if (aConverters[i].isOK()) + aConverters[i].checkSupports(c); + if (aConverters[i].isOK()) + { + bSomethingCapable = true; + nMostCapable = i; + } + } + if (!bSomethingCapable) + break; + ++c; + } + sal_Unicode cEnd = c; + printf(" if (c < 0x%x)\n", c); + printf(" return %s;\n", aConverters[nMostCapable].getName()); + while(c < 0xFFFF) + { + bool bNothingCapable = true; + for (size_t i = 0; i < std::size(aConverters); ++i) + { + aConverters[i].checkSupports(c); + if (aConverters[i].isOK()) + { + bNothingCapable = false; + break; + } + } + if (!bNothingCapable) + break; + ++c; + } + if (cEnd != c) + { + if (c < 0xFFFF) + { + printf(" if (c < 0x%x)\n", c); + printf(" return RTL_TEXTENCODING_DONTKNOW;\n"); + } + else + printf(" return RTL_TEXTENCODING_DONTKNOW;\n"); + } + } + + printf("}\n"); + fflush(stdout); + + return EXIT_SUCCESS; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/stream/GenericTypeSerializer.cxx b/tools/source/stream/GenericTypeSerializer.cxx new file mode 100644 index 0000000000..3eefb008ea --- /dev/null +++ b/tools/source/stream/GenericTypeSerializer.cxx @@ -0,0 +1,224 @@ +/* -*- 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 <tools/GenericTypeSerializer.hxx> +#include <sal/config.h> +#include <sal/log.hxx> +#include <vector> + +namespace tools +{ +constexpr sal_uInt16 COL_NAME_USER = 0x8000; + +constexpr sal_Int32 RECT_EMPTY_VALUE_RIGHT_BOTTOM = -32767; + +void GenericTypeSerializer::readColor(Color& rColor) +{ + sal_uInt16 nColorNameID(0); + + mrStream.ReadUInt16(nColorNameID); + + if (nColorNameID & COL_NAME_USER) + { + sal_uInt16 nRed(0); + sal_uInt16 nGreen(0); + sal_uInt16 nBlue(0); + + mrStream.ReadUInt16(nRed); + mrStream.ReadUInt16(nGreen); + mrStream.ReadUInt16(nBlue); + + rColor = Color(nRed >> 8, nGreen >> 8, nBlue >> 8); + } + else + { + static const std::vector<Color> staticColorArray = { + COL_BLACK, // COL_BLACK + COL_BLUE, // COL_BLUE + COL_GREEN, // COL_GREEN + COL_CYAN, // COL_CYAN + COL_RED, // COL_RED + COL_MAGENTA, // COL_MAGENTA + COL_BROWN, // COL_BROWN + COL_GRAY, // COL_GRAY + COL_LIGHTGRAY, // COL_LIGHTGRAY + COL_LIGHTBLUE, // COL_LIGHTBLUE + COL_LIGHTGREEN, // COL_LIGHTGREEN + COL_LIGHTCYAN, // COL_LIGHTCYAN + COL_LIGHTRED, // COL_LIGHTRED + COL_LIGHTMAGENTA, // COL_LIGHTMAGENTA + COL_YELLOW, // COL_YELLOW + COL_WHITE, // COL_WHITE + COL_WHITE, // COL_MENUBAR + COL_BLACK, // COL_MENUBARTEXT + COL_WHITE, // COL_POPUPMENU + COL_BLACK, // COL_POPUPMENUTEXT + COL_BLACK, // COL_WINDOWTEXT + COL_WHITE, // COL_WINDOWWORKSPACE + COL_BLACK, // COL_HIGHLIGHT + COL_WHITE, // COL_HIGHLIGHTTEXT + COL_BLACK, // COL_3DTEXT + COL_LIGHTGRAY, // COL_3DFACE + COL_WHITE, // COL_3DLIGHT + COL_GRAY, // COL_3DSHADOW + COL_LIGHTGRAY, // COL_SCROLLBAR + COL_WHITE, // COL_FIELD + COL_BLACK // COL_FIELDTEXT + }; + + if (nColorNameID < staticColorArray.size()) + rColor = staticColorArray[nColorNameID]; + else + rColor = COL_BLACK; + } +} + +void GenericTypeSerializer::writeColor(const Color& rColor) +{ + mrStream.WriteUInt16(COL_NAME_USER); + + sal_uInt16 nR = rColor.GetRed(); + sal_uInt16 nG = rColor.GetGreen(); + sal_uInt16 nB = rColor.GetBlue(); + + mrStream.WriteUInt16((nR << 8) + nR); + mrStream.WriteUInt16((nG << 8) + nG); + mrStream.WriteUInt16((nB << 8) + nB); +} + +void GenericTypeSerializer::readPoint(Point& rPoint) +{ + sal_Int32 nX(0); + sal_Int32 nY(0); + + mrStream.ReadInt32(nX); + mrStream.ReadInt32(nY); + + rPoint.setX(nX); + rPoint.setY(nY); +} + +void GenericTypeSerializer::writePoint(const Point& rPoint) +{ + mrStream.WriteInt32(rPoint.getX()); + mrStream.WriteInt32(rPoint.getY()); +} + +void GenericTypeSerializer::readSize(Size& rSize) +{ + sal_Int32 nWidth(0); + sal_Int32 nHeight(0); + + mrStream.ReadInt32(nWidth); + mrStream.ReadInt32(nHeight); + + rSize.setWidth(nWidth); + rSize.setHeight(nHeight); + + // sanitize negative size dimensions + if (rSize.Width() < 0) + { + SAL_WARN("tools", "negative width"); + rSize.setWidth(0); + } + if (rSize.Height() < 0) + { + SAL_WARN("tools", "negative height"); + rSize.setHeight(0); + } +} + +void GenericTypeSerializer::writeSize(const Size& rSize) +{ + mrStream.WriteInt32(rSize.getWidth()); + mrStream.WriteInt32(rSize.getHeight()); +} + +void GenericTypeSerializer::readRectangle(Rectangle& rRectangle) +{ + sal_Int32 nLeft(0); + sal_Int32 nTop(0); + sal_Int32 nRight(0); + sal_Int32 nBottom(0); + + mrStream.ReadInt32(nLeft); + mrStream.ReadInt32(nTop); + mrStream.ReadInt32(nRight); + mrStream.ReadInt32(nBottom); + + if (nRight == RECT_EMPTY_VALUE_RIGHT_BOTTOM || nBottom == RECT_EMPTY_VALUE_RIGHT_BOTTOM) + { + rRectangle.SetEmpty(); + } + else + { + rRectangle.SetLeft(nLeft); + rRectangle.SetTop(nTop); + rRectangle.SetRight(nRight); + rRectangle.SetBottom(nBottom); + } +} + +void GenericTypeSerializer::writeRectangle(const Rectangle& rRectangle) +{ + if (rRectangle.IsEmpty()) + { + mrStream.WriteInt32(0); + mrStream.WriteInt32(0); + mrStream.WriteInt32(RECT_EMPTY_VALUE_RIGHT_BOTTOM); + mrStream.WriteInt32(RECT_EMPTY_VALUE_RIGHT_BOTTOM); + } + else + { + mrStream.WriteInt32(rRectangle.Left()); + mrStream.WriteInt32(rRectangle.Top()); + mrStream.WriteInt32(rRectangle.Right()); + mrStream.WriteInt32(rRectangle.Bottom()); + } +} + +void GenericTypeSerializer::readFraction(Fraction& rFraction) +{ + sal_Int32 nNumerator(0); + sal_Int32 nDenominator(0); + + mrStream.ReadInt32(nNumerator); + mrStream.ReadInt32(nDenominator); + + rFraction = Fraction(nNumerator, nDenominator); +} + +void GenericTypeSerializer::writeFraction(Fraction const& rFraction) +{ + if (!rFraction.IsValid()) + { + SAL_WARN("tools.fraction", "'writeFraction()' write an invalid fraction"); + mrStream.WriteInt32(0); + mrStream.WriteInt32(0); + } + else + { + mrStream.WriteInt32(rFraction.GetNumerator()); + mrStream.WriteInt32(rFraction.GetDenominator()); + } +} + +} // end namespace tools + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/stream/stream.cxx b/tools/source/stream/stream.cxx new file mode 100644 index 0000000000..6318348de5 --- /dev/null +++ b/tools/source/stream/stream.cxx @@ -0,0 +1,2001 @@ +/* -*- 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 . + */ + +// TODO: Read->RefreshBuffer-> React to changes from m_nBufActualLen + +#include <sal/config.h> + +#include <cassert> +#include <cstddef> +#include <memory> + +#include <string.h> + +#include <o3tl/safeint.hxx> +#include <osl/endian.h> +#include <osl/diagnose.h> +#include <rtl/strbuf.hxx> +#include <rtl/string.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <tools/long.hxx> + +#include <comphelper/fileformat.h> +#include <comphelper/fileurl.hxx> + +static void swapNibbles(unsigned char &c) +{ + unsigned char nSwapTmp=c; + nSwapTmp <<= 4; + c >>= 4; + c |= nSwapTmp; +} + +#include <tools/debug.hxx> +#include <tools/stream.hxx> +#include <osl/thread.h> +#include <algorithm> + +// !!! Do not inline if already the operators <<,>> are inline +template <typename T, std::enable_if_t<std::is_integral_v<T> && sizeof(T) == 2, int> = 0> +static void SwapNumber(T& r) + { r = OSL_SWAPWORD(r); } +template <typename T, std::enable_if_t<std::is_integral_v<T> && sizeof(T) == 4, int> = 0> +static void SwapNumber(T& r) + { r = OSL_SWAPDWORD(r); } +template <typename T, std::enable_if_t<std::is_integral_v<T> && sizeof(T) == 8, int> = 0> +static void SwapNumber(T& r) + { + union + { + T n; + sal_uInt32 c[2]; + } s; + + s.n = r; + std::swap(s.c[0], s.c[1]); // swap the 32 bit words + // swap the bytes in the words + s.c[0] = OSL_SWAPDWORD(s.c[0]); + s.c[1] = OSL_SWAPDWORD(s.c[1]); + r = s.n; + } + +#ifdef UNX +static void SwapFloat( float& r ) + { + union + { + float f; + sal_uInt32 c; + } s; + + s.f = r; + s.c = OSL_SWAPDWORD( s.c ); + r = s.f; + } + +static void SwapDouble( double& r ) + { + if( sizeof(double) != 8 ) + { + SAL_WARN( "tools.stream", "Can only swap 8-Byte-doubles" ); + } + else + { + union + { + double d; + sal_uInt32 c[2]; + } s; + + s.d = r; + s.c[0] ^= s.c[1]; // swap 32-bit values in situ + s.c[1] ^= s.c[0]; + s.c[0] ^= s.c[1]; + s.c[0] = OSL_SWAPDWORD(s.c[0]); // swap dword itself in situ + s.c[1] = OSL_SWAPDWORD(s.c[1]); + r = s.d; + } + } +#endif + +//SDO + +void SvStream::readNumberWithoutSwap_(void * pDataDest, int nDataSize) +{ + if (m_isIoRead && nDataSize <= m_nBufFree) + { + for (int i = 0; i < nDataSize; i++) + static_cast<char*>(pDataDest)[i] = m_pBufPos[i]; + m_nBufActualPos += nDataSize; + m_pBufPos += nDataSize; + m_nBufFree -= nDataSize; + } + else + { + ReadBytes( pDataDest, nDataSize ); + } +} + + +void SvStream::writeNumberWithoutSwap_(const void * pDataSrc, int nDataSize) +{ + if (m_isIoWrite && nDataSize <= m_nBufFree) + { + for (int i = 0; i < nDataSize; i++) + m_pBufPos[i] = static_cast<const char*>(pDataSrc)[i]; + m_nBufFree -= nDataSize; + m_nBufActualPos += nDataSize; + if (m_nBufActualPos > m_nBufActualLen) + m_nBufActualLen = m_nBufActualPos; + m_pBufPos += nDataSize; + m_isDirty = true; + } + else + { + WriteBytes( pDataSrc, nDataSize ); + } +} + + +void SvLockBytes::close() +{ + if (m_bOwner) + delete m_pStream; + m_pStream = nullptr; +} + + +// virtual +ErrCode SvLockBytes::ReadAt(sal_uInt64 const nPos, void * pBuffer, std::size_t nCount, + std::size_t * pRead) const +{ + if (!m_pStream) + { + OSL_FAIL("SvLockBytes::ReadAt(): Bad stream"); + return ERRCODE_NONE; + } + + m_pStream->Seek(nPos); + std::size_t nTheRead = m_pStream->ReadBytes(pBuffer, nCount); + if (pRead) + *pRead = nTheRead; + return m_pStream->GetErrorCode(); +} + +// virtual +ErrCode SvLockBytes::WriteAt(sal_uInt64 const nPos, const void * pBuffer, std::size_t nCount, + std::size_t * pWritten) +{ + if (!m_pStream) + { + OSL_FAIL("SvLockBytes::WriteAt(): Bad stream"); + return ERRCODE_NONE; + } + + m_pStream->Seek(nPos); + std::size_t nTheWritten = m_pStream->WriteBytes(pBuffer, nCount); + if (pWritten) + *pWritten = nTheWritten; + return m_pStream->GetErrorCode(); +} + +// virtual +ErrCode SvLockBytes::Flush() const +{ + if (!m_pStream) + { + OSL_FAIL("SvLockBytes::Flush(): Bad stream"); + return ERRCODE_NONE; + } + + m_pStream->Flush(); + return m_pStream->GetErrorCode(); +} + +// virtual +ErrCode SvLockBytes::SetSize(sal_uInt64 const nSize) +{ + if (!m_pStream) + { + OSL_FAIL("SvLockBytes::SetSize(): Bad stream"); + return ERRCODE_NONE; + } + + m_pStream->SetStreamSize(nSize); + return m_pStream->GetErrorCode(); +} + +ErrCode SvLockBytes::Stat(SvLockBytesStat * pStat) const +{ + if (!m_pStream) + { + OSL_FAIL("SvLockBytes::Stat(): Bad stream"); + return ERRCODE_NONE; + } + + if (pStat) + pStat->nSize = m_pStream->TellEnd(); + return ERRCODE_NONE; +} + + +std::size_t SvStream::GetData( void* pData, std::size_t nSize ) +{ + if( !GetError() ) + { + DBG_ASSERT( m_xLockBytes.is(), "pure virtual function" ); + std::size_t nRet(0); + m_nError = m_xLockBytes->ReadAt(m_nActPos, pData, nSize, &nRet); + m_nActPos += nRet; + return nRet; + } + else return 0; +} + +std::size_t SvStream::PutData( const void* pData, std::size_t nSize ) +{ + if( !GetError() ) + { + DBG_ASSERT( m_xLockBytes.is(), "pure virtual function" ); + std::size_t nRet(0); + m_nError = m_xLockBytes->WriteAt(m_nActPos, pData, nSize, &nRet); + m_nActPos += nRet; + return nRet; + } + else return 0; +} + +sal_uInt64 SvStream::SeekPos(sal_uInt64 const nPos) +{ + // check if a truncated STREAM_SEEK_TO_END was passed + assert(nPos != SAL_MAX_UINT32); + if( !GetError() && nPos == STREAM_SEEK_TO_END ) + { + DBG_ASSERT( m_xLockBytes.is(), "pure virtual function" ); + SvLockBytesStat aStat; + m_xLockBytes->Stat( &aStat ); + m_nActPos = aStat.nSize; + } + else + m_nActPos = nPos; + return m_nActPos; +} + +void SvStream::FlushData() +{ + if( !GetError() ) + { + DBG_ASSERT( m_xLockBytes.is(), "pure virtual function" ); + m_nError = m_xLockBytes->Flush(); + } +} + +void SvStream::SetSize(sal_uInt64 const nSize) +{ + DBG_ASSERT( m_xLockBytes.is(), "pure virtual function" ); + m_nError = m_xLockBytes->SetSize( nSize ); +} + +SvStream::SvStream() : + m_nActPos(0) + + , m_pBufPos(nullptr) + , m_nBufSize(0) + , m_nBufActualLen(0) + , m_nBufActualPos(0) + , m_nBufFree(0) + , m_isIoRead(false) + , m_isIoWrite(false) + + , m_isDirty(false) + , m_isEof(false) + + , m_nCompressMode(SvStreamCompressFlags::NONE) +#if defined UNX + , m_eLineDelimiter(LINEEND_LF) // UNIX-Format +#else + , m_eLineDelimiter(LINEEND_CRLF) // DOS-Format +#endif + , m_eStreamCharSet(osl_getThreadTextEncoding()) + + , m_nCryptMask(0) + + , m_nVersion(0) + + , m_nBufFilePos(0) + , m_eStreamMode(StreamMode::NONE) + , m_isWritable(true) + +{ + SetEndian( SvStreamEndian::LITTLE ); + + ClearError(); +} + +SvStream::SvStream( SvLockBytes* pLockBytesP ) : SvStream() +{ + m_xLockBytes = pLockBytesP; + if( pLockBytesP ) { + const SvStream* pStrm = pLockBytesP->GetStream(); + if( pStrm ) { + SetError( pStrm->GetErrorCode() ); + } + } + SetBufferSize( 256 ); +} + +SvStream::~SvStream() +{ + if (m_xLockBytes.is()) + Flush(); +} + +void SvStream::ClearError() +{ + m_isEof = false; + m_nError = ERRCODE_NONE; +} + +void SvStream::SetError( ErrCode nErrorCode ) +{ + if (m_nError == ERRCODE_NONE) + m_nError = nErrorCode; +} + +void SvStream::SetEndian( SvStreamEndian nNewFormat ) +{ +#ifdef OSL_BIGENDIAN + m_isSwap = nNewFormat == SvStreamEndian::LITTLE; +#else + m_isSwap = nNewFormat == SvStreamEndian::BIG; +#endif +} + +SvStreamEndian SvStream::GetEndian() const +{ +#ifdef OSL_BIGENDIAN + return m_isSwap ? SvStreamEndian::LITTLE : SvStreamEndian::BIG; +#else + return m_isSwap ? SvStreamEndian::BIG : SvStreamEndian::LITTLE; +#endif +} + +void SvStream::SetBufferSize( sal_uInt16 nBufferSize ) +{ + sal_uInt64 const nActualFilePos = Tell(); + bool bDontSeek = (m_pRWBuf == nullptr); + + if (m_isDirty && m_isWritable) // due to Windows NT: Access denied + FlushBuffer(); + + if (m_nBufSize) + { + m_pRWBuf.reset(); + m_nBufFilePos += m_nBufActualPos; + } + + m_pRWBuf = nullptr; + m_nBufActualLen = 0; + m_nBufActualPos = 0; + m_nBufSize = nBufferSize; + if (m_nBufSize) + m_pRWBuf.reset(new sal_uInt8[ m_nBufSize ]); + m_pBufPos = m_pRWBuf.get(); + m_isIoRead = m_isIoWrite = false; + if( !bDontSeek ) + SeekPos( nActualFilePos ); +} + +void SvStream::ClearBuffer() +{ + m_nBufActualLen = 0; + m_nBufActualPos = 0; + m_nBufFilePos = 0; + m_pBufPos = m_pRWBuf.get(); + m_isDirty = false; + m_isIoRead = m_isIoWrite = false; + + m_isEof = false; +} + +void SvStream::ResetError() +{ + ClearError(); +} + +bool SvStream::ReadByteStringLine( OUString& rStr, rtl_TextEncoding eSrcCharSet, + sal_Int32 nMaxBytesToRead ) +{ + OString aStr; + bool bRet = ReadLine( aStr, nMaxBytesToRead); + rStr = OStringToOUString(aStr, eSrcCharSet); + return bRet; +} + +bool SvStream::ReadLine( OString& rStr, sal_Int32 nMaxBytesToRead ) +{ + OStringBuffer aBuf(4096); + bool rv = ReadLine(aBuf, nMaxBytesToRead); + rStr = aBuf.makeStringAndClear(); + return rv; +} + +bool SvStream::ReadLine( OStringBuffer& aBuf, sal_Int32 nMaxBytesToRead ) +{ + char buf[256+1]; + bool bEnd = false; + sal_uInt64 nOldFilePos = Tell(); + char c = 0; + std::size_t nTotalLen = 0; + + aBuf.setLength(0); + while( !bEnd && !GetError() ) // Don't test for EOF as we + // are reading block-wise! + { + sal_uInt16 nLen = static_cast<sal_uInt16>(ReadBytes(buf, sizeof(buf)-1)); + if ( !nLen ) + { + if ( aBuf.isEmpty() ) + { + // Exit on first block-read error + m_isEof = true; + aBuf.setLength(0); + return false; + } + else + break; + } + + sal_uInt16 j, n; + for( j = n = 0; j < nLen ; ++j ) + { + c = buf[j]; + if ( c == '\n' || c == '\r' ) + { + bEnd = true; + break; + } + if ( n < j ) + buf[n] = c; + ++n; + } + nTotalLen += j; + if (nTotalLen > o3tl::make_unsigned(nMaxBytesToRead)) + { + n -= nTotalLen - nMaxBytesToRead; + nTotalLen = nMaxBytesToRead; + bEnd = true; + } + if ( n ) + aBuf.append(buf, n); + } + + if ( !bEnd && !GetError() && !aBuf.isEmpty() ) + bEnd = true; + + nOldFilePos += nTotalLen; + if( Tell() > nOldFilePos ) + nOldFilePos++; + Seek( nOldFilePos ); // Seek pointer due to BlockRead above + + if ( bEnd && (c=='\r' || c=='\n') ) // Special treatment for DOS files + { + char cTemp; + std::size_t nLen = ReadBytes(&cTemp, sizeof(cTemp)); + if ( nLen ) { + if( cTemp == c || (cTemp != '\n' && cTemp != '\r') ) + Seek( nOldFilePos ); + } + } + + if ( bEnd ) + m_isEof = false; + return bEnd; +} + +bool SvStream::ReadUniStringLine( OUString& rStr, sal_Int32 nMaxCodepointsToRead ) +{ + sal_Unicode buf[256+1]; + bool bEnd = false; + sal_uInt64 nOldFilePos = Tell(); + sal_Unicode c = 0; + std::size_t nTotalLen = 0; + + DBG_ASSERT( sizeof(sal_Unicode) == sizeof(sal_uInt16), "ReadUniStringLine: swapping sizeof(sal_Unicode) not implemented" ); + + OUStringBuffer aBuf(4096); + while( !bEnd && !GetError() ) // Don't test for EOF as we + // are reading block-wise! + { + sal_uInt16 nLen = static_cast<sal_uInt16>(ReadBytes( buf, sizeof(buf)-sizeof(sal_Unicode))); + nLen /= sizeof(sal_Unicode); + if ( !nLen ) + { + if ( aBuf.isEmpty() ) + { + // exit on first BlockRead error + m_isEof = true; + rStr.clear(); + return false; + } + else + break; + } + + sal_uInt16 j, n; + for( j = n = 0; j < nLen ; ++j ) + { + if (m_isSwap) + SwapNumber( buf[n] ); + c = buf[j]; + if ( c == '\n' || c == '\r' ) + { + bEnd = true; + break; + } + // erAck 26.02.01: Old behavior was no special treatment of '\0' + // character here, but a following rStr+=c did ignore it. Is this + // really intended? Or should a '\0' better terminate a line? + // The nOldFilePos stuff wasn't correct then anyways. + if ( c ) + { + if ( n < j ) + buf[n] = c; + ++n; + } + } + nTotalLen += j; + if (nTotalLen > o3tl::make_unsigned(nMaxCodepointsToRead)) + { + n -= nTotalLen - nMaxCodepointsToRead; + nTotalLen = nMaxCodepointsToRead; + bEnd = true; + } + if ( n ) + aBuf.append( buf, n ); + } + + if ( !bEnd && !GetError() && !aBuf.isEmpty() ) + bEnd = true; + + nOldFilePos += nTotalLen * sizeof(sal_Unicode); + if( Tell() > nOldFilePos ) + nOldFilePos += sizeof(sal_Unicode); + Seek( nOldFilePos ); // seek due to BlockRead above + + if ( bEnd && (c=='\r' || c=='\n') ) // special treatment for DOS files + { + sal_Unicode cTemp; + ReadBytes( &cTemp, sizeof(cTemp) ); + if (m_isSwap) + SwapNumber( cTemp ); + if( cTemp == c || (cTemp != '\n' && cTemp != '\r') ) + Seek( nOldFilePos ); + } + + if ( bEnd ) + m_isEof = false; + rStr = aBuf.makeStringAndClear(); + return bEnd; +} + +bool SvStream::ReadUniOrByteStringLine( OUString& rStr, rtl_TextEncoding eSrcCharSet, + sal_Int32 nMaxCodepointsToRead ) +{ + if ( eSrcCharSet == RTL_TEXTENCODING_UNICODE ) + return ReadUniStringLine( rStr, nMaxCodepointsToRead ); + else + return ReadByteStringLine( rStr, eSrcCharSet, nMaxCodepointsToRead ); +} + +OString read_zeroTerminated_uInt8s_ToOString(SvStream& rStream) +{ + OStringBuffer aOutput(256); + + char buf[ 256 + 1 ]; + bool bEnd = false; + sal_uInt64 nFilePos = rStream.Tell(); + + while( !bEnd && !rStream.GetError() ) + { + std::size_t nLen = rStream.ReadBytes(buf, sizeof(buf)-1); + if (!nLen) + break; + + std::size_t nReallyRead = nLen; + const char* pPtr = buf; + while (nLen && *pPtr) + { + ++pPtr; + --nLen; + } + + bEnd = ( nReallyRead < sizeof(buf)-1 ) // read less than attempted to read + || ( ( nLen > 0 ) // OR it is inside the block we read + && ( 0 == *pPtr ) // AND found a string terminator + ); + + aOutput.append(buf, pPtr - buf); + } + + nFilePos += aOutput.getLength(); + if (rStream.Tell() > nFilePos) + rStream.Seek(nFilePos+1); // seek due to FileRead above + return aOutput.makeStringAndClear(); +} + +OUString read_zeroTerminated_uInt8s_ToOUString(SvStream& rStream, rtl_TextEncoding eEnc) +{ + return OStringToOUString( + read_zeroTerminated_uInt8s_ToOString(rStream), eEnc); +} + +/** Attempt to write a prefixed sequence of nUnits 16bit units from an OUString, + returned value is number of bytes written */ +std::size_t write_uInt16s_FromOUString(SvStream& rStrm, std::u16string_view rStr, + std::size_t nUnits) +{ + DBG_ASSERT( sizeof(sal_Unicode) == sizeof(sal_uInt16), "write_uInt16s_FromOUString: swapping sizeof(sal_Unicode) not implemented" ); + std::size_t nWritten; + if (!rStrm.IsEndianSwap()) + nWritten = rStrm.WriteBytes(rStr.data(), nUnits * sizeof(sal_Unicode)); + else + { + std::size_t nLen = nUnits; + sal_Unicode aBuf[384]; + sal_Unicode* const pTmp = ( nLen > 384 ? new sal_Unicode[nLen] : aBuf); + memcpy( pTmp, rStr.data(), nLen * sizeof(sal_Unicode) ); + sal_Unicode* p = pTmp; + const sal_Unicode* const pStop = pTmp + nLen; + while ( p < pStop ) + { + SwapNumber( *p ); + p++; + } + nWritten = rStrm.WriteBytes( pTmp, nLen * sizeof(sal_Unicode) ); + if ( pTmp != aBuf ) + delete [] pTmp; + } + return nWritten; +} + +bool SvStream::WriteUnicodeOrByteText( std::u16string_view rStr, rtl_TextEncoding eDestCharSet ) +{ + if ( eDestCharSet == RTL_TEXTENCODING_UNICODE ) + { + write_uInt16s_FromOUString(*this, rStr, rStr.size()); + return m_nError == ERRCODE_NONE; + } + else + { + OString aStr(OUStringToOString(rStr, eDestCharSet)); + write_uInt8s_FromOString(*this, aStr, aStr.getLength()); + return m_nError == ERRCODE_NONE; + } +} + +bool SvStream::WriteByteStringLine( std::u16string_view rStr, rtl_TextEncoding eDestCharSet ) +{ + return WriteLine(OUStringToOString(rStr, eDestCharSet)); +} + +bool SvStream::WriteLine(std::string_view rStr) +{ + WriteBytes(rStr.data(), rStr.size()); + endl(*this); + return m_nError == ERRCODE_NONE; +} + +bool SvStream::WriteUniOrByteChar( sal_Unicode ch, rtl_TextEncoding eDestCharSet ) +{ + if ( eDestCharSet == RTL_TEXTENCODING_UNICODE ) + WriteUnicode(ch); + else + { + OString aStr(&ch, 1, eDestCharSet); + WriteBytes(aStr.getStr(), aStr.getLength()); + } + return m_nError == ERRCODE_NONE; +} + +void SvStream::StartWritingUnicodeText() +{ + m_isSwap = false; // Switch to no endian swapping + // BOM, Byte Order Mark, U+FEFF, see + // http://www.unicode.org/faq/utf_bom.html#BOM + // Upon read: 0xfeff(-257) => no swap; 0xfffe(-2) => swap + WriteUInt16(0xfeff); +} + +void SvStream::StartReadingUnicodeText( rtl_TextEncoding eReadBomCharSet ) +{ + if (!( eReadBomCharSet == RTL_TEXTENCODING_DONTKNOW || + eReadBomCharSet == RTL_TEXTENCODING_UNICODE || + eReadBomCharSet == RTL_TEXTENCODING_UTF8)) + return; // nothing to read + + const sal_uInt64 nOldPos = Tell(); + bool bGetBack = true; + unsigned char nFlag(0); + ReadUChar( nFlag ); + switch ( nFlag ) + { + case 0xfe: // UTF-16BE? + if ( eReadBomCharSet == RTL_TEXTENCODING_DONTKNOW || + eReadBomCharSet == RTL_TEXTENCODING_UNICODE) + { + ReadUChar(nFlag); + if (nFlag == 0xff) + { + SetEndian(SvStreamEndian::BIG); + bGetBack = false; + } + } + break; + case 0xff: // UTF-16LE? + if ( eReadBomCharSet == RTL_TEXTENCODING_DONTKNOW || + eReadBomCharSet == RTL_TEXTENCODING_UNICODE) + { + ReadUChar(nFlag); + if (nFlag == 0xfe) + { + SetEndian(SvStreamEndian::LITTLE); + bGetBack = false; + } + } + break; + case 0xef: // UTF-8? + if ( eReadBomCharSet == RTL_TEXTENCODING_DONTKNOW || + eReadBomCharSet == RTL_TEXTENCODING_UTF8) + { + ReadUChar(nFlag); + if (nFlag == 0xbb) + { + ReadUChar(nFlag); + if (nFlag == 0xbf) + bGetBack = false; // it is UTF-8 + } + } + break; + default: + ; // nothing + } + if (bGetBack) + Seek(nOldPos); // no BOM, pure data +} + +sal_uInt64 SvStream::SeekRel(sal_Int64 const nPos) +{ + sal_uInt64 nActualPos = Tell(); + + if ( nPos >= 0 ) + { + if (SAL_MAX_UINT64 - nActualPos > o3tl::make_unsigned(nPos)) + nActualPos += nPos; + } + else + { + sal_uInt64 const nAbsPos = static_cast<sal_uInt64>(-nPos); + if ( nActualPos >= nAbsPos ) + nActualPos -= nAbsPos; + } + + assert((m_pBufPos != nullptr) == bool(m_pRWBuf)); + if (m_pRWBuf) + { + m_pBufPos = m_pRWBuf.get() + nActualPos; + } + return Seek( nActualPos ); +} + +template <typename T> SvStream& SvStream::ReadNumber(T& r) +{ + T n = 0; + readNumberWithoutSwap(n); + if (good()) + { + if (m_isSwap) + SwapNumber(n); + r = n; + } + return *this; +} + +SvStream& SvStream::ReadUInt16(sal_uInt16& r) { return ReadNumber(r); } +SvStream& SvStream::ReadUInt32(sal_uInt32& r) { return ReadNumber(r); } +SvStream& SvStream::ReadUInt64(sal_uInt64& r) { return ReadNumber(r); } +SvStream& SvStream::ReadInt16(sal_Int16& r) { return ReadNumber(r); } +SvStream& SvStream::ReadInt32(sal_Int32& r) { return ReadNumber(r); } +SvStream& SvStream::ReadInt64(sal_Int64& r) { return ReadNumber(r); } + +SvStream& SvStream::ReadSChar( signed char& r ) +{ + if (m_isIoRead && sizeof(signed char) <= m_nBufFree) + { + r = *m_pBufPos; + m_nBufActualPos += sizeof(signed char); + m_pBufPos += sizeof(signed char); + m_nBufFree -= sizeof(signed char); + } + else + ReadBytes( &r, sizeof(signed char) ); + return *this; +} + +// Special treatment for Chars due to PutBack + +SvStream& SvStream::ReadChar( char& r ) +{ + if (m_isIoRead && sizeof(char) <= m_nBufFree) + { + r = *m_pBufPos; + m_nBufActualPos += sizeof(char); + m_pBufPos += sizeof(char); + m_nBufFree -= sizeof(char); + } + else + ReadBytes( &r, sizeof(char) ); + return *this; +} + +SvStream& SvStream::ReadUChar( unsigned char& r ) +{ + if (m_isIoRead && sizeof(char) <= m_nBufFree) + { + r = *m_pBufPos; + m_nBufActualPos += sizeof(char); + m_pBufPos += sizeof(char); + m_nBufFree -= sizeof(char); + } + else + ReadBytes( &r, sizeof(char) ); + return *this; +} + +SvStream& SvStream::ReadUtf16(sal_Unicode& r) { return ReadNumber(r); } + +SvStream& SvStream::ReadCharAsBool( bool& r ) +{ + if (m_isIoRead && sizeof(char) <= m_nBufFree) + { + SAL_WARN_IF( + *m_pBufPos > 1, "tools.stream", unsigned(*m_pBufPos) << " not 0/1"); + r = *m_pBufPos != 0; + m_nBufActualPos += sizeof(char); + m_pBufPos += sizeof(char); + m_nBufFree -= sizeof(char); + } + else + { + unsigned char c; + if (ReadBytes(&c, 1) == 1) + { + SAL_WARN_IF(c > 1, "tools.stream", unsigned(c) << " not 0/1"); + r = c != 0; + } + } + return *this; +} + +SvStream& SvStream::ReadFloat(float& r) +{ + float n = 0; + readNumberWithoutSwap(n); + if (good()) + { +#if defined UNX + if (m_isSwap) + SwapFloat(n); +#endif + r = n; + } + return *this; +} + +SvStream& SvStream::ReadDouble(double& r) +{ + double n = 0; + readNumberWithoutSwap(n); + if (good()) + { +#if defined UNX + if (m_isSwap) + SwapDouble(n); +#endif + r = n; + } + return *this; +} + +SvStream& SvStream::ReadStream( SvStream& rStream ) +{ + const sal_uInt32 cBufLen = 0x8000; + std::unique_ptr<char[]> pBuf( new char[ cBufLen ] ); + + sal_uInt32 nCount; + do { + nCount = ReadBytes( pBuf.get(), cBufLen ); + rStream.WriteBytes( pBuf.get(), nCount ); + } while( nCount == cBufLen ); + + return *this; +} + +template <typename T> SvStream& SvStream::WriteNumber(T n) +{ + if (m_isSwap) + SwapNumber(n); + writeNumberWithoutSwap(n); + return *this; +} + +SvStream& SvStream::WriteUInt16(sal_uInt16 v) { return WriteNumber(v); } +SvStream& SvStream::WriteUInt32(sal_uInt32 v) { return WriteNumber(v); } +SvStream& SvStream::WriteUInt64(sal_uInt64 v) { return WriteNumber(v); } +SvStream& SvStream::WriteInt16(sal_Int16 v) { return WriteNumber(v); } +SvStream& SvStream::WriteInt32(sal_Int32 v) { return WriteNumber(v); } +SvStream& SvStream::WriteInt64(sal_Int64 v) { return WriteNumber(v); } + +SvStream& SvStream::WriteSChar( signed char v ) +{ + //SDO + if (m_isIoWrite && sizeof(signed char) <= m_nBufFree) + { + *m_pBufPos = v; + m_pBufPos++; // sizeof(char); + m_nBufActualPos++; + if (m_nBufActualPos > m_nBufActualLen) // Append ? + m_nBufActualLen = m_nBufActualPos; + m_nBufFree--; // = sizeof(char); + m_isDirty = true; + } + else + WriteBytes( &v, sizeof(signed char) ); + return *this; +} + +// Special treatment for Chars due to PutBack + +SvStream& SvStream::WriteChar( char v ) +{ + //SDO + if (m_isIoWrite && sizeof(char) <= m_nBufFree) + { + *m_pBufPos = v; + m_pBufPos++; // sizeof(char); + m_nBufActualPos++; + if (m_nBufActualPos > m_nBufActualLen) // Append ? + m_nBufActualLen = m_nBufActualPos; + m_nBufFree--; // = sizeof(char); + m_isDirty = true; + } + else + WriteBytes( &v, sizeof(char) ); + return *this; +} + +SvStream& SvStream::WriteUChar( unsigned char v ) +{ +//SDO + if (m_isIoWrite && sizeof(char) <= m_nBufFree) + { + *reinterpret_cast<unsigned char*>(m_pBufPos) = v; + m_pBufPos++; // = sizeof(char); + m_nBufActualPos++; // = sizeof(char); + if (m_nBufActualPos > m_nBufActualLen) // Append ? + m_nBufActualLen = m_nBufActualPos; + m_nBufFree--; + m_isDirty = true; + } + else + WriteBytes( &v, sizeof(char) ); + return *this; +} + +SvStream& SvStream::WriteUInt8( sal_uInt8 v ) +{ + return WriteUChar(v); +} + +SvStream& SvStream::WriteUnicode( sal_Unicode v ) +{ + return WriteUInt16(v); +} + +SvStream& SvStream::WriteFloat( float v ) +{ +#ifdef UNX + if (m_isSwap) + SwapFloat(v); +#endif + writeNumberWithoutSwap(v); + return *this; +} + +SvStream& SvStream::WriteDouble ( const double& r ) +{ +#if defined UNX + if (m_isSwap) + { + double nHelp = r; + SwapDouble(nHelp); + writeNumberWithoutSwap(nHelp); + return *this; + } + else +#endif + { + writeNumberWithoutSwap(r); + } + return *this; +} + +SvStream& SvStream::WriteStream( SvStream& rStream ) +{ + const sal_uInt32 cBufLen = 0x8000; + std::unique_ptr<char[]> pBuf( new char[ cBufLen ] ); + sal_uInt32 nCount; + do { + nCount = rStream.ReadBytes( pBuf.get(), cBufLen ); + WriteBytes( pBuf.get(), nCount ); + } while( nCount == cBufLen ); + + return *this; +} + +sal_uInt64 SvStream::WriteStream( SvStream& rStream, sal_uInt64 nSize ) +{ + const sal_uInt32 cBufLen = 0x8000; + std::unique_ptr<char[]> pBuf( new char[ cBufLen ] ); + sal_uInt32 nCurBufLen = cBufLen; + sal_uInt32 nCount; + sal_uInt64 nWriteSize = nSize; + + do + { + nCurBufLen = std::min<sal_uInt64>(nCurBufLen, nWriteSize); + nCount = rStream.ReadBytes(pBuf.get(), nCurBufLen); + WriteBytes( pBuf.get(), nCount ); + nWriteSize -= nCount; + } + while( nWriteSize && nCount == nCurBufLen ); + + return nSize - nWriteSize; +} + +OUString SvStream::ReadUniOrByteString( rtl_TextEncoding eSrcCharSet ) +{ + // read UTF-16 string directly from stream ? + if (eSrcCharSet == RTL_TEXTENCODING_UNICODE) + return read_uInt32_lenPrefixed_uInt16s_ToOUString(*this); + return read_uInt16_lenPrefixed_uInt8s_ToOUString(*this, eSrcCharSet); +} + +SvStream& SvStream::WriteUniOrByteString( std::u16string_view rStr, rtl_TextEncoding eDestCharSet ) +{ + // write UTF-16 string directly into stream ? + if (eDestCharSet == RTL_TEXTENCODING_UNICODE) + write_uInt32_lenPrefixed_uInt16s_FromOUString(*this, rStr); + else + write_uInt16_lenPrefixed_uInt8s_FromOUString(*this, rStr, eDestCharSet); + return *this; +} + +void SvStream::FlushBuffer() +{ + if (m_isDirty) // Does stream require a flush? + { + SeekPos(m_nBufFilePos); + if (m_nCryptMask) + CryptAndWriteBuffer(m_pRWBuf.get(), m_nBufActualLen); + else if (PutData(m_pRWBuf.get(), m_nBufActualLen) != m_nBufActualLen) + SetError(SVSTREAM_WRITE_ERROR); + m_isDirty = false; + } +} + +std::size_t SvStream::ReadBytes( void* pData, std::size_t nCount ) +{ + std::size_t nSaveCount = nCount; + + if (!m_pRWBuf) + { + nCount = GetData( pData,nCount); + if (m_nCryptMask) + EncryptBuffer(pData, nCount); + m_nBufFilePos += nCount; + } + else + { + // check if block is completely within buffer + m_isIoRead = true; + m_isIoWrite = false; + if (nCount <= o3tl::make_unsigned(m_nBufActualLen - m_nBufActualPos)) + { + // => yes + if (nCount != 0) + memcpy(pData, m_pBufPos, nCount); + m_nBufActualPos = m_nBufActualPos + static_cast<sal_uInt16>(nCount); + m_pBufPos += nCount; + m_nBufFree = m_nBufFree - static_cast<sal_uInt16>(nCount); + } + else + { + FlushBuffer(); + + // Does data block fit into buffer? + if (nCount > m_nBufSize) + { + // => No! Thus read directly + // into target area without using the buffer + + m_isIoRead = false; + + SeekPos(m_nBufFilePos + m_nBufActualPos); + m_nBufActualLen = 0; + m_pBufPos = m_pRWBuf.get(); + nCount = GetData( pData, nCount ); + if (m_nCryptMask) + EncryptBuffer(pData, nCount); + m_nBufFilePos += nCount; + m_nBufFilePos += m_nBufActualPos; + m_nBufActualPos = 0; + } + else + { + // => Yes. Fill buffer first, then copy to target area + + m_nBufFilePos += m_nBufActualPos; + SeekPos(m_nBufFilePos); + + // TODO: Typecast before GetData, sal_uInt16 nCountTmp + std::size_t nCountTmp = GetData( m_pRWBuf.get(), m_nBufSize ); + if (m_nCryptMask) + EncryptBuffer(m_pRWBuf.get(), nCountTmp); + m_nBufActualLen = static_cast<sal_uInt16>(nCountTmp); + if( nCount > nCountTmp ) + { + nCount = nCountTmp; // trim count back, EOF see below + } + memcpy( pData, m_pRWBuf.get(), nCount ); + m_nBufActualPos = static_cast<sal_uInt16>(nCount); + m_pBufPos = m_pRWBuf.get() + nCount; + } + } + } + m_isEof = false; + m_nBufFree = m_nBufActualLen - m_nBufActualPos; + if (nCount != nSaveCount && m_nError != ERRCODE_IO_PENDING) + m_isEof = true; + if (nCount == nSaveCount && m_nError == ERRCODE_IO_PENDING) + m_nError = ERRCODE_NONE; + return nCount; +} + +std::size_t SvStream::WriteBytes( const void* pData, std::size_t nCount ) +{ + if( !nCount ) + return 0; + + if (!m_isWritable) + { + SetError( ERRCODE_IO_CANTWRITE ); + return 0; + } + + if (!m_pRWBuf) + { + if (m_nCryptMask) + nCount = CryptAndWriteBuffer( pData, nCount ); + else + nCount = PutData( pData, nCount ); + m_nBufFilePos += nCount; + return nCount; + } + + m_isIoRead = false; + m_isIoWrite = true; + if (nCount <= o3tl::make_unsigned(m_nBufSize - m_nBufActualPos)) + { + memcpy( m_pBufPos, pData, nCount ); + m_nBufActualPos = m_nBufActualPos + static_cast<sal_uInt16>(nCount); + // Update length if buffer was updated + if (m_nBufActualPos > m_nBufActualLen) + m_nBufActualLen = m_nBufActualPos; + + m_pBufPos += nCount; + m_isDirty = true; + } + else + { + FlushBuffer(); + + // Does data block fit into buffer? + if (nCount > m_nBufSize) + { + m_isIoWrite = false; + m_nBufFilePos += m_nBufActualPos; + m_nBufActualLen = 0; + m_nBufActualPos = 0; + m_pBufPos = m_pRWBuf.get(); + SeekPos(m_nBufFilePos); + if (m_nCryptMask) + nCount = CryptAndWriteBuffer( pData, nCount ); + else + nCount = PutData( pData, nCount ); + m_nBufFilePos += nCount; + } + else + { + // Copy block to buffer + memcpy( m_pRWBuf.get(), pData, nCount ); + + // Mind the order! + m_nBufFilePos += m_nBufActualPos; + m_nBufActualPos = static_cast<sal_uInt16>(nCount); + m_pBufPos = m_pRWBuf.get() + nCount; + m_nBufActualLen = static_cast<sal_uInt16>(nCount); + m_isDirty = true; + } + } + m_nBufFree = m_nBufSize - m_nBufActualPos; + return nCount; +} + +sal_uInt64 SvStream::Seek(sal_uInt64 const nFilePos) +{ + m_isIoRead = m_isIoWrite = false; + m_isEof = false; + if (!m_pRWBuf) + { + m_nBufFilePos = SeekPos( nFilePos ); + DBG_ASSERT(Tell() == m_nBufFilePos,"Out Of Sync!"); + return m_nBufFilePos; + } + + // Is seek position within buffer? + if (nFilePos >= m_nBufFilePos && nFilePos <= (m_nBufFilePos + m_nBufActualLen)) + { + m_nBufActualPos = static_cast<sal_uInt16>(nFilePos - m_nBufFilePos); + m_pBufPos = m_pRWBuf.get() + m_nBufActualPos; + // Update m_nBufFree to avoid crash upon PutBack + m_nBufFree = m_nBufActualLen - m_nBufActualPos; + } + else + { + FlushBuffer(); + m_nBufActualLen = 0; + m_nBufActualPos = 0; + m_pBufPos = m_pRWBuf.get(); + m_nBufFilePos = SeekPos( nFilePos ); + } + return m_nBufFilePos + m_nBufActualPos; +} + +bool checkSeek(SvStream &rSt, sal_uInt64 nOffset) +{ + const sal_uInt64 nMaxSeek = rSt.TellEnd(); + return (nOffset <= nMaxSeek && rSt.Seek(nOffset) == nOffset); +} + +namespace tools +{ +bool isEmptyFileUrl(const OUString& rUrl) +{ + if (!comphelper::isFileUrl(rUrl)) + { + return false; + } + + SvFileStream aStream(rUrl, StreamMode::READ); + if (!aStream.IsOpen()) + { + return false; + } + + return aStream.remainingSize() == 0; +} +} + +//STREAM_SEEK_TO_END in some of the Seek backends is special cased to be +//efficient, in others e.g. SotStorageStream it's really horribly slow, and in +//those this should be overridden +sal_uInt64 SvStream::remainingSize() +{ + sal_uInt64 const nCurr = Tell(); + sal_uInt64 const nEnd = TellEnd(); + sal_uInt64 nMaxAvailable = nEnd > nCurr ? (nEnd-nCurr) : 0; + return nMaxAvailable; +} + +sal_uInt64 SvStream::TellEnd() +{ + FlushBuffer(); + sal_uInt64 const nCurr = Tell(); + sal_uInt64 const nEnd = Seek(STREAM_SEEK_TO_END); + Seek(nCurr); + return nEnd; +} + +void SvStream::Flush() +{ + FlushBuffer(); + if (m_isWritable) + FlushData(); +} + +void SvStream::RefreshBuffer() +{ + FlushBuffer(); + SeekPos(m_nBufFilePos); + m_nBufActualLen = static_cast<sal_uInt16>(GetData( m_pRWBuf.get(), m_nBufSize )); + if (m_nBufActualLen && m_nError == ERRCODE_IO_PENDING) + m_nError = ERRCODE_NONE; + if (m_nCryptMask) + EncryptBuffer(m_pRWBuf.get(), static_cast<std::size_t>(m_nBufActualLen)); + m_isIoRead = m_isIoWrite = false; +} + +#define CRYPT_BUFSIZE 1024 + +/// Encrypt and write +std::size_t SvStream::CryptAndWriteBuffer( const void* pStart, std::size_t nLen) +{ + unsigned char pTemp[CRYPT_BUFSIZE]; + unsigned char const * pDataPtr = static_cast<unsigned char const *>(pStart); + std::size_t nCount = 0; + std::size_t nBufCount; + unsigned char nMask = m_nCryptMask; + do + { + if( nLen >= CRYPT_BUFSIZE ) + nBufCount = CRYPT_BUFSIZE; + else + nBufCount = nLen; + nLen -= nBufCount; + memcpy( pTemp, pDataPtr, static_cast<sal_uInt16>(nBufCount) ); + // ******** Encrypt ******** + for (unsigned char & rn : pTemp) + { + unsigned char aCh = rn; + aCh ^= nMask; + swapNibbles(aCh); + rn = aCh; + } + // ************************* + nCount += PutData( pTemp, nBufCount ); + pDataPtr += nBufCount; + } + while ( nLen ); + return nCount; +} + +void SvStream::EncryptBuffer(void* pStart, std::size_t nLen) const +{ + unsigned char* pTemp = static_cast<unsigned char*>(pStart); + unsigned char nMask = m_nCryptMask; + + for ( std::size_t n=0; n < nLen; n++, pTemp++ ) + { + unsigned char aCh = *pTemp; + swapNibbles(aCh); + aCh ^= nMask; + *pTemp = aCh; + } +} + +static unsigned char implGetCryptMask(const char* pStr, sal_Int32 nLen, tools::Long nVersion) +{ + unsigned char nCryptMask = 0; + + if (!nLen) + return nCryptMask; + + if( nVersion <= SOFFICE_FILEFORMAT_31 ) + { + while( nLen ) + { + nCryptMask ^= *pStr; + pStr++; + nLen--; + } + } + else // BugFix #25888# + { + for( sal_Int32 i = 0; i < nLen; i++ ) { + nCryptMask ^= pStr[i]; + if( nCryptMask & 0x80 ) { + nCryptMask <<= 1; + nCryptMask++; + } + else + nCryptMask <<= 1; + } + } + + if( !nCryptMask ) + nCryptMask = 67; + + return nCryptMask; +} + +void SvStream::SetCryptMaskKey(const OString& rCryptMaskKey) +{ + m_aCryptMaskKey = rCryptMaskKey; + m_nCryptMask = implGetCryptMask(m_aCryptMaskKey.getStr(), + m_aCryptMaskKey.getLength(), GetVersion()); +} + +bool SvStream::SetStreamSize(sal_uInt64 const nSize) +{ +#ifdef DBG_UTIL + sal_uInt64 nFPos = Tell(); +#endif + sal_uInt16 nBuf = m_nBufSize; + SetBufferSize( 0 ); + SetSize( nSize ); + if (nSize < m_nBufFilePos) + { + m_nBufFilePos = nSize; + } + SetBufferSize( nBuf ); +#ifdef DBG_UTIL + DBG_ASSERT(Tell()==nFPos,"SetStreamSize failed"); +#endif + return (m_nError == ERRCODE_NONE); +} + +SvStream& endl( SvStream& rStr ) +{ + LineEnd eDelim = rStr.GetLineDelimiter(); + if ( eDelim == LINEEND_CR ) + rStr.WriteChar('\r'); + else if( eDelim == LINEEND_LF ) + rStr.WriteChar('\n'); + else + rStr.WriteChar('\r').WriteChar('\n'); + return rStr; +} + +SvStream& endlu( SvStream& rStrm ) +{ + switch ( rStrm.GetLineDelimiter() ) + { + case LINEEND_CR : + rStrm.WriteUnicode('\r'); + break; + case LINEEND_LF : + rStrm.WriteUnicode('\n'); + break; + default: + rStrm.WriteUnicode('\r').WriteUnicode('\n'); + } + return rStrm; +} + +SvStream& endlub( SvStream& rStrm ) +{ + if ( rStrm.GetStreamCharSet() == RTL_TEXTENCODING_UNICODE ) + return endlu( rStrm ); + else + return endl( rStrm ); +} + +SvMemoryStream::SvMemoryStream( void* pBuffer, std::size_t bufSize, + StreamMode eMode ) +{ + if( eMode & StreamMode::WRITE ) + m_isWritable = true; + else + m_isWritable = false; + nEndOfData = bufSize; + bOwnsData = false; + pBuf = static_cast<sal_uInt8 *>(pBuffer); + nResize = 0; + nSize = bufSize; + nPos = 0; + SetBufferSize( 0 ); +} + +SvMemoryStream::SvMemoryStream( std::size_t nInitSize, std::size_t nResizeOffset ) +{ + m_isWritable = true; + bOwnsData = true; + nEndOfData = 0; + nResize = nResizeOffset; + nPos = 0; + pBuf = nullptr; + if( nResize != 0 && nResize < 16 ) + nResize = 16; + if( nInitSize ) + AllocateMemory( nInitSize ); + nSize = nInitSize; + SetBufferSize( 64 ); +} + +SvMemoryStream::~SvMemoryStream() +{ + if( pBuf ) + { + if( bOwnsData ) + FreeMemory(); + else + FlushBuffer(); + } +} + +void SvMemoryStream::SetBuffer( void* pNewBuf, std::size_t nCount, + std::size_t nEOF ) +{ + SetBufferSize( 0 ); // Init buffering in the base class + Seek( 0 ); + if( bOwnsData && pNewBuf != pBuf ) + FreeMemory(); + + pBuf = static_cast<sal_uInt8 *>(pNewBuf); + nPos = 0; + nSize = nCount; + nResize = 0; + bOwnsData = false; + + if( nEOF > nCount ) + nEOF = nCount; + nEndOfData = nEOF; + + ResetError(); +} + +std::size_t SvMemoryStream::GetData( void* pData, std::size_t nCount ) +{ + std::size_t nMaxCount = nEndOfData-nPos; + if( nCount > nMaxCount ) + nCount = nMaxCount; + if (nCount != 0) + { + memcpy( pData, pBuf+nPos, nCount ); + } + nPos += nCount; + return nCount; +} + +std::size_t SvMemoryStream::PutData( const void* pData, std::size_t nCount ) +{ + if( GetError() ) + return 0; + + std::size_t nMaxCount = nSize-nPos; + + // check for overflow + if( nCount > nMaxCount ) + { + if( nResize == 0 ) + { + // copy as much as possible + nCount = nMaxCount; + SetError( SVSTREAM_OUTOFMEMORY ); + } + else + { + tools::Long nNewResize; + if( nSize && nSize > nResize ) + nNewResize = nSize; + else + nNewResize = nResize; + + if( (nCount-nMaxCount) < nResize ) + { + // lacking memory is smaller than nResize, + // resize accordingly + if( !ReAllocateMemory( nNewResize) ) + { + nCount = 0; + SetError( SVSTREAM_WRITE_ERROR ); + } + } + else + { + // lacking memory is larger than nResize, + // resize by (nCount-nMaxCount) + resize offset + if( !ReAllocateMemory( nCount-nMaxCount+nNewResize ) ) + { + nCount = 0; + SetError( SVSTREAM_WRITE_ERROR ); + } + } + } + } + assert(pBuf && "Possibly Reallocate failed"); + memcpy( pBuf+nPos, pData, nCount); + + nPos += nCount; + if( nPos > nEndOfData ) + nEndOfData = nPos; + return nCount; +} + +sal_uInt64 SvMemoryStream::SeekPos(sal_uInt64 const nNewPos) +{ + // nEndOfData: First position in stream not allowed to read from + // nSize: Size of allocated buffer + + // check if a truncated STREAM_SEEK_TO_END was passed + assert(nNewPos != SAL_MAX_UINT32); + if( nNewPos < nEndOfData ) + nPos = nNewPos; + else if( nNewPos == STREAM_SEEK_TO_END ) + nPos = nEndOfData; + else + { + if( nNewPos >= nSize ) // Does buffer need extension? + { + if( nResize ) // Is extension possible? + { + tools::Long nDiff = static_cast<tools::Long>(nNewPos - nSize + 1); + nDiff += static_cast<tools::Long>(nResize); + ReAllocateMemory( nDiff ); + nPos = nNewPos; + nEndOfData = nNewPos; + } + else // Extension not possible, set pos to end of data + { + // SetError( SVSTREAM_OUTOFMEMORY ); + nPos = nEndOfData; + } + } + else // Expand buffer size + { + nPos = nNewPos; + nEndOfData = nNewPos; + } + } + return nPos; +} + +void SvMemoryStream::FlushData() +{ +} + +void SvMemoryStream::ResetError() +{ + SvStream::ClearError(); +} + +void SvMemoryStream::AllocateMemory( std::size_t nNewSize ) +{ + pBuf = new sal_uInt8[nNewSize]; +} + +// (using Bozo algorithm) +bool SvMemoryStream::ReAllocateMemory( tools::Long nDiff ) +{ + if (!m_isWritable || !bOwnsData) + return false; + + bool bRetVal = false; + tools::Long nTemp = static_cast<tools::Long>(nSize); + nTemp += nDiff; + std::size_t nNewSize = static_cast<std::size_t>(nTemp); + + if( nNewSize ) + { + sal_uInt8* pNewBuf = new sal_uInt8[nNewSize]; + + bRetVal = true; // Success! + if( nNewSize < nSize ) // Are we shrinking? + { + memcpy( pNewBuf, pBuf, nNewSize ); + if( nPos > nNewSize ) + nPos = 0; + if( nEndOfData >= nNewSize ) + nEndOfData = nNewSize-1; + } + else + { + if (nSize != 0) + { + memcpy( pNewBuf, pBuf, nSize ); + } + memset(pNewBuf + nSize, 0x00, nNewSize - nSize); + } + + FreeMemory(); + + pBuf = pNewBuf; + nSize = nNewSize; + } + else + { + bRetVal = true; + FreeMemory(); + pBuf = nullptr; + nSize = 0; + nEndOfData = 0; + nPos = 0; + } + + return bRetVal; +} + +void SvMemoryStream::FreeMemory() +{ + assert(bOwnsData); + if (bOwnsData) + { + delete[] pBuf; + pBuf = nullptr; + } +} + +void* SvMemoryStream::SwitchBuffer() +{ + FlushBuffer(); + if( !bOwnsData ) + return nullptr; + Seek( STREAM_SEEK_TO_BEGIN ); + + void* pRetVal = pBuf; + pBuf = nullptr; + nEndOfData = 0; + nResize = 64; + nPos = 0; + + ResetError(); + + std::size_t nInitSize = 512; + AllocateMemory(nInitSize); + nSize = nInitSize; + + SetBufferSize( 64 ); + return pRetVal; +} + +void SvMemoryStream::SetSize(sal_uInt64 const nNewSize) +{ + if (!m_isWritable) + { + SetError(SVSTREAM_INVALID_HANDLE); + return; + } + + tools::Long nDiff = static_cast<tools::Long>(nNewSize) - static_cast<tools::Long>(nSize); + ReAllocateMemory( nDiff ); +} + +void SvMemoryStream::MakeReadOnly() +{ + FlushBuffer(); + m_isWritable = false; + nResize = 0; + SetBufferSize( 0 ); +} + +// Create an OString of nLen bytes from rStream +// coverity[ +taint_sanitize ] +OString read_uInt8s_ToOString(SvStream& rStrm, std::size_t nLen) +{ + rtl_String *pStr = nullptr; + if (nLen) + { + nLen = std::min<std::size_t>(nLen, SAL_MAX_INT32); + //limit allocation to size of file, but + 1 to set eof state + nLen = std::min<sal_uInt64>(nLen, rStrm.remainingSize() + 1); + //alloc a (ref-count 1) rtl_String of the desired length. + //rtl_String's buffer is uninitialized, except for null termination + pStr = rtl_string_alloc(sal::static_int_cast<sal_Int32>(nLen)); + SAL_WARN_IF(!pStr, "tools.stream", "allocation failed"); + if (pStr) + { + std::size_t nWasRead = rStrm.ReadBytes(pStr->buffer, nLen); + if (nWasRead != nLen) + { + //on (typically unlikely) short read set length to what we could + //read, and null terminate. Excess buffer capacity remains of + //course, could create a (true) replacement OString if it matters. + pStr->length = sal::static_int_cast<sal_Int32>(nWasRead); + pStr->buffer[pStr->length] = 0; + } + } + } + + //take ownership of buffer and return, otherwise return empty string + return pStr ? OString(pStr, SAL_NO_ACQUIRE) : OString(); +} + +// Create an OUString of nLen sal_Unicode code units from rStream +// coverity[ +taint_sanitize ] +OUString read_uInt16s_ToOUString(SvStream& rStrm, std::size_t nLen) +{ + rtl_uString *pStr = nullptr; + if (nLen) + { + nLen = std::min<std::size_t>(nLen, SAL_MAX_INT32); + //limit allocation to size of file, but + 1 to set eof state + nLen = o3tl::sanitizing_min<sal_uInt64>(nLen, (rStrm.remainingSize() + 2) / 2); + //alloc a (ref-count 1) rtl_uString of the desired length. + //rtl_String's buffer is uninitialized, except for null termination + pStr = rtl_uString_alloc(sal::static_int_cast<sal_Int32>(nLen)); + SAL_WARN_IF(!pStr, "tools.stream", "allocation failed"); + if (pStr) + { + std::size_t nWasRead = rStrm.ReadBytes(pStr->buffer, nLen*2)/2; + if (nWasRead != nLen) + { + //on (typically unlikely) short read set length to what we could + //read, and null terminate. Excess buffer capacity remains of + //course, could create a (true) replacement OUString if it matters. + pStr->length = sal::static_int_cast<sal_Int32>(nWasRead); + pStr->buffer[pStr->length] = 0; + } + if (rStrm.IsEndianSwap()) + { + for (sal_Int32 i = 0; i < pStr->length; ++i) + pStr->buffer[i] = OSL_SWAPWORD(pStr->buffer[i]); + } + } + } + + // take ownership of buffer and return, otherwise return empty string + // coverity[tainted_data] - unhelpful untrusted loop bound + return pStr ? OUString(pStr, SAL_NO_ACQUIRE) : OUString(); +} + +namespace +{ + template <typename T, typename O> T tmpl_convertLineEnd(const T &rIn, LineEnd eLineEnd) + { + // Determine linebreaks and compute length + bool bConvert = false; // Needs conversion + sal_Int32 nStrLen = rIn.getLength(); + sal_Int32 nLineEndLen = (eLineEnd == LINEEND_CRLF) ? 2 : 1; + sal_Int32 nLen = 0; // Target length + sal_Int32 i = 0; // Source counter + + while (i < nStrLen) + { + // \r or \n causes linebreak + if ( (rIn[i] == '\r') || (rIn[i] == '\n') ) + { + nLen = nLen + nLineEndLen; + + // If set already, skip expensive test + if ( !bConvert ) + { + // Do we need to convert? + if ( ((eLineEnd != LINEEND_LF) && (rIn[i] == '\n')) || + ((eLineEnd == LINEEND_CRLF) && (i+1) < nStrLen && (rIn[i+1] != '\n')) || + ((eLineEnd == LINEEND_LF) && + ((rIn[i] == '\r') || ((i+1) < nStrLen && rIn[i+1] == '\r'))) || + ((eLineEnd == LINEEND_CR) && + ((rIn[i] == '\n') || ((i+1) < nStrLen && rIn[i+1] == '\n'))) ) + bConvert = true; + } + + // skip char if \r\n or \n\r + if ( (i+1) < nStrLen && ((rIn[i+1] == '\r') || (rIn[i+1] == '\n')) && + (rIn[i] != rIn[i+1]) ) + ++i; + } + else + ++nLen; + ++i; + } + + if (!bConvert) + return rIn; + + // convert linebreaks, insert string + O aNewData(nLen); + i = 0; + while (i < nStrLen) + { + // \r or \n causes linebreak + if ( (rIn[i] == '\r') || (rIn[i] == '\n') ) + { + if ( eLineEnd == LINEEND_CRLF ) + { + aNewData.append('\r'); + aNewData.append('\n'); + } + else + { + if ( eLineEnd == LINEEND_CR ) + aNewData.append('\r'); + else + aNewData.append('\n'); + } + + if ( (i+1) < nStrLen && ((rIn[i+1] == '\r') || (rIn[i+1] == '\n')) && + (rIn[i] != rIn[i+1]) ) + ++i; + } + else + { + aNewData.append(rIn[i]); + } + + ++i; + } + + return aNewData.makeStringAndClear(); + } +} + +OString convertLineEnd(const OString &rIn, LineEnd eLineEnd) +{ + return tmpl_convertLineEnd<OString, OStringBuffer>(rIn, eLineEnd); +} + +OUString convertLineEnd(const OUString &rIn, LineEnd eLineEnd) +{ + return tmpl_convertLineEnd<OUString, OUStringBuffer>(rIn, eLineEnd); +} + +std::size_t write_uInt32_lenPrefixed_uInt16s_FromOUString(SvStream& rStrm, + std::u16string_view rStr) +{ + std::size_t nWritten = 0; + sal_uInt32 nUnits = std::min<std::size_t>(rStr.size(), std::numeric_limits<sal_uInt32>::max()); + SAL_WARN_IF(static_cast<std::size_t>(nUnits) != static_cast<std::size_t>(rStr.size()), + "tools.stream", + "string too long for prefix count to fit in output type"); + rStrm.WriteUInt32(nUnits); + if (rStrm.good()) + { + nWritten += sizeof(sal_uInt32); + nWritten += write_uInt16s_FromOUString(rStrm, rStr, nUnits); + } + return nWritten; +} + +std::size_t write_uInt16_lenPrefixed_uInt16s_FromOUString(SvStream& rStrm, + std::u16string_view rStr) +{ + std::size_t nWritten = 0; + sal_uInt16 nUnits = std::min<std::size_t>(rStr.size(), std::numeric_limits<sal_uInt16>::max()); + SAL_WARN_IF(nUnits != rStr.size(), + "tools.stream", + "string too long for prefix count to fit in output type"); + rStrm.WriteUInt16(nUnits); + if (rStrm.good()) + { + nWritten += sizeof(nUnits); + nWritten += write_uInt16s_FromOUString(rStrm, rStr, nUnits); + } + return nWritten; +} + +std::size_t write_uInt16_lenPrefixed_uInt8s_FromOString(SvStream& rStrm, + std::string_view rStr) +{ + std::size_t nWritten = 0; + sal_uInt16 nUnits = std::min<std::size_t>(rStr.size(), std::numeric_limits<sal_uInt16>::max()); + SAL_WARN_IF(static_cast<std::size_t>(nUnits) != static_cast<std::size_t>(rStr.size()), + "tools.stream", + "string too long for sal_uInt16 count to fit in output type"); + rStrm.WriteUInt16( nUnits ); + if (rStrm.good()) + { + nWritten += sizeof(sal_uInt16); + nWritten += write_uInt8s_FromOString(rStrm, rStr, nUnits); + } + return nWritten; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/stream/strmunx.cxx b/tools/source/stream/strmunx.cxx new file mode 100644 index 0000000000..881d7a28c0 --- /dev/null +++ b/tools/source/stream/strmunx.cxx @@ -0,0 +1,476 @@ +/* -*- 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 <stdio.h> +#include <fcntl.h> +#include <errno.h> + +#include <tools/stream.hxx> +#include <map> + +#include <mutex> +#include <osl/thread.h> +#include <sal/log.hxx> + +#include <osl/file.hxx> +#include <osl/detail/file.h> + +using namespace osl; + +// InternalLock ---------------------------------------------------------------- + +namespace { + +std::mutex& LockMutex() +{ + static std::mutex SINGLETON; + return SINGLETON; +} + +std::map<SvFileStream const *, osl::DirectoryItem> gLocks; + +bool lockFile( const SvFileStream* pStream ) +{ + osl::DirectoryItem aItem; + if (osl::DirectoryItem::get( pStream->GetFileName(), aItem) != osl::FileBase::E_None ) + { + SAL_INFO("tools.stream", "Failed to lookup stream for locking"); + return true; + } + + osl::FileStatus aStatus( osl_FileStatus_Mask_Type ); + if ( aItem.getFileStatus( aStatus ) != osl::FileBase::E_None ) + { + SAL_INFO("tools.stream", "Failed to stat stream for locking"); + return true; + } + if( aStatus.getFileType() == osl::FileStatus::Directory ) + return true; + + std::unique_lock aGuard( LockMutex() ); + for( const auto& [rLockStream, rLockItem] : gLocks ) + { + if( aItem.isIdenticalTo( rLockItem ) ) + { + StreamMode nLockMode = rLockStream->GetStreamMode(); + StreamMode nNewMode = pStream->GetStreamMode(); + bool bDenyByOptions = (nLockMode & StreamMode::SHARE_DENYALL) || + ( (nLockMode & StreamMode::SHARE_DENYWRITE) && (nNewMode & StreamMode::WRITE) ) || + ( (nLockMode & StreamMode::SHARE_DENYREAD) && (nNewMode & StreamMode::READ) ); + + if( bDenyByOptions ) + { + return false; // file is already locked + } + } + } + gLocks[pStream] = aItem; + return true; +} + +void unlockFile( SvFileStream const * pStream ) +{ + std::unique_lock aGuard( LockMutex() ); + gLocks.erase(pStream); +} + +} + +static ErrCode GetSvError( int nErrno ) +{ + static struct { int nErr; ErrCode sv; } const errArr[] = + { + { 0, ERRCODE_NONE }, + { EACCES, SVSTREAM_ACCESS_DENIED }, + { EBADF, SVSTREAM_INVALID_HANDLE }, +#if defined(NETBSD) || \ + defined(FREEBSD) || defined(MACOSX) || defined(OPENBSD) || \ + defined(__FreeBSD_kernel__) || defined(DRAGONFLY) || \ + defined(IOS) || defined(HAIKU) + { EDEADLK, SVSTREAM_LOCKING_VIOLATION }, +#else + { EDEADLOCK, SVSTREAM_LOCKING_VIOLATION }, +#endif + { EINVAL, SVSTREAM_INVALID_PARAMETER }, + { EMFILE, SVSTREAM_TOO_MANY_OPEN_FILES }, + { ENFILE, SVSTREAM_TOO_MANY_OPEN_FILES }, + { ENOENT, SVSTREAM_FILE_NOT_FOUND }, + { EPERM, SVSTREAM_ACCESS_DENIED }, + { EROFS, SVSTREAM_ACCESS_DENIED }, + { EAGAIN, SVSTREAM_LOCKING_VIOLATION }, + { EISDIR, SVSTREAM_PATH_NOT_FOUND }, + { ELOOP, SVSTREAM_PATH_NOT_FOUND }, +#if !defined(NETBSD) && !defined (FREEBSD) && \ + !defined(MACOSX) && !defined(OPENBSD) && !defined(__FreeBSD_kernel__) && \ + !defined(DRAGONFLY) + { EMULTIHOP, SVSTREAM_PATH_NOT_FOUND }, + { ENOLINK, SVSTREAM_PATH_NOT_FOUND }, +#endif + { ENOTDIR, SVSTREAM_PATH_NOT_FOUND }, + { ETXTBSY, SVSTREAM_ACCESS_DENIED }, + { EEXIST, SVSTREAM_CANNOT_MAKE }, + { ENOSPC, SVSTREAM_DISK_FULL }, + { int(0xFFFF), SVSTREAM_GENERALERROR } + }; + + ErrCode nRetVal = SVSTREAM_GENERALERROR; // default error + int i=0; + do + { + if ( errArr[i].nErr == nErrno ) + { + nRetVal = errArr[i].sv; + break; + } + ++i; + } + while( errArr[i].nErr != 0xFFFF ); + return nRetVal; +} + +static ErrCode GetSvError( oslFileError nErrno ) +{ + static struct { oslFileError nErr; ErrCode sv; } const errArr[] = + { + { osl_File_E_None, ERRCODE_NONE }, + { osl_File_E_ACCES, SVSTREAM_ACCESS_DENIED }, + { osl_File_E_BADF, SVSTREAM_INVALID_HANDLE }, + { osl_File_E_DEADLK, SVSTREAM_LOCKING_VIOLATION }, + { osl_File_E_INVAL, SVSTREAM_INVALID_PARAMETER }, + { osl_File_E_MFILE, SVSTREAM_TOO_MANY_OPEN_FILES }, + { osl_File_E_NFILE, SVSTREAM_TOO_MANY_OPEN_FILES }, + { osl_File_E_NOENT, SVSTREAM_FILE_NOT_FOUND }, + { osl_File_E_PERM, SVSTREAM_ACCESS_DENIED }, + { osl_File_E_ROFS, SVSTREAM_ACCESS_DENIED }, + { osl_File_E_AGAIN, SVSTREAM_LOCKING_VIOLATION }, + { osl_File_E_ISDIR, SVSTREAM_PATH_NOT_FOUND }, + { osl_File_E_LOOP, SVSTREAM_PATH_NOT_FOUND }, + { osl_File_E_MULTIHOP, SVSTREAM_PATH_NOT_FOUND }, + { osl_File_E_NOLINK, SVSTREAM_PATH_NOT_FOUND }, + { osl_File_E_NOTDIR, SVSTREAM_PATH_NOT_FOUND }, + { osl_File_E_EXIST, SVSTREAM_CANNOT_MAKE }, + { osl_File_E_NOSPC, SVSTREAM_DISK_FULL }, + { oslFileError(0xFFFF), SVSTREAM_GENERALERROR } + }; + + ErrCode nRetVal = SVSTREAM_GENERALERROR; // default error + int i=0; + do + { + if ( errArr[i].nErr == nErrno ) + { + nRetVal = errArr[i].sv; + break; + } + ++i; + } + while( errArr[i].nErr != oslFileError(0xFFFF) ); + return nRetVal; +} + +SvFileStream::SvFileStream( const OUString& rFileName, StreamMode nOpenMode ) +{ + bIsOpen = false; + m_isWritable = false; + + SetBufferSize( 1024 ); + // convert URL to SystemPath, if necessary + OUString aSystemFileName; + if( FileBase::getSystemPathFromFileURL( rFileName , aSystemFileName ) + != FileBase::E_None ) + { + aSystemFileName = rFileName; + } + Open( aSystemFileName, nOpenMode ); +} + +SvFileStream::SvFileStream() +{ + bIsOpen = false; + m_isWritable = false; + SetBufferSize( 1024 ); +} + +SvFileStream::~SvFileStream() +{ + Close(); +} + +std::size_t SvFileStream::GetData( void* pData, std::size_t nSize ) +{ + SAL_INFO("tools", OString::number(static_cast<sal_Int64>(nSize)) << " Bytes from " << aFilename); + + sal_uInt64 nRead = 0; + if ( IsOpen() ) + { + oslFileError rc = osl_readFile(mxFileHandle,pData,static_cast<sal_uInt64>(nSize),&nRead); + if ( rc != osl_File_E_None ) + { + SetError( ::GetSvError( rc )); + return -1; + } + } + return static_cast<std::size_t>(nRead); +} + +std::size_t SvFileStream::PutData( const void* pData, std::size_t nSize ) +{ + SAL_INFO("tools", OString::number(static_cast<sal_Int64>(nSize)) << " Bytes to " << aFilename); + + sal_uInt64 nWrite = 0; + if ( IsOpen() ) + { + oslFileError rc = osl_writeFile(mxFileHandle,pData,static_cast<sal_uInt64>(nSize),&nWrite); + if ( rc != osl_File_E_None ) + { + SetError( ::GetSvError( rc ) ); + return -1; + } + else if( !nWrite ) + SetError( SVSTREAM_DISK_FULL ); + } + return static_cast<std::size_t>(nWrite); +} + +sal_uInt64 SvFileStream::SeekPos(sal_uInt64 const nPos) +{ + // check if a truncated STREAM_SEEK_TO_END was passed + assert(nPos != sal_uInt64(sal_uInt32(STREAM_SEEK_TO_END))); + if ( IsOpen() ) + { + oslFileError rc; + sal_uInt64 nNewPos; + if ( nPos != STREAM_SEEK_TO_END ) + rc = osl_setFilePos( mxFileHandle, osl_Pos_Absolut, nPos ); + else + rc = osl_setFilePos( mxFileHandle, osl_Pos_End, 0 ); + + if ( rc != osl_File_E_None ) + { + SetError( SVSTREAM_SEEK_ERROR ); + return 0; + } + if ( nPos != STREAM_SEEK_TO_END ) + return nPos; + osl_getFilePos( mxFileHandle, &nNewPos ); + return nNewPos; + } + SetError( SVSTREAM_GENERALERROR ); + return 0; +} + +void SvFileStream::FlushData() +{ + auto rc = osl_syncFile(mxFileHandle); + if (rc != osl_File_E_None) + SetError( ::GetSvError( rc )); + } + +bool SvFileStream::LockFile() +{ + int nLockMode = 0; + + if ( ! IsOpen() ) + return false; + + if (m_eStreamMode & StreamMode::SHARE_DENYALL) + { + if (m_isWritable) + nLockMode = F_WRLCK; + else + nLockMode = F_RDLCK; + } + + if (m_eStreamMode & StreamMode::SHARE_DENYREAD) + { + if (m_isWritable) + nLockMode = F_WRLCK; + else + { + SetError(SVSTREAM_LOCKING_VIOLATION); + return false; + } + } + + if (m_eStreamMode & StreamMode::SHARE_DENYWRITE) + { + if (m_isWritable) + nLockMode = F_WRLCK; + else + nLockMode = F_RDLCK; + } + + if (!nLockMode) + return true; + + if( !lockFile( this ) ) + { + SAL_WARN("tools.stream", "InternalLock on " << aFilename << "failed"); + return false; + } + + return true; +} + +void SvFileStream::UnlockFile() +{ + if ( ! IsOpen() ) + return; + + unlockFile( this ); +} + +void SvFileStream::Open( const OUString& rFilename, StreamMode nOpenMode ) +{ + sal_uInt32 uFlags; + oslFileHandle nHandleTmp; + + Close(); + errno = 0; + m_eStreamMode = nOpenMode; + m_eStreamMode &= ~StreamMode::TRUNC; // don't truncate on reopen + + aFilename = rFilename; + + SAL_INFO("tools", aFilename); + + OUString aFileURL; + osl::DirectoryItem aItem; + osl::FileStatus aStatus( osl_FileStatus_Mask_Type | osl_FileStatus_Mask_LinkTargetURL ); + + // FIXME: we really need to switch to a pure URL model ... + if ( osl::File::getFileURLFromSystemPath( aFilename, aFileURL ) != osl::FileBase::E_None ) + aFileURL = aFilename; + + // don't both stat()ing a temporary file, unnecessary + bool bStatValid = true; + if (!(nOpenMode & StreamMode::TEMPORARY)) + { + bStatValid = ( osl::DirectoryItem::get( aFileURL, aItem) == osl::FileBase::E_None && + aItem.getFileStatus( aStatus ) == osl::FileBase::E_None ); + + // SvFileStream can't open a directory + if( bStatValid && aStatus.getFileType() == osl::FileStatus::Directory ) + { + SetError( ::GetSvError( EISDIR ) ); + return; + } + } + + if ( !( nOpenMode & StreamMode::WRITE ) ) + uFlags = osl_File_OpenFlag_Read; + else if ( !( nOpenMode & StreamMode::READ ) ) + uFlags = osl_File_OpenFlag_Write; + else + uFlags = osl_File_OpenFlag_Read | osl_File_OpenFlag_Write; + + // Fix (MDA, 18.01.95): Don't open with O_CREAT upon RD_ONLY + // Important for Read-Only-Filesystems (e.g, CDROM) + if ( (!( nOpenMode & StreamMode::NOCREATE )) && ( uFlags != osl_File_OpenFlag_Read ) ) + uFlags |= osl_File_OpenFlag_Create; + if ( nOpenMode & StreamMode::TRUNC ) + uFlags |= osl_File_OpenFlag_Trunc; + + uFlags |= osl_File_OpenFlag_NoExcl | osl_File_OpenFlag_NoLock; + + if ( nOpenMode & StreamMode::WRITE) + { + if ( nOpenMode & StreamMode::COPY_ON_SYMLINK ) + { + if ( bStatValid && aStatus.getFileType() == osl::FileStatus::Link && + aStatus.getLinkTargetURL().getLength() > 0 ) + { + // delete the symbolic link, and replace it with the contents of the link + if (osl::File::remove( aFileURL ) == osl::FileBase::E_None ) + { + File::copy( aStatus.getLinkTargetURL(), aFileURL ); + SAL_INFO("tools.stream", + "Removing link and replacing with file contents (" << + aStatus.getLinkTargetURL() << ") -> (" << aFileURL << ")."); + } + } + } + } + + oslFileError rc = osl_openFile( aFileURL.pData, &nHandleTmp, uFlags ); + if ( rc != osl_File_E_None ) + { + if ( uFlags & osl_File_OpenFlag_Write ) + { + // Change to read-only + uFlags &= ~osl_File_OpenFlag_Write; + rc = osl_openFile( aFileURL.pData, &nHandleTmp, uFlags ); + } + } + if ( rc == osl_File_E_None ) + { + mxFileHandle = nHandleTmp; + bIsOpen = true; + if ( uFlags & osl_File_OpenFlag_Write ) + m_isWritable = true; + + if ( !LockFile() ) // whole file + { + osl_closeFile( nHandleTmp ); + bIsOpen = false; + m_isWritable = false; + mxFileHandle = nullptr; + } + } + else + SetError( ::GetSvError( rc ) ); +} + +void SvFileStream::Close() +{ + UnlockFile(); + + if ( IsOpen() ) + { + SAL_INFO("tools", "Closing " << aFilename); + FlushBuffer(); + osl_closeFile( mxFileHandle ); + mxFileHandle = nullptr; + } + + bIsOpen = false; + m_isWritable = false; + SvStream::ClearBuffer(); + SvStream::ClearError(); +} + +/// set filepointer to beginning of file +void SvFileStream::ResetError() +{ + SvStream::ClearError(); +} + +void SvFileStream::SetSize (sal_uInt64 const nSize) +{ + if (IsOpen()) + { + oslFileError rc = osl_setFileSize( mxFileHandle, nSize ); + if (rc != osl_File_E_None ) + { + SetError ( ::GetSvError( rc )); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/stream/strmwnt.cxx b/tools/source/stream/strmwnt.cxx new file mode 100644 index 0000000000..d7d3a73ed2 --- /dev/null +++ b/tools/source/stream/strmwnt.cxx @@ -0,0 +1,415 @@ +/* -*- 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 . + */ + +// TODO: StreamMode <-> AllocateMemory + +#include <string.h> +#include <limits.h> + +#ifdef _WIN32 +#if !defined WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> +#endif + +#include <osl/thread.h> +#include <tools/stream.hxx> +#include <o3tl/char16_t2wchar_t.hxx> + +#include <osl/file.hxx> +using namespace osl; + +static ErrCode GetSvError( DWORD nWntError ) +{ + static struct { DWORD wnt; ErrCode sv; } errArr[] = + { + { ERROR_SUCCESS, ERRCODE_NONE }, + { ERROR_ACCESS_DENIED, SVSTREAM_ACCESS_DENIED }, + { ERROR_ACCOUNT_DISABLED, SVSTREAM_ACCESS_DENIED }, + { ERROR_ACCOUNT_EXPIRED, SVSTREAM_ACCESS_DENIED }, + { ERROR_ACCOUNT_RESTRICTION, SVSTREAM_ACCESS_DENIED }, + { ERROR_ATOMIC_LOCKS_NOT_SUPPORTED, SVSTREAM_INVALID_PARAMETER }, + { ERROR_BAD_PATHNAME, SVSTREAM_PATH_NOT_FOUND }, + // Filename too long + { ERROR_BUFFER_OVERFLOW, SVSTREAM_INVALID_PARAMETER }, + { ERROR_DIRECTORY, SVSTREAM_INVALID_PARAMETER }, + { ERROR_DRIVE_LOCKED, SVSTREAM_LOCKING_VIOLATION }, + { ERROR_FILE_NOT_FOUND, SVSTREAM_FILE_NOT_FOUND }, + { ERROR_FILENAME_EXCED_RANGE, SVSTREAM_INVALID_PARAMETER }, + { ERROR_INVALID_ACCESS, SVSTREAM_INVALID_ACCESS }, + { ERROR_INVALID_DRIVE, SVSTREAM_PATH_NOT_FOUND }, + { ERROR_INVALID_HANDLE, SVSTREAM_INVALID_HANDLE }, + { ERROR_INVALID_NAME, SVSTREAM_PATH_NOT_FOUND }, + { ERROR_INVALID_PARAMETER, SVSTREAM_INVALID_PARAMETER }, + { ERROR_IS_SUBST_PATH, SVSTREAM_INVALID_PARAMETER }, + { ERROR_IS_SUBST_TARGET, SVSTREAM_INVALID_PARAMETER }, + { ERROR_LOCK_FAILED, SVSTREAM_LOCKING_VIOLATION }, + { ERROR_LOCK_VIOLATION, SVSTREAM_LOCKING_VIOLATION }, + { ERROR_NEGATIVE_SEEK, SVSTREAM_SEEK_ERROR }, + { ERROR_PATH_NOT_FOUND, SVSTREAM_PATH_NOT_FOUND }, + { ERROR_READ_FAULT, SVSTREAM_READ_ERROR }, + { ERROR_SEEK, SVSTREAM_SEEK_ERROR }, + { ERROR_SEEK_ON_DEVICE, SVSTREAM_SEEK_ERROR }, + { ERROR_SHARING_BUFFER_EXCEEDED,SVSTREAM_SHARE_BUFF_EXCEEDED }, + { ERROR_SHARING_PAUSED, SVSTREAM_SHARING_VIOLATION }, + { ERROR_SHARING_VIOLATION, SVSTREAM_SHARING_VIOLATION }, + { ERROR_TOO_MANY_OPEN_FILES, SVSTREAM_TOO_MANY_OPEN_FILES }, + { ERROR_WRITE_FAULT, SVSTREAM_WRITE_ERROR }, + { ERROR_WRITE_PROTECT, SVSTREAM_ACCESS_DENIED }, + { ERROR_DISK_FULL, SVSTREAM_DISK_FULL }, + + { DWORD(0xFFFFFFFF), SVSTREAM_GENERALERROR } + }; + + ErrCode nRetVal = SVSTREAM_GENERALERROR; // default error + int i=0; + do + { + if( errArr[i].wnt == nWntError ) + { + nRetVal = errArr[i].sv; + break; + } + i++; + } while( errArr[i].wnt != DWORD(0xFFFFFFFF) ); + return nRetVal; +} + +SvFileStream::SvFileStream( const OUString& rFileName, StreamMode nMode ) +{ + bIsOpen = false; + nLockCounter = 0; + m_isWritable = false; + + SetBufferSize( 8192 ); + // convert URL to SystemPath, if necessary + OUString aFileName; + + if ( FileBase::getSystemPathFromFileURL( rFileName, aFileName ) != FileBase::E_None ) + aFileName = rFileName; + Open( aFileName, nMode ); +} + +SvFileStream::SvFileStream() +{ + bIsOpen = false; + nLockCounter = 0; + m_isWritable = false; + + SetBufferSize( 8192 ); +} + +SvFileStream::~SvFileStream() +{ + Close(); +} + +/// Does not check for EOF, makes isEof callable +std::size_t SvFileStream::GetData( void* pData, std::size_t nSize ) +{ + DWORD nCount = 0; + if( IsOpen() ) + { + bool bResult = ReadFile(mxFileHandle,pData,nSize,&nCount,nullptr); + if( !bResult ) + { + std::size_t nTestError = GetLastError(); + SetError(::GetSvError( nTestError ) ); + } + } + return nCount; +} + +std::size_t SvFileStream::PutData( const void* pData, std::size_t nSize ) +{ + DWORD nCount = 0; + if( IsOpen() ) + { + if(!WriteFile(mxFileHandle,pData,nSize,&nCount,nullptr)) + SetError(::GetSvError( GetLastError() ) ); + } + return nCount; +} + +sal_uInt64 SvFileStream::SeekPos(sal_uInt64 const nPos) +{ + // check if a truncated STREAM_SEEK_TO_END was passed + assert(nPos != SAL_MAX_UINT32); + LARGE_INTEGER nNewPos, nActPos; + nNewPos.QuadPart = 0; + nActPos.QuadPart = nPos; + bool result = false; + if( IsOpen() ) + { + if( nPos != STREAM_SEEK_TO_END ) + { + result = SetFilePointerEx(mxFileHandle, nActPos, &nNewPos, FILE_BEGIN); + } + else + { + result = SetFilePointerEx(mxFileHandle, nNewPos, &nNewPos, FILE_END); + } + if (!result) + { + SetError(::GetSvError(GetLastError())); + return 0; + } + } + else + SetError( SVSTREAM_GENERALERROR ); + return static_cast<sal_uInt64>(nNewPos.QuadPart); +} + +void SvFileStream::FlushData() +{ + if( IsOpen() ) + { + if( !FlushFileBuffers(mxFileHandle) ) + SetError(::GetSvError(GetLastError())); + } +} + +bool SvFileStream::LockFile() +{ + bool bRetVal = false; + if( !nLockCounter ) + { + if( IsOpen() ) + { + bRetVal = ::LockFile(mxFileHandle,0L,0L,LONG_MAX,0L ); + if( bRetVal ) + { + nLockCounter = 1; + } + else + SetError(::GetSvError(GetLastError())); + } + } + else + { + nLockCounter++; + bRetVal = true; + } + return bRetVal; +} + +void SvFileStream::UnlockFile() +{ + if( nLockCounter > 0) + { + if( nLockCounter == 1) + { + if( IsOpen() ) + { + if( ::UnlockFile(mxFileHandle,0L,0L,LONG_MAX,0L ) ) + { + nLockCounter = 0; + } + else + SetError(::GetSvError(GetLastError())); + } + } + else + { + nLockCounter--; + } + } +} + +/* + NOCREATE TRUNC NT-Action + ---------------------------------------------- + 0 (Create) 0 OPEN_ALWAYS + 0 (Create) 1 CREATE_ALWAYS + 1 0 OPEN_EXISTING + 1 1 TRUNCATE_EXISTING +*/ +void SvFileStream::Open( const OUString& rFilename, StreamMode nMode ) +{ + OUString aParsedFilename(rFilename); + + SetLastError( ERROR_SUCCESS ); + Close(); + SvStream::ClearBuffer(); + + m_eStreamMode = nMode; + m_eStreamMode &= ~StreamMode::TRUNC; // don't truncate on reopen + + aFilename = aParsedFilename; + SetLastError( ERROR_SUCCESS ); // might be changed by Redirector + + DWORD nOpenAction; + DWORD nShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; + DWORD nAccessMode = 0; + UINT nOldErrorMode = SetErrorMode( SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX ); + + if( nMode & StreamMode::SHARE_DENYREAD) + nShareMode &= ~FILE_SHARE_READ; + + if( nMode & StreamMode::SHARE_DENYWRITE) + nShareMode &= ~FILE_SHARE_WRITE; + + if( nMode & StreamMode::SHARE_DENYALL) + nShareMode = 0; + + if( nMode & StreamMode::READ ) + nAccessMode |= GENERIC_READ; + if( nMode & StreamMode::WRITE ) + nAccessMode |= GENERIC_WRITE; + + if( nAccessMode == GENERIC_READ ) // ReadOnly ? + nMode |= StreamMode::NOCREATE; // Don't create if readonly + + // Assignment based on true/false table above + if( !(nMode & StreamMode::NOCREATE) ) + { + if( nMode & StreamMode::TRUNC ) + nOpenAction = CREATE_ALWAYS; + else + nOpenAction = OPEN_ALWAYS; + } + else + { + if( nMode & StreamMode::TRUNC ) + nOpenAction = TRUNCATE_EXISTING; + else + nOpenAction = OPEN_EXISTING; + } + + DWORD nAttributes = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS; + + if ( nMode & StreamMode::TEMPORARY ) + nAttributes |= FILE_ATTRIBUTE_TEMPORARY; + if ( nMode & StreamMode::DELETE_ON_CLOSE ) + nAttributes |= FILE_FLAG_DELETE_ON_CLOSE; + + mxFileHandle = CreateFileW( + o3tl::toW(aFilename.getStr()), + nAccessMode, + nShareMode, + nullptr, + nOpenAction, + nAttributes, + nullptr + ); + + if( mxFileHandle!=INVALID_HANDLE_VALUE && ( + // Did Create Always overwrite a file? + GetLastError() == ERROR_ALREADY_EXISTS || + // Did Create Always open a new file? + GetLastError() == ERROR_FILE_NOT_FOUND )) + { + // If so, no error + if( nOpenAction == OPEN_ALWAYS || nOpenAction == CREATE_ALWAYS ) + SetLastError( ERROR_SUCCESS ); + } + + // Otherwise, determine if we're allowed to read + if( (mxFileHandle==INVALID_HANDLE_VALUE) && + (nAccessMode & GENERIC_WRITE)) + { + ErrCode nErr = ::GetSvError( GetLastError() ); + if(nErr==SVSTREAM_ACCESS_DENIED || nErr==SVSTREAM_SHARING_VIOLATION) + { + nMode &= ~StreamMode::WRITE; + nAccessMode = GENERIC_READ; + // OV, 28.1.97: Win32 sets file to length 0 + // if Openaction is CREATE_ALWAYS + nOpenAction = OPEN_EXISTING; + SetLastError( ERROR_SUCCESS ); + mxFileHandle = CreateFileW( + o3tl::toW(aFilename.getStr()), + GENERIC_READ, + nShareMode, + nullptr, + nOpenAction, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS, + nullptr + ); + if( GetLastError() == ERROR_ALREADY_EXISTS ) + SetLastError( ERROR_SUCCESS ); + } + } + + if( GetLastError() != ERROR_SUCCESS ) + { + bIsOpen = false; + SetError(::GetSvError( GetLastError() ) ); + } + else + { + bIsOpen = true; + // pInstanceData->bIsEof = false; + if( nAccessMode & GENERIC_WRITE ) + m_isWritable = true; + } + SetErrorMode( nOldErrorMode ); +} + +void SvFileStream::Close() +{ + if( IsOpen() ) + { + if( nLockCounter ) + { + nLockCounter = 1; + UnlockFile(); + } + FlushBuffer(); + CloseHandle( mxFileHandle ); + } + bIsOpen = false; + nLockCounter= 0; + m_isWritable = false; + SvStream::ClearBuffer(); + SvStream::ClearError(); +} + +/// Reset filepointer to beginning of file +void SvFileStream::ResetError() +{ + SvStream::ClearError(); +} + +void SvFileStream::SetSize(sal_uInt64 const nSize) +{ + + if( IsOpen() ) + { + bool bError = false; + HANDLE hFile = mxFileHandle; + DWORD const nOld = SetFilePointer( hFile, 0L, nullptr, FILE_CURRENT ); + if( nOld != 0xffffffff ) + { + if( SetFilePointer(hFile,nSize,nullptr,FILE_BEGIN ) != 0xffffffff) + { + bool bSucc = SetEndOfFile( hFile ); + if( !bSucc ) + bError = true; + } + if( SetFilePointer( hFile,nOld,nullptr,FILE_BEGIN ) == 0xffffffff) + bError = true; + } + if( bError ) + SetError(::GetSvError( GetLastError() ) ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/stream/vcompat.cxx b/tools/source/stream/vcompat.cxx new file mode 100644 index 0000000000..bf85c0c9a4 --- /dev/null +++ b/tools/source/stream/vcompat.cxx @@ -0,0 +1,68 @@ +/* -*- 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 <tools/stream.hxx> +#include <tools/vcompat.hxx> + +VersionCompatRead::VersionCompatRead(SvStream& rStm) + : mrRStm(rStm) + , mnCompatPos(0) + , mnTotalSize(0) + , mnVersion(1) +{ + if (mrRStm.GetError()) + return; + + mrRStm.ReadUInt16(mnVersion); + mrRStm.ReadUInt32(mnTotalSize); + mnCompatPos = mrRStm.Tell(); +} + +VersionCompatWrite::VersionCompatWrite(SvStream& rStm, sal_uInt16 nVersion) + : mrWStm(rStm) + , mnCompatPos(0) + , mnTotalSize(0) +{ + if (mrWStm.GetError()) + return; + + mrWStm.WriteUInt16(nVersion); + mnCompatPos = mrWStm.Tell(); + mnTotalSize = mnCompatPos + 4; + mrWStm.SeekRel(4); +} + +VersionCompatRead::~VersionCompatRead() +{ + const sal_uInt32 nReadSize = mrRStm.Tell() - mnCompatPos; + + if (mnTotalSize > nReadSize) + mrRStm.SeekRel(mnTotalSize - nReadSize); +} + +VersionCompatWrite::~VersionCompatWrite() +{ + const sal_uInt32 nEndPos = mrWStm.Tell(); + + mrWStm.Seek(mnCompatPos); + mrWStm.WriteUInt32(nEndPos - mnTotalSize); + mrWStm.Seek(nEndPos); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/string/tenccvt.cxx b/tools/source/string/tenccvt.cxx new file mode 100644 index 0000000000..152200a353 --- /dev/null +++ b/tools/source/string/tenccvt.cxx @@ -0,0 +1,79 @@ +/* -*- 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 <rtl/tencinfo.h> +#include <tools/tenccvt.hxx> + +rtl_TextEncoding GetExtendedCompatibilityTextEncoding( rtl_TextEncoding eEncoding ) +{ + // Latin1 + if ( eEncoding == RTL_TEXTENCODING_ISO_8859_1 ) + return RTL_TEXTENCODING_MS_1252; + // Turkey + else if ( eEncoding == RTL_TEXTENCODING_ISO_8859_9 ) + return RTL_TEXTENCODING_MS_1254; + else + return eEncoding; +} + +rtl_TextEncoding GetExtendedTextEncoding( rtl_TextEncoding eEncoding ) +{ + // Cyr + if ( eEncoding == RTL_TEXTENCODING_ISO_8859_5 ) + return RTL_TEXTENCODING_MS_1251; + // Greek (2 Characters different: A1 (0x2018/0x0385), A2 (0x2019/0x0386) - + // so it is handled in this function and not in GetExtendedCompatibilityTextEncoding) + else if ( eEncoding == RTL_TEXTENCODING_ISO_8859_7 ) + return RTL_TEXTENCODING_MS_1253; + // East-Europe - Latin2 + else if ( eEncoding == RTL_TEXTENCODING_ISO_8859_2 ) + return RTL_TEXTENCODING_MS_1250; + // Latin-15 - Latin 1 with euro sign + else if ( eEncoding == RTL_TEXTENCODING_ISO_8859_15 ) + return RTL_TEXTENCODING_MS_1252; + else + return GetExtendedCompatibilityTextEncoding( eEncoding ); +} + +rtl_TextEncoding GetOneByteTextEncoding( rtl_TextEncoding eEncoding ) +{ + rtl_TextEncodingInfo aTextEncInfo; + aTextEncInfo.StructSize = sizeof( aTextEncInfo ); + if ( rtl_getTextEncodingInfo( eEncoding, &aTextEncInfo ) ) + { + if ( aTextEncInfo.MaximumCharSize > 1 ) + return RTL_TEXTENCODING_MS_1252; + else + return eEncoding; + } + else + return RTL_TEXTENCODING_MS_1252; +} + +rtl_TextEncoding GetSOLoadTextEncoding( rtl_TextEncoding eEncoding ) +{ + return GetExtendedCompatibilityTextEncoding( GetOneByteTextEncoding( eEncoding ) ); +} + +rtl_TextEncoding GetSOStoreTextEncoding( rtl_TextEncoding eEncoding ) +{ + return GetExtendedTextEncoding( GetOneByteTextEncoding( eEncoding ) ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/xml/XmlWalker.cxx b/tools/source/xml/XmlWalker.cxx new file mode 100644 index 0000000000..c0e8a2abef --- /dev/null +++ b/tools/source/xml/XmlWalker.cxx @@ -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/. + */ + +#include <tools/stream.hxx> +#include <tools/XmlWalker.hxx> + +#include <libxml/tree.h> +#include <libxml/parser.h> +#include <libxml/xmlstring.h> +#include <vector> + +namespace tools +{ +struct XmlWalkerImpl +{ + XmlWalkerImpl() + : mpDocPtr(nullptr) + , mpRoot(nullptr) + , mpCurrent(nullptr) + { + } + + xmlDocPtr mpDocPtr; + xmlNodePtr mpRoot; + xmlNodePtr mpCurrent; + + std::vector<xmlNodePtr> mpStack; +}; + +XmlWalker::XmlWalker() + : mpImpl(std::make_unique<XmlWalkerImpl>()) +{ +} + +XmlWalker::~XmlWalker() +{ + if (mpImpl) + xmlFreeDoc(mpImpl->mpDocPtr); +} + +bool XmlWalker::open(SvStream* pStream) +{ + std::size_t nSize = pStream->remainingSize(); + std::vector<sal_uInt8> aBuffer(nSize + 1); + pStream->ReadBytes(aBuffer.data(), nSize); + aBuffer[nSize] = 0; + mpImpl->mpDocPtr = xmlParseDoc(reinterpret_cast<xmlChar*>(aBuffer.data())); + if (mpImpl->mpDocPtr == nullptr) + return false; + mpImpl->mpRoot = xmlDocGetRootElement(mpImpl->mpDocPtr); + mpImpl->mpCurrent = mpImpl->mpRoot; + mpImpl->mpStack.push_back(mpImpl->mpCurrent); + return true; +} + +OString XmlWalker::name() { return reinterpret_cast<const char*>(mpImpl->mpCurrent->name); } + +OString XmlWalker::namespaceHref() +{ + return reinterpret_cast<const char*>(mpImpl->mpCurrent->ns->href); +} + +OString XmlWalker::namespacePrefix() +{ + return reinterpret_cast<const char*>(mpImpl->mpCurrent->ns->prefix); +} + +OString XmlWalker::content() +{ + OString aContent; + if (mpImpl->mpCurrent->xmlChildrenNode != nullptr) + { + xmlChar* pContent + = xmlNodeListGetString(mpImpl->mpDocPtr, mpImpl->mpCurrent->xmlChildrenNode, 1); + aContent = OString(reinterpret_cast<const char*>(pContent)); + xmlFree(pContent); + } + return aContent; +} + +void XmlWalker::children() +{ + mpImpl->mpStack.push_back(mpImpl->mpCurrent); + mpImpl->mpCurrent = mpImpl->mpCurrent->xmlChildrenNode; +} + +void XmlWalker::parent() +{ + mpImpl->mpCurrent = mpImpl->mpStack.back(); + mpImpl->mpStack.pop_back(); +} + +OString XmlWalker::attribute(const OString& sName) const +{ + xmlChar* xmlAttribute + = xmlGetProp(mpImpl->mpCurrent, reinterpret_cast<const xmlChar*>(sName.getStr())); + OString aAttributeContent( + xmlAttribute == nullptr ? "" : reinterpret_cast<const char*>(xmlAttribute)); + xmlFree(xmlAttribute); + + return aAttributeContent; +} + +void XmlWalker::next() { mpImpl->mpCurrent = mpImpl->mpCurrent->next; } + +bool XmlWalker::isValid() const { return mpImpl->mpCurrent != nullptr; } + +} // end tools namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/xml/XmlWriter.cxx b/tools/source/xml/XmlWriter.cxx new file mode 100644 index 0000000000..424b13c67b --- /dev/null +++ b/tools/source/xml/XmlWriter.cxx @@ -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/. + */ + +#include <tools/stream.hxx> +#include <tools/XmlWriter.hxx> + +#include <libxml/xmlwriter.h> + +namespace tools +{ +namespace +{ +int funcWriteCallback(void* pContext, const char* sBuffer, int nLen) +{ + SvStream* pStream = static_cast<SvStream*>(pContext); + return static_cast<int>(pStream->WriteBytes(sBuffer, nLen)); +} + +int funcCloseCallback(void* pContext) +{ + SvStream* pStream = static_cast<SvStream*>(pContext); + pStream->Flush(); + return 0; // 0 or -1 in case of error +} + +} // end anonymous namespace + +struct XmlWriterImpl +{ + XmlWriterImpl(SvStream* pStream) + : mpStream(pStream) + , mpWriter(nullptr) + , mbWriteXmlHeader(true) + { + } + + SvStream* mpStream; + xmlTextWriterPtr mpWriter; + bool mbWriteXmlHeader; +}; + +XmlWriter::XmlWriter(SvStream* pStream) + : mpImpl(std::make_unique<XmlWriterImpl>(pStream)) +{ +} + +XmlWriter::~XmlWriter() +{ + if (mpImpl && mpImpl->mpWriter != nullptr) + endDocument(); +} + +bool XmlWriter::startDocument(sal_Int32 nIndent, bool bWriteXmlHeader) +{ + mpImpl->mbWriteXmlHeader = bWriteXmlHeader; + xmlCharEncodingHandlerPtr pEncodingHandler = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_UTF8); + xmlOutputBufferPtr xmlOutBuffer = xmlOutputBufferCreateIO(funcWriteCallback, funcCloseCallback, + mpImpl->mpStream, pEncodingHandler); + mpImpl->mpWriter = xmlNewTextWriter(xmlOutBuffer); + if (mpImpl->mpWriter == nullptr) + return false; + xmlTextWriterSetIndent(mpImpl->mpWriter, nIndent); + if (mpImpl->mbWriteXmlHeader) + (void)xmlTextWriterStartDocument(mpImpl->mpWriter, nullptr, "UTF-8", nullptr); + return true; +} + +void XmlWriter::endDocument() +{ + if (mpImpl->mbWriteXmlHeader) + (void)xmlTextWriterEndDocument(mpImpl->mpWriter); + xmlFreeTextWriter(mpImpl->mpWriter); + mpImpl->mpWriter = nullptr; +} + +void XmlWriter::startElement(const OString& sPrefix, const OString& sName, + const OString& sNamespaceUri) +{ + xmlChar* xmlName = BAD_CAST(sName.getStr()); + xmlChar* xmlPrefix = nullptr; + xmlChar* xmlNamespaceUri = nullptr; + if (!sPrefix.isEmpty()) + xmlPrefix = BAD_CAST(sPrefix.getStr()); + if (!sNamespaceUri.isEmpty()) + xmlNamespaceUri = BAD_CAST(sNamespaceUri.getStr()); + + (void)xmlTextWriterStartElementNS(mpImpl->mpWriter, xmlPrefix, xmlName, xmlNamespaceUri); +} + +void XmlWriter::startElement(const char* pName) +{ + xmlChar* xmlName = BAD_CAST(pName); + (void)xmlTextWriterStartElement(mpImpl->mpWriter, xmlName); +} + +void XmlWriter::startElement(const OString& rName) +{ + xmlChar* xmlName = BAD_CAST(rName.getStr()); + (void)xmlTextWriterStartElement(mpImpl->mpWriter, xmlName); +} + +void XmlWriter::endElement() { (void)xmlTextWriterEndElement(mpImpl->mpWriter); } + +void XmlWriter::attributeBase64(const char* pName, std::vector<sal_uInt8> const& rValueInBytes) +{ + std::vector<char> aSignedBytes(rValueInBytes.begin(), rValueInBytes.end()); + attributeBase64(pName, aSignedBytes); +} + +void XmlWriter::attributeBase64(const char* pName, std::vector<char> const& rValueInBytes) +{ + xmlChar* xmlName = BAD_CAST(pName); + (void)xmlTextWriterStartAttribute(mpImpl->mpWriter, xmlName); + (void)xmlTextWriterWriteBase64(mpImpl->mpWriter, rValueInBytes.data(), 0, rValueInBytes.size()); + (void)xmlTextWriterEndAttribute(mpImpl->mpWriter); +} + +void XmlWriter::attribute(const char* name, const OString& value) +{ + xmlChar* xmlName = BAD_CAST(name); + xmlChar* xmlValue = BAD_CAST(value.getStr()); + (void)xmlTextWriterWriteAttribute(mpImpl->mpWriter, xmlName, xmlValue); +} + +void XmlWriter::attribute(const OString& name, const OString& value) +{ + xmlChar* xmlName = BAD_CAST(name.getStr()); + xmlChar* xmlValue = BAD_CAST(value.getStr()); + (void)xmlTextWriterWriteAttribute(mpImpl->mpWriter, xmlName, xmlValue); +} + +void XmlWriter::attribute(const char* name, std::u16string_view value) +{ + attribute(name, OUStringToOString(value, RTL_TEXTENCODING_UTF8)); +} + +void XmlWriter::attribute(const char* name, const sal_Int32 aNumber) +{ + attribute(name, OString::number(aNumber)); +} + +void XmlWriter::attributeDouble(const char* name, const double aNumber) +{ + attribute(name, OString::number(aNumber)); +} + +void XmlWriter::content(const OString& sValue) +{ + xmlChar* xmlValue = BAD_CAST(sValue.getStr()); + (void)xmlTextWriterWriteString(mpImpl->mpWriter, xmlValue); +} + +void XmlWriter::content(std::u16string_view sValue) +{ + content(OUStringToOString(sValue, RTL_TEXTENCODING_UTF8)); +} + +void XmlWriter::element(const char* sName) +{ + startElement(sName); + endElement(); +} + +} // end tools namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/source/zcodec/zcodec.cxx b/tools/source/zcodec/zcodec.cxx new file mode 100644 index 0000000000..c925ecde8a --- /dev/null +++ b/tools/source/zcodec/zcodec.cxx @@ -0,0 +1,405 @@ +/* -*- 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 <algorithm> + +#include <tools/stream.hxx> + +#include <zlib.h> + +#include <tools/zcodec.hxx> +#include <tools/long.hxx> + +/* gzip flag byte */ +// GZ_ASCII_FLAG = 0x01; /* bit 0 set: file probably ascii text */ +constexpr sal_uInt8 GZ_HEAD_CRC = 0x02; /* bit 1 set: header CRC present */ +constexpr sal_uInt8 GZ_EXTRA_FIELD = 0x04; /* bit 2 set: extra field present */ +constexpr sal_uInt8 GZ_ORIG_NAME = 0x08; /* bit 3 set: original file name present */ +constexpr sal_uInt8 GZ_COMMENT = 0x10; /* bit 4 set: file comment present */ +constexpr sal_uInt8 GZ_RESERVED = 0xE0; /* bits 5..7: reserved */ +constexpr sal_uInt16 GZ_MAGIC_BYTES_LE = 0x8B1F; /* gzip magic bytes, little endian */ +constexpr sal_uInt8 GZ_DEFLATE = 0x08; +constexpr sal_uInt8 GZ_FS_UNKNOWN = 0xFF; + +ZCodec::ZCodec( size_t nInBufSize, size_t nOutBufSize ) + : meState(STATE_INIT) + , mbStatus(false) + , mbFinish(false) + , mnInBufSize(nInBufSize) + , mnInToRead(0) + , mpOStm(nullptr) + , mnOutBufSize(nOutBufSize) + , mnUncompressedSize(0) + , mnInBufCRC32(0) + , mnLastModifiedTime(0) + , mnCompressLevel(0) + , mbGzLib(false) +{ + mpsC_Stream = new z_stream; +} + +ZCodec::~ZCodec() +{ + auto pStream = static_cast<z_stream*>(mpsC_Stream); + delete pStream; +} + +bool ZCodec::IsZCompressed( SvStream& rIStm ) +{ + sal_uInt64 nCurPos = rIStm.Tell(); + rIStm.Seek( 0 ); + sal_uInt16 nFirstTwoBytes = 0; + rIStm.ReadUInt16( nFirstTwoBytes ); + rIStm.Seek( nCurPos ); + return nFirstTwoBytes == GZ_MAGIC_BYTES_LE; +} + +void ZCodec::BeginCompression( int nCompressLevel, bool gzLib ) +{ + assert(meState == STATE_INIT); + mbStatus = true; + mbFinish = false; + mpOStm = nullptr; + mnInToRead = 0xffffffff; + mpInBuf.reset(); + mpOutBuf.reset(); + auto pStream = static_cast<z_stream*>(mpsC_Stream); + pStream->total_out = pStream->total_in = 0; + mnCompressLevel = nCompressLevel; + mbGzLib = gzLib; + pStream->zalloc = nullptr; + pStream->zfree = nullptr; + pStream->opaque = nullptr; + pStream->avail_out = pStream->avail_in = 0; +} + +tools::Long ZCodec::EndCompression() +{ + tools::Long retvalue = 0; + auto pStream = static_cast<z_stream*>(mpsC_Stream); + + if (meState != STATE_INIT) + { + if (meState == STATE_COMPRESS) + { + if (mbStatus) + { + do + { + ImplWriteBack(); + } + while ( deflate( pStream, Z_FINISH ) != Z_STREAM_END ); + + ImplWriteBack(); + } + + retvalue = pStream->total_in; + deflateEnd( pStream ); + if ( mbGzLib ) + { + // metadata must be set to compress as gz format + assert(!msFilename.isEmpty()); + // overwrite zlib checksum + mpOStm->Seek(STREAM_SEEK_TO_END); + mpOStm->SeekRel(-4); + mpOStm->WriteUInt32( mnInBufCRC32 ); // Uncompressed buffer CRC32 + mpOStm->WriteUInt32( mnUncompressedSize ); // Uncompressed size mod 2^32 + mpOStm->Seek( 0 ); + mpOStm->WriteUInt16( GZ_MAGIC_BYTES_LE ) // Magic bytes + .WriteUInt8( GZ_DEFLATE ) // Compression algorithm + .WriteUInt8( GZ_ORIG_NAME ) // Filename + .WriteUInt32( mnLastModifiedTime ) // Modification time + .WriteUInt8( 0 ) // Extra flags + .WriteUInt8( GZ_FS_UNKNOWN ) // Operating system + .WriteBytes( msFilename.pData->buffer, msFilename.pData->length ); + mpOStm->WriteUInt8( 0 ); // null terminate the filename string + } + } + else + { + retvalue = pStream->total_out; + inflateEnd( pStream ); + } + mpOutBuf.reset(); + mpInBuf.reset(); + meState = STATE_INIT; + } + return mbStatus ? retvalue : -1; +} + +void ZCodec::SetCompressionMetadata( const OString& sFilename, sal_uInt32 nLastModifiedTime, sal_uInt32 nInBufCRC32 ) +{ + assert( mbGzLib ); + msFilename = sFilename; + mnLastModifiedTime = nLastModifiedTime; + mnInBufCRC32 = nInBufCRC32; +} + +void ZCodec::Compress( SvStream& rIStm, SvStream& rOStm ) +{ + assert(meState == STATE_INIT); + mpOStm = &rOStm; + rIStm.Seek(0); + mnUncompressedSize = rIStm.TellEnd(); + InitCompress(); + mpInBuf.reset(new sal_uInt8[ mnInBufSize ]); + auto pStream = static_cast<z_stream*>(mpsC_Stream); + for (;;) + { + pStream->next_in = mpInBuf.get(); + pStream->avail_in = rIStm.ReadBytes( mpInBuf.get(), mnInBufSize ); + if (pStream->avail_in == 0) + break; + if ( pStream->avail_out == 0 ) + ImplWriteBack(); + if ( deflate( pStream, Z_NO_FLUSH ) < 0 ) + { + mbStatus = false; + break; + } + }; +} + +tools::Long ZCodec::Decompress( SvStream& rIStm, SvStream& rOStm ) +{ + int err; + size_t nInToRead; + auto pStream = static_cast<z_stream*>(mpsC_Stream); + tools::Long nOldTotal_Out = pStream->total_out; + + assert(meState == STATE_INIT); + mpOStm = &rOStm; + InitDecompress(rIStm); + pStream->avail_out = mnOutBufSize; + mpOutBuf.reset(new sal_uInt8[ pStream->avail_out ]); + pStream->next_out = mpOutBuf.get(); + do + { + if ( pStream->avail_out == 0 ) ImplWriteBack(); + if ( pStream->avail_in == 0 && mnInToRead ) + { + nInToRead = std::min( mnInBufSize, mnInToRead ); + pStream->next_in = mpInBuf.get(); + pStream->avail_in = rIStm.ReadBytes(mpInBuf.get(), nInToRead); + mnInToRead -= nInToRead; + } + err = mbStatus ? inflate(pStream, Z_NO_FLUSH) : Z_ERRNO; + if (err < 0 || err == Z_NEED_DICT) + { + mbStatus = false; + break; + } + + } + while ( ( err != Z_STREAM_END) && ( pStream->avail_in || mnInToRead ) ); + ImplWriteBack(); + + return mbStatus ? static_cast<tools::Long>(pStream->total_out - nOldTotal_Out) : -1; +} + +void ZCodec::Write( SvStream& rOStm, const sal_uInt8* pData, sal_uInt32 nSize ) +{ + if (meState == STATE_INIT) + { + mpOStm = &rOStm; + InitCompress(); + } + assert(&rOStm == mpOStm); + + auto pStream = static_cast<z_stream*>(mpsC_Stream); + pStream->avail_in = nSize; + pStream->next_in = pData; + + while ( pStream->avail_in || ( pStream->avail_out == 0 ) ) + { + if ( pStream->avail_out == 0 ) + ImplWriteBack(); + + if ( deflate( pStream, Z_NO_FLUSH ) < 0 ) + { + mbStatus = false; + break; + } + } +} + +tools::Long ZCodec::Read( SvStream& rIStm, sal_uInt8* pData, sal_uInt32 nSize ) +{ + int err; + size_t nInToRead; + + if ( mbFinish ) + return 0; // pStream->total_out; + + if (meState == STATE_INIT) + { + InitDecompress(rIStm); + } + auto pStream = static_cast<z_stream*>(mpsC_Stream); + pStream->avail_out = nSize; + pStream->next_out = pData; + do + { + if ( pStream->avail_in == 0 && mnInToRead ) + { + nInToRead = std::min(mnInBufSize, mnInToRead); + pStream->next_in = mpInBuf.get(); + pStream->avail_in = rIStm.ReadBytes(mpInBuf.get(), nInToRead); + mnInToRead -= nInToRead; + } + err = mbStatus ? inflate(pStream, Z_NO_FLUSH) : Z_ERRNO; + if (err < 0 || err == Z_NEED_DICT) + { + // Accept Z_BUF_ERROR as EAGAIN or EWOULDBLOCK. + mbStatus = (err == Z_BUF_ERROR); + break; + } + } + while ( (err != Z_STREAM_END) && + (pStream->avail_out != 0) && + (pStream->avail_in || mnInToRead) ); + if ( err == Z_STREAM_END ) + mbFinish = true; + + return (mbStatus ? static_cast<tools::Long>(nSize - pStream->avail_out) : -1); +} + +void ZCodec::ImplWriteBack() +{ + auto pStream = static_cast<z_stream*>(mpsC_Stream); + size_t nAvail = mnOutBufSize - pStream->avail_out; + + if ( nAvail > 0 ) + { + pStream->next_out = mpOutBuf.get(); + mpOStm->WriteBytes( mpOutBuf.get(), nAvail ); + pStream->avail_out = mnOutBufSize; + } +} + +void ZCodec::InitCompress() +{ + assert(meState == STATE_INIT); + if (mbGzLib) + { + // Seek just enough so that the zlib header is overwritten after compression + // with the gz header + // 10 header bytes + filename length + null terminator - 2 bytes for + // zlib header that gets overwritten + mpOStm->Seek(10 + msFilename.getLength() + 1 - 2); + } + meState = STATE_COMPRESS; + auto pStream = static_cast<z_stream*>(mpsC_Stream); + mbStatus = deflateInit2_( + pStream, mnCompressLevel, Z_DEFLATED, MAX_WBITS, MAX_MEM_LEVEL, + Z_DEFAULT_STRATEGY, ZLIB_VERSION, sizeof (z_stream)) >= 0; + mpOutBuf.reset(new sal_uInt8[mnOutBufSize]); + pStream->next_out = mpOutBuf.get(); + pStream->avail_out = mnOutBufSize; +} + +void ZCodec::InitDecompress(SvStream & inStream) +{ + assert(meState == STATE_INIT); + auto pStream = static_cast<z_stream*>(mpsC_Stream); + if ( mbStatus && mbGzLib ) + { + sal_uInt8 j, nMethod, nFlags; + sal_uInt16 nFirstTwoBytes; + inStream.Seek( 0 ); + inStream.ReadUInt16( nFirstTwoBytes ); + if ( nFirstTwoBytes != GZ_MAGIC_BYTES_LE ) + mbStatus = false; + inStream.ReadUChar( nMethod ); + inStream.ReadUChar( nFlags ); + if ( nMethod != Z_DEFLATED ) + mbStatus = false; + if ( ( nFlags & GZ_RESERVED ) != 0 ) + mbStatus = false; + /* Discard time, xflags and OS code: */ + inStream.SeekRel( 6 ); + /* skip the extra field */ + if ( nFlags & GZ_EXTRA_FIELD ) + { + sal_uInt8 n1, n2; + inStream.ReadUChar( n1 ).ReadUChar( n2 ); + inStream.SeekRel( n1 + ( n2 << 8 ) ); + } + /* skip the original file name */ + if ( nFlags & GZ_ORIG_NAME) + { + do + { + inStream.ReadUChar( j ); + } + while ( j && !inStream.eof() ); + } + /* skip the .gz file comment */ + if ( nFlags & GZ_COMMENT ) + { + do + { + inStream.ReadUChar( j ); + } + while ( j && !inStream.eof() ); + } + /* skip the header crc */ + if ( nFlags & GZ_HEAD_CRC ) + inStream.SeekRel( 2 ); + if ( mbStatus ) + mbStatus = inflateInit2( pStream, -MAX_WBITS) == Z_OK; + } + else + { + mbStatus = ( inflateInit( pStream ) >= 0 ); + } + if ( mbStatus ) + meState = STATE_DECOMPRESS; + mpInBuf.reset(new sal_uInt8[ mnInBufSize ]); +} + +bool ZCodec::AttemptDecompression(SvStream& rIStm, SvStream& rOStm) +{ + assert(meState == STATE_INIT); + sal_uInt64 nStreamPos = rIStm.Tell(); + BeginCompression(ZCODEC_DEFAULT_COMPRESSION, true/*gzLib*/); + InitDecompress(rIStm); + EndCompression(); + if ( !mbStatus || rIStm.GetError() ) + { + rIStm.Seek(nStreamPos); + return false; + } + rIStm.Seek(nStreamPos); + BeginCompression(ZCODEC_DEFAULT_COMPRESSION, true/*gzLib*/); + Decompress(rIStm, rOStm); + EndCompression(); + if( !mbStatus || rIStm.GetError() || rOStm.GetError() ) + { + rIStm.Seek(nStreamPos); + return false; + } + rIStm.Seek(nStreamPos); + rOStm.Seek(0); + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |