├── .gitattributes ├── .gitignore ├── LICENSE.md ├── OpenMcdf.sln ├── OpenMcdf.sln.DotSettings ├── README.md ├── appveyor.yml ├── docs └── OpenMcdfHelp.shfbproj ├── src ├── CFException.cs ├── CFItem.cs ├── CFItemComparer.cs ├── CFStorage.cs ├── CFStream.cs ├── CompoundFile.cs ├── DirectoryEntry.cs ├── Enums.cs ├── Extensions │ ├── CFStreamExtensions.cs │ └── OLEProperties │ │ ├── Interfaces │ │ ├── IBinarySerializable.cs │ │ └── ITypedPropertyValue.cs │ │ ├── PropertyFactory.cs │ │ ├── PropertyIdentifierAndOffset.cs │ │ ├── PropertyReader.cs │ │ ├── PropertySet.cs │ │ ├── PropertySetStream.cs │ │ ├── ProperyIdentifiers.cs │ │ ├── TypedPropertyValue.cs │ │ ├── VTPropertyType.cs │ │ └── VTVectorHeader.cs ├── Header.cs ├── IDirectoryEntry.cs ├── OpenMcdf.csproj ├── Polyfills.cs ├── RBTree │ └── RBTree.cs ├── Sector.cs ├── SectorCollection.cs ├── StreamRW.cs └── StreamView.cs └── tests ├── OpenMcdf.Test ├── AssetDeployer.cs ├── AuthoringTests.txt ├── CFSStreamExtensionsTest.cs ├── CFSStreamTest.cs ├── CFSTorageTest.cs ├── CompoundFileTest.cs ├── Helpers.cs ├── OpenMcdf.Test.csproj ├── Performance │ ├── MemoryTest.cs │ ├── PerfTest.cs │ └── PerformanceTestStuite.cs ├── Properties │ └── AssemblyInfo.cs ├── RBTreeTest.cs ├── SectorCollectionTest.cs └── StreamRWTest.cs └── assets ├── 2_MB-W.ppt ├── BUG_16_.xls ├── CorruptedDoc_bug3547815.doc ├── CorruptedDoc_bug3547815_B.doc ├── CorruptedDoc_bug36.doc ├── CyclicFAT.cfs ├── MultipleStorage.cfs ├── MultipleStorage2.cfs ├── MultipleStorage3.cfs ├── MultipleStorage4.cfs ├── _thumbs_bug_24.db ├── report.xls ├── reportOverwriteMultiple.xls ├── reportREAD.xls ├── report_name_fix.xls └── testbad.ole /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Windows Store app package directory 170 | AppPackages/ 171 | BundleArtifacts/ 172 | 173 | # Visual Studio cache files 174 | # files ending in .cache can be ignored 175 | *.[Cc]ache 176 | # but keep track of directories ending in .cache 177 | !*.[Cc]ache/ 178 | 179 | # Others 180 | ClientBin/ 181 | [Ss]tyle[Cc]op.* 182 | ~$* 183 | *~ 184 | *.dbmdl 185 | *.dbproj.schemaview 186 | *.pfx 187 | *.publishsettings 188 | node_modules/ 189 | orleans.codegen.cs 190 | 191 | # RIA/Silverlight projects 192 | Generated_Code/ 193 | 194 | # Backup & report files from converting an old project file 195 | # to a newer Visual Studio version. Backup files are not needed, 196 | # because we have git ;-) 197 | _UpgradeReport_Files/ 198 | Backup*/ 199 | UpgradeLog*.XML 200 | UpgradeLog*.htm 201 | 202 | # SQL Server files 203 | *.mdf 204 | *.ldf 205 | 206 | # Business Intelligence projects 207 | *.rdl.data 208 | *.bim.layout 209 | *.bim_*.settings 210 | 211 | # Microsoft Fakes 212 | FakesAssemblies/ 213 | 214 | # GhostDoc plugin setting file 215 | *.GhostDoc.xml 216 | 217 | # Node.js Tools for Visual Studio 218 | .ntvs_analysis.dat 219 | 220 | # Visual Studio 6 build log 221 | *.plg 222 | 223 | # Visual Studio 6 workspace options file 224 | *.opt 225 | 226 | # Visual Studio LightSwitch build output 227 | **/*.HTMLClient/GeneratedArtifacts 228 | **/*.DesktopClient/GeneratedArtifacts 229 | **/*.DesktopClient/ModelManifest.xml 230 | **/*.Server/GeneratedArtifacts 231 | **/*.Server/ModelManifest.xml 232 | _Pvt_Extensions 233 | 234 | # LightSwitch generated files 235 | GeneratedArtifacts/ 236 | ModelManifest.xml 237 | 238 | # Paket dependency manager 239 | .paket/paket.exe 240 | 241 | # FAKE - F# Make 242 | .fake/ 243 | -------------------------------------------------------------------------------- /OpenMcdf.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2010 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6C619B4F-100F-4D60-BEF1-60B770D24E5E}" 7 | ProjectSection(SolutionItems) = preProject 8 | .gitattributes = .gitattributes 9 | .gitignore = .gitignore 10 | appveyor.yml = appveyor.yml 11 | LICENSE.md = LICENSE.md 12 | README.md = README.md 13 | EndProjectSection 14 | EndProject 15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{8DC8730A-F254-4848-B272-BDFFCB5FDC00}" 16 | ProjectSection(SolutionItems) = preProject 17 | tests\assets\2_MB-W.ppt = tests\assets\2_MB-W.ppt 18 | tests\assets\_thumbs_bug_24.db = tests\assets\_thumbs_bug_24.db 19 | tests\assets\BUG_16_.xls = tests\assets\BUG_16_.xls 20 | tests\assets\CorruptedDoc_bug3547815.doc = tests\assets\CorruptedDoc_bug3547815.doc 21 | tests\assets\CorruptedDoc_bug3547815_B.doc = tests\assets\CorruptedDoc_bug3547815_B.doc 22 | tests\assets\CyclicFAT.cfs = tests\assets\CyclicFAT.cfs 23 | tests\assets\MultipleStorage.cfs = tests\assets\MultipleStorage.cfs 24 | tests\assets\MultipleStorage2.cfs = tests\assets\MultipleStorage2.cfs 25 | tests\assets\MultipleStorage3.cfs = tests\assets\MultipleStorage3.cfs 26 | tests\assets\MultipleStorage4.cfs = tests\assets\MultipleStorage4.cfs 27 | tests\assets\report.xls = tests\assets\report.xls 28 | tests\assets\report_name_fix.xls = tests\assets\report_name_fix.xls 29 | tests\assets\reportREAD.xls = tests\assets\reportREAD.xls 30 | tests\assets\testbad.ole = tests\assets\testbad.ole 31 | EndProjectSection 32 | EndProject 33 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{73814657-FC73-4066-AABD-86062F2A132E}" 34 | EndProject 35 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenMcdf.Test", "tests\OpenMcdf.Test\OpenMcdf.Test.csproj", "{FD339266-8842-40B4-9230-F8E84FC42AC2}" 36 | EndProject 37 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf", "src\OpenMcdf.csproj", "{9128CCC3-88DF-4F31-883E-47A04825276F}" 38 | EndProject 39 | Global 40 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 41 | Debug|Any CPU = Debug|Any CPU 42 | Release|Any CPU = Release|Any CPU 43 | EndGlobalSection 44 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 45 | {FD339266-8842-40B4-9230-F8E84FC42AC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {FD339266-8842-40B4-9230-F8E84FC42AC2}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {FD339266-8842-40B4-9230-F8E84FC42AC2}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {FD339266-8842-40B4-9230-F8E84FC42AC2}.Release|Any CPU.Build.0 = Release|Any CPU 49 | {9128CCC3-88DF-4F31-883E-47A04825276F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 50 | {9128CCC3-88DF-4F31-883E-47A04825276F}.Debug|Any CPU.Build.0 = Debug|Any CPU 51 | {9128CCC3-88DF-4F31-883E-47A04825276F}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {9128CCC3-88DF-4F31-883E-47A04825276F}.Release|Any CPU.Build.0 = Release|Any CPU 53 | EndGlobalSection 54 | GlobalSection(SolutionProperties) = preSolution 55 | HideSolutionNode = FALSE 56 | EndGlobalSection 57 | GlobalSection(NestedProjects) = preSolution 58 | {8DC8730A-F254-4848-B272-BDFFCB5FDC00} = {73814657-FC73-4066-AABD-86062F2A132E} 59 | {FD339266-8842-40B4-9230-F8E84FC42AC2} = {73814657-FC73-4066-AABD-86062F2A132E} 60 | EndGlobalSection 61 | GlobalSection(ExtensibilityGlobals) = postSolution 62 | SolutionGuid = {C391C94D-1FBE-4F24-9E09-AB72A6FE9F7B} 63 | EndGlobalSection 64 | EndGlobal 65 | -------------------------------------------------------------------------------- /OpenMcdf.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | CF 3 | CFS 4 | DIFAT 5 | FAT 6 | OA 7 | RW -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | OpenMCDF 2 | =========================== 3 | **for .NET Framework and .NET Standard (including .NET Core)** 4 | 5 | [![NuGet downloads](https://img.shields.io/nuget/dt/OpenMcdf-2.svg?label=nuget%20downloads)](https://www.nuget.org/packages/OpenMcdf-2/) 6 | [![NuGet version](https://img.shields.io/nuget/v/OpenMcdf-2.svg)](https://www.nuget.org/packages/OpenMcdf-2/) 7 | [![Github All Releases](https://img.shields.io/github/downloads/CodeCavePro/OpenMCDF/total.svg?label=GitHub%20downloads)]() 8 | [![License](https://img.shields.io/github/license/CodeCavePro/OpenMCDF.svg)](https://github.com/CodeCavePro/OpenMCDF/blob/master/LICENSE.md) 9 | 10 | [![AppVeyor build](https://img.shields.io/appveyor/ci/salaros/openmcdf/master.svg?logo=appveyor)](https://ci.appveyor.com/project/salaros/openmcdf) 11 | [![AppVeyor test](https://img.shields.io/appveyor/tests/salaros/openmcdf.svg?logo=appveyor)](https://ci.appveyor.com/project/salaros/openmcdf/build/tests) 12 | 13 | # Description 14 | 15 | OpenMCDF is a 100% .net / C# component that allows developers to manipulate Microsoft Compound Document File Format for OLE structured storage. It supports read/write operations on streams and storages and traversal of structures tree. 16 | 17 | ## Features 18 | 19 | * COM Structured Storage for your .NET applications 20 | * Read / Write OLE compound files in .NET / C# 21 | * No COM Interop required 22 | * No external dependencies 23 | * Easy to use 24 | * Lazy loading to reduce memory consumption for read operations 25 | * Version 4 of the compound file format supported 26 | * Append data to existing streams 27 | * Get partial stream data from a given offset 28 | * Differential update of existing compound files 29 | * Can handle thousands of structured storage items 30 | * Support for native .net Stream object 31 | * Mono platform supported 32 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Operating system (build VM template) 2 | os: Windows Server 2016 3 | 4 | # If the build configuration does not specify build worker image 5 | # then Visual Studio 2015 image is used. 6 | image: Visual Studio 2017 7 | 8 | # Restrict to Git branches below 9 | branches: 10 | only: 11 | - master 12 | 13 | # Scripts that run after cloning repository 14 | install: 15 | - nuget restore 16 | 17 | # Cache files until appveyor.yml is modified. 18 | cache: 19 | - packages -> **\packages.config # preserve "packages" directory in the root of build folder but will reset it if packages.config is modified 20 | - '%LocalAppData%\NuGet\Cache' # NuGet < v3 21 | - '%LocalAppData%\NuGet\v3-cache' # NuGet v3 22 | 23 | # Version format 24 | version: 2.1.1 25 | 26 | environment: 27 | VERSION_SIMPLE: '{version}' 28 | VERSION_INFORMATIONAL: '{version}' 29 | VERSION_SUFFIX: '-preview-$(APPVEYOR_BUILD_NUMBER)' 30 | VERSION_SUFFIX_TAG: '.$(APPVEYOR_BUILD_NUMBER)' 31 | 32 | init: 33 | - ps: | 34 | $env:VERSION_SIMPLE = $env:APPVEYOR_BUILD_VERSION 35 | $env:VERSION_INFORMATIONAL = $env:APPVEYOR_BUILD_VERSION 36 | Write-Host "Starting with $env:APPVEYOR_BUILD_VERSION as base version and $env:VERSION_INFORMATIONAL as informational version"; 37 | 38 | if ($env:APPVEYOR_REPO_TAG -eq "true" -and $env:APPVEYOR_REPO_TAG_NAME) 39 | { 40 | $env:VERSION_SUFFIX = $env:VERSION_SUFFIX_TAG 41 | $tag_version = $env:APPVEYOR_REPO_TAG_NAME.TrimStart("v") 42 | Write-Host "Building a tag: $tag_version"; 43 | if ($tag_version -match '^([0-9]+\.[0-9]+\.[0-9]+)$' -Or $tag_version -match '([0-9]+[\.-][0-9]+[\.-][0-9]+-[-A-Za-z0-9]+)') 44 | { 45 | $env:VERSION_INFORMATIONAL = "$tag_version" 46 | } 47 | } 48 | else 49 | { 50 | $env:VERSION_INFORMATIONAL = "$env:VERSION_INFORMATIONAL$env:VERSION_SUFFIX" 51 | } 52 | 53 | Update-AppveyorBuild -Version $env:VERSION_INFORMATIONAL 54 | Write-Host "Using build version: $env:APPVEYOR_BUILD_VERSION"; 55 | 56 | configuration: Debug 57 | 58 | dotnet_csproj: 59 | patch: true 60 | file: '**\*.csproj' 61 | assembly_version: $(VERSION_SIMPLE) 62 | file_version: $(VERSION_SIMPLE) 63 | version: $(VERSION_INFORMATIONAL) 64 | package_version: $(VERSION_INFORMATIONAL) 65 | informational_version: $(VERSION_INFORMATIONAL) 66 | 67 | assembly_info: 68 | patch: true 69 | file: '**\AssemblyInfo.*' 70 | assembly_version: $(VERSION_SIMPLE) 71 | assembly_file_version: $(VERSION_SIMPLE) 72 | assembly_informational_version: $(VERSION_INFORMATIONAL) 73 | 74 | # Run scripts below before 75 | before_build: 76 | - where msbuild 77 | - cmd: msbuild /t:Clean 78 | 79 | # To run your custom scripts instead of automatic MSBuild 80 | build_script: 81 | - cmd: msbuild /t:Rebuild /p:Configuration=%configuration% 82 | 83 | # NuGet files qualified as artifacts 84 | artifacts: 85 | - path: 'bin\**\*.nupkg' # find the NuGet files 86 | name: OpenMCDF 87 | 88 | # Deploy to GitHub releases 89 | deploy: 90 | - 91 | provider: GitHub 92 | auth_token: 93 | secure: 2+d0KgCbWQpUR8TZfzvUEzbi4NQP6F/Tt0PUwLn6jXZCyO8FnrFVFJPsFa0QBQFl 94 | artifact: OpenMCDF 95 | description: |- 96 | Supports the following .NET frameworks and standards 97 | * .NET 4.0 98 | * [.NET 4.5.2](https://github.com/Microsoft/dotnet/blob/master/releases/net452/README.md) 99 | * [.NET 4.6.1](https://github.com/Microsoft/dotnet/blob/master/releases/net461/README.md) 100 | * [.NET 4.7](https://github.com/Microsoft/dotnet/blob/master/releases/net47/README.md) 101 | * [.NET Standard 1.6](https://github.com/dotnet/standard/blob/master/docs/versions/netstandard1.6.md) (.NET Core >=1.0, .NET >=4.6.1, Mono >=4.6 etc) 102 | * [.NET Standard 2.0](https://github.com/dotnet/standard/blob/master/docs/versions/netstandard2.0.md) (.NET Core >=2.0, .NET >=4.6.1, Mono >=5.4 etc) 103 | draft: false 104 | force_update: true 105 | prerelease: false 106 | release: "OpenMCDF v$(APPVEYOR_REPO_TAG_NAME)" 107 | tag: $(APPVEYOR_REPO_TAG_NAME) 108 | on: 109 | appveyor_repo_tag: true 110 | 111 | - 112 | provider: NuGet 113 | api_key: 114 | secure: i6oWn60J7ZOM4UuYcvxbuk9OAEp6or+Wq7izyJDPNlcLIhG2UKsxz7G/8erhdY3M 115 | artifact: OpenMCDF 116 | server: # remove to push to NuGet.org 117 | skip_symbols: false 118 | symbol_server: # remove to push symbols to SymbolSource.org 119 | on: 120 | appveyor_repo_tag: true 121 | 122 | - 123 | provider: NuGet 124 | server: https://ci.appveyor.com/nuget/salaros/api/v2/package 125 | symbol_server: https://ci.appveyor.com/nuget/salaros/api/v2/package 126 | api_key: 127 | secure: 3zmnmVBweTgdk4SBM/rWHdC9JOM9s0pxm1bw1d+WHDo= 128 | artifact: OpenMCDF 129 | 130 | # Start builds on tags only (GitHub and BitBucket) 131 | skip_non_tags: false 132 | 133 | # Turn off tests 134 | test: 135 | assemblies: 136 | - 'bin\%configuration%\tests\*.dll' 137 | -------------------------------------------------------------------------------- /docs/OpenMcdfHelp.shfbproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 7 | Debug 8 | AnyCPU 9 | 2.0 10 | {6b2e0fe5-8246-4f87-9663-d72ebadfc53f} 11 | 2015.6.5.0 12 | 14 | Documentation 15 | Documentation 16 | Documentation 17 | 18 | .\Help\ 19 | OpenMCDF 20 | Copyright 2010 - 2015 &#169%3b Federico Blaseotto 21 | Open MCDF 22 | Open MCDF 23 | Open MCDF 24 | Summary, AutoDocumentCtors 25 | 26 | 27 | 28 | ironfede%40users.sourceforge.net 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | VS2013 40 | en-US 41 | 42 | 43 | 44 | 45 | InheritedMembers, InheritedFrameworkMembers 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 2.0 56 | Hierarchical 57 | 2 58 | False 59 | C# 60 | Blank 61 | False 62 | False 63 | Guid 64 | AboveNamespaces 65 | OnlyWarningsAndErrors 66 | HtmlHelp1 67 | False 68 | .NET Framework 4.0 69 | True 70 | False 71 | False 72 | True 73 | 74 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/CFException.cs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | * 5 | * The Original Code is OpenMCDF - Compound Document Format library. 6 | * 7 | * The Initial Developer of the Original Code is Federico Blaseotto.*/ 8 | 9 | using System; 10 | #if !NETSTANDARD1_6 11 | using System.Runtime.Serialization; 12 | #endif 13 | 14 | namespace OpenMcdf 15 | { 16 | /// 17 | /// 18 | /// OpenMCDF base exception. 19 | /// 20 | [Serializable] 21 | public class CFException : Exception 22 | { 23 | public CFException() 24 | { 25 | } 26 | 27 | #if !NETSTANDARD1_6 28 | protected CFException(SerializationInfo info, StreamingContext context) 29 | : base(info, context) 30 | { 31 | } 32 | #endif 33 | 34 | public CFException(string message) 35 | : base(message, null) 36 | { 37 | } 38 | 39 | public CFException(string message, Exception innerException) 40 | : base(message, innerException) 41 | { 42 | } 43 | } 44 | 45 | /// 46 | /// 47 | /// Raised when a data setter/getter method is invoked 48 | /// on a stream or storage object after the disposal of the owner 49 | /// compound file object. 50 | /// 51 | [Serializable] 52 | public class CFDisposedException : CFException 53 | { 54 | public CFDisposedException() 55 | { 56 | } 57 | 58 | #if !NETSTANDARD1_6 59 | protected CFDisposedException(SerializationInfo info, StreamingContext context) 60 | : base(info, context) 61 | { 62 | } 63 | #endif 64 | 65 | public CFDisposedException(string message) 66 | : base(message, null) 67 | { 68 | } 69 | 70 | public CFDisposedException(string message, Exception innerException) 71 | : base(message, innerException) 72 | { 73 | } 74 | } 75 | 76 | /// 77 | /// 78 | /// Raised when opening a file with invalid header 79 | /// or not supported COM/OLE Structured storage version. 80 | /// 81 | [Serializable] 82 | public class CFFileFormatException : CFException 83 | { 84 | public CFFileFormatException() 85 | { 86 | } 87 | 88 | #if !NETSTANDARD1_6 89 | protected CFFileFormatException(SerializationInfo info, StreamingContext context) 90 | : base(info, context) 91 | { 92 | } 93 | #endif 94 | public CFFileFormatException(string message) 95 | : base(message, null) 96 | { 97 | } 98 | 99 | public CFFileFormatException(string message, Exception innerException) 100 | : base(message, innerException) 101 | { 102 | } 103 | } 104 | 105 | /// 106 | /// 107 | /// Raised when a named stream or a storage object 108 | /// are not found in a parent storage. 109 | /// 110 | [Serializable] 111 | public class CFItemNotFound : CFException 112 | { 113 | #if !NETSTANDARD1_6 114 | protected CFItemNotFound(SerializationInfo info, StreamingContext context) 115 | : base(info, context) 116 | { 117 | } 118 | #endif 119 | 120 | public CFItemNotFound() 121 | : base("Entry not found") 122 | { 123 | } 124 | 125 | public CFItemNotFound(string message) 126 | : base(message, null) 127 | { 128 | } 129 | 130 | public CFItemNotFound(string message, Exception innerException) 131 | : base(message, innerException) 132 | { 133 | } 134 | } 135 | 136 | /// 137 | /// 138 | /// Raised when a method call is invalid for the current object state 139 | /// 140 | [Serializable] 141 | public class CFInvalidOperation : CFException 142 | { 143 | public CFInvalidOperation() 144 | { 145 | } 146 | 147 | #if !NETSTANDARD1_6 148 | protected CFInvalidOperation(SerializationInfo info, StreamingContext context) 149 | : base(info, context) 150 | { 151 | } 152 | #endif 153 | 154 | public CFInvalidOperation(string message) 155 | : base(message, null) 156 | { 157 | } 158 | 159 | public CFInvalidOperation(string message, Exception innerException) 160 | : base(message, innerException) 161 | { 162 | } 163 | } 164 | 165 | /// 166 | /// 167 | /// Raised when trying to add a duplicated CFItem 168 | /// 169 | /// 170 | /// Items are compared by name as indicated by specs. 171 | /// Two items with the same name CANNOT be added within 172 | /// the same storage or sub-storage. 173 | /// 174 | [Serializable] 175 | public class CFDuplicatedItemException : CFException 176 | { 177 | public CFDuplicatedItemException() 178 | { 179 | } 180 | 181 | #if !NETSTANDARD1_6 182 | protected CFDuplicatedItemException(SerializationInfo info, StreamingContext context) 183 | : base(info, context) 184 | { 185 | } 186 | #endif 187 | 188 | public CFDuplicatedItemException(string message) 189 | : base(message, null) 190 | { 191 | } 192 | 193 | public CFDuplicatedItemException(string message, Exception innerException) 194 | : base(message, innerException) 195 | { 196 | } 197 | } 198 | 199 | /// 200 | /// 201 | /// Raised when trying to load a Compound File with invalid, corrupted or mismatched fields (4.1 - specifications) 202 | /// 203 | /// 204 | /// This exception is NOT raised when Compound file has been opened with NO_VALIDATION_EXCEPTION option. 205 | /// 206 | [Serializable] 207 | public class CFCorruptedFileException : CFException 208 | { 209 | public CFCorruptedFileException() 210 | { 211 | } 212 | 213 | #if !NETSTANDARD1_6 214 | protected CFCorruptedFileException(SerializationInfo info, StreamingContext context) 215 | : base(info, context) 216 | { 217 | } 218 | #endif 219 | 220 | public CFCorruptedFileException(string message) 221 | : base(message, null) 222 | { 223 | } 224 | 225 | public CFCorruptedFileException(string message, Exception innerException) 226 | : base(message, innerException) 227 | { 228 | } 229 | } 230 | } -------------------------------------------------------------------------------- /src/CFItem.cs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | * 5 | * The Original Code is OpenMCDF - Compound Document Format library. 6 | * 7 | * The Initial Developer of the Original Code is Federico Blaseotto.*/ 8 | 9 | using System; 10 | 11 | namespace OpenMcdf 12 | { 13 | /// 14 | /// 15 | /// Abstract base class for Structured Storage entities. 16 | /// 17 | /// 18 | /// 19 | /// const String STORAGE_NAME = "report.xls"; 20 | /// CompoundFile cf = new CompoundFile(STORAGE_NAME); 21 | /// FileStream output = new FileStream("LogEntries.txt", FileMode.Create); 22 | /// TextWriter tw = new StreamWriter(output); 23 | /// // CFItem represents both storage and stream items 24 | /// VisitedEntryAction va = delegate(CFItem item) 25 | /// { 26 | /// tw.WriteLine(item.Name); 27 | /// }; 28 | /// cf.RootStorage.VisitEntries(va, true); 29 | /// tw.Close(); 30 | /// 31 | /// 32 | public abstract class CFItem : IComparable 33 | { 34 | protected CompoundFile CompoundFile { get; } 35 | 36 | protected void CheckDisposed() 37 | { 38 | if (CompoundFile.IsClosed) 39 | throw new CFDisposedException( 40 | "Owner Compound file has been closed and owned items have been invalidated"); 41 | } 42 | 43 | protected CFItem() 44 | { 45 | } 46 | 47 | protected CFItem(CompoundFile compoundFile) 48 | { 49 | CompoundFile = compoundFile; 50 | } 51 | 52 | #region IDirectoryEntry Members 53 | 54 | internal IDirectoryEntry DirEntry { get; set; } 55 | 56 | internal int CompareTo(CFItem other) 57 | { 58 | return DirEntry.CompareTo(other.DirEntry); 59 | } 60 | 61 | #endregion 62 | 63 | #region IComparable Members 64 | 65 | public int CompareTo(object obj) 66 | { 67 | return DirEntry.CompareTo(((CFItem) obj).DirEntry); 68 | } 69 | 70 | #endregion 71 | 72 | public static bool operator ==(CFItem leftItem, CFItem rightItem) 73 | { 74 | // If both are null, or both are same instance, return true. 75 | if (ReferenceEquals(leftItem, rightItem)) 76 | { 77 | return true; 78 | } 79 | 80 | // If one is null, but not both, return false. 81 | if (((object) leftItem == null) || ((object) rightItem == null)) 82 | { 83 | return false; 84 | } 85 | 86 | // Return true if the fields match: 87 | return leftItem.CompareTo(rightItem) == 0; 88 | } 89 | 90 | public static bool operator !=(CFItem leftItem, CFItem rightItem) 91 | { 92 | return !(leftItem == rightItem); 93 | } 94 | 95 | public override bool Equals(object obj) 96 | { 97 | return CompareTo(obj) == 0; 98 | } 99 | 100 | public override int GetHashCode() 101 | { 102 | // ReSharper disable once NonReadonlyMemberInGetHashCode 103 | return DirEntry.GetEntryName().GetHashCode(); 104 | } 105 | 106 | /// 107 | /// Get entity name 108 | /// 109 | public string Name 110 | { 111 | get 112 | { 113 | var n = DirEntry.GetEntryName(); 114 | return !string.IsNullOrEmpty(n) ? n.TrimEnd('\0') : string.Empty; 115 | } 116 | } 117 | 118 | /// 119 | /// Size in bytes of the item. It has a valid value 120 | /// only if entity is a stream, otherwise it is setted to zero. 121 | /// 122 | public long Size => DirEntry.Size; 123 | 124 | 125 | /// 126 | /// Return true if item is Storage 127 | /// 128 | /// 129 | /// This check doesn't use reflection or runtime type information 130 | /// and doesn't suffer related performance penalties. 131 | /// 132 | public bool IsStorage => DirEntry.StgType == StgType.StgStorage; 133 | 134 | /// 135 | /// Return true if item is a Stream 136 | /// 137 | /// 138 | /// This check doesn't use reflection or runtime type information 139 | /// and doesn't suffer related performance penalties. 140 | /// 141 | public bool IsStream => DirEntry.StgType == StgType.StgStream; 142 | 143 | /// 144 | /// Return true if item is the Root Storage 145 | /// 146 | /// 147 | /// This check doesn't use reflection or runtime type information 148 | /// and doesn't suffer related performance penalties. 149 | /// 150 | public bool IsRoot => DirEntry.StgType == StgType.StgRoot; 151 | 152 | /// 153 | /// Get/Set the Creation Date of the current item 154 | /// 155 | public DateTime CreationDate 156 | { 157 | get => DateTime.FromFileTime(BitConverter.ToInt64(DirEntry.CreationDate, 0)); 158 | 159 | set 160 | { 161 | if (DirEntry.StgType != StgType.StgStream && DirEntry.StgType != StgType.StgRoot) 162 | DirEntry.CreationDate = BitConverter.GetBytes((value.ToFileTime())); 163 | else 164 | throw new CFException("Creation Date can only be set on storage entries"); 165 | } 166 | } 167 | 168 | /// 169 | /// Get/Set the Modify Date of the current item 170 | /// 171 | public DateTime ModifyDate 172 | { 173 | get => DateTime.FromFileTime(BitConverter.ToInt64(DirEntry.ModifyDate, 0)); 174 | 175 | set 176 | { 177 | if (DirEntry.StgType != StgType.StgStream && DirEntry.StgType != StgType.StgRoot) 178 | DirEntry.ModifyDate = BitConverter.GetBytes((value.ToFileTime())); 179 | else 180 | throw new CFException("Modify Date can only be set on storage entries"); 181 | } 182 | } 183 | 184 | /// 185 | /// Get/Set Object class Guid for Root and Storage entries. 186 | /// 187 | public Guid CLSID 188 | { 189 | get => DirEntry.StorageCLSID; 190 | set 191 | { 192 | if (DirEntry.StgType != StgType.StgStream) 193 | { 194 | DirEntry.StorageCLSID = value; 195 | } 196 | else 197 | throw new CFException("Object class GUID can only be set on Root and Storage entries"); 198 | } 199 | } 200 | 201 | int IComparable.CompareTo(CFItem other) 202 | { 203 | return DirEntry.CompareTo(other.DirEntry); 204 | } 205 | 206 | public override string ToString() 207 | { 208 | if (DirEntry != null) 209 | return "[" + DirEntry.LeftSibling + "," + DirEntry.SID + "," + DirEntry.RightSibling + 210 | "]" + " " + DirEntry.GetEntryName(); 211 | return string.Empty; 212 | } 213 | } 214 | } -------------------------------------------------------------------------------- /src/CFItemComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace OpenMcdf 4 | { 5 | internal class CFItemComparer : IComparer 6 | { 7 | public int Compare(CFItem x, CFItem y) 8 | { 9 | // X CompareTo Y : X > Y --> 1 ; X < Y --> -1 10 | return (x.DirEntry.CompareTo(y.DirEntry)); 11 | 12 | //Compare X < Y --> -1 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/CFStream.cs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | * 5 | * The Original Code is OpenMCDF - Compound Document Format library. 6 | * 7 | * The Initial Developer of the Original Code is Federico Blaseotto.*/ 8 | 9 | using System.IO; 10 | 11 | namespace OpenMcdf 12 | { 13 | /// 14 | /// OLE structured storage stream Object 15 | /// It is contained inside a Storage object in a file-directory 16 | /// relationship and indexed by its name. 17 | /// 18 | public class CFStream : CFItem 19 | { 20 | internal CFStream(CompoundFile compoundFile, IDirectoryEntry dirEntry) 21 | : base(compoundFile) 22 | { 23 | if (dirEntry == null || dirEntry.SID < 0) 24 | throw new CFException("Attempting to add a CFStream using an uninitialized directory"); 25 | 26 | DirEntry = dirEntry; 27 | } 28 | 29 | /// 30 | /// Set the data associated with the stream object. 31 | /// 32 | /// 33 | /// 34 | /// byte[] b = new byte[]{0x0,0x1,0x2,0x3}; 35 | /// CompoundFile cf = new CompoundFile(); 36 | /// CFStream myStream = cf.RootStorage.AddStream("MyStream"); 37 | /// myStream.SetData(b); 38 | /// 39 | /// 40 | /// Data bytes to write to this stream 41 | /// Existing associated data will be lost after method invocation 42 | public void SetData(byte[] data) 43 | { 44 | CheckDisposed(); 45 | 46 | CompoundFile.FreeData(this); 47 | CompoundFile.WriteData(this, data); 48 | } 49 | 50 | /// 51 | /// Write a data buffer to a specific position into current CFStream object 52 | /// 53 | /// Data buffer to Write 54 | /// Position into the stream object to start writing from 55 | /// Current stream will be extended to receive data buffer over 56 | /// its current size 57 | public void Write(byte[] data, long position) 58 | { 59 | Write(data, position, 0, data.Length); 60 | } 61 | 62 | /// 63 | /// Write count bytes of a data buffer to a specific position into 64 | /// the current CFStream object starting from the specified position. 65 | /// 66 | /// Data buffer to copy bytes from 67 | /// Position into the stream object to start writing from 68 | /// The zero-based byte offset in buffer at which to 69 | /// begin copying bytes to the current CFStream. 70 | /// The number of bytes to be written to the current CFStream 71 | /// Current stream will be extended to receive data buffer over 72 | /// its current size. 73 | internal void Write(byte[] data, long position, int offset, int count) 74 | { 75 | CheckDisposed(); 76 | CompoundFile.WriteData(this, data, position, offset, count); 77 | } 78 | 79 | /// 80 | /// Append the provided data to stream data. 81 | /// 82 | /// 83 | /// 84 | /// byte[] b = new byte[]{0x0,0x1,0x2,0x3}; 85 | /// byte[] b2 = new byte[]{0x4,0x5,0x6,0x7}; 86 | /// CompoundFile cf = new CompoundFile(); 87 | /// CFStream myStream = cf.RootStorage.AddStream("MyStream"); 88 | /// myStream.SetData(b); // here we could also have invoked .AppendData 89 | /// myStream.AppendData(b2); 90 | /// cf.Save("MyLargeStreamsFile.cfs); 91 | /// cf.Close(); 92 | /// 93 | /// 94 | /// Data bytes to append to this stream 95 | /// 96 | /// This method allows user to create stream with more than 2GB of data, 97 | /// appending data to the end of existing ones. 98 | /// Large streams (>2GB) are only supported by CFS version 4. 99 | /// Append data can also be invoked on streams with no data in order 100 | /// to simplify its use inside loops. 101 | /// 102 | public void Append(byte[] data) 103 | { 104 | CheckDisposed(); 105 | if (Size > 0) 106 | { 107 | CompoundFile.AppendData(this, data); 108 | } 109 | else 110 | { 111 | CompoundFile.WriteData(this, data); 112 | } 113 | } 114 | 115 | /// 116 | /// Get all the data associated with the stream object. 117 | /// 118 | /// 119 | /// 120 | /// CompoundFile cf2 = new CompoundFile("AFileName.cfs"); 121 | /// CFStream st = cf2.RootStorage.GetStream("MyStream"); 122 | /// byte[] buffer = st.ReadAll(); 123 | /// 124 | /// 125 | /// Array of byte containing stream data 126 | /// 127 | /// Raised when the owner compound file has been closed. 128 | /// 129 | public byte[] GetData() 130 | { 131 | CheckDisposed(); 132 | 133 | return CompoundFile.GetData(this); 134 | } 135 | 136 | /// 137 | /// Read bytes associated with the stream object, starting from 138 | /// read. 139 | /// 140 | /// Array of bytes that will contain stream data 141 | /// The zero-based byte position in the stream at which to begin reading 142 | /// the data from. 143 | /// The maximum number of bytes to be read from the current stream. 144 | /// The count of bytes effectively read 145 | /// Method may read a number of bytes lesser then the requested one. 146 | /// 147 | /// CompoundFile cf = null; 148 | /// byte[] b = Helpers.GetBuffer(1024 * 2, 0xAA); //2MB buffer 149 | /// CFStream item = cf.RootStorage.GetStream("AStream"); 150 | /// 151 | /// cf = new CompoundFile("$AFILENAME.cfs", CFSUpdateMode.ReadOnly, CFSConfiguration.Default); 152 | /// item = cf.RootStorage.GetStream("AStream"); 153 | /// 154 | /// byte[] buffer = new byte[2048]; 155 | /// item.Read(buffer, 0, 2048); 156 | /// Assert.IsTrue(Helpers.CompareBuffer(b, buffer)); 157 | /// 158 | /// 159 | /// 160 | /// Raised when the owner compound file has been closed. 161 | /// 162 | public int Read(byte[] buffer, long position, int count) 163 | { 164 | CheckDisposed(); 165 | return CompoundFile.ReadData(this, position, buffer, 0, count); 166 | } 167 | 168 | /// 169 | /// Read bytes associated with the stream object, starting from 170 | /// a provided . Method returns the effective count of bytes 171 | /// read. 172 | /// 173 | /// Array of bytes that will contain stream data 174 | /// The zero-based byte position in the stream at which to begin reading 175 | /// the data from. 176 | /// The zero-based byte offset in buffer at which to begin storing the data read from the current stream. 177 | /// The maximum number of bytes to be read from the current stream. 178 | /// The count of bytes effectively read 179 | /// Method may read a number of bytes lesser then the requested one. 180 | /// 181 | /// CompoundFile cf = null; 182 | /// byte[] b = Helpers.GetBuffer(1024 * 2, 0xAA); //2MB buffer 183 | /// CFStream item = cf.RootStorage.GetStream("AStream"); 184 | /// 185 | /// cf = new CompoundFile("$AFILENAME.cfs", CFSUpdateMode.ReadOnly, CFSConfiguration.Default); 186 | /// item = cf.RootStorage.GetStream("AStream"); 187 | /// 188 | /// byte[] buffer = new byte[2048]; 189 | /// item.Read(buffer, 0, 2048); 190 | /// Assert.IsTrue(Helpers.CompareBuffer(b, buffer)); 191 | /// 192 | /// 193 | /// 194 | /// Raised when the owner compound file has been closed. 195 | /// 196 | internal int Read(byte[] buffer, long position, int offset, int count) 197 | { 198 | CheckDisposed(); 199 | return CompoundFile.ReadData(this, position, buffer, offset, count); 200 | } 201 | 202 | 203 | /// 204 | /// Copy data from an existing stream. 205 | /// 206 | /// A stream to read from 207 | /// 208 | /// Input stream will NOT be closed after method invocation. 209 | /// Existing associated data will be deleted. 210 | /// 211 | public void CopyFrom(Stream input) 212 | { 213 | CheckDisposed(); 214 | 215 | var buffer = new byte[input.Length]; 216 | 217 | if (input.CanSeek) 218 | { 219 | input.Seek(0, SeekOrigin.Begin); 220 | } 221 | 222 | input.Read(buffer, 0, (int) input.Length); 223 | SetData(buffer); 224 | } 225 | 226 | /// 227 | /// Resize stream padding with zero if enlarging, trimming data if reducing size. 228 | /// 229 | /// New length to assign to this stream 230 | public void Resize(long length) 231 | { 232 | CompoundFile.SetStreamLength(this, length); 233 | } 234 | } 235 | } -------------------------------------------------------------------------------- /src/DirectoryEntry.cs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | * 5 | * The Original Code is OpenMCDF - Compound Document Format library. 6 | * 7 | * The Initial Developer of the Original Code is Federico Blaseotto.*/ 8 | 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Text; 12 | using System.IO; 13 | using RedBlackTree; 14 | 15 | namespace OpenMcdf 16 | { 17 | internal class DirectoryEntry : IDirectoryEntry 18 | { 19 | internal const int THIS_IS_GREATER = 1; 20 | internal const int OTHER_IS_GREATER = -1; 21 | private readonly IList _dirRepository; 22 | 23 | public int SID { get; set; } = -1; 24 | 25 | internal static int nostream 26 | = unchecked((int) 0xFFFFFFFF); 27 | 28 | private DirectoryEntry(string name, StgType stgType, IList dirRepository) 29 | { 30 | _dirRepository = dirRepository; 31 | 32 | StgType = stgType; 33 | 34 | switch (stgType) 35 | { 36 | case StgType.StgStream: 37 | 38 | _storageCLSID = new Guid("00000000000000000000000000000000"); 39 | CreationDate = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 40 | ModifyDate = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 41 | break; 42 | 43 | case StgType.StgStorage: 44 | CreationDate = BitConverter.GetBytes((DateTime.Now.ToFileTime())); 45 | break; 46 | 47 | case StgType.StgRoot: 48 | CreationDate = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 49 | ModifyDate = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 50 | break; 51 | } 52 | 53 | SetEntryName(name); 54 | } 55 | 56 | public byte[] EntryName 57 | { 58 | get; 59 | private set; 60 | } = new byte[64]; 61 | 62 | public string GetEntryName() 63 | { 64 | if (EntryName != null && EntryName.Length > 0) 65 | { 66 | return Encoding.Unicode.GetString(EntryName).Remove((_nameLength - 1) / 2); 67 | } 68 | return string.Empty; 69 | } 70 | 71 | public void SetEntryName(string entryName) 72 | { 73 | if ( 74 | entryName.Contains(@"\") || 75 | entryName.Contains(@"/") || 76 | entryName.Contains(@":") || 77 | entryName.Contains(@"!") 78 | ) 79 | throw new CFException( 80 | "Invalid character in entry: the characters '\\', '/', ':','!' cannot be used in entry name"); 81 | 82 | if (entryName.Length > 31) 83 | throw new CFException("Entry name MUST be smaller than 31 characters"); 84 | 85 | 86 | var temp = Encoding.Unicode.GetBytes(entryName); 87 | var newName = new byte[64]; 88 | Buffer.BlockCopy(temp, 0, newName, 0, temp.Length); 89 | newName[temp.Length] = 0x00; 90 | newName[temp.Length + 1] = 0x00; 91 | 92 | EntryName = newName; 93 | _nameLength = (ushort) (temp.Length + 2); 94 | } 95 | 96 | private ushort _nameLength; 97 | 98 | public ushort NameLength 99 | { 100 | get => _nameLength; 101 | set => throw new NotImplementedException(); 102 | } 103 | 104 | public StgType StgType { get; set; } 105 | 106 | public StgColor StgColor { get; set; } = StgColor.Black; 107 | 108 | public int LeftSibling { get; set; } = nostream; 109 | 110 | public int RightSibling { get; set; } = nostream; 111 | 112 | public int Child { get; set; } = nostream; 113 | 114 | private Guid _storageCLSID 115 | = Guid.NewGuid(); 116 | 117 | public Guid StorageCLSID 118 | { 119 | get => _storageCLSID; 120 | set => _storageCLSID = value; 121 | } 122 | 123 | public int StateBits { get; set; } 124 | 125 | public byte[] CreationDate { get; set; } = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 126 | 127 | public byte[] ModifyDate { get; set; } = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 128 | 129 | public int StartSetc { get; set; } = Sector.ENDOFCHAIN; 130 | 131 | public long Size { get; set; } 132 | 133 | 134 | public int CompareTo(object obj) 135 | { 136 | if (!(obj is IDirectoryEntry otherDir)) 137 | throw new CFException("Invalid casting: compared object does not implement IDirectorEntry interface"); 138 | 139 | if (NameLength > otherDir.NameLength) 140 | { 141 | return THIS_IS_GREATER; 142 | } 143 | if (NameLength < otherDir.NameLength) 144 | { 145 | return OTHER_IS_GREATER; 146 | } 147 | var thisName = Encoding.Unicode.GetString(EntryName, 0, NameLength); 148 | var otherName = Encoding.Unicode.GetString(otherDir.EntryName, 0, otherDir.NameLength); 149 | 150 | for (var z = 0; z < thisName.Length; z++) 151 | { 152 | var thisChar = char.ToUpperInvariant(thisName[z]); 153 | var otherChar = char.ToUpperInvariant(otherName[z]); 154 | 155 | if (thisChar > otherChar) 156 | return THIS_IS_GREATER; 157 | if (thisChar < otherChar) 158 | return OTHER_IS_GREATER; 159 | } 160 | 161 | return 0; 162 | 163 | // return String.Compare(Encoding.Unicode.GetString(this.EntryName).ToUpper(), Encoding.Unicode.GetString(other.EntryName).ToUpper()); 164 | } 165 | 166 | public override bool Equals(object obj) 167 | { 168 | return CompareTo(obj) == 0; 169 | } 170 | 171 | /// 172 | /// FNV hash, short for Fowler/Noll/Vo 173 | /// 174 | /// 175 | /// (not warranted) unique hash for byte array 176 | private static ulong FnvHash(byte[] buffer) 177 | { 178 | ulong h = 2166136261; 179 | int i; 180 | 181 | for (i = 0; i < buffer.Length; i++) 182 | h = (h * 16777619) ^ buffer[i]; 183 | 184 | return h; 185 | } 186 | 187 | public override int GetHashCode() 188 | { 189 | // ReSharper disable once NonReadonlyMemberInGetHashCode 190 | return (int) FnvHash(EntryName); 191 | } 192 | 193 | public void Write(Stream stream) 194 | { 195 | var rw = new StreamRW(stream); 196 | 197 | rw.Write(EntryName); 198 | rw.Write(_nameLength); 199 | rw.Write((byte) StgType); 200 | rw.Write((byte) StgColor); 201 | rw.Write(LeftSibling); 202 | rw.Write(RightSibling); 203 | rw.Write(Child); 204 | rw.Write(_storageCLSID.ToByteArray()); 205 | rw.Write(StateBits); 206 | rw.Write(CreationDate); 207 | rw.Write(ModifyDate); 208 | rw.Write(StartSetc); 209 | rw.Write(Size); 210 | 211 | rw.Close(); 212 | } 213 | 214 | //public Byte[] ToByteArray() 215 | //{ 216 | // MemoryStream ms 217 | // = new MemoryStream(128); 218 | 219 | // BinaryWriter bw = new BinaryWriter(ms); 220 | 221 | // byte[] paddedName = new byte[64]; 222 | // Array.Copy(entryName, paddedName, entryName.Length); 223 | 224 | // bw.Write(paddedName); 225 | // bw.Write(nameLength); 226 | // bw.Write((byte)stgType); 227 | // bw.Write((byte)stgColor); 228 | // bw.Write(leftSibling); 229 | // bw.Write(rightSibling); 230 | // bw.Write(child); 231 | // bw.Write(storageCLSID.ToByteArray()); 232 | // bw.Write(stateBits); 233 | // bw.Write(creationDate); 234 | // bw.Write(modifyDate); 235 | // bw.Write(startSetc); 236 | // bw.Write(size); 237 | 238 | // return ms.ToArray(); 239 | //} 240 | 241 | public void Read(Stream stream, CFSVersion ver = CFSVersion.Ver_3) 242 | { 243 | var rw = new StreamRW(stream); 244 | 245 | EntryName = rw.ReadBytes(64); 246 | _nameLength = rw.ReadUInt16(); 247 | StgType = (StgType) rw.ReadByte(); 248 | //rw.ReadByte();//Ignore color, only black tree 249 | StgColor = (StgColor) rw.ReadByte(); 250 | LeftSibling = rw.ReadInt32(); 251 | RightSibling = rw.ReadInt32(); 252 | Child = rw.ReadInt32(); 253 | 254 | // Thanks to bugaccount (BugTrack id 3519554) 255 | if (StgType == StgType.StgInvalid) 256 | { 257 | LeftSibling = nostream; 258 | RightSibling = nostream; 259 | Child = nostream; 260 | } 261 | 262 | _storageCLSID = new Guid(rw.ReadBytes(16)); 263 | StateBits = rw.ReadInt32(); 264 | CreationDate = rw.ReadBytes(8); 265 | ModifyDate = rw.ReadBytes(8); 266 | StartSetc = rw.ReadInt32(); 267 | 268 | if (ver == CFSVersion.Ver_3) 269 | { 270 | // avoid dirty read for version 3 files (max size: 32bit integer) 271 | // where most significant bits are not initialized to zero 272 | 273 | Size = rw.ReadInt32(); 274 | rw.ReadBytes(4); //discard most significant 4 (possibly) dirty bytes 275 | } 276 | else 277 | { 278 | Size = rw.ReadInt64(); 279 | } 280 | } 281 | 282 | public string Name => GetEntryName(); 283 | 284 | 285 | public IRbNode Left 286 | { 287 | get 288 | { 289 | if (LeftSibling == nostream) 290 | return null; 291 | 292 | return _dirRepository[LeftSibling]; 293 | } 294 | set 295 | { 296 | LeftSibling = value != null ? ((IDirectoryEntry) value).SID : nostream; 297 | 298 | if (LeftSibling != nostream) 299 | _dirRepository[LeftSibling].Parent = this; 300 | } 301 | } 302 | 303 | public IRbNode Right 304 | { 305 | get 306 | { 307 | if (RightSibling == nostream) 308 | return null; 309 | 310 | return _dirRepository[RightSibling]; 311 | } 312 | set 313 | { 314 | RightSibling = value != null ? ((IDirectoryEntry) value).SID : nostream; 315 | 316 | if (RightSibling != nostream) 317 | _dirRepository[RightSibling].Parent = this; 318 | } 319 | } 320 | 321 | public Color Color 322 | { 323 | get => (Color) StgColor; 324 | set => StgColor = (StgColor) value; 325 | } 326 | 327 | private IDirectoryEntry _parent; 328 | 329 | public IRbNode Parent 330 | { 331 | get => _parent; 332 | set => _parent = value as IDirectoryEntry; 333 | } 334 | 335 | public IRbNode Grandparent() 336 | { 337 | return _parent?.Parent; 338 | } 339 | 340 | public IRbNode Sibling() 341 | { 342 | return Equals(this, Parent.Left) ? Parent.Right : Parent.Left; 343 | } 344 | 345 | public IRbNode Uncle() 346 | { 347 | return _parent != null ? Parent.Sibling() : null; 348 | } 349 | 350 | internal static IDirectoryEntry New(string name, StgType stgType, IList dirRepository) 351 | { 352 | DirectoryEntry de; 353 | if (dirRepository != null) 354 | { 355 | de = new DirectoryEntry(name, stgType, dirRepository); 356 | // No invalid directory entry found 357 | dirRepository.Add(de); 358 | de.SID = dirRepository.Count - 1; 359 | } 360 | else 361 | throw new ArgumentNullException("dirRepository", "Directory repository cannot be null in New() method"); 362 | 363 | return de; 364 | } 365 | 366 | internal static IDirectoryEntry Mock(string name, StgType stgType) 367 | { 368 | var de = new DirectoryEntry(name, stgType, null); 369 | 370 | return de; 371 | } 372 | 373 | internal static IDirectoryEntry TryNew(string name, StgType stgType, IList dirRepository) 374 | { 375 | var de = new DirectoryEntry(name, stgType, dirRepository); 376 | 377 | // If we are not adding an invalid dirEntry as 378 | // in a normal loading from file (invalid directories MAY pad a sector) 379 | // Find first available invalid slot (if any) to reuse it 380 | for (var i = 0; i < dirRepository.Count; i++) 381 | { 382 | if (dirRepository[i].StgType != StgType.StgInvalid) 383 | continue; 384 | 385 | dirRepository[i] = de; 386 | de.SID = i; 387 | return de; 388 | } 389 | 390 | // No invalid directory entry found 391 | dirRepository.Add(de); 392 | de.SID = dirRepository.Count - 1; 393 | 394 | return de; 395 | } 396 | 397 | public override string ToString() 398 | { 399 | return Name + " [" + SID + "]" + (StgType == StgType.StgStream ? "Stream" : "Storage"); 400 | } 401 | 402 | public void AssignValueTo(IRbNode other) 403 | { 404 | if (!(other is DirectoryEntry d)) 405 | return; 406 | 407 | d.SetEntryName(GetEntryName()); 408 | 409 | d.CreationDate = new byte[CreationDate.Length]; 410 | CreationDate.CopyTo(d.CreationDate, 0); 411 | 412 | d.ModifyDate = new byte[ModifyDate.Length]; 413 | ModifyDate.CopyTo(d.ModifyDate, 0); 414 | 415 | d.Size = Size; 416 | d.StartSetc = StartSetc; 417 | d.StateBits = StateBits; 418 | d.StgType = StgType; 419 | d._storageCLSID = new Guid(_storageCLSID.ToByteArray()); 420 | d.Child = Child; 421 | } 422 | } 423 | } -------------------------------------------------------------------------------- /src/Enums.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | 4 | namespace OpenMcdf 5 | { 6 | /// 7 | /// Configuration parameters for the compound files. 8 | /// They can be OR-combined to configure 9 | /// Compound file behavior. 10 | /// All flags are NOT set by Default. 11 | /// 12 | [Flags] 13 | public enum CFSConfiguration 14 | { 15 | /// 16 | /// Sector Recycling turn off, 17 | /// free sectors erasing off, 18 | /// format validation exception raised 19 | /// 20 | Default = 1, 21 | 22 | /// 23 | /// Sector recycling reduces data writing performances 24 | /// but avoids space wasting in scenarios with frequently 25 | /// data manipulation of the same streams. 26 | /// 27 | SectorRecycle = 2, 28 | 29 | /// 30 | /// Free sectors are erased to avoid information leakage 31 | /// 32 | EraseFreeSectors = 4, 33 | 34 | /// 35 | /// No exception is raised when a validation error occurs. 36 | /// This can possibly lead to a security issue but gives 37 | /// a chance to corrupted files to load. 38 | /// 39 | NoValidationException = 8, 40 | 41 | /// 42 | /// If this flag is set true, 43 | /// backing stream is kept open after CompoundFile disposal 44 | /// 45 | LeaveOpen = 16, 46 | } 47 | 48 | /// 49 | /// Binary File Format Version. Sector size is 512 byte for version 3, 50 | /// 4096 for version 4 51 | /// 52 | [SuppressMessage("ReSharper", "InconsistentNaming")] 53 | public enum CFSVersion 54 | { 55 | /// 56 | /// Compound file version 3 - The default and most common version available. Sector size 512 bytes, 2GB max file size. 57 | /// 58 | Ver_3 = 3, 59 | 60 | /// 61 | /// Compound file version 4 - Sector size is 4096 bytes. Using this version could bring some compatibility problem with existing applications. 62 | /// 63 | Ver_4 = 4 64 | } 65 | 66 | /// 67 | /// Update mode of the compound file. 68 | /// Default is ReadOnly. 69 | /// 70 | public enum CFSUpdateMode 71 | { 72 | /// 73 | /// ReadOnly update mode prevents overwriting 74 | /// of the opened file. 75 | /// Data changes are allowed but they have to be 76 | /// persisted on a different file when required 77 | /// using method 78 | /// 79 | ReadOnly, 80 | 81 | /// 82 | /// Update mode allows subsequent data changing operations 83 | /// to be persisted directly on the opened file or stream 84 | /// using the Commit 85 | /// method when required. Warning: this option may cause existing data loss if misused. 86 | /// 87 | Update 88 | } 89 | 90 | public enum StgType 91 | { 92 | StgInvalid = 0, 93 | StgStorage = 1, 94 | StgStream = 2, 95 | StgLockbytes = 3, 96 | StgProperty = 4, 97 | StgRoot = 5 98 | } 99 | 100 | public enum StgColor 101 | { 102 | Red = 0, 103 | Black = 1 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Extensions/CFStreamExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace OpenMcdf.Extensions 5 | { 6 | public static class CFStreamExtension 7 | { 8 | private class StreamDecorator : Stream 9 | { 10 | private CFStream _cfStream; 11 | private long _position; 12 | 13 | public StreamDecorator(CFStream cfstream) 14 | { 15 | _cfStream = cfstream; 16 | } 17 | 18 | public override bool CanRead => true; 19 | 20 | public override bool CanSeek => true; 21 | 22 | public override bool CanWrite => true; 23 | 24 | public override void Flush() 25 | { 26 | // nothing to do; 27 | } 28 | 29 | public override long Length => _cfStream.Size; 30 | 31 | public override long Position 32 | { 33 | get => _position; 34 | set => _position = value; 35 | } 36 | 37 | public override int Read(byte[] buffer, int offset, int count) 38 | { 39 | if (count > buffer.Length) 40 | throw new ArgumentException("Count parameter exceeds buffer size"); 41 | 42 | if (buffer == null) 43 | throw new ArgumentNullException("Buffer cannot be null"); 44 | 45 | if (offset < 0 || count < 0) 46 | throw new ArgumentOutOfRangeException("Offset and Count parameters must be non-negative numbers"); 47 | 48 | if (_position >= _cfStream.Size) 49 | return 0; 50 | 51 | count = _cfStream.Read(buffer, _position, offset, count); 52 | _position += count; 53 | return count; 54 | } 55 | 56 | public override long Seek(long offset, SeekOrigin origin) 57 | { 58 | switch (origin) 59 | { 60 | case SeekOrigin.Begin: 61 | _position = offset; 62 | break; 63 | case SeekOrigin.Current: 64 | _position += offset; 65 | break; 66 | case SeekOrigin.End: 67 | _position -= offset; 68 | break; 69 | default: 70 | throw new Exception("Invalid origin selected"); 71 | } 72 | 73 | return _position; 74 | } 75 | 76 | public override void SetLength(long value) 77 | { 78 | _cfStream.Resize(value); 79 | } 80 | 81 | public override void Write(byte[] buffer, int offset, int count) 82 | { 83 | _cfStream.Write(buffer, _position, offset, count); 84 | _position += count; 85 | } 86 | 87 | #if !NETSTANDARD1_6 && !NETSTANDARD2_0 88 | public override void Close() 89 | { 90 | // Do nothing 91 | } 92 | #endif 93 | } 94 | 95 | /// 96 | /// Return the current CFStream object 97 | /// as a Stream object. 98 | /// 99 | /// Current CFStream object 100 | /// A Stream object representing structured stream data 101 | public static Stream AsIoStream(this CFStream cfStream) 102 | { 103 | return new StreamDecorator(cfStream); 104 | } 105 | 106 | /// 107 | /// Return the current CFStream object 108 | /// as a OLE properties Stream. 109 | /// 110 | /// 111 | /// A OLE Propertie stream 112 | public static OLEProperties.PropertySetStream AsOleProperties(this CFStream cfStream) 113 | { 114 | var result = new OLEProperties.PropertySetStream(); 115 | result.Read(new BinaryReader(new StreamDecorator(cfStream))); 116 | return result; 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /src/Extensions/OLEProperties/Interfaces/IBinarySerializable.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace OpenMcdf.Extensions.OLEProperties.Interfaces 4 | { 5 | public interface IBinarySerializable 6 | { 7 | void Write(BinaryWriter bw); 8 | void Read(BinaryReader br); 9 | } 10 | } -------------------------------------------------------------------------------- /src/Extensions/OLEProperties/Interfaces/ITypedPropertyValue.cs: -------------------------------------------------------------------------------- 1 | namespace OpenMcdf.Extensions.OLEProperties.Interfaces 2 | { 3 | public interface ITypedPropertyValue : IBinarySerializable 4 | { 5 | bool IsArray { get; set; } 6 | 7 | bool IsVector { get; set; } 8 | 9 | object PropertyValue { get; set; } 10 | 11 | VtPropertyType VtType 12 | { 13 | get; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/Extensions/OLEProperties/PropertyFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using OpenMcdf.Extensions.OLEProperties.Interfaces; 5 | 6 | namespace OpenMcdf.Extensions.OLEProperties 7 | { 8 | internal class PropertyFactory 9 | { 10 | public ITypedPropertyValue NewProperty(VtPropertyType vType, PropertyContext ctx) 11 | { 12 | ITypedPropertyValue pr; 13 | 14 | switch (vType) 15 | { 16 | case VtPropertyType.VtI2: 17 | pr = new VtI2Property(vType); 18 | break; 19 | case VtPropertyType.VtI4: 20 | pr = new VtI4Property(vType); 21 | break; 22 | case VtPropertyType.VtR4: 23 | pr = new VtR4Property(vType); 24 | break; 25 | case VtPropertyType.VtLpstr: 26 | pr = new VtLpstrProperty(vType, ctx.CodePage); 27 | break; 28 | case VtPropertyType.VtFiletime: 29 | pr = new VtFiletimeProperty(vType); 30 | break; 31 | case VtPropertyType.VtDecimal: 32 | pr = new VtDecimalProperty(vType); 33 | break; 34 | case VtPropertyType.VtBool: 35 | pr = new VtBoolProperty(vType); 36 | break; 37 | case VtPropertyType.VtVectorHeader: 38 | pr = new VtVectorHeader(vType); 39 | break; 40 | case VtPropertyType.VtEmpty: 41 | pr = new VtEmptyProperty(vType); 42 | break; 43 | default: 44 | throw new Exception("Unrecognized property type"); 45 | } 46 | 47 | return pr; 48 | } 49 | 50 | 51 | #region Property implementations 52 | 53 | internal class VtEmptyProperty : TypedPropertyValue 54 | { 55 | public VtEmptyProperty(VtPropertyType vType) : base(vType) 56 | { 57 | } 58 | 59 | public override void Read(BinaryReader br) 60 | { 61 | propertyValue = null; 62 | } 63 | 64 | public override void Write(BinaryWriter bw) 65 | { 66 | } 67 | } 68 | 69 | internal class VtI2Property : TypedPropertyValue 70 | { 71 | public VtI2Property(VtPropertyType vType) : base(vType) 72 | { 73 | } 74 | 75 | public override void Read(BinaryReader br) 76 | { 77 | propertyValue = br.ReadInt16(); 78 | } 79 | 80 | public override void Write(BinaryWriter bw) 81 | { 82 | bw.Write((short) propertyValue); 83 | } 84 | } 85 | 86 | internal class VtI4Property : TypedPropertyValue 87 | { 88 | public VtI4Property(VtPropertyType vType) : base(vType) 89 | { 90 | } 91 | 92 | public override void Read(BinaryReader br) 93 | { 94 | propertyValue = br.ReadInt32(); 95 | } 96 | 97 | public override void Write(BinaryWriter bw) 98 | { 99 | bw.Write((int) propertyValue); 100 | } 101 | } 102 | 103 | internal class VtR4Property : TypedPropertyValue 104 | { 105 | public VtR4Property(VtPropertyType vType) : base(vType) 106 | { 107 | } 108 | 109 | public override void Read(BinaryReader br) 110 | { 111 | propertyValue = br.ReadSingle(); 112 | } 113 | 114 | public override void Write(BinaryWriter bw) 115 | { 116 | bw.Write((float) propertyValue); 117 | } 118 | } 119 | 120 | internal class VtR8Property : TypedPropertyValue 121 | { 122 | public VtR8Property(VtPropertyType vType) : base(vType) 123 | { 124 | } 125 | 126 | public override void Read(BinaryReader br) 127 | { 128 | propertyValue = br.ReadDouble(); 129 | } 130 | 131 | public override void Write(BinaryWriter bw) 132 | { 133 | bw.Write((double) propertyValue); 134 | } 135 | } 136 | 137 | internal class VtCyProperty : TypedPropertyValue 138 | { 139 | public VtCyProperty(VtPropertyType vType) : base(vType) 140 | { 141 | } 142 | 143 | public override void Read(BinaryReader br) 144 | { 145 | propertyValue = br.ReadInt64() / 10000; 146 | } 147 | 148 | public override void Write(BinaryWriter bw) 149 | { 150 | bw.Write((long) propertyValue * 10000); 151 | } 152 | } 153 | 154 | internal class VtDateProperty : TypedPropertyValue 155 | { 156 | public VtDateProperty(VtPropertyType vType) : base(vType) 157 | { 158 | } 159 | 160 | public override void Read(BinaryReader br) 161 | { 162 | var temp = br.ReadDouble(); 163 | 164 | #if NETSTANDARD1_6 165 | propertyValue = DateTimeExtensions.FromOADate(temp); 166 | #else 167 | propertyValue = DateTime.FromOADate(temp); 168 | #endif 169 | } 170 | 171 | public override void Write(BinaryWriter bw) 172 | { 173 | bw.Write(((DateTime) propertyValue).ToOADate()); 174 | } 175 | } 176 | 177 | internal class VtLpstrProperty : TypedPropertyValue 178 | { 179 | private uint _size; 180 | private byte[] _data; 181 | private readonly int _codePage; 182 | 183 | public VtLpstrProperty(VtPropertyType vType, int codePage) : base(vType) 184 | { 185 | _codePage = codePage; 186 | } 187 | 188 | public override void Read(BinaryReader br) 189 | { 190 | _size = br.ReadUInt32(); 191 | _data = br.ReadBytes((int) _size); 192 | propertyValue = Encoding.GetEncoding(_codePage).GetString(_data); 193 | var m = (int) _size % 4; 194 | br.ReadBytes(m); // padding 195 | } 196 | 197 | public override void Write(BinaryWriter bw) 198 | { 199 | _data = Encoding.GetEncoding(_codePage).GetBytes((string) propertyValue); 200 | _size = (uint) _data.Length; 201 | var m = (int) _size % 4; 202 | bw.Write(_data); 203 | for (var i = 0; i < m; i++) // padding 204 | bw.Write(0); 205 | } 206 | } 207 | 208 | internal class VtFiletimeProperty : TypedPropertyValue 209 | { 210 | public VtFiletimeProperty(VtPropertyType vType) : base(vType) 211 | { 212 | } 213 | 214 | public override void Read(BinaryReader br) 215 | { 216 | var tmp = br.ReadInt64(); 217 | propertyValue = DateTime.FromFileTime(tmp); 218 | } 219 | 220 | public override void Write(BinaryWriter bw) 221 | { 222 | bw.Write(((DateTime) propertyValue).ToFileTime()); 223 | } 224 | } 225 | 226 | internal class VtDecimalProperty : TypedPropertyValue 227 | { 228 | public VtDecimalProperty(VtPropertyType vType) : base(vType) 229 | { 230 | } 231 | 232 | public override void Read(BinaryReader br) 233 | { 234 | decimal d; 235 | 236 | br.ReadInt16(); // wReserved 237 | var scale = br.ReadByte(); 238 | var sign = br.ReadByte(); 239 | 240 | var u = br.ReadUInt32(); 241 | d = Convert.ToDecimal(Math.Pow(2, 64)) * u; 242 | d += br.ReadUInt64(); 243 | 244 | if (sign != 0) 245 | d = -d; 246 | d /= (10 << scale); 247 | 248 | propertyValue = d; 249 | } 250 | 251 | public override void Write(BinaryWriter bw) 252 | { 253 | bw.Write((short) propertyValue); 254 | } 255 | } 256 | 257 | internal class VtBoolProperty : TypedPropertyValue 258 | { 259 | public VtBoolProperty(VtPropertyType vType) : base(vType) 260 | { 261 | } 262 | 263 | public override void Read(BinaryReader br) 264 | { 265 | propertyValue = br.ReadUInt16() == 0xFFFF; 266 | //br.ReadUInt16();//padding 267 | } 268 | } 269 | 270 | internal class VtVectorHeader : TypedPropertyValue 271 | { 272 | public VtVectorHeader(VtPropertyType vType) : base(vType) 273 | { 274 | } 275 | 276 | public override void Read(BinaryReader br) 277 | { 278 | propertyValue = br.ReadUInt32(); 279 | } 280 | } 281 | 282 | #endregion 283 | } 284 | } -------------------------------------------------------------------------------- /src/Extensions/OLEProperties/PropertyIdentifierAndOffset.cs: -------------------------------------------------------------------------------- 1 | namespace OpenMcdf.Extensions.OLEProperties 2 | { 3 | public class PropertyIdentifierAndOffset 4 | { 5 | public PropertyIdentifiersSummaryInfo PropertyIdentifier { get; set; } 6 | public uint Offset { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /src/Extensions/OLEProperties/PropertyReader.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using OpenMcdf.Extensions.OLEProperties.Interfaces; 4 | 5 | namespace OpenMcdf.Extensions.OLEProperties 6 | { 7 | public enum Behavior 8 | { 9 | CaseSensitive, 10 | CaseInsensitive 11 | } 12 | 13 | public class PropertyContext 14 | { 15 | public int CodePage { get; set; } 16 | public Behavior Behavior { get; set; } 17 | public uint Locale { get; set; } 18 | } 19 | 20 | public enum PropertyDimensions 21 | { 22 | IsScalar, 23 | IsVector, 24 | IsArray 25 | } 26 | 27 | public class PropertyReader 28 | { 29 | private readonly PropertyContext _ctx = new PropertyContext(); 30 | private readonly PropertyFactory _factory; 31 | 32 | public PropertyReader() 33 | { 34 | _factory = new PropertyFactory(); 35 | } 36 | 37 | public List ReadProperty(PropertyIdentifiersSummaryInfo propertyIdentifier, 38 | BinaryReader br) 39 | { 40 | var res = new List(); 41 | var dim = PropertyDimensions.IsScalar; 42 | 43 | var pVal = br.ReadUInt16(); 44 | 45 | var vType = (VtPropertyType) (pVal & 0x00FF); 46 | 47 | if ((pVal & 0x1000) != 0) 48 | dim = PropertyDimensions.IsVector; 49 | else if ((pVal & 0x2000) != 0) 50 | dim = PropertyDimensions.IsArray; 51 | 52 | var isVariant = ((pVal & 0x00FF) == 0x000C); 53 | 54 | br.ReadUInt16(); // Ushort Padding 55 | 56 | // ReSharper disable once SwitchStatementMissingSomeCases 57 | switch (dim) 58 | { 59 | case PropertyDimensions.IsVector: 60 | 61 | var vectorHeader = _factory.NewProperty(VtPropertyType.VtVectorHeader, _ctx); 62 | vectorHeader.Read(br); 63 | 64 | var nItems = (uint) vectorHeader.PropertyValue; 65 | 66 | for (var i = 0; i < nItems; i++) 67 | { 68 | VtPropertyType vTypeItem; 69 | 70 | if (isVariant) 71 | { 72 | var pValItem = br.ReadUInt16(); 73 | vTypeItem = (VtPropertyType) (pValItem & 0x00FF); 74 | br.ReadUInt16(); // Ushort Padding 75 | } 76 | else 77 | { 78 | vTypeItem = vType; 79 | } 80 | 81 | var p = _factory.NewProperty(vTypeItem, _ctx); 82 | 83 | p.Read(br); 84 | res.Add(p); 85 | } 86 | 87 | break; 88 | 89 | //Scalar property 90 | default: 91 | var pr = _factory.NewProperty(vType, _ctx); 92 | 93 | pr.Read(br); 94 | 95 | if (propertyIdentifier == PropertyIdentifiersSummaryInfo.CodePageString) 96 | { 97 | _ctx.CodePage = (short) pr.PropertyValue; 98 | } 99 | 100 | res.Add(pr); 101 | break; 102 | } 103 | 104 | return res; 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /src/Extensions/OLEProperties/PropertySet.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using OpenMcdf.Extensions.OLEProperties.Interfaces; 3 | 4 | namespace OpenMcdf.Extensions.OLEProperties 5 | { 6 | public class PropertySet 7 | { 8 | public uint Size { get; set; } 9 | 10 | public uint NumProperties { get; set; } 11 | 12 | public List PropertyIdentifierAndOffsets { get; set; } = new List(); 13 | 14 | public List Properties { get; set; } = new List(); 15 | } 16 | } -------------------------------------------------------------------------------- /src/Extensions/OLEProperties/PropertySetStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OpenMcdf.Extensions.OLEProperties 4 | { 5 | public class PropertySetStream 6 | { 7 | public ushort ByteOrder { get; set; } 8 | public ushort Version { get; set; } 9 | public uint SystemIdentifier { get; set; } 10 | public Guid CLSID { get; set; } 11 | public uint NumPropertySets { get; set; } 12 | public Guid Fmtid0 { get; set; } 13 | public uint Offset0 { get; set; } 14 | public Guid Fmtid1 { get; set; } 15 | public uint Offset1 { get; set; } 16 | public PropertySet PropertySet0 { get; set; } 17 | public PropertySet PropertySet1 { get; set; } 18 | 19 | public void Read(System.IO.BinaryReader br) 20 | { 21 | ByteOrder = br.ReadUInt16(); 22 | Version = br.ReadUInt16(); 23 | SystemIdentifier = br.ReadUInt32(); 24 | CLSID = new Guid(br.ReadBytes(16)); 25 | NumPropertySets = br.ReadUInt32(); 26 | Fmtid0 = new Guid(br.ReadBytes(16)); 27 | Offset0 = br.ReadUInt32(); 28 | 29 | if (NumPropertySets == 2) 30 | { 31 | Fmtid1 = new Guid(br.ReadBytes(16)); 32 | Offset1 = br.ReadUInt32(); 33 | } 34 | 35 | PropertySet0 = new PropertySet 36 | { 37 | Size = br.ReadUInt32(), 38 | NumProperties = br.ReadUInt32() 39 | }; 40 | 41 | // Read property offsets 42 | for (var i = 0; i < PropertySet0.NumProperties; i++) 43 | { 44 | var pio = new PropertyIdentifierAndOffset(); 45 | pio.PropertyIdentifier = (PropertyIdentifiersSummaryInfo) br.ReadUInt32(); 46 | pio.Offset = br.ReadUInt32(); 47 | PropertySet0.PropertyIdentifierAndOffsets.Add(pio); 48 | } 49 | 50 | // Read properties 51 | var pr = new PropertyReader(); 52 | for (var i = 0; i < PropertySet0.NumProperties; i++) 53 | { 54 | br.BaseStream.Seek(Offset0 + PropertySet0.PropertyIdentifierAndOffsets[i].Offset, 55 | System.IO.SeekOrigin.Begin); 56 | PropertySet0.Properties.AddRange( 57 | pr.ReadProperty(PropertySet0.PropertyIdentifierAndOffsets[i].PropertyIdentifier, br)); 58 | } 59 | } 60 | 61 | public void Write(System.IO.BinaryWriter bw) 62 | { 63 | throw new NotImplementedException(); 64 | } 65 | 66 | // private void LoadFromStream(Stream inStream) 67 | // { 68 | // BinaryReader br = new BinaryReader(inStream); 69 | // PropertySetStream psStream = new PropertySetStream(); 70 | // psStream.Read(br); 71 | // br.Close(); 72 | 73 | // propertySets.Clear(); 74 | 75 | // if (psStream.NumPropertySets == 1) 76 | // { 77 | // propertySets.Add(psStream.PropertySet0); 78 | // } 79 | // else 80 | // { 81 | // propertySets.Add(psStream.PropertySet0); 82 | // propertySets.Add(psStream.PropertySet1); 83 | // } 84 | 85 | // return; 86 | // } 87 | } 88 | } -------------------------------------------------------------------------------- /src/Extensions/OLEProperties/ProperyIdentifiers.cs: -------------------------------------------------------------------------------- 1 | namespace OpenMcdf.Extensions.OLEProperties 2 | { 3 | public enum PropertyIdentifiersSummaryInfo : uint 4 | { 5 | CodePageString = 0x00000001, 6 | PidsiTitle = 0x00000002, 7 | PidsiSubject = 0x00000003, 8 | PidsiAuthor = 0x00000004, 9 | PidsiKeywords = 0x00000005, 10 | PidsiComments = 0x00000006, 11 | PidsiTemplate = 0x00000007, 12 | PidsiLastauthor = 0x00000008, 13 | PidsiRevnumber = 0x00000009, 14 | PidsiAppname = 0x00000012, 15 | PidsiEdittime = 0x0000000A, 16 | PidsiLastprinted = 0x0000000B, 17 | PidsiCreateDtm = 0x0000000C, 18 | PidsiLastsaveDtm = 0x0000000D, 19 | PidsiPagecount = 0x0000000E, 20 | PidsiWordcount = 0x0000000F, 21 | PidsiCharcount = 0x00000010, 22 | PidsiDocSecurity = 0x00000013 23 | } 24 | 25 | public enum PropertyIdentifiersDocumentSummaryInfo : uint 26 | { 27 | CodePageString = 0x00000001, 28 | PiddsiCategory = 0x00000002, //Category VT_LPSTR 29 | PiddsiPresformat = 0x00000003, //PresentationTarget VT_LPSTR 30 | PiddsiBytecount = 0x00000004, //Bytes VT_I4 31 | PiddsiLinecount = 0x00000005, // Lines VT_I4 32 | PiddsiParcount = 0x00000006, // Paragraphs VT_I4 33 | PiddsiSlidecount = 0x00000007, // Slides VT_I4 34 | PiddsiNotecount = 0x00000008, // Notes VT_I4 35 | PiddsiHiddencount = 0x00000009, // HiddenSlides VT_I4 36 | PiddsiMmclipcount = 0x0000000A, // MMClips VT_I4 37 | PiddsiScale = 0x0000000B, //ScaleCrop VT_BOOL 38 | PiddsiHeadingpair = 0x0000000C, // HeadingPairs VT_VARIANT | VT_VECTOR 39 | PiddsiDocparts = 0x0000000D, //TitlesofParts VT_VECTOR | VT_LPSTR 40 | PiddsiManager = 0x0000000E, // Manager VT_LPSTR 41 | PiddsiCompany = 0x0000000F, // Company VT_LPSTR 42 | PiddsiLinksdirty = 0x00000010, //LinksUpToDate VT_BOOL 43 | } 44 | 45 | public static class Extensions 46 | { 47 | public static string GetDescription(this PropertyIdentifiersSummaryInfo identifier) 48 | { 49 | switch (identifier) 50 | { 51 | case PropertyIdentifiersSummaryInfo.CodePageString: 52 | return "CodePage"; 53 | case PropertyIdentifiersSummaryInfo.PidsiTitle: 54 | return "Title"; 55 | case PropertyIdentifiersSummaryInfo.PidsiSubject: 56 | return "Subject"; 57 | case PropertyIdentifiersSummaryInfo.PidsiAuthor: 58 | return "Author"; 59 | case PropertyIdentifiersSummaryInfo.PidsiLastauthor: 60 | return "Last Author"; 61 | case PropertyIdentifiersSummaryInfo.PidsiAppname: 62 | return "Application Name"; 63 | case PropertyIdentifiersSummaryInfo.PidsiCreateDtm: 64 | return "Create Time"; 65 | case PropertyIdentifiersSummaryInfo.PidsiLastsaveDtm: 66 | return "Last Modified Time"; 67 | case PropertyIdentifiersSummaryInfo.PidsiKeywords: 68 | return "Keywords"; 69 | case PropertyIdentifiersSummaryInfo.PidsiDocSecurity: 70 | return "Document Security"; 71 | default: return string.Empty; 72 | } 73 | } 74 | 75 | public static string GetDescription(this PropertyIdentifiersDocumentSummaryInfo identifier) 76 | { 77 | switch (identifier) 78 | { 79 | case PropertyIdentifiersDocumentSummaryInfo.CodePageString: 80 | return "CodePage"; 81 | case PropertyIdentifiersDocumentSummaryInfo.PiddsiCategory: 82 | return "Category"; 83 | case PropertyIdentifiersDocumentSummaryInfo.PiddsiCompany: 84 | return "Company"; 85 | case PropertyIdentifiersDocumentSummaryInfo.PiddsiDocparts: 86 | return "Titles of Parts"; 87 | case PropertyIdentifiersDocumentSummaryInfo.PiddsiHeadingpair: 88 | return "Heading Pairs"; 89 | case PropertyIdentifiersDocumentSummaryInfo.PiddsiHiddencount: 90 | return "Hidden Slides"; 91 | case PropertyIdentifiersDocumentSummaryInfo.PiddsiLinecount: 92 | return "Line Count"; 93 | case PropertyIdentifiersDocumentSummaryInfo.PiddsiLinksdirty: 94 | return "Links up to date"; 95 | case PropertyIdentifiersDocumentSummaryInfo.PiddsiManager: 96 | return "Manager"; 97 | case PropertyIdentifiersDocumentSummaryInfo.PiddsiMmclipcount: 98 | return "MMClips"; 99 | case PropertyIdentifiersDocumentSummaryInfo.PiddsiNotecount: 100 | return "Notes"; 101 | case PropertyIdentifiersDocumentSummaryInfo.PiddsiParcount: 102 | return "Paragraphs"; 103 | case PropertyIdentifiersDocumentSummaryInfo.PiddsiPresformat: 104 | return "Presenteation Target"; 105 | case PropertyIdentifiersDocumentSummaryInfo.PiddsiScale: 106 | return "Scale"; 107 | case PropertyIdentifiersDocumentSummaryInfo.PiddsiSlidecount: 108 | return "Slides"; 109 | default: return string.Empty; 110 | } 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /src/Extensions/OLEProperties/TypedPropertyValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenMcdf.Extensions.OLEProperties.Interfaces; 3 | 4 | namespace OpenMcdf.Extensions.OLEProperties 5 | { 6 | public class TypedPropertyValue : ITypedPropertyValue 7 | { 8 | public VtPropertyType VtType 9 | { 10 | get; 11 | //set { _VTType = value; } 12 | } 13 | 14 | protected object propertyValue; 15 | 16 | public TypedPropertyValue(VtPropertyType vtType) 17 | { 18 | VtType = vtType; 19 | } 20 | 21 | public virtual object PropertyValue 22 | { 23 | get => propertyValue; 24 | 25 | set => propertyValue = value; 26 | } 27 | 28 | 29 | public bool IsArray 30 | { 31 | get => throw new NotImplementedException(); 32 | 33 | set => throw new NotImplementedException(); 34 | } 35 | 36 | public bool IsVector 37 | { 38 | get => throw new NotImplementedException(); 39 | 40 | set => throw new NotImplementedException(); 41 | } 42 | 43 | public virtual void Read(System.IO.BinaryReader br) 44 | { 45 | } 46 | 47 | public virtual void Write(System.IO.BinaryWriter bw) 48 | { 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/Extensions/OLEProperties/VTPropertyType.cs: -------------------------------------------------------------------------------- 1 | namespace OpenMcdf.Extensions.OLEProperties 2 | { 3 | public enum VtPropertyType : ushort 4 | { 5 | VtEmpty = 0x0000, 6 | VtNull = 0x0001, 7 | VtI2 = 0x0002, 8 | VtI4 = 0x0003, 9 | VtR4 = 0x0004, 10 | VtR8 = 0x0005, 11 | VtCy = 0x0006, 12 | VtDate = 0x0007, 13 | VtBstr = 0x0008, 14 | VtError = 0x000A, 15 | VtBool = 0x000B, 16 | VtDecimal = 0x000E, 17 | VtI1 = 0x0010, 18 | VtUi1 = 0x0011, 19 | VtUi2 = 0x0012, 20 | VtUi4 = 0x0013, 21 | VtI8 = 0x0014, // MUST be an 8-byte signed integer. 22 | VtUi8 = 0x0015, // MUST be an 8-byte unsigned integer. 23 | VtInt = 0x0016, // MUST be a 4-byte signed integer. 24 | VtUint = 0x0017, // MUST be a 4-byte unsigned integer. 25 | VtLpstr = 0x001E, // MUST be a CodePageString. 26 | VtLpwstr = 0x001F, // MUST be a UnicodeString. 27 | VtFiletime = 0x0040, // MUST be a FILETIME (Packet Version). 28 | VtBlob = 0x0041, // MUST be a BLOB. 29 | 30 | VtStream = 31 | 0x0042, // MUST be an IndirectPropertyName. The storage representing the (non-simple) property set MUST have a stream element with this name. 32 | 33 | VtStorage = 34 | 0x0043, // MUST be an IndirectPropertyName. The storage representing the (non-simple) property set MUST have a storage element with this name. 35 | 36 | VtStreamedObject = 37 | 0x0044, // MUST be an IndirectPropertyName. The storage representing the (non-simple) property set MUST have a stream element with this name. 38 | 39 | VtStoredObject = 40 | 0x0045, // MUST be an IndirectPropertyName. The storage representing the (non-simple) property set MUST have a storage element with this name. 41 | VtBlobObject = 0x0046, //MUST be a BLOB. 42 | VtCF = 0x0047, //MUST be a ClipboardData. 43 | VtCLSID = 0x0048, //MUST be a GUID (Packet Version) 44 | VtVersionedStream = 0x0049, //MUST be a Versioned Stream, NOT allowed in simple property 45 | VtVectorHeader = 0x1000, //--- NOT NORMATIVE 46 | VtArrayHeader = 0x2000, //--- NOT NORMATIVE 47 | } 48 | } -------------------------------------------------------------------------------- /src/Extensions/OLEProperties/VTVectorHeader.cs: -------------------------------------------------------------------------------- 1 | namespace OpenMcdf.Extensions.OLEProperties 2 | { 3 | } -------------------------------------------------------------------------------- /src/Header.cs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | * 5 | * The Original Code is OpenMCDF - Compound Document Format library. 6 | * 7 | * The Initial Developer of the Original Code is Federico Blaseotto.*/ 8 | 9 | 10 | using System.IO; 11 | using System.Linq; 12 | 13 | namespace OpenMcdf 14 | { 15 | internal class Header 16 | { 17 | //0 8 Compound document file identifier: D0H CFH 11H E0H A1H B1H 1AH E1H 18 | 19 | public byte[] HeaderSignature { get; private set; } 20 | 21 | //8 16 Unique identifier (UID) of this file (not of interest in the following, may be all 0) 22 | 23 | public byte[] CLSID { get; set; } 24 | 25 | //24 2 Revision number of the file format (most used is 003EH) 26 | 27 | public ushort MinorVersion { get; private set; } 28 | 29 | //26 2 Version number of the file format (most used is 0003H) 30 | 31 | public ushort MajorVersion { get; private set; } 32 | 33 | //28 2 Byte order identifier (➜4.2): FEH FFH = Little-Endian FFH FEH = Big-Endian 34 | 35 | public ushort ByteOrder { get; private set; } 36 | 37 | //30 2 Size of a sector in the compound document file (➜3.1) in power-of-two (ssz), real sector 38 | //size is sec_size = 2ssz bytes (minimum value is 7 which means 128 bytes, most used 39 | //value is 9 which means 512 bytes) 40 | 41 | public ushort SectorShift { get; private set; } 42 | 43 | //32 2 Size of a short-sector in the short-stream container stream (➜6.1) in power-of-two (sssz), 44 | //real short-sector size is short_sec_size = 2sssz bytes (maximum value is sector size 45 | //ssz, see above, most used value is 6 which means 64 bytes) 46 | 47 | public ushort MiniSectorShift { get; private set; } 48 | 49 | //34 10 Not used 50 | 51 | public byte[] UnUsed { get; private set; } 52 | 53 | //44 4 Total number of sectors used Directory (➜5.2) 54 | 55 | public int DirectorySectorsNumber { get; set; } 56 | 57 | //44 4 Total number of sectors used for the sector allocation table (➜5.2) 58 | 59 | public int FATSectorsNumber { get; set; } 60 | 61 | //48 4 SecID of first sector of the directory stream (➜7) 62 | 63 | public int FirstDirectorySectorId { get; set; } 64 | 65 | //52 4 Not used 66 | 67 | public uint UnUsed2 { get; private set; } 68 | 69 | //56 4 Minimum size of a standard stream (in bytes, minimum allowed and most used size is 4096 70 | //bytes), streams with an actual size smaller than (and not equal to) this value are stored as 71 | //short-streams (➜6) 72 | 73 | public uint MinSizeStandardStream { get; set; } 74 | 75 | //60 4 SecID of first sector of the short-sector allocation table (➜6.2), or –2 (End Of Chain 76 | //SecID, ➜3.1) if not extant 77 | 78 | /// 79 | /// This integer field contains the starting sector number for the mini FAT 80 | /// 81 | public int FirstMiniFATSectorId { get; set; } 82 | 83 | //64 4 Total number of sectors used for the short-sector allocation table (➜6.2) 84 | 85 | public uint MiniFATSectorsNumber { get; set; } 86 | 87 | //68 4 SecID of first sector of the master sector allocation table (➜5.1), or –2 (End Of Chain 88 | //SecID, ➜3.1) if no additional sectors used 89 | 90 | public int FirstDIFATSectorId { get; set; } 91 | 92 | //72 4 Total number of sectors used for the master sector allocation table (➜5.1) 93 | 94 | public uint DIFATSectorsNumber { get; set; } 95 | 96 | //76 436 First part of the master sector allocation table (➜5.1) containing 109 SecIDs 97 | 98 | public int[] DIFAT { get; } 99 | 100 | /// 101 | /// Structured Storage signature 102 | /// 103 | protected byte[] OleCFSSignature { get; } 104 | 105 | public Header() 106 | : this(3) 107 | { 108 | } 109 | 110 | public Header(ushort version) 111 | { 112 | DIFAT = new int[109]; 113 | FirstDIFATSectorId = Sector.ENDOFCHAIN; 114 | FirstMiniFATSectorId = unchecked((int) 0xFFFFFFFE); 115 | MinSizeStandardStream = 4096; 116 | FirstDirectorySectorId = Sector.ENDOFCHAIN; 117 | UnUsed = new byte[6]; 118 | MiniSectorShift = 6; 119 | SectorShift = 9; 120 | ByteOrder = 0xFFFE; 121 | MajorVersion = 0x0003; 122 | MinorVersion = 0x003E; 123 | CLSID = new byte[16]; 124 | HeaderSignature = new byte[] {0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1}; 125 | 126 | OleCFSSignature = new byte[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 }; 127 | 128 | switch (version) 129 | { 130 | case 3: 131 | MajorVersion = 3; 132 | SectorShift = 0x0009; 133 | break; 134 | 135 | case 4: 136 | MajorVersion = 4; 137 | SectorShift = 0x000C; 138 | break; 139 | 140 | default: 141 | throw new CFException("Invalid Compound File Format version"); 142 | } 143 | 144 | for (var i = 0; i < 109; i++) 145 | { 146 | DIFAT[i] = Sector.FREESECT; 147 | } 148 | } 149 | 150 | public void Write(Stream stream) 151 | { 152 | var rw = new StreamRW(stream); 153 | 154 | rw.Write(HeaderSignature); 155 | rw.Write(CLSID); 156 | rw.Write(MinorVersion); 157 | rw.Write(MajorVersion); 158 | rw.Write(ByteOrder); 159 | rw.Write(SectorShift); 160 | rw.Write(MiniSectorShift); 161 | rw.Write(UnUsed); 162 | rw.Write(DirectorySectorsNumber); 163 | rw.Write(FATSectorsNumber); 164 | rw.Write(FirstDirectorySectorId); 165 | rw.Write(UnUsed2); 166 | rw.Write(MinSizeStandardStream); 167 | rw.Write(FirstMiniFATSectorId); 168 | rw.Write(MiniFATSectorsNumber); 169 | rw.Write(FirstDIFATSectorId); 170 | rw.Write(DIFATSectorsNumber); 171 | 172 | foreach (var i in DIFAT) 173 | { 174 | rw.Write(i); 175 | } 176 | 177 | if (MajorVersion == 4) 178 | { 179 | var zeroHead = new byte[3584]; 180 | rw.Write(zeroHead); 181 | } 182 | 183 | rw.Close(); 184 | } 185 | 186 | public void Read(Stream stream) 187 | { 188 | var rw = new StreamRW(stream); 189 | 190 | HeaderSignature = rw.ReadBytes(8); 191 | CheckSignature(); 192 | CLSID = rw.ReadBytes(16); 193 | MinorVersion = rw.ReadUInt16(); 194 | MajorVersion = rw.ReadUInt16(); 195 | CheckVersion(); 196 | ByteOrder = rw.ReadUInt16(); 197 | SectorShift = rw.ReadUInt16(); 198 | MiniSectorShift = rw.ReadUInt16(); 199 | UnUsed = rw.ReadBytes(6); 200 | DirectorySectorsNumber = rw.ReadInt32(); 201 | FATSectorsNumber = rw.ReadInt32(); 202 | FirstDirectorySectorId = rw.ReadInt32(); 203 | UnUsed2 = rw.ReadUInt32(); 204 | MinSizeStandardStream = rw.ReadUInt32(); 205 | FirstMiniFATSectorId = rw.ReadInt32(); 206 | MiniFATSectorsNumber = rw.ReadUInt32(); 207 | FirstDIFATSectorId = rw.ReadInt32(); 208 | DIFATSectorsNumber = rw.ReadUInt32(); 209 | 210 | for (var i = 0; i < 109; i++) 211 | { 212 | DIFAT[i] = rw.ReadInt32(); 213 | } 214 | 215 | rw.Close(); 216 | } 217 | 218 | private void CheckVersion() 219 | { 220 | if (MajorVersion != 3 && MajorVersion != 4) 221 | throw new CFFileFormatException( 222 | "Unsupported Binary File Format version: OpenMcdf only supports Compound Files with major version equal to 3 or 4 "); 223 | } 224 | 225 | private void CheckSignature() 226 | { 227 | if (HeaderSignature.Where((t, i) => t != OleCFSSignature[i]).Any()) 228 | { 229 | throw new CFFileFormatException("Invalid OLE structured storage file"); 230 | } 231 | } 232 | } 233 | } -------------------------------------------------------------------------------- /src/IDirectoryEntry.cs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | * 5 | * The Original Code is OpenMCDF - Compound Document Format library. 6 | * 7 | * The Initial Developer of the Original Code is Federico Blaseotto.*/ 8 | 9 | using System; 10 | using RedBlackTree; 11 | 12 | namespace OpenMcdf 13 | { 14 | internal interface IDirectoryEntry : IRbNode 15 | { 16 | int Child { get; set; } 17 | byte[] CreationDate { get; set; } 18 | byte[] EntryName { get; } 19 | string GetEntryName(); 20 | int LeftSibling { get; set; } 21 | byte[] ModifyDate { get; set; } 22 | string Name { get; } 23 | ushort NameLength { get; set; } 24 | void Read(System.IO.Stream stream, CFSVersion ver = CFSVersion.Ver_3); 25 | int RightSibling { get; set; } 26 | void SetEntryName(string entryName); 27 | int SID { get; set; } 28 | long Size { get; set; } 29 | int StartSetc { get; set; } 30 | int StateBits { get; set; } 31 | StgColor StgColor { get; set; } 32 | StgType StgType { get; set; } 33 | Guid StorageCLSID { get; set; } 34 | void Write(System.IO.Stream stream); 35 | } 36 | } -------------------------------------------------------------------------------- /src/OpenMcdf.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | OpenMcdf-2 6 | OpenMcdf-2 7 | netstandard1.6;netstandard2.0;net40;net461 8 | OpenMcdf 9 | OpenMcdf 10 | MS Compound File Storage .NET Implementation 11 | Federico Blaseotto 12 | Federico Blaseotto, Zhmayev Yaroslav 13 | Copyright © 2010-2015, Federico Blaseotto; 2016-2017 Zhmayev Yaroslav 14 | 2.1.2 15 | 2.1.2.0 16 | 2.1.2.0 17 | en 18 | https://raw.githubusercontent.com/CodeCavePro/OpenMCDF/master/LICENSE.md 19 | true 20 | https://github.com/CodeCavePro/OpenMCDF 21 | https://github.com/CodeCavePro/OpenMCDF 22 | Git 23 | Structured Storage, Compound file, Mono, OLE 24 | 25 | library 26 | 27 | 28 | 29 | 30 | ..\bin\Release\ 31 | ..\bin\Release\netstandard2.0\OpenMcdf.xml 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | ..\bin\Debug\ 42 | 43 | 44 | 45 | TRACE;DEBUG;NETSTANDARD2_0 46 | 47 | 48 | 49 | TRACE;DEBUG;NETSTANDARD1_6 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/Polyfills.cs: -------------------------------------------------------------------------------- 1 | #if NETSTANDARD1_6 2 | 3 | using System; 4 | using System.IO; 5 | 6 | #endif 7 | 8 | using System.Runtime.CompilerServices; 9 | 10 | // ReSharper disable CheckNamespace 11 | 12 | [assembly: InternalsVisibleTo("OpenMcdf.Test")] 13 | [assembly: InternalsVisibleTo("OpenMcdf.Extensions")] 14 | 15 | #if NETSTANDARD1_6 16 | 17 | public static class StreamExtension 18 | { 19 | public static void Close(this Stream stream) 20 | { 21 | } 22 | } 23 | 24 | public static class BinaryReaderExtension 25 | { 26 | public static void Close(this BinaryReader stream) 27 | { 28 | } 29 | } 30 | 31 | public class SerializableAttribute : Attribute 32 | { 33 | } 34 | 35 | /// 36 | /// Adapted this implementation for .NET Standard 1.6: 37 | /// https://github.com/dotnet/coreclr/blob/release/1.0.0-rc1/src/mscorlib/src/System/DateTime.cs 38 | /// 39 | public static class DateTimeExtensions 40 | { 41 | // Number of 100ns ticks per time unit 42 | private const long TICKS_PER_MILLISECOND = 10000; 43 | private const long TICKS_PER_DAY = TICKS_PER_MILLISECOND * 1000 * 60 * 60 * 24; 44 | 45 | // Number of milliseconds per time unit 46 | private const int MILLIS_PER_DAY = 1000 * 60 * 60 * 24; 47 | 48 | // The minimum OA date is 0100/01/01 (Note it's year 100). 49 | // The maximum OA date is 9999/12/31 50 | private const long OA_DATE_MIN_AS_TICKS = (36524 - 365) * TICKS_PER_DAY; 51 | 52 | // All OA dates must be greater than (not >=) OADateMinAsDouble 53 | private const double OA_DATE_MIN_AS_DOUBLE = -657435.0; 54 | // All OA dates must be less than (not <=) OADateMaxAsDouble 55 | private const double OA_DATE_MAX_AS_DOUBLE = 2958466.0; 56 | 57 | private static readonly long DoubleDateOffset = new DateTime(1899, 1, 1).Ticks; 58 | 59 | // Converts the DateTime instance into an OLE Automation compatible 60 | // double date. 61 | public static double ToOADate(this DateTime date) 62 | { 63 | long value = date.Ticks; 64 | if (value == 0) 65 | return 0.0; // Returns OleAut's zero'ed date value. 66 | if (value < TICKS_PER_DAY 67 | ) // This is a fix for VB. They want the default day to be 1/1/0001 rather then 12/30/1899. 68 | value += 69 | DoubleDateOffset; // We could have moved this fix down but we would like to keep the bounds check. 70 | if (value < OA_DATE_MIN_AS_TICKS) 71 | throw new OverflowException(); 72 | 73 | // Currently, our max date == OA's max date (12/31/9999), so we don't 74 | // need an overflow check in that direction. 75 | long millis = (value - DoubleDateOffset) / TICKS_PER_MILLISECOND; 76 | if (millis < 0) 77 | { 78 | long frac = millis % MILLIS_PER_DAY; 79 | if (frac != 0) millis -= (MILLIS_PER_DAY + frac) * 2; 80 | } 81 | return (double)millis / MILLIS_PER_DAY; 82 | } 83 | /// 84 | /// Creates a DateTime from an OLE Automation Date. 85 | /// 86 | /// The date in double format. 87 | /// 88 | public static DateTime FromOADate(double doubleDate) 89 | { 90 | return new DateTime(DoubleDateToTicks(doubleDate), DateTimeKind.Unspecified); 91 | } 92 | 93 | // Converts an OLE Date to a tick count. 94 | // This function is duplicated in COMDateTime.cpp 95 | internal static long DoubleDateToTicks(double value) 96 | { 97 | // The check done this way will take care of NaN 98 | if (!(value < OA_DATE_MAX_AS_DOUBLE) || !(value > OA_DATE_MIN_AS_DOUBLE)) 99 | throw new ArgumentException(); 100 | 101 | // Conversion to long will not cause an overflow here, as at this point the "value" is in between OADateMinAsDouble and OADateMaxAsDouble 102 | long millis = (long)(value * MILLIS_PER_DAY + (value >= 0 ? 0.5 : -0.5)); 103 | // The interesting thing here is when you have a value like 12.5 it all positive 12 days and 12 hours from 01/01/1899 104 | // However if you a value of -12.25 it is minus 12 days but still positive 6 hours, almost as though you meant -11.75 all negative 105 | // This line below fixes up the millis in the negative case 106 | if (millis < 0) 107 | { 108 | millis -= (millis % MILLIS_PER_DAY) * 2; 109 | } 110 | 111 | millis += DoubleDateOffset / TICKS_PER_MILLISECOND; 112 | 113 | if (millis < 0 || millis >= DateTime.MaxValue.Ticks) 114 | throw new ArgumentException(); 115 | return millis * TICKS_PER_MILLISECOND; 116 | } 117 | } 118 | 119 | #endif -------------------------------------------------------------------------------- /src/RBTree/RBTree.cs: -------------------------------------------------------------------------------- 1 | #define ASSERT 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | #if ASSERT 8 | 9 | using System.Diagnostics; 10 | 11 | #endif 12 | 13 | // ------------------------------------------------------------- 14 | // This is a porting from java code, under MIT license of | 15 | // the beautiful Red-Black Tree implementation you can find at | 16 | // http://en.literateprograms.org/Red-black_tree_(Java)#chunk | 17 | // Many Thanks to original Implementors. | 18 | // ------------------------------------------------------------- 19 | 20 | // ReSharper disable once CheckNamespace 21 | namespace RedBlackTree 22 | { 23 | public class RbTreeException : Exception 24 | { 25 | public RbTreeException(string msg) 26 | : base(msg) 27 | { 28 | } 29 | } 30 | 31 | public class RbTreeDuplicatedItemException : RbTreeException 32 | { 33 | public RbTreeDuplicatedItemException(string msg) 34 | : base(msg) 35 | { 36 | } 37 | } 38 | 39 | public enum Color 40 | { 41 | Red = 0, 42 | Black = 1 43 | } 44 | 45 | /// 46 | /// Red Black Node interface 47 | /// 48 | public interface IRbNode : IComparable 49 | { 50 | IRbNode Left { get; set; } 51 | 52 | IRbNode Right { get; set; } 53 | 54 | 55 | Color Color { get; set; } 56 | 57 | 58 | IRbNode Parent { get; set; } 59 | 60 | 61 | IRbNode Grandparent(); 62 | 63 | 64 | IRbNode Sibling(); 65 | // { 66 | //#if ASSERT 67 | // Debug.Assert(Parent != null); // Root node has no sibling 68 | //#endif 69 | // if (this == Parent.Left) 70 | // return Parent.Right; 71 | // else 72 | // return Parent.Left; 73 | // } 74 | 75 | IRbNode Uncle(); 76 | // { 77 | //#if ASSERT 78 | // Debug.Assert(Parent != null); // Root node has no uncle 79 | // Debug.Assert(Parent.Parent != null); // Children of root have no uncle 80 | //#endif 81 | // return Parent.Sibling(); 82 | // } 83 | // } 84 | 85 | void AssignValueTo(IRbNode other); 86 | } 87 | 88 | public class RbTree 89 | { 90 | public IRbNode Root { get; set; } 91 | 92 | private static Color NodeColor(IRbNode n) 93 | { 94 | return n == null ? Color.Black : n.Color; 95 | } 96 | 97 | public RbTree() 98 | { 99 | } 100 | 101 | public RbTree(IRbNode root) 102 | { 103 | Root = root; 104 | } 105 | 106 | 107 | private IRbNode LookupNode(IRbNode template) 108 | { 109 | var n = Root; 110 | 111 | while (n != null) 112 | { 113 | var compResult = template.CompareTo(n); 114 | 115 | if (compResult == 0) 116 | { 117 | return n; 118 | } 119 | n = compResult < 0 ? n.Left : n.Right; 120 | } 121 | 122 | return null; 123 | } 124 | 125 | public bool TryLookup(IRbNode template, out IRbNode val) 126 | { 127 | var n = LookupNode(template); 128 | 129 | if (n == null) 130 | { 131 | val = null; 132 | return false; 133 | } 134 | val = n; 135 | return true; 136 | } 137 | 138 | private void ReplaceNode(IRbNode oldn, IRbNode newn) 139 | { 140 | if (oldn.Parent == null) 141 | { 142 | Root = newn; 143 | } 144 | else 145 | { 146 | if (Equals(oldn, oldn.Parent.Left)) 147 | oldn.Parent.Left = newn; 148 | else 149 | oldn.Parent.Right = newn; 150 | } 151 | if (newn != null) 152 | { 153 | newn.Parent = oldn.Parent; 154 | } 155 | } 156 | 157 | private void RotateLeft(IRbNode n) 158 | { 159 | var r = n.Right; 160 | ReplaceNode(n, r); 161 | n.Right = r.Left; 162 | if (r.Left != null) 163 | { 164 | r.Left.Parent = n; 165 | } 166 | r.Left = n; 167 | n.Parent = r; 168 | } 169 | 170 | private void RotateRight(IRbNode n) 171 | { 172 | var l = n.Left; 173 | ReplaceNode(n, l); 174 | n.Left = l.Right; 175 | 176 | if (l.Right != null) 177 | { 178 | l.Right.Parent = n; 179 | } 180 | 181 | l.Right = n; 182 | n.Parent = l; 183 | } 184 | 185 | 186 | public void Insert(IRbNode newNode) 187 | { 188 | newNode.Color = Color.Red; 189 | var insertedNode = newNode; 190 | 191 | if (Root == null) 192 | { 193 | Root = insertedNode; 194 | } 195 | else 196 | { 197 | var n = Root; 198 | while (true) 199 | { 200 | var compResult = newNode.CompareTo(n); 201 | if (compResult == 0) 202 | { 203 | throw new RbTreeDuplicatedItemException( 204 | "RBNode " + newNode + " already present in tree"); 205 | } 206 | if (compResult < 0) 207 | { 208 | if (n.Left == null) 209 | { 210 | n.Left = insertedNode; 211 | 212 | break; 213 | } 214 | n = n.Left; 215 | } 216 | else 217 | { 218 | //assert compResult > 0; 219 | if (n.Right == null) 220 | { 221 | n.Right = insertedNode; 222 | 223 | break; 224 | } 225 | n = n.Right; 226 | } 227 | } 228 | insertedNode.Parent = n; 229 | } 230 | 231 | InsertCase1(insertedNode); 232 | 233 | NodeInserted?.Invoke(insertedNode); 234 | } 235 | 236 | //------------------------------------ 237 | private void InsertCase1(IRbNode n) 238 | { 239 | if (n.Parent == null) 240 | n.Color = Color.Black; 241 | else 242 | InsertCase2(n); 243 | } 244 | 245 | //----------------------------------- 246 | private void InsertCase2(IRbNode n) 247 | { 248 | if (NodeColor(n.Parent) == Color.Black) 249 | return; // Tree is still valid 250 | InsertCase3(n); 251 | } 252 | 253 | //---------------------------- 254 | private void InsertCase3(IRbNode n) 255 | { 256 | if (NodeColor(n.Uncle()) == Color.Red) 257 | { 258 | n.Parent.Color = Color.Black; 259 | n.Uncle().Color = Color.Black; 260 | n.Grandparent().Color = Color.Red; 261 | InsertCase1(n.Grandparent()); 262 | } 263 | else 264 | { 265 | InsertCase4(n); 266 | } 267 | } 268 | 269 | //---------------------------- 270 | private void InsertCase4(IRbNode n) 271 | { 272 | if (Equals(n, n.Parent.Right) && Equals(n.Parent, n.Grandparent().Left)) 273 | { 274 | RotateLeft(n.Parent); 275 | n = n.Left; 276 | } 277 | else if (Equals(n, n.Parent.Left) && Equals(n.Parent, n.Grandparent().Right)) 278 | { 279 | RotateRight(n.Parent); 280 | n = n.Right; 281 | } 282 | 283 | InsertCase5(n); 284 | } 285 | 286 | //---------------------------- 287 | private void InsertCase5(IRbNode n) 288 | { 289 | n.Parent.Color = Color.Black; 290 | n.Grandparent().Color = Color.Red; 291 | if (Equals(n, n.Parent.Left) && Equals(n.Parent, n.Grandparent().Left)) 292 | { 293 | RotateRight(n.Grandparent()); 294 | } 295 | else 296 | { 297 | //assert n == n.parent.right && n.parent == n.grandparent().right; 298 | RotateLeft(n.Grandparent()); 299 | } 300 | } 301 | 302 | private static IRbNode MaximumNode(IRbNode n) 303 | { 304 | //assert n != null; 305 | while (n.Right != null) 306 | { 307 | n = n.Right; 308 | } 309 | 310 | return n; 311 | } 312 | 313 | 314 | public void Delete(IRbNode template, out IRbNode deletedAlt) 315 | { 316 | deletedAlt = null; 317 | var n = LookupNode(template); 318 | if (n == null) 319 | return; // Key not found, do nothing 320 | if (n.Left != null && n.Right != null) 321 | { 322 | // Copy key/value from predecessor and then delete it instead 323 | var pred = MaximumNode(n.Left); 324 | pred.AssignValueTo(n); 325 | n = pred; 326 | deletedAlt = pred; 327 | } 328 | 329 | //assert n.left == null || n.right == null; 330 | var child = n.Right ?? n.Left; 331 | if (NodeColor(n) == Color.Black) 332 | { 333 | n.Color = NodeColor(child); 334 | DeleteCase1(n); 335 | } 336 | 337 | ReplaceNode(n, child); 338 | 339 | if (NodeColor(Root) == Color.Red) 340 | { 341 | Root.Color = Color.Black; 342 | } 343 | } 344 | 345 | private void DeleteCase1(IRbNode n) 346 | { 347 | if (n.Parent == null) 348 | return; 349 | DeleteCase2(n); 350 | } 351 | 352 | 353 | private void DeleteCase2(IRbNode n) 354 | { 355 | if (NodeColor(n.Sibling()) == Color.Red) 356 | { 357 | n.Parent.Color = Color.Red; 358 | n.Sibling().Color = Color.Black; 359 | if (Equals(n, n.Parent.Left)) 360 | RotateLeft(n.Parent); 361 | else 362 | RotateRight(n.Parent); 363 | } 364 | 365 | DeleteCase3(n); 366 | } 367 | 368 | private void DeleteCase3(IRbNode n) 369 | { 370 | if (NodeColor(n.Parent) == Color.Black && 371 | NodeColor(n.Sibling()) == Color.Black && 372 | NodeColor(n.Sibling().Left) == Color.Black && 373 | NodeColor(n.Sibling().Right) == Color.Black) 374 | { 375 | n.Sibling().Color = Color.Red; 376 | DeleteCase1(n.Parent); 377 | } 378 | else 379 | DeleteCase4(n); 380 | } 381 | 382 | private void DeleteCase4(IRbNode n) 383 | { 384 | if (NodeColor(n.Parent) == Color.Red && 385 | NodeColor(n.Sibling()) == Color.Black && 386 | NodeColor(n.Sibling().Left) == Color.Black && 387 | NodeColor(n.Sibling().Right) == Color.Black) 388 | { 389 | n.Sibling().Color = Color.Red; 390 | n.Parent.Color = Color.Black; 391 | } 392 | else 393 | DeleteCase5(n); 394 | } 395 | 396 | private void DeleteCase5(IRbNode n) 397 | { 398 | if (Equals(n, n.Parent.Left) && 399 | NodeColor(n.Sibling()) == Color.Black && 400 | NodeColor(n.Sibling().Left) == Color.Red && 401 | NodeColor(n.Sibling().Right) == Color.Black) 402 | { 403 | n.Sibling().Color = Color.Red; 404 | n.Sibling().Left.Color = Color.Black; 405 | RotateRight(n.Sibling()); 406 | } 407 | else if (Equals(n, n.Parent.Right) && 408 | NodeColor(n.Sibling()) == Color.Black && 409 | NodeColor(n.Sibling().Right) == Color.Red && 410 | NodeColor(n.Sibling().Left) == Color.Black) 411 | { 412 | n.Sibling().Color = Color.Red; 413 | n.Sibling().Right.Color = Color.Black; 414 | RotateLeft(n.Sibling()); 415 | } 416 | 417 | DeleteCase6(n); 418 | } 419 | 420 | private void DeleteCase6(IRbNode n) 421 | { 422 | n.Sibling().Color = NodeColor(n.Parent); 423 | n.Parent.Color = Color.Black; 424 | if (Equals(n, n.Parent.Left)) 425 | { 426 | //assert nodeColor(n.sibling().right) == Color.RED; 427 | n.Sibling().Right.Color = Color.Black; 428 | RotateLeft(n.Parent); 429 | } 430 | else 431 | { 432 | //assert nodeColor(n.sibling().left) == Color.RED; 433 | n.Sibling().Left.Color = Color.Black; 434 | RotateRight(n.Parent); 435 | } 436 | } 437 | 438 | public void VisitTree(Action action) 439 | { 440 | //IN Order visit 441 | var walker = Root; 442 | 443 | if (walker != null) 444 | DoVisitTree(action, walker); 445 | } 446 | 447 | private static void DoVisitTree(Action action, IRbNode walker) 448 | { 449 | if (walker.Left != null) 450 | { 451 | DoVisitTree(action, walker.Left); 452 | } 453 | 454 | action?.Invoke(walker); 455 | 456 | if (walker.Right != null) 457 | { 458 | DoVisitTree(action, walker.Right); 459 | } 460 | } 461 | 462 | internal void VisitTreeNodes(Action action) 463 | { 464 | //IN Order visit 465 | var walker = Root; 466 | 467 | if (walker != null) 468 | DoVisitTreeNodes(action, walker); 469 | } 470 | 471 | private static void DoVisitTreeNodes(Action action, IRbNode walker) 472 | { 473 | if (walker.Left != null) 474 | { 475 | DoVisitTreeNodes(action, walker.Left); 476 | } 477 | 478 | action?.Invoke(walker); 479 | 480 | if (walker.Right != null) 481 | { 482 | DoVisitTreeNodes(action, walker.Right); 483 | } 484 | } 485 | 486 | public class RbTreeEnumerator : IEnumerator 487 | { 488 | int _position = -1; 489 | 490 | private readonly Queue _heap = new Queue(); 491 | 492 | internal RbTreeEnumerator(RbTree tree) 493 | { 494 | tree.VisitTreeNodes(item => _heap.Enqueue(item)); 495 | } 496 | 497 | public IRbNode Current => _heap.ElementAt(_position); 498 | 499 | public void Dispose() 500 | { 501 | } 502 | 503 | object System.Collections.IEnumerator.Current => _heap.ElementAt(_position); 504 | 505 | public bool MoveNext() 506 | { 507 | _position++; 508 | return (_position < _heap.Count); 509 | } 510 | 511 | public void Reset() 512 | { 513 | _position = -1; 514 | } 515 | } 516 | 517 | public RbTreeEnumerator GetEnumerator() 518 | { 519 | return new RbTreeEnumerator(this); 520 | } 521 | 522 | private static int _indentStep = 15; 523 | 524 | public void Print() 525 | { 526 | PrintHelper(Root, 0); 527 | } 528 | 529 | private static void PrintHelper(IRbNode n, int indent) 530 | { 531 | if (n == null) 532 | { 533 | Trace.WriteLine(""); 534 | return; 535 | } 536 | 537 | if (n.Left != null) 538 | { 539 | PrintHelper(n.Left, indent + _indentStep); 540 | } 541 | 542 | for (var i = 0; i < indent; i++) 543 | Trace.Write(" "); 544 | if (n.Color == Color.Black) 545 | Trace.WriteLine(" " + n + " "); 546 | else 547 | Trace.WriteLine("<" + n + ">"); 548 | 549 | if (n.Right != null) 550 | { 551 | PrintHelper(n.Right, indent + _indentStep); 552 | } 553 | } 554 | 555 | internal void FireNodeOperation(IRbNode node, NodeOperation operation) 556 | { 557 | if (NodeOperation != null) 558 | NodeOperation(node, operation); 559 | } 560 | 561 | //internal void FireValueAssigned(RBNode node, V value) 562 | //{ 563 | // if (ValueAssignedAction != null) 564 | // ValueAssignedAction(node, value); 565 | //} 566 | 567 | internal event Action NodeInserted; 568 | 569 | //internal event Action> NodeDeleted; 570 | internal event Action NodeOperation; 571 | } 572 | 573 | internal enum NodeOperation 574 | { 575 | LeftAssigned, 576 | RightAssigned, 577 | ColorAssigned, 578 | ParentAssigned, 579 | ValueAssigned 580 | } 581 | } -------------------------------------------------------------------------------- /src/Sector.cs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | * 5 | * The Original Code is OpenMCDF - Compound Document Format library. 6 | * 7 | * The Initial Developer of the Original Code is Federico Blaseotto.*/ 8 | 9 | 10 | using System; 11 | using System.IO; 12 | 13 | 14 | namespace OpenMcdf 15 | { 16 | internal enum SectorType 17 | { 18 | Normal, 19 | Mini, 20 | FAT, 21 | DIFAT, 22 | RangeLockSector, 23 | Directory 24 | } 25 | 26 | internal class Sector : IDisposable 27 | { 28 | public const int MINISECTOR_SIZE = 64; 29 | 30 | public const int FREESECT = unchecked((int) 0xFFFFFFFF); 31 | public const int ENDOFCHAIN = unchecked((int) 0xFFFFFFFE); 32 | public const int FATSECT = unchecked((int) 0xFFFFFFFD); 33 | public const int DIFSECT = unchecked((int) 0xFFFFFFFC); 34 | 35 | private readonly Stream _stream; 36 | 37 | private readonly object _lockObject = new object(); 38 | 39 | public bool DirtyFlag { get; set; } 40 | 41 | public bool IsStreamed => (_stream != null && Size != MINISECTOR_SIZE) && (Id * Size) + Size < _stream.Length; 42 | 43 | public Sector(int size, Stream stream) 44 | { 45 | Size = size; 46 | _stream = stream; 47 | } 48 | 49 | public Sector(int size, byte[] data) 50 | { 51 | Size = size; 52 | _data = data; 53 | _stream = null; 54 | } 55 | 56 | public Sector(int size) 57 | { 58 | Size = size; 59 | _data = null; 60 | _stream = null; 61 | } 62 | 63 | internal SectorType Type { get; set; } 64 | 65 | public int Id { get; set; } = -1; 66 | 67 | public int Size { get; private set; } 68 | 69 | private byte[] _data; 70 | 71 | public byte[] GetData() 72 | { 73 | if (_data != null) 74 | return _data; 75 | 76 | _data = new byte[Size]; 77 | 78 | if (!IsStreamed) 79 | return _data; 80 | 81 | _stream.Seek(Size + Id * (long) Size, SeekOrigin.Begin); 82 | _stream.Read(_data, 0, Size); 83 | 84 | return _data; 85 | } 86 | 87 | public void ZeroData() 88 | { 89 | _data = new byte[Size]; 90 | DirtyFlag = true; 91 | } 92 | 93 | public void InitFATData() 94 | { 95 | _data = new byte[Size]; 96 | 97 | for (var i = 0; i < Size; i++) 98 | _data[i] = 0xFF; 99 | 100 | DirtyFlag = true; 101 | } 102 | 103 | internal void ReleaseData() 104 | { 105 | _data = null; 106 | } 107 | 108 | /// 109 | /// When called from user code, release all resources, otherwise, in the case runtime called it, 110 | /// only unmanaged resources are released. 111 | /// 112 | /// If true, method has been called from User code, if false it's been called from .net runtime 113 | protected virtual void Dispose(bool disposing) 114 | { 115 | try 116 | { 117 | if (_disposed) 118 | return; 119 | 120 | lock (_lockObject) 121 | { 122 | if (disposing) 123 | { 124 | // Call from user code... 125 | } 126 | 127 | _data = null; 128 | DirtyFlag = false; 129 | Id = ENDOFCHAIN; 130 | Size = 0; 131 | } 132 | } 133 | finally 134 | { 135 | _disposed = true; 136 | } 137 | } 138 | 139 | #region IDisposable Members 140 | 141 | private bool _disposed; //false 142 | 143 | void IDisposable.Dispose() 144 | { 145 | Dispose(true); 146 | GC.SuppressFinalize(this); 147 | } 148 | 149 | #endregion 150 | } 151 | } -------------------------------------------------------------------------------- /src/SectorCollection.cs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | * 5 | * The Original Code is OpenMCDF - Compound Document Format library. 6 | * 7 | * The Initial Developer of the Original Code is Federico Blaseotto.*/ 8 | 9 | using System; 10 | using System.Collections; 11 | using System.Collections.Generic; 12 | 13 | namespace OpenMcdf 14 | { 15 | /// 16 | /// Action to implement when transaction support - sector 17 | /// has to be written to the underlying stream (see specs). 18 | /// 19 | public delegate void Ver3SizeLimitReached(); 20 | 21 | /// 22 | /// 23 | /// Ad-hoc Heap Friendly sector collection to avoid using 24 | /// large array that may create some problem to GC collection 25 | /// (see http://www.simple-talk.com/dotnet/.net-framework/the-dangers-of-the-large-object-heap/ ) 26 | /// 27 | internal class SectorCollection : IList 28 | { 29 | private const int MAX_SECTOR_V4_COUNT_LOCK_RANGE = 524287; //0x7FFFFF00 for Version 4 30 | private const int SLICE_SIZE = 4096; 31 | 32 | private readonly List _largeArraySlices; 33 | 34 | private bool _sizeLimitReached; 35 | 36 | public SectorCollection() 37 | { 38 | _largeArraySlices = new List(); 39 | } 40 | 41 | private void DoCheckSizeLimitReached() 42 | { 43 | if (_sizeLimitReached || (Count - 1 <= MAX_SECTOR_V4_COUNT_LOCK_RANGE)) 44 | return; 45 | 46 | OnVer3SizeLimitReached?.Invoke(); 47 | _sizeLimitReached = true; 48 | } 49 | 50 | public event Ver3SizeLimitReached OnVer3SizeLimitReached; 51 | 52 | #region IList Members 53 | 54 | public int IndexOf(Sector item) 55 | { 56 | throw new NotImplementedException(); 57 | } 58 | 59 | public void Insert(int index, Sector item) 60 | { 61 | throw new NotImplementedException(); 62 | } 63 | 64 | public void RemoveAt(int index) 65 | { 66 | throw new NotImplementedException(); 67 | } 68 | 69 | public Sector this[int index] 70 | { 71 | get 72 | { 73 | var itemIndex = index / SLICE_SIZE; 74 | var itemOffset = index % SLICE_SIZE; 75 | 76 | if ((index > -1) && (index < Count)) 77 | { 78 | return (Sector) _largeArraySlices[itemIndex][itemOffset]; 79 | } 80 | throw new ArgumentOutOfRangeException("index", index, "Argument out of range"); 81 | } 82 | 83 | set 84 | { 85 | var itemIndex = index / SLICE_SIZE; 86 | var itemOffset = index % SLICE_SIZE; 87 | 88 | if (index > -1 && index < Count) 89 | { 90 | _largeArraySlices[itemIndex][itemOffset] = value; 91 | } 92 | else 93 | throw new ArgumentOutOfRangeException("index", index, "Argument out of range"); 94 | } 95 | } 96 | 97 | #endregion 98 | 99 | #region ICollection Members 100 | 101 | public void Add(Sector item) 102 | { 103 | DoCheckSizeLimitReached(); 104 | 105 | var itemIndex = Count / SLICE_SIZE; 106 | 107 | if (itemIndex < _largeArraySlices.Count) 108 | { 109 | _largeArraySlices[itemIndex].Add(item); 110 | Count++; 111 | } 112 | else 113 | { 114 | var ar = new ArrayList(SLICE_SIZE) { item }; 115 | _largeArraySlices.Add(ar); 116 | Count++; 117 | } 118 | } 119 | 120 | public void Clear() 121 | { 122 | foreach (var slice in _largeArraySlices) 123 | { 124 | slice.Clear(); 125 | } 126 | 127 | _largeArraySlices.Clear(); 128 | 129 | Count = 0; 130 | } 131 | 132 | public bool Contains(Sector item) 133 | { 134 | throw new NotImplementedException(); 135 | } 136 | 137 | public void CopyTo(Sector[] array, int arrayIndex) 138 | { 139 | throw new NotImplementedException(); 140 | } 141 | 142 | public int Count { get; private set; } 143 | 144 | public bool IsReadOnly => false; 145 | 146 | public bool Remove(Sector item) 147 | { 148 | throw new NotImplementedException(); 149 | } 150 | 151 | #endregion 152 | 153 | #region IEnumerable Members 154 | 155 | public IEnumerator GetEnumerator() 156 | { 157 | for (var i = 0; i < _largeArraySlices.Count; i++) 158 | { 159 | for (var j = 0; j < _largeArraySlices[i].Count; j++) 160 | { 161 | yield return (Sector) _largeArraySlices[i][j]; 162 | } 163 | } 164 | } 165 | 166 | #endregion 167 | 168 | #region IEnumerable Members 169 | 170 | IEnumerator IEnumerable.GetEnumerator() 171 | { 172 | foreach (var arrayList in _largeArraySlices) 173 | { 174 | foreach (var obj in arrayList) 175 | { 176 | yield return obj; 177 | } 178 | } 179 | } 180 | 181 | #endregion 182 | } 183 | } -------------------------------------------------------------------------------- /src/StreamRW.cs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | * 5 | * The Original Code is OpenMCDF - Compound Document Format library. 6 | * 7 | * The Initial Developer of the Original Code is Federico Blaseotto.*/ 8 | 9 | using System.IO; 10 | 11 | namespace OpenMcdf 12 | { 13 | internal class StreamRW 14 | { 15 | private readonly byte[] _buffer; 16 | private readonly Stream _stream; 17 | 18 | public StreamRW(Stream stream) 19 | { 20 | _stream = stream; 21 | _buffer = new byte[8]; 22 | } 23 | 24 | public long Seek(long offset) 25 | { 26 | return _stream.Seek(offset, SeekOrigin.Begin); 27 | } 28 | 29 | public byte ReadByte() 30 | { 31 | _stream.Read(_buffer, 0, 1); 32 | return _buffer[0]; 33 | } 34 | 35 | public ushort ReadUInt16() 36 | { 37 | _stream.Read(_buffer, 0, 2); 38 | return (ushort) (_buffer[0] | (_buffer[1] << 8)); 39 | } 40 | 41 | public int ReadInt32() 42 | { 43 | _stream.Read(_buffer, 0, 4); 44 | return _buffer[0] | (_buffer[1] << 8) | (_buffer[2] << 16) | (_buffer[3] << 24); 45 | } 46 | 47 | public uint ReadUInt32() 48 | { 49 | _stream.Read(_buffer, 0, 4); 50 | return (uint) (_buffer[0] | (_buffer[1] << 8) | (_buffer[2] << 16) | (_buffer[3] << 24)); 51 | } 52 | 53 | public long ReadInt64() 54 | { 55 | _stream.Read(_buffer, 0, 8); 56 | var ls = (uint) (_buffer[0] | (_buffer[1] << 8) | (_buffer[2] << 16) | (_buffer[3] << 24)); 57 | var ms = (uint) ((_buffer[4]) | (_buffer[5] << 8) | (_buffer[6] << 16) | (_buffer[7] << 24)); 58 | return (long) (((ulong) ms << 32) | ls); 59 | } 60 | 61 | public ulong ReadUInt64() 62 | { 63 | _stream.Read(_buffer, 0, 8); 64 | return (ulong) (_buffer[0] | (_buffer[1] << 8) | (_buffer[2] << 16) | (_buffer[3] << 24) | (_buffer[4] << 32) | 65 | (_buffer[5] << 40) | (_buffer[6] << 48) | (_buffer[7] << 56)); 66 | } 67 | 68 | public byte[] ReadBytes(int count) 69 | { 70 | var result = new byte[count]; 71 | _stream.Read(result, 0, count); 72 | return result; 73 | } 74 | 75 | public byte[] ReadBytes(int count, out int rCount) 76 | { 77 | var result = new byte[count]; 78 | rCount = _stream.Read(result, 0, count); 79 | return result; 80 | } 81 | 82 | public void Write(byte b) 83 | { 84 | _buffer[0] = b; 85 | _stream.Write(_buffer, 0, 1); 86 | } 87 | 88 | public void Write(ushort value) 89 | { 90 | _buffer[0] = (byte) value; 91 | _buffer[1] = (byte) (value >> 8); 92 | 93 | _stream.Write(_buffer, 0, 2); 94 | } 95 | 96 | public void Write(int value) 97 | { 98 | _buffer[0] = (byte) value; 99 | _buffer[1] = (byte) (value >> 8); 100 | _buffer[2] = (byte) (value >> 16); 101 | _buffer[3] = (byte) (value >> 24); 102 | 103 | _stream.Write(_buffer, 0, 4); 104 | } 105 | 106 | public void Write(long value) 107 | { 108 | _buffer[0] = (byte) value; 109 | _buffer[1] = (byte) (value >> 8); 110 | _buffer[2] = (byte) (value >> 16); 111 | _buffer[3] = (byte) (value >> 24); 112 | _buffer[4] = (byte) (value >> 32); 113 | _buffer[5] = (byte) (value >> 40); 114 | _buffer[6] = (byte) (value >> 48); 115 | _buffer[7] = (byte) (value >> 56); 116 | 117 | _stream.Write(_buffer, 0, 8); 118 | } 119 | 120 | public void Write(uint value) 121 | { 122 | _buffer[0] = (byte) value; 123 | _buffer[1] = (byte) (value >> 8); 124 | _buffer[2] = (byte) (value >> 16); 125 | _buffer[3] = (byte) (value >> 24); 126 | 127 | _stream.Write(_buffer, 0, 4); 128 | } 129 | 130 | public void Write(byte[] value) 131 | { 132 | _stream.Write(value, 0, value.Length); 133 | } 134 | 135 | public void Close() 136 | { 137 | //Nothing to do ;-) 138 | } 139 | } 140 | } -------------------------------------------------------------------------------- /src/StreamView.cs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | * 5 | * The Original Code is OpenMCDF - Compound Document Format library. 6 | * 7 | * The Initial Developer of the Original Code is Federico Blaseotto.*/ 8 | 9 | 10 | using System; 11 | using System.Collections.Generic; 12 | using System.IO; 13 | 14 | namespace OpenMcdf 15 | { 16 | /// 17 | /// 18 | /// Stream decorator for a Sector or miniSector chain 19 | /// 20 | internal class StreamView : Stream 21 | { 22 | private readonly int _sectorSize; 23 | 24 | private long _position; 25 | 26 | private readonly Stream _stream; 27 | 28 | private readonly bool _isFatStream; 29 | 30 | public StreamView(IList sectorChain, int sectorSize, Stream stream) 31 | { 32 | if (sectorSize <= 0) 33 | throw new CFException("Sector size must be greater than zero"); 34 | 35 | BaseSectorChain = sectorChain ?? throw new CFException("Sector Chain cannot be null"); 36 | _sectorSize = sectorSize; 37 | _stream = stream; 38 | } 39 | 40 | public StreamView(IList sectorChain, int sectorSize, long length, Queue availableSectors, 41 | Stream stream, bool isFatStream = false) 42 | : this(sectorChain, sectorSize, stream) 43 | { 44 | _isFatStream = isFatStream; 45 | AdjustLength(length, availableSectors); 46 | } 47 | 48 | public IEnumerable FreeSectors { get; } = new List(); 49 | 50 | public IList BaseSectorChain { get; } 51 | 52 | public override bool CanRead => true; 53 | 54 | public override bool CanSeek => true; 55 | 56 | public override bool CanWrite => true; 57 | 58 | public override void Flush() 59 | { 60 | } 61 | 62 | private long _length; 63 | 64 | public override long Length => _length; 65 | 66 | public override long Position 67 | { 68 | get => _position; 69 | 70 | set 71 | { 72 | if (_position > _length - 1) 73 | throw new ArgumentOutOfRangeException(nameof(value)); 74 | 75 | _position = value; 76 | } 77 | } 78 | 79 | #if !NETSTANDARD1_6 && !NETSTANDARD2_0 80 | public override void Close() 81 | { 82 | base.Close(); 83 | } 84 | #endif 85 | 86 | private byte[] _buf = new byte[4]; 87 | 88 | public int ReadInt32() 89 | { 90 | Read(_buf, 0, 4); 91 | return (((_buf[0] | (_buf[1] << 8)) | (_buf[2] << 16)) | (_buf[3] << 24)); 92 | } 93 | 94 | public override int Read(byte[] buffer, int offset, int count) 95 | { 96 | var nRead = 0; 97 | int nToRead; 98 | 99 | if (BaseSectorChain == null || BaseSectorChain.Count <= 0) 100 | return 0; 101 | 102 | // First sector 103 | var secIndex = (int) (_position / _sectorSize); 104 | 105 | // Bytes to read count is the min between request count 106 | // and sector border 107 | 108 | nToRead = Math.Min( 109 | BaseSectorChain[0].Size - ((int) _position % _sectorSize), 110 | count); 111 | 112 | if (secIndex < BaseSectorChain.Count) 113 | { 114 | Buffer.BlockCopy( 115 | BaseSectorChain[secIndex].GetData(), 116 | (int) (_position % _sectorSize), 117 | buffer, 118 | offset, 119 | nToRead 120 | ); 121 | } 122 | 123 | nRead += nToRead; 124 | 125 | secIndex++; 126 | 127 | // Central sectors 128 | while (nRead < (count - _sectorSize)) 129 | { 130 | nToRead = _sectorSize; 131 | 132 | Buffer.BlockCopy( 133 | BaseSectorChain[secIndex].GetData(), 134 | 0, 135 | buffer, 136 | offset + nRead, 137 | nToRead 138 | ); 139 | 140 | nRead += nToRead; 141 | secIndex++; 142 | } 143 | 144 | // Last sector 145 | nToRead = count - nRead; 146 | 147 | if (nToRead != 0) 148 | { 149 | Buffer.BlockCopy( 150 | BaseSectorChain[secIndex].GetData(), 151 | 0, 152 | buffer, 153 | offset + nRead, 154 | nToRead 155 | ); 156 | 157 | nRead += nToRead; 158 | } 159 | 160 | _position += nRead; 161 | 162 | return nRead; 163 | } 164 | 165 | public override long Seek(long offset, SeekOrigin origin) 166 | { 167 | switch (origin) 168 | { 169 | case SeekOrigin.Begin: 170 | _position = offset; 171 | break; 172 | 173 | case SeekOrigin.Current: 174 | _position += offset; 175 | break; 176 | 177 | case SeekOrigin.End: 178 | _position = Length - offset; 179 | break; 180 | 181 | default: 182 | throw new ArgumentOutOfRangeException(nameof(origin), origin, null); 183 | } 184 | 185 | AdjustLength(_position); 186 | 187 | return _position; 188 | } 189 | 190 | private void AdjustLength(long value, Queue availableSectors = null) 191 | { 192 | _length = value; 193 | 194 | var delta = value - (BaseSectorChain.Count * (long) _sectorSize); 195 | 196 | if (delta <= 0) 197 | return; 198 | 199 | // enlargement required 200 | var nSec = (int) Math.Ceiling(((double) delta / _sectorSize)); 201 | 202 | while (nSec > 0) 203 | { 204 | Sector t; 205 | 206 | if (availableSectors == null || availableSectors.Count == 0) 207 | { 208 | t = new Sector(_sectorSize, _stream); 209 | 210 | if (_sectorSize == Sector.MINISECTOR_SIZE) 211 | t.Type = SectorType.Mini; 212 | } 213 | else 214 | { 215 | t = availableSectors.Dequeue(); 216 | } 217 | 218 | if (_isFatStream) 219 | { 220 | t.InitFATData(); 221 | } 222 | BaseSectorChain.Add(t); 223 | nSec--; 224 | } 225 | } 226 | 227 | public override void SetLength(long value) 228 | { 229 | AdjustLength(value); 230 | } 231 | 232 | public void WriteInt32(int val) 233 | { 234 | var buffer = new byte[4]; 235 | buffer[0] = (byte) val; 236 | buffer[1] = (byte) (val << 8); 237 | buffer[2] = (byte) (val << 16); 238 | buffer[3] = (byte) (val << 32); 239 | Write(buffer, 0, 4); 240 | } 241 | 242 | public override void Write(byte[] buffer, int offset, int count) 243 | { 244 | var byteWritten = 0; 245 | int roundByteWritten; 246 | 247 | // Assure length 248 | if ((_position + count) > _length) 249 | AdjustLength((_position + count)); 250 | 251 | if (BaseSectorChain == null) 252 | return; 253 | 254 | // First sector 255 | var secOffset = (int) (_position / _sectorSize); 256 | var secShift = (int) _position % _sectorSize; 257 | 258 | roundByteWritten = Math.Min(_sectorSize - (int) (_position % _sectorSize), count); 259 | 260 | if (secOffset < BaseSectorChain.Count) 261 | { 262 | Buffer.BlockCopy( 263 | buffer, 264 | offset, 265 | BaseSectorChain[secOffset].GetData(), 266 | secShift, 267 | roundByteWritten 268 | ); 269 | 270 | BaseSectorChain[secOffset].DirtyFlag = true; 271 | } 272 | 273 | byteWritten += roundByteWritten; 274 | offset += roundByteWritten; 275 | secOffset++; 276 | 277 | // Central sectors 278 | while (byteWritten < (count - _sectorSize)) 279 | { 280 | roundByteWritten = _sectorSize; 281 | 282 | Buffer.BlockCopy( 283 | buffer, 284 | offset, 285 | BaseSectorChain[secOffset].GetData(), 286 | 0, 287 | roundByteWritten 288 | ); 289 | 290 | BaseSectorChain[secOffset].DirtyFlag = true; 291 | 292 | byteWritten += roundByteWritten; 293 | offset += roundByteWritten; 294 | secOffset++; 295 | } 296 | 297 | // Last sector 298 | roundByteWritten = count - byteWritten; 299 | 300 | if (roundByteWritten != 0) 301 | { 302 | Buffer.BlockCopy( 303 | buffer, 304 | offset, 305 | BaseSectorChain[secOffset].GetData(), 306 | 0, 307 | roundByteWritten 308 | ); 309 | 310 | BaseSectorChain[secOffset].DirtyFlag = true; 311 | 312 | offset += roundByteWritten; 313 | byteWritten += roundByteWritten; 314 | } 315 | 316 | _position += count; 317 | } 318 | } 319 | } -------------------------------------------------------------------------------- /tests/OpenMcdf.Test/AssetDeployer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using NUnit.Framework; 4 | 5 | namespace OpenMcdf.Test 6 | { 7 | [SetUpFixture] 8 | public class AssetDeployer 9 | { 10 | [OneTimeSetUp] 11 | public void DeployAssetOnce() 12 | { 13 | Directory.SetCurrentDirectory(TestContext.CurrentContext.WorkDirectory); 14 | 15 | Console.WriteLine($"Test context working directory: {TestContext.CurrentContext.WorkDirectory}"); 16 | Console.WriteLine($"Test context test directory: {TestContext.CurrentContext.TestDirectory}"); 17 | 18 | var codeBaseDir = new DirectoryInfo(TestContext.CurrentContext.TestDirectory); 19 | var testAssets = Path.Combine("tests", "assets"); 20 | var assetsDir = Path.Combine(codeBaseDir.FullName, testAssets); 21 | while (!Directory.Exists(assetsDir) && 22 | !Path.GetPathRoot(codeBaseDir.FullName).Equals(codeBaseDir.FullName)) 23 | { 24 | codeBaseDir = codeBaseDir.Parent; 25 | assetsDir = Path.Combine(codeBaseDir.FullName, testAssets); 26 | } 27 | 28 | foreach (var assetPath in Directory.GetFiles(assetsDir)) 29 | { 30 | var assetInTests = Path.Combine(TestContext.CurrentContext.WorkDirectory, Path.GetFileName(assetPath)); 31 | File.Copy(assetPath, assetInTests, true); 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /tests/OpenMcdf.Test/AuthoringTests.txt: -------------------------------------------------------------------------------- 1 | ========================================================================== 2 | Visual Studio Team System: Overview of Authoring and Running Tests 3 | ========================================================================== 4 | 5 | This overview describes the features for authoring and running tests in 6 | Visual Studio Team System and Visual Studio Team Edition for Software Testers. 7 | 8 | Opening Tests 9 | ------------- 10 | To open a test, open a test project or a test metadata file (a file with 11 | extension .vsmdi) that contains the definition of the test. You can find 12 | test projects and metadata files in Solution Explorer. 13 | 14 | Viewing Tests 15 | ------------- 16 | To see which tests are available to you, open the Test View window. Or, 17 | if you have installed Team Edition for Software Testers, you can also open 18 | the Test List Editor window to view tests. 19 | 20 | To open the Test View window, click the Test menu, point to Windows, and 21 | then click Test View. To open the Test List Editor window (if you have 22 | installed Team Edition for Software Testers), click Test, point to Windows, 23 | and then click Test List Editor. 24 | 25 | Running Tests 26 | ------------- 27 | You can run tests from the Test View window and the Test List Editor window. 28 | See Viewing Tests to learn how to open these windows. To run one or more 29 | tests displayed in the Test View window, first select the tests in that 30 | window; to select multiple tests, hold either the Shift or CTRL key while 31 | clicking tests. Then click the Run Tests button in the Test View window 32 | toolbar. 33 | 34 | If you have installed Visual Studio Team Edition for Software Testers, you can 35 | also use the Test List Editor window to run tests. To run tests in Test List Editor, 36 | select the check box next to each test that you want to run. Then click the 37 | Run Tests button in the Test List Editor window toolbar. 38 | 39 | Viewing Test Results 40 | -------------------- 41 | When you run a test or a series of tests, the results of the test run will be 42 | shown in the Test Results window. Each individual test in the run is shown on 43 | a separate line so that you can see its status. The window contains an 44 | embedded status bar in the top half of the window that provides you with 45 | summary details of the complete test run. 46 | 47 | To see more detailed results for a particular test result, double-click it in 48 | the Test Results window. This opens a window that provides more information 49 | about the particular test result, such as any specific error messages returned 50 | by the test. 51 | 52 | Changing the way that tests are run 53 | ----------------------------------- 54 | Each time you run one or more tests, a collection of settings is used to 55 | determine how those tests are run. These settings are contained in a “test 56 | run configuration” file. 57 | 58 | Here is a partial list of the changes you can make with a test run 59 | configuration file: 60 | 61 | - Change the naming scheme for each test run. 62 | - Change the test controller that the tests are run on so that you can run 63 | tests remotely. 64 | - Gather code coverage data for the code being tested so that you can see 65 | which lines of code are covered by your tests. 66 | - Enable and disable test deployment. 67 | - Specify additional files to deploy before tests are run. 68 | - Select a different host, ASP.NET, for running ASP.NET unit tests. 69 | - Select a different host, the smart device test host, for running smart device unit tests. 70 | - Set various properties for the test agents that run your tests. 71 | - Run custom scripts at the start and end of each test run so that you can 72 | set up the test environment exactly as required each time tests are run. 73 | - Set time limits for tests and test runs. 74 | - Set the browser mix and the number of times to repeat Web tests in the 75 | test run. 76 | 77 | By default, a test run configuration file is created whenever you create a 78 | new test project. You make changes to this file by double-clicking it in 79 | Solution Explorer and then changing its settings. (Test run configuration 80 | files have the extension .testrunconfig.) 81 | 82 | A solution can contain multiple test run configuration files. Only one of 83 | those files, known as the “Active” test run configuration file, is used to 84 | determine the settings that are currently used for test runs. You select 85 | the active test run configuration by clicking Select Active Test Run 86 | Configuration on the Test menu. 87 | 88 | ------------------------------------------------------------------------------- 89 | 90 | Test Types 91 | ---------- 92 | Using Visual Studio Team Edition for Software Testers, you can create a number 93 | of different test types: 94 | 95 | Unit test: Use a unit test to create a programmatic test in C++, Visual C# or 96 | Visual Basic that exercises source code. A unit test calls the methods of a 97 | class, passing suitable parameters, and verifies that the returned value is 98 | what you expect. 99 | There are three specialized variants of unit tests: 100 | - Data-driven unit tests are created when you configure a unit test to be 101 | called repeatedly for each row of a data source. The data from each row 102 | is used by the unit test as input data. 103 | - ASP.NET unit tests are unit tests that exercise code in an ASP.NET Web 104 | application. 105 | - Smart device unit tests are unit tests that are deployed to a smart device 106 | or emulator and then executed by the smart device test host. 107 | 108 | Web Test: Web tests consist of an ordered series of HTTP requests that you 109 | record in a browser session using Microsoft Internet Explorer. You can have 110 | the test report specific details about the pages or sites it requests, such 111 | as whether a particular page contains a specified string. 112 | 113 | Load Test: You use a load test to encapsulate non-manual tests, such as 114 | unit, Web, and generic tests, and then run them simultaneously by using 115 | virtual users. Running these tests under load generates test results, 116 | including performance and other counters, in tables and in graphs. 117 | 118 | Generic test: A generic test is an existing program wrapped to function as a 119 | test in Visual Studio. The following are examples of tests or programs that 120 | you can turn into generic tests: 121 | - An existing test that uses process exit codes to communicate whether the 122 | test passed or failed. 0 indicates passing and any other value indicates 123 | a failure. 124 | - A general program to obtain specific functionality during a test scenario. 125 | - A test or program that uses a special XML file (called a “summary results 126 | file”), to communicate detailed results. 127 | 128 | Manual test: The manual test type is used when the test tasks are to be 129 | completed by a test engineer as opposed to an automated script. 130 | 131 | Ordered test: Use an ordered test to execute a set of tests in an order you 132 | specify. 133 | 134 | ------------------------------------------------------------------------------- 135 | 136 | 137 | -------------------------------------------------------------------------------- /tests/OpenMcdf.Test/CFSStreamExtensionsTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using NUnit.Framework; 4 | using OpenMcdf.Extensions; 5 | 6 | namespace OpenMcdf.Test 7 | { 8 | /// 9 | /// Summary description for UnitTest1 10 | /// 11 | [TestFixture] 12 | public class CFSStreamExtensionsTest 13 | { 14 | [Test] 15 | public void Test_AS_IOSTREAM_READ() 16 | { 17 | CompoundFile cf = new CompoundFile("MultipleStorage.cfs"); 18 | 19 | Stream s = cf.RootStorage.GetStorage("MyStorage").GetStream("MyStream").AsIoStream(); 20 | BinaryReader br = new BinaryReader(s); 21 | byte[] result = br.ReadBytes(32); 22 | Assert.IsTrue(Helpers.CompareBuffer(Helpers.GetBuffer(32, 1), result)); 23 | } 24 | 25 | [Test] 26 | public void Test_AS_IOSTREAM_WRITE() 27 | { 28 | const String cmp = "Hello World of BinaryWriter !"; 29 | 30 | CompoundFile cf = new CompoundFile(); 31 | Stream s = cf.RootStorage.AddStream("ANewStream").AsIoStream(); 32 | BinaryWriter bw = new BinaryWriter(s); 33 | bw.Write(cmp); 34 | cf.Save("$ACFFile.cfs"); 35 | cf.Close(); 36 | 37 | cf = new CompoundFile("$ACFFile.cfs"); 38 | BinaryReader br = new BinaryReader(cf.RootStorage.GetStream("ANewStream").AsIoStream()); 39 | String st = br.ReadString(); 40 | Assert.IsTrue(st == cmp); 41 | cf.Close(); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /tests/OpenMcdf.Test/CFSTorageTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using NUnit.Framework; 5 | 6 | namespace OpenMcdf.Test 7 | { 8 | /// 9 | /// Summary description for CFTorageTest 10 | /// 11 | [TestFixture] 12 | public class CFSTorageTest 13 | { 14 | [Test] 15 | public void Test_CREATE_STORAGE() 16 | { 17 | const String STORAGE_NAME = "NewStorage"; 18 | CompoundFile cf = new CompoundFile(); 19 | 20 | CFStorage st = cf.RootStorage.AddStorage(STORAGE_NAME); 21 | 22 | Assert.IsNotNull(st); 23 | Assert.AreEqual(STORAGE_NAME, st.Name); 24 | } 25 | 26 | [Test] 27 | public void Test_CREATE_STORAGE_WITH_CREATION_DATE() 28 | { 29 | const String STORAGE_NAME = "NewStorage1"; 30 | CompoundFile cf = new CompoundFile(); 31 | 32 | CFStorage st = cf.RootStorage.AddStorage(STORAGE_NAME); 33 | st.CreationDate = DateTime.Now; 34 | 35 | Assert.IsNotNull(st); 36 | Assert.AreEqual(STORAGE_NAME, st.Name); 37 | 38 | cf.Save("ProvaData.cfs"); 39 | cf.Close(); 40 | } 41 | 42 | [Test] 43 | public void Test_VISIT_ENTRIES() 44 | { 45 | const String STORAGE_NAME = "report.xls"; 46 | CompoundFile cf = new CompoundFile(STORAGE_NAME); 47 | 48 | FileStream output = new FileStream("LogEntries.txt", FileMode.Create); 49 | TextWriter tw = new StreamWriter(output); 50 | 51 | Action va = delegate(CFItem item) { tw.WriteLine(item.Name); }; 52 | 53 | cf.RootStorage.VisitEntries(va, true); 54 | 55 | tw.Close(); 56 | } 57 | 58 | 59 | [Test] 60 | public void Test_TRY_GET_STREAM_STORAGE() 61 | { 62 | String FILENAME = "MultipleStorage.cfs"; 63 | CompoundFile cf = new CompoundFile(FILENAME); 64 | 65 | CFStorage st = cf.RootStorage.TryGetStorage("MyStorage"); 66 | Assert.IsNotNull(st); 67 | 68 | try 69 | { 70 | CFStorage nf = cf.RootStorage.TryGetStorage("IDONTEXIST"); 71 | Assert.IsNull(nf); 72 | } 73 | catch (Exception) 74 | { 75 | Assert.Fail("Exception raised for try_get method"); 76 | } 77 | 78 | try 79 | { 80 | CFStream s = st.TryGetStream("MyStream"); 81 | Assert.IsNotNull(s); 82 | CFStream ns = st.TryGetStream("IDONTEXIST2"); 83 | Assert.IsNull(ns); 84 | } 85 | catch (Exception) 86 | { 87 | Assert.Fail("Exception raised for try_get method"); 88 | } 89 | } 90 | 91 | [Test] 92 | public void Test_VISIT_ENTRIES_CORRUPTED_FILE_VALIDATION_ON() 93 | { 94 | CompoundFile f = null; 95 | 96 | try 97 | { 98 | f = new CompoundFile("CorruptedDoc_bug3547815.doc", CFSUpdateMode.ReadOnly, 99 | CFSConfiguration.NoValidationException); 100 | } 101 | catch 102 | { 103 | Assert.Fail("No exception has to be fired on creation due to lazy loading"); 104 | } 105 | 106 | FileStream output = null; 107 | 108 | try 109 | { 110 | output = new FileStream("LogEntriesCorrupted_1.txt", FileMode.Create); 111 | 112 | using (TextWriter tw = new StreamWriter(output)) 113 | { 114 | Action va = delegate(CFItem item) { tw.WriteLine(item.Name); }; 115 | 116 | f.RootStorage.VisitEntries(va, true); 117 | tw.Flush(); 118 | } 119 | } 120 | catch (Exception ex) 121 | { 122 | Assert.IsTrue(ex is CFCorruptedFileException); 123 | Assert.IsTrue(f != null && f.IsClosed); 124 | } 125 | finally 126 | { 127 | if (output != null) 128 | output.Close(); 129 | } 130 | } 131 | 132 | [Test] 133 | public void Test_VISIT_ENTRIES_CORRUPTED_FILE_VALIDATION_OFF_BUT_CAN_LOAD() 134 | { 135 | CompoundFile f = null; 136 | 137 | try 138 | { 139 | //Corrupted file has invalid children item sid reference 140 | f = new CompoundFile("CorruptedDoc_bug3547815_B.doc", CFSUpdateMode.ReadOnly, 141 | CFSConfiguration.NoValidationException); 142 | } 143 | catch 144 | { 145 | Assert.Fail("No exception has to be fired on creation due to lazy loading"); 146 | } 147 | 148 | FileStream output = null; 149 | 150 | try 151 | { 152 | output = new FileStream("LogEntriesCorrupted_2.txt", FileMode.Create); 153 | 154 | 155 | using (TextWriter tw = new StreamWriter(output)) 156 | { 157 | Action va = delegate(CFItem item) { tw.WriteLine(item.Name); }; 158 | 159 | f.RootStorage.VisitEntries(va, true); 160 | tw.Flush(); 161 | } 162 | } 163 | catch 164 | { 165 | Assert.Fail("Fail is corrupted but it has to be loaded anyway by test design"); 166 | } 167 | finally 168 | { 169 | if (output != null) 170 | output.Close(); 171 | } 172 | } 173 | 174 | 175 | [Test] 176 | public void Test_VISIT_STORAGE() 177 | { 178 | String FILENAME = "testVisiting.xls"; 179 | 180 | // Remove... 181 | if (File.Exists(FILENAME)) 182 | File.Delete(FILENAME); 183 | 184 | //Create... 185 | 186 | CompoundFile ncf = new CompoundFile(); 187 | 188 | CFStorage l1 = ncf.RootStorage.AddStorage("Storage Level 1"); 189 | l1.AddStream("l1ns1"); 190 | l1.AddStream("l1ns2"); 191 | l1.AddStream("l1ns3"); 192 | 193 | CFStorage l2 = l1.AddStorage("Storage Level 2"); 194 | l2.AddStream("l2ns1"); 195 | l2.AddStream("l2ns2"); 196 | 197 | ncf.Save(FILENAME); 198 | ncf.Close(); 199 | 200 | 201 | // Read... 202 | 203 | CompoundFile cf = new CompoundFile(FILENAME); 204 | 205 | FileStream output = new FileStream("reportVisit.txt", FileMode.Create); 206 | TextWriter sw = new StreamWriter(output); 207 | 208 | Console.SetOut(sw); 209 | 210 | Action va = delegate(CFItem target) { sw.WriteLine(target.Name); }; 211 | 212 | cf.RootStorage.VisitEntries(va, true); 213 | 214 | cf.Close(); 215 | sw.Close(); 216 | } 217 | 218 | [Test] 219 | public void Test_DELETE_DIRECTORY() 220 | { 221 | String FILENAME = "MultipleStorage2.cfs"; 222 | CompoundFile cf = new CompoundFile(FILENAME, CFSUpdateMode.ReadOnly, CFSConfiguration.Default); 223 | 224 | CFStorage st = cf.RootStorage.GetStorage("MyStorage"); 225 | 226 | Assert.IsNotNull(st); 227 | 228 | st.Delete("AnotherStorage"); 229 | 230 | cf.Save("MultipleStorage_Delete.cfs"); 231 | 232 | cf.Close(); 233 | } 234 | 235 | [Test] 236 | public void Test_DELETE_MINISTREAM_STREAM() 237 | { 238 | String FILENAME = "MultipleStorage2.cfs"; 239 | CompoundFile cf = new CompoundFile(FILENAME); 240 | 241 | CFStorage found = null; 242 | Action action = delegate(CFItem item) 243 | { 244 | if (item.Name == "AnotherStorage") found = item as CFStorage; 245 | }; 246 | cf.RootStorage.VisitEntries(action, true); 247 | 248 | Assert.IsNotNull(found); 249 | 250 | found.Delete("AnotherStream"); 251 | 252 | cf.Save("MultipleDeleteMiniStream"); 253 | cf.Close(); 254 | } 255 | 256 | [Test] 257 | public void Test_DELETE_STREAM() 258 | { 259 | String FILENAME = "MultipleStorage3.cfs"; 260 | CompoundFile cf = new CompoundFile(FILENAME); 261 | 262 | CFStorage found = null; 263 | Action action = delegate(CFItem item) 264 | { 265 | if (item.Name == "AnotherStorage") 266 | found = item as CFStorage; 267 | }; 268 | 269 | cf.RootStorage.VisitEntries(action, true); 270 | 271 | Assert.IsNotNull(found); 272 | 273 | found.Delete("Another2Stream"); 274 | 275 | cf.Save("MultipleDeleteStream"); 276 | cf.Close(); 277 | } 278 | 279 | [Test] 280 | public void Test_CHECK_DISPOSED_() 281 | { 282 | const String FILENAME = "MultipleStorage.cfs"; 283 | CompoundFile cf = new CompoundFile(FILENAME); 284 | 285 | CFStorage st = cf.RootStorage.GetStorage("MyStorage"); 286 | cf.Close(); 287 | 288 | try 289 | { 290 | byte[] temp = st.GetStream("MyStream").GetData(); 291 | Assert.Fail("Stream without media"); 292 | } 293 | catch (Exception ex) 294 | { 295 | Assert.IsTrue(ex is CFDisposedException); 296 | } 297 | } 298 | 299 | [Test] 300 | public void Test_LAZY_LOAD_CHILDREN_() 301 | { 302 | using (var cf = new CompoundFile()) 303 | { 304 | cf.RootStorage.AddStorage("Level_1") 305 | .AddStorage("Level_2") 306 | .AddStream("Level2Stream") 307 | .SetData(Helpers.GetBuffer(100)); 308 | 309 | cf.Save("$Hel1"); 310 | cf.Close(); 311 | } 312 | 313 | using (var cf = new CompoundFile("$Hel1", CFSUpdateMode.ReadOnly, CFSConfiguration.Default)) 314 | { 315 | IList i = cf.GetAllNamedEntries("Level2Stream"); 316 | Assert.IsNotNull(i[0]); 317 | Assert.IsTrue(i[0] is CFStream); 318 | Assert.IsTrue((i[0] as CFStream).GetData().Length == 100); 319 | cf.Save("$Hel2"); 320 | cf.Close(); 321 | } 322 | 323 | if (File.Exists("$Hel1")) 324 | { 325 | File.Delete("$Hel1"); 326 | } 327 | if (File.Exists("$Hel2")) 328 | { 329 | File.Delete("$Hel2"); 330 | } 331 | } 332 | 333 | [Test] 334 | public void Test_FIX_BUG_31() 335 | { 336 | CompoundFile cf = new CompoundFile(); 337 | cf.RootStorage.AddStorage("Level_1") 338 | .AddStream("Level2Stream") 339 | .SetData(Helpers.GetBuffer(100)); 340 | 341 | cf.Save("$Hel1"); 342 | 343 | cf.Close(); 344 | 345 | CompoundFile cf1 = new CompoundFile("$Hel1"); 346 | try 347 | { 348 | CFStream cs = cf1.RootStorage.GetStorage("Level_1").AddStream("Level2Stream"); 349 | } 350 | catch (Exception ex) 351 | { 352 | Assert.IsTrue(ex.GetType() == typeof(CFDuplicatedItemException)); 353 | } 354 | } 355 | 356 | [Test] 357 | public void Test_CORRUPTEDDOC_BUG36_SHOULD_THROW_CORRUPTED_FILE_EXCEPTION() 358 | { 359 | try 360 | { 361 | using (CompoundFile file = new CompoundFile("CorruptedDoc_bug36.doc", CFSUpdateMode.ReadOnly, 362 | CFSConfiguration.NoValidationException)) 363 | { 364 | //Many thanks to theseus for bug reporting 365 | } 366 | } 367 | catch (Exception ex) 368 | { 369 | Assert.IsInstanceOf(ex); 370 | } 371 | } 372 | } 373 | } -------------------------------------------------------------------------------- /tests/OpenMcdf.Test/Helpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | 5 | namespace OpenMcdf.Test 6 | { 7 | public static class Helpers 8 | { 9 | public static byte[] GetBuffer(int count) 10 | { 11 | Random r = new Random(); 12 | byte[] b = new byte[count]; 13 | r.NextBytes(b); 14 | return b; 15 | } 16 | 17 | public static byte[] GetBuffer(int count, byte c) 18 | { 19 | byte[] b = new byte[count]; 20 | for (int i = 0; i < b.Length; i++) 21 | { 22 | b[i] = c; 23 | } 24 | 25 | return b; 26 | } 27 | 28 | public static bool CompareBuffer(byte[] b, byte[] p) 29 | { 30 | bool res = CompareBuffer(b, p, b.Length); 31 | return res && (b.Length == p.Length); 32 | } 33 | 34 | public static bool CompareBuffer(byte[] b, byte[] p, int count) 35 | { 36 | if (b == null && p == null) 37 | throw new Exception("Null buffers"); 38 | 39 | if (b == null && p != null) 40 | return false; 41 | 42 | if (b != null && p == null) 43 | return false; 44 | 45 | 46 | for (int i = 0; i < count; i++) 47 | { 48 | if (b[i] != p[i]) 49 | return false; 50 | } 51 | 52 | return true; 53 | } 54 | 55 | internal static void StressMemory() 56 | { 57 | const int N_LOOP = 20; 58 | const int MB_SIZE = 10; 59 | 60 | byte[] b = GetBuffer(1024 * 1024 * MB_SIZE); //2GB buffer 61 | byte[] cmp = { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7 }; 62 | 63 | CompoundFile cf = new CompoundFile(CFSVersion.Ver_4, CFSConfiguration.Default); 64 | CFStream st = cf.RootStorage.AddStream("MySuperLargeStream"); 65 | cf.Save("LARGE.cfs"); 66 | cf.Close(); 67 | 68 | //Console.WriteLine("Closed save"); 69 | //Console.ReadKey(); 70 | 71 | cf = new CompoundFile("LARGE.cfs", CFSUpdateMode.Update, CFSConfiguration.Default); 72 | CFStream cfst = cf.RootStorage.GetStream("MySuperLargeStream"); 73 | 74 | Stopwatch sw = new Stopwatch(); 75 | sw.Start(); 76 | for (int i = 0; i < N_LOOP; i++) 77 | { 78 | cfst.Append(b); 79 | cf.Commit(true); 80 | 81 | Console.WriteLine(" Updated " + i.ToString()); 82 | //Console.ReadKey(); 83 | } 84 | 85 | cfst.Append(cmp); 86 | cf.Commit(true); 87 | sw.Stop(); 88 | 89 | 90 | cf.Close(); 91 | 92 | Console.WriteLine(sw.Elapsed.TotalMilliseconds); 93 | sw.Reset(); 94 | 95 | //Console.WriteLine(sw.Elapsed.TotalMilliseconds); 96 | 97 | //Console.WriteLine("Closed Transacted"); 98 | //Console.ReadKey(); 99 | 100 | cf = new CompoundFile("LARGE.cfs"); 101 | int count = 8; 102 | sw.Reset(); 103 | sw.Start(); 104 | byte[] data = new byte[count]; 105 | count = cf.RootStorage.GetStream("MySuperLargeStream").Read(data, b.Length * (long)N_LOOP, count); 106 | sw.Stop(); 107 | Console.Write(count); 108 | cf.Close(); 109 | 110 | Console.WriteLine("Closed Final " + sw.ElapsedMilliseconds); 111 | Console.ReadKey(); 112 | } 113 | 114 | internal static void DummyFile() 115 | { 116 | Console.WriteLine("Start"); 117 | FileStream fs = new FileStream("myDummyFile", FileMode.Create); 118 | fs.Close(); 119 | 120 | Stopwatch sw = new Stopwatch(); 121 | 122 | byte[] b = GetBuffer(1024 * 1024 * 50); //2GB buffer 123 | 124 | fs = new FileStream("myDummyFile", FileMode.Open); 125 | sw.Start(); 126 | for (int i = 0; i < 42; i++) 127 | { 128 | fs.Seek(b.Length * i, SeekOrigin.Begin); 129 | fs.Write(b, 0, b.Length); 130 | } 131 | 132 | fs.Close(); 133 | sw.Stop(); 134 | Console.WriteLine("Stop - " + sw.ElapsedMilliseconds); 135 | sw.Reset(); 136 | 137 | Console.ReadKey(); 138 | } 139 | 140 | internal static void AddNodes(String depth, CFStorage cfs) 141 | { 142 | Action va = delegate (CFItem target) 143 | { 144 | String temp = target.Name + (target is CFStorage ? "" : " (" + target.Size + " bytes )"); 145 | 146 | //Stream 147 | 148 | Console.WriteLine(depth + temp); 149 | 150 | if (target is CFStorage) 151 | { 152 | //Storage 153 | 154 | String newDepth = depth + " "; 155 | 156 | //Recursion into the storage 157 | AddNodes(newDepth, (CFStorage)target); 158 | } 159 | }; 160 | 161 | //Visit NON-recursively (first level only) 162 | cfs.VisitEntries(va, false); 163 | } 164 | 165 | internal static void CreateFile(string fileName) 166 | { 167 | const int MAX_STREAM_COUNT = 5000; 168 | 169 | CompoundFile cf = new CompoundFile(); 170 | for (int i = 0; i < MAX_STREAM_COUNT; i++) 171 | { 172 | cf.RootStorage.AddStream("Test" + i).SetData(GetBuffer(300)); 173 | } 174 | cf.Save(fileName); 175 | cf.Close(); 176 | } 177 | } 178 | } -------------------------------------------------------------------------------- /tests/OpenMcdf.Test/OpenMcdf.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 15.0 5 | Debug 6 | AnyCPU 7 | 9.0.30729 8 | 2.0 9 | {FD339266-8842-40B4-9230-F8E84FC42AC2} 10 | Library 11 | Properties 12 | OpenMcdf.Test 13 | OpenMcdf.Test 14 | v4.6.2 15 | 512 16 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 17 | 18 | 19 | 3.5 20 | 21 | http://localhost/OpenMcdfTest/ 22 | true 23 | Web 24 | true 25 | Foreground 26 | 7 27 | Days 28 | false 29 | false 30 | true 31 | 0 32 | 1.0.0.%2a 33 | true 34 | false 35 | true 36 | 37 | 38 | 39 | true 40 | full 41 | false 42 | ..\..\bin\Debug\tests 43 | DEBUG;TRACE 44 | prompt 45 | 4 46 | AllRules.ruleset 47 | false 48 | 49 | 50 | pdbonly 51 | true 52 | ..\..\bin\Release\tests 53 | TRACE 54 | prompt 55 | 4 56 | AllRules.ruleset 57 | false 58 | 59 | 60 | false 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 3.5 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | False 95 | Microsoft .NET Framework 4 %28x86 and x64%29 96 | true 97 | 98 | 99 | False 100 | .NET Framework 3.5 SP1 Client Profile 101 | false 102 | 103 | 104 | False 105 | .NET Framework 3.5 SP1 106 | false 107 | 108 | 109 | False 110 | Windows Installer 3.1 111 | true 112 | 113 | 114 | 115 | 116 | 1.0.4 117 | 118 | 119 | 1.0.4 120 | 121 | 122 | 3.9.0 123 | 124 | 125 | 126 | 127 | {9128ccc3-88df-4f31-883e-47a04825276f} 128 | OpenMcdf 129 | 130 | 131 | 132 | 139 | -------------------------------------------------------------------------------- /tests/OpenMcdf.Test/Performance/MemoryTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using NBench; 5 | 6 | namespace OpenMcdf.Test.Performance 7 | { 8 | public class MemoryTest : PerformanceTestStuite 9 | { 10 | private Counter _testCounter; 11 | 12 | [PerfSetup] 13 | public void Setup(BenchmarkContext context) 14 | { 15 | _testCounter = context.GetCounter("TestCounter"); 16 | } 17 | 18 | [PerfBenchmark(NumberOfIterations = 1, RunMode = RunMode.Iterations, TestMode = TestMode.Test, 19 | SkipWarmups = false)] 20 | [CounterMeasurement("TestCounter")] 21 | [CounterTotalAssertion("TestCounter", MustBe.LessThanOrEqualTo, 7000.0d)] // max 7 sec 22 | [MemoryAssertion(MemoryMetric.TotalBytesAllocated, MustBe.LessThanOrEqualTo, 23 | 450 * 1024 * 1024)] // max 450 Mb in RAM 24 | public void PerfMem_MultipleCodeFeatures() 25 | { 26 | const int N_FACTOR = 1000; 27 | 28 | byte[] bA = Helpers.GetBuffer(20 * 1024 * N_FACTOR, 0x0A); 29 | byte[] bB = Helpers.GetBuffer(5 * 1024, 0x0B); 30 | byte[] bC = Helpers.GetBuffer(5 * 1024, 0x0C); 31 | byte[] bD = Helpers.GetBuffer(5 * 1024, 0x0D); 32 | byte[] bE = Helpers.GetBuffer(8 * 1024 * N_FACTOR + 1, 0x1A); 33 | byte[] bF = Helpers.GetBuffer(16 * 1024 * N_FACTOR, 0x1B); 34 | byte[] bG = Helpers.GetBuffer(14 * 1024 * N_FACTOR, 0x1C); 35 | byte[] bH = Helpers.GetBuffer(12 * 1024 * N_FACTOR, 0x1D); 36 | byte[] bE2 = Helpers.GetBuffer(8 * 1024 * N_FACTOR, 0x2A); 37 | byte[] bMini = Helpers.GetBuffer(1027, 0xEE); 38 | 39 | Stopwatch sw = new Stopwatch(); 40 | sw.Start(); 41 | 42 | var cf = new CompoundFile(CFSVersion.Ver_3, CFSConfiguration.SectorRecycle); 43 | cf.RootStorage.AddStream("A").SetData(bA); 44 | cf.Save("OneStream.cfs"); 45 | 46 | cf.Close(); 47 | 48 | cf = new CompoundFile("OneStream.cfs", CFSUpdateMode.ReadOnly, CFSConfiguration.SectorRecycle); 49 | 50 | cf.RootStorage.AddStream("B").SetData(bB); 51 | cf.RootStorage.AddStream("C").SetData(bC); 52 | cf.RootStorage.AddStream("D").SetData(bD); 53 | cf.RootStorage.AddStream("E").SetData(bE); 54 | cf.RootStorage.AddStream("F").SetData(bF); 55 | cf.RootStorage.AddStream("G").SetData(bG); 56 | cf.RootStorage.AddStream("H").SetData(bH); 57 | 58 | cf.Save("8_Streams.cfs"); 59 | 60 | cf.Close(); 61 | 62 | File.Copy("8_Streams.cfs", "6_Streams.cfs", true); 63 | 64 | cf = new CompoundFile("6_Streams.cfs", CFSUpdateMode.Update, 65 | CFSConfiguration.SectorRecycle | CFSConfiguration.EraseFreeSectors); 66 | cf.RootStorage.Delete("D"); 67 | cf.RootStorage.Delete("G"); 68 | cf.Commit(); 69 | 70 | cf.Close(); 71 | 72 | File.Copy("6_Streams.cfs", "6_Streams_Shrinked.cfs", true); 73 | 74 | cf = new CompoundFile("6_Streams_Shrinked.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle); 75 | cf.RootStorage.AddStream("ZZZ").SetData(bF); 76 | cf.RootStorage.GetStream("E").Append(bE2); 77 | cf.Commit(); 78 | cf.Close(); 79 | 80 | cf = new CompoundFile("6_Streams_Shrinked.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle); 81 | cf.RootStorage.CLSID = new Guid("EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"); 82 | cf.Commit(); 83 | cf.Close(); 84 | 85 | cf = new CompoundFile("6_Streams_Shrinked.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle); 86 | cf.RootStorage.AddStorage("MyStorage").AddStream("ANS").Append(bE); 87 | cf.Commit(); 88 | cf.Close(); 89 | 90 | cf = new CompoundFile("6_Streams_Shrinked.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle); 91 | cf.RootStorage.AddStorage("AnotherStorage").AddStream("ANS").Append(bE); 92 | cf.RootStorage.Delete("MyStorage"); 93 | cf.Commit(); 94 | cf.Close(); 95 | 96 | CompoundFile.ShrinkCompoundFile("6_Streams_Shrinked.cfs"); 97 | 98 | cf = new CompoundFile("6_Streams_Shrinked.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle); 99 | cf.RootStorage.AddStorage("MiniStorage").AddStream("miniSt").Append(bMini); 100 | cf.RootStorage.GetStorage("MiniStorage").AddStream("miniSt2").Append(bMini); 101 | cf.Commit(); 102 | cf.Close(); 103 | 104 | cf = new CompoundFile("6_Streams_Shrinked.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle); 105 | cf.RootStorage.GetStorage("MiniStorage").Delete("miniSt"); 106 | 107 | 108 | cf.RootStorage.GetStorage("MiniStorage").GetStream("miniSt2").Append(bE); 109 | cf.Commit(); 110 | cf.Close(); 111 | 112 | cf = new CompoundFile("6_Streams_Shrinked.cfs", CFSUpdateMode.ReadOnly, CFSConfiguration.SectorRecycle); 113 | 114 | var myStream = cf.RootStorage.GetStream("C"); 115 | var data = myStream.GetData(); 116 | Console.WriteLine(data[0] + " : " + data[data.Length - 1]); 117 | 118 | myStream = cf.RootStorage.GetStream("B"); 119 | data = myStream.GetData(); 120 | Console.WriteLine(data[0] + " : " + data[data.Length - 1]); 121 | 122 | cf.Close(); 123 | 124 | sw.Stop(); 125 | Console.WriteLine(sw.ElapsedMilliseconds); 126 | 127 | for (int i = 0; i < sw.ElapsedMilliseconds; i++) 128 | { 129 | _testCounter.Increment(); 130 | } 131 | } 132 | 133 | [PerfBenchmark(NumberOfIterations = 1, RunMode = RunMode.Iterations, TestMode = TestMode.Test, 134 | SkipWarmups = false)] 135 | [CounterMeasurement("TestCounter")] 136 | [CounterTotalAssertion("TestCounter", MustBe.LessThanOrEqualTo, 5500.0d)] // max 5.5 sec 137 | [MemoryAssertion(MemoryMetric.TotalBytesAllocated, MustBe.LessThanOrEqualTo, 138 | 8 * 1024 * 1024)] // max 8 Mb in RAM 139 | public void PerfMem_MultipleStreamCommit() 140 | { 141 | File.Copy("report.xls", "reportOverwriteMultiple.xls", true); 142 | 143 | Stopwatch sw = new Stopwatch(); 144 | 145 | using (var cf = new CompoundFile("reportOverwriteMultiple.xls", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle)) 146 | { 147 | sw.Start(); 148 | 149 | Random r = new Random(); 150 | 151 | for (int i = 0; i < 1000; i++) 152 | { 153 | byte[] buffer = Helpers.GetBuffer(r.Next(100, 3500), 0x0A); 154 | 155 | if (i > 0) 156 | { 157 | if (r.Next(0, 100) > 50) 158 | { 159 | cf.RootStorage.Delete("MyNewStream" + (i - 1).ToString()); 160 | } 161 | } 162 | 163 | CFStream addedStream = cf.RootStorage.AddStream("MyNewStream" + i.ToString()); 164 | 165 | addedStream.SetData(buffer); 166 | 167 | // Random commit, not on single addition 168 | if (r.Next(0, 100) > 50) 169 | cf.Commit(); 170 | } 171 | 172 | cf.Close(); 173 | sw.Stop(); 174 | } 175 | 176 | Console.WriteLine(sw.ElapsedMilliseconds); 177 | 178 | for (int i = 0; i < sw.ElapsedMilliseconds; i++) 179 | { 180 | _testCounter.Increment(); 181 | } 182 | } 183 | } 184 | } -------------------------------------------------------------------------------- /tests/OpenMcdf.Test/Performance/PerfTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using NBench; 5 | 6 | namespace OpenMcdf.Test.Performance 7 | { 8 | public class PerfTest : PerformanceTestStuite 9 | { 10 | private string _fileName; 11 | private Counter _testCounter; 12 | 13 | [PerfSetup] 14 | public void Setup(BenchmarkContext context) 15 | { 16 | _fileName = Path.Combine(Path.GetTempPath(), "PerfLoad.cfs"); 17 | _testCounter = context.GetCounter("TestCounter"); 18 | } 19 | 20 | [PerfBenchmark(Description = "Getting a stream of large file must take less than 200 ms", 21 | NumberOfIterations = 1, RunMode = RunMode.Iterations, TestMode = TestMode.Test, SkipWarmups = true)] 22 | [CounterTotalAssertion("TestCounter", MustBe.LessThanOrEqualTo, 200.0d)] // max 0.2 sec 23 | [CounterMeasurement("TestCounter")] 24 | public void Perf_SteamOfLargeFile() 25 | { 26 | if (!File.Exists(_fileName)) 27 | { 28 | Helpers.CreateFile(_fileName); 29 | } 30 | 31 | using (var cf = new CompoundFile(_fileName)) 32 | { 33 | Stopwatch sw = new Stopwatch(); 34 | sw.Start(); 35 | CFStream s = cf.RootStorage.GetStream("Test1"); 36 | sw.Stop(); 37 | 38 | var executionTime = sw.ElapsedMilliseconds; 39 | for (int i = 0; i < sw.ElapsedMilliseconds; i++) 40 | { 41 | _testCounter.Increment(); 42 | } 43 | 44 | Console.WriteLine($"Took {executionTime} seconds"); 45 | } 46 | } 47 | 48 | [PerfCleanup] 49 | public void Cleanup() 50 | { 51 | if (File.Exists(_fileName)) 52 | { 53 | File.Delete(_fileName); 54 | } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /tests/OpenMcdf.Test/Performance/PerformanceTestStuite.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Linq; 4 | using NBench.Reporting.Targets; 5 | using NBench.Sdk; 6 | using NBench.Sdk.Compiler; 7 | using NUnit.Framework; 8 | 9 | namespace OpenMcdf.Test.Performance 10 | { 11 | public abstract class PerformanceTestStuite 12 | { 13 | [TestCaseSource(nameof(Benchmarks))] 14 | public void PerformanceTests(Benchmark benchmark) 15 | { 16 | Benchmark.PrepareForRun(); 17 | benchmark.Run(); 18 | benchmark.Finish(); 19 | } 20 | 21 | public static IEnumerable Benchmarks() 22 | { 23 | var discovery = new ReflectionDiscovery(new ActionBenchmarkOutput(report => { }, results => 24 | { 25 | foreach (var assertion in results.AssertionResults) 26 | { 27 | Assert.True(assertion.Passed, results.BenchmarkName + " " + assertion.Message); 28 | Console.WriteLine(assertion.Message); 29 | } 30 | })); 31 | 32 | var benchmarks = discovery.FindBenchmarks(typeof(T)).ToList(); 33 | 34 | foreach (var benchmark in benchmarks) 35 | { 36 | var name = benchmark.BenchmarkName.Split('+')[1]; 37 | yield return new TestCaseData(benchmark).SetName(name); 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /tests/OpenMcdf.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Resources; 2 | using System.Reflection; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("OpenMcdf.Test")] 9 | [assembly: AssemblyDescription("This project is used for profiling memory and performances of OpenMCDF")] 10 | [assembly: AssemblyCompany("Federico Blaseotto, Zhmayev Yaroslav")] 11 | [assembly: AssemblyProduct("OpenMcdf.Test")] 12 | [assembly: AssemblyCopyright("Copyright © 2010-2017, Federico Blaseotto; 2016-2017 Zhmayev Yaroslav")] 13 | 14 | // Setting ComVisible to false makes the types in this assembly not visible 15 | // to COM componenets. If you need to access a type in this assembly from 16 | // COM, set the ComVisible attribute to true on that type. 17 | [assembly: ComVisible(false)] 18 | 19 | // The following GUID is for the ID of the typelib if this project is exposed to COM 20 | [assembly: Guid("7b918910-a8be-4e22-85d6-d927eae56003")] 21 | 22 | // Version information for an assembly consists of the following four values: 23 | // 24 | // Major Version 25 | // Minor Version 26 | // Build Number 27 | // Revision 28 | // 29 | // You can specify all the values or you can default the Revision and Build Numbers 30 | // by using the '*' as shown below: 31 | [assembly: AssemblyVersion("2.1.1.*")] 32 | 33 | [assembly: NeutralResourcesLanguage("en")] 34 | -------------------------------------------------------------------------------- /tests/OpenMcdf.Test/RBTreeTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using NUnit.Framework; 4 | using RedBlackTree; 5 | 6 | namespace OpenMcdf.Test 7 | { 8 | /// 9 | /// Summary description for RBTreeTest 10 | /// 11 | [TestFixture] 12 | public class RBTreeTest 13 | { 14 | internal IList GetDirectoryRepository(int count) 15 | { 16 | List repo = new List(); 17 | for (int i = 0; i < count; i++) 18 | { 19 | IDirectoryEntry de = DirectoryEntry.New(i.ToString(), StgType.StgInvalid, repo); 20 | } 21 | 22 | return repo; 23 | } 24 | 25 | [Test] 26 | public void Test_RBTREE_INSERT() 27 | { 28 | RbTree rbTree = new RbTree(); 29 | System.Collections.Generic.IList repo = GetDirectoryRepository(25); 30 | 31 | foreach (var item in repo) 32 | { 33 | rbTree.Insert(item); 34 | } 35 | 36 | for (int i = 0; i < repo.Count; i++) 37 | { 38 | IRbNode c; 39 | rbTree.TryLookup(DirectoryEntry.Mock(i.ToString(), StgType.StgInvalid), out c); 40 | Assert.IsTrue(c is IDirectoryEntry); 41 | Assert.IsTrue(((IDirectoryEntry) c).Name == i.ToString()); 42 | //Assert.IsTrue(c.IsStream); 43 | } 44 | } 45 | 46 | 47 | [Test] 48 | public void Test_RBTREE_DELETE() 49 | { 50 | RbTree rbTree = new RbTree(); 51 | System.Collections.Generic.IList repo = GetDirectoryRepository(25); 52 | 53 | 54 | foreach (var item in repo) 55 | { 56 | rbTree.Insert(item); 57 | } 58 | 59 | try 60 | { 61 | IRbNode n; 62 | rbTree.Delete(DirectoryEntry.Mock("5", StgType.StgInvalid), out n); 63 | rbTree.Delete(DirectoryEntry.Mock("24", StgType.StgInvalid), out n); 64 | rbTree.Delete(DirectoryEntry.Mock("7", StgType.StgInvalid), out n); 65 | } 66 | catch (Exception ex) 67 | { 68 | Assert.Fail("Item removal failed: " + ex.Message); 69 | } 70 | 71 | 72 | // CFItem c; 73 | // bool s = rbTree.TryLookup(new CFMock("7", StgType.StgStream), out c); 74 | 75 | 76 | // Assert.IsFalse(s); 77 | 78 | // c = null; 79 | 80 | // Assert.IsTrue(rbTree.TryLookup(new CFMock("6", StgType.StgStream), out c)); 81 | // Assert.IsTrue(c.IsStream); 82 | // Assert.IsTrue(rbTree.TryLookup(new CFMock("12", StgType.StgStream), out c)); 83 | // Assert.IsTrue(c.Name == "12"); 84 | 85 | 86 | //} 87 | } 88 | 89 | private static void VerifyProperties(RbTree t) 90 | { 91 | VerifyProperty1(t.Root); 92 | VerifyProperty2(t.Root); 93 | // Property 3 is implicit 94 | VerifyProperty4(t.Root); 95 | VerifyProperty5(t.Root); 96 | } 97 | 98 | private static Color NodeColor(IRbNode n) 99 | { 100 | return n == null ? Color.Black : n.Color; 101 | } 102 | 103 | private static void VerifyProperty1(IRbNode n) 104 | { 105 | Assert.IsTrue(NodeColor(n) == Color.Red || NodeColor(n) == Color.Black); 106 | 107 | if (n == null) return; 108 | VerifyProperty1(n.Left); 109 | VerifyProperty1(n.Right); 110 | } 111 | 112 | private static void VerifyProperty2(IRbNode root) 113 | { 114 | Assert.IsTrue(NodeColor(root) == Color.Black); 115 | } 116 | 117 | private static void VerifyProperty4(IRbNode n) 118 | { 119 | if (NodeColor(n) == Color.Red) 120 | { 121 | Assert.IsTrue((NodeColor(n.Left) == Color.Black)); 122 | Assert.IsTrue((NodeColor(n.Right) == Color.Black)); 123 | Assert.IsTrue((NodeColor(n.Parent) == Color.Black)); 124 | } 125 | 126 | if (n == null) return; 127 | VerifyProperty4(n.Left); 128 | VerifyProperty4(n.Right); 129 | } 130 | 131 | private static void VerifyProperty5(IRbNode root) 132 | { 133 | VerifyProperty5Helper(root, 0, -1); 134 | } 135 | 136 | private static int VerifyProperty5Helper(IRbNode n, int blackCount, int pathBlackCount) 137 | { 138 | if (NodeColor(n) == Color.Black) 139 | { 140 | blackCount++; 141 | } 142 | if (n == null) 143 | { 144 | if (pathBlackCount == -1) 145 | { 146 | pathBlackCount = blackCount; 147 | } 148 | else 149 | { 150 | Assert.IsTrue(blackCount == pathBlackCount); 151 | } 152 | return pathBlackCount; 153 | } 154 | 155 | pathBlackCount = VerifyProperty5Helper(n.Left, blackCount, pathBlackCount); 156 | pathBlackCount = VerifyProperty5Helper(n.Right, blackCount, pathBlackCount); 157 | 158 | return pathBlackCount; 159 | } 160 | 161 | 162 | [Test] 163 | public void Test_RBTREE_ENUMERATE() 164 | { 165 | RbTree rbTree = new RbTree(); 166 | System.Collections.Generic.IList repo = GetDirectoryRepository(10000); 167 | 168 | foreach (var item in repo) 169 | { 170 | rbTree.Insert(item); 171 | } 172 | 173 | VerifyProperties(rbTree); 174 | //rbTree.Print(); 175 | } 176 | } 177 | } -------------------------------------------------------------------------------- /tests/OpenMcdf.Test/SectorCollectionTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System; 3 | 4 | namespace OpenMcdf.Test 5 | { 6 | /// 7 | ///This is a test class for SectorCollectionTest and is intended 8 | ///to contain all SectorCollectionTest Unit Tests 9 | /// 10 | [TestFixture] 11 | public class SectorCollectionTest 12 | { 13 | /// 14 | ///A test for Count 15 | /// 16 | [Test] 17 | public void CountTest() 18 | { 19 | int count = 0; 20 | 21 | SectorCollection target = new SectorCollection(); // TODO: Initialize to an appropriate value 22 | int actual; 23 | actual = target.Count; 24 | 25 | Assert.IsTrue(actual == count); 26 | Sector s = new Sector(4096); 27 | 28 | target.Add(s); 29 | Assert.IsTrue(target.Count == actual + 1); 30 | 31 | 32 | for (int i = 0; i < 5000; i++) 33 | target.Add(s); 34 | 35 | Assert.IsTrue(target.Count == actual + 1 + 5000); 36 | } 37 | 38 | /// 39 | ///A test for Item 40 | /// 41 | [Test] 42 | public void ItemTest() 43 | { 44 | int count = 37; 45 | 46 | SectorCollection target = new SectorCollection(); 47 | int index = 0; 48 | 49 | Sector expected = new Sector(4096); 50 | target.Add(null); 51 | 52 | Sector actual; 53 | target[index] = expected; 54 | actual = target[index]; 55 | 56 | Assert.AreEqual(expected, actual); 57 | Assert.IsNotNull(actual); 58 | Assert.IsTrue(actual.Id == expected.Id); 59 | 60 | actual = null; 61 | 62 | try 63 | { 64 | actual = target[count + 100]; 65 | } 66 | catch (Exception ex) 67 | { 68 | Assert.IsTrue(ex is ArgumentOutOfRangeException); 69 | } 70 | 71 | try 72 | { 73 | actual = target[-1]; 74 | } 75 | catch (Exception ex) 76 | { 77 | Assert.IsTrue(ex is ArgumentOutOfRangeException); 78 | } 79 | } 80 | 81 | /// 82 | ///A test for SectorCollection Constructor 83 | /// 84 | [Test] 85 | public void SectorCollectionConstructorTest() 86 | { 87 | SectorCollection target = new SectorCollection(); 88 | 89 | Assert.IsNotNull(target); 90 | Assert.IsTrue(target.Count == 0); 91 | 92 | Sector s = new Sector(4096); 93 | target.Add(s); 94 | Assert.IsTrue(target.Count == 1); 95 | } 96 | 97 | /// 98 | ///A test for Add 99 | /// 100 | [Test] 101 | public void AddTest() 102 | { 103 | SectorCollection target = new SectorCollection(); 104 | for (int i = 0; i < 579; i++) 105 | { 106 | target.Add(null); 107 | } 108 | 109 | 110 | Sector item = new Sector(4096); 111 | target.Add(item); 112 | Assert.IsTrue(target.Count == 580); 113 | } 114 | 115 | /// 116 | ///A test for GetEnumerator 117 | /// 118 | [Test] 119 | public void GetEnumeratorTest() 120 | { 121 | SectorCollection target = new SectorCollection(); 122 | for (int i = 0; i < 579; i++) 123 | { 124 | target.Add(null); 125 | } 126 | 127 | 128 | Sector item = new Sector(4096); 129 | target.Add(item); 130 | Assert.IsTrue(target.Count == 580); 131 | 132 | int cnt = 0; 133 | foreach (Sector s in target) 134 | { 135 | cnt++; 136 | } 137 | 138 | Assert.IsTrue(cnt == target.Count); 139 | } 140 | } 141 | } -------------------------------------------------------------------------------- /tests/OpenMcdf.Test/StreamRWTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using NUnit.Framework; 4 | 5 | namespace OpenMcdf.Test 6 | { 7 | [TestFixture] 8 | public class StreamRWTest 9 | { 10 | [Test] 11 | public void ReadInt64_MaxSizeRead() 12 | { 13 | Int64 input = Int64.MaxValue; 14 | byte[] bytes = BitConverter.GetBytes(input); 15 | long actual = 0; 16 | using (MemoryStream memStream = new MemoryStream(bytes)) 17 | { 18 | OpenMcdf.StreamRW reader = new OpenMcdf.StreamRW(memStream); 19 | actual = reader.ReadInt64(); 20 | } 21 | Assert.AreEqual((long) input, actual); 22 | } 23 | 24 | [Test] 25 | public void ReadInt64_SmallNumber() 26 | { 27 | Int64 input = 1234; 28 | byte[] bytes = BitConverter.GetBytes(input); 29 | long actual = 0; 30 | using (MemoryStream memStream = new MemoryStream(bytes)) 31 | { 32 | OpenMcdf.StreamRW reader = new OpenMcdf.StreamRW(memStream); 33 | actual = reader.ReadInt64(); 34 | } 35 | Assert.AreEqual((long) input, actual); 36 | } 37 | 38 | [Test] 39 | public void ReadInt64_Int32MaxPlusTen() 40 | { 41 | Int64 input = (Int64) Int32.MaxValue + 10; 42 | byte[] bytes = BitConverter.GetBytes(input); 43 | long actual = 0; 44 | using (MemoryStream memStream = new MemoryStream(bytes)) 45 | { 46 | OpenMcdf.StreamRW reader = new OpenMcdf.StreamRW(memStream); 47 | actual = reader.ReadInt64(); 48 | } 49 | Assert.AreEqual((long) input, actual); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /tests/assets/2_MB-W.ppt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeCavePro/OpenMCDF/564e9675e5af0610ebfa8a2598e86cca7c7952d5/tests/assets/2_MB-W.ppt -------------------------------------------------------------------------------- /tests/assets/BUG_16_.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeCavePro/OpenMCDF/564e9675e5af0610ebfa8a2598e86cca7c7952d5/tests/assets/BUG_16_.xls -------------------------------------------------------------------------------- /tests/assets/CorruptedDoc_bug3547815.doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeCavePro/OpenMCDF/564e9675e5af0610ebfa8a2598e86cca7c7952d5/tests/assets/CorruptedDoc_bug3547815.doc -------------------------------------------------------------------------------- /tests/assets/CorruptedDoc_bug3547815_B.doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeCavePro/OpenMCDF/564e9675e5af0610ebfa8a2598e86cca7c7952d5/tests/assets/CorruptedDoc_bug3547815_B.doc -------------------------------------------------------------------------------- /tests/assets/CorruptedDoc_bug36.doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeCavePro/OpenMCDF/564e9675e5af0610ebfa8a2598e86cca7c7952d5/tests/assets/CorruptedDoc_bug36.doc -------------------------------------------------------------------------------- /tests/assets/CyclicFAT.cfs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeCavePro/OpenMCDF/564e9675e5af0610ebfa8a2598e86cca7c7952d5/tests/assets/CyclicFAT.cfs -------------------------------------------------------------------------------- /tests/assets/MultipleStorage.cfs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeCavePro/OpenMCDF/564e9675e5af0610ebfa8a2598e86cca7c7952d5/tests/assets/MultipleStorage.cfs -------------------------------------------------------------------------------- /tests/assets/MultipleStorage2.cfs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeCavePro/OpenMCDF/564e9675e5af0610ebfa8a2598e86cca7c7952d5/tests/assets/MultipleStorage2.cfs -------------------------------------------------------------------------------- /tests/assets/MultipleStorage3.cfs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeCavePro/OpenMCDF/564e9675e5af0610ebfa8a2598e86cca7c7952d5/tests/assets/MultipleStorage3.cfs -------------------------------------------------------------------------------- /tests/assets/MultipleStorage4.cfs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeCavePro/OpenMCDF/564e9675e5af0610ebfa8a2598e86cca7c7952d5/tests/assets/MultipleStorage4.cfs -------------------------------------------------------------------------------- /tests/assets/_thumbs_bug_24.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeCavePro/OpenMCDF/564e9675e5af0610ebfa8a2598e86cca7c7952d5/tests/assets/_thumbs_bug_24.db -------------------------------------------------------------------------------- /tests/assets/report.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeCavePro/OpenMCDF/564e9675e5af0610ebfa8a2598e86cca7c7952d5/tests/assets/report.xls -------------------------------------------------------------------------------- /tests/assets/reportOverwriteMultiple.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeCavePro/OpenMCDF/564e9675e5af0610ebfa8a2598e86cca7c7952d5/tests/assets/reportOverwriteMultiple.xls -------------------------------------------------------------------------------- /tests/assets/reportREAD.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeCavePro/OpenMCDF/564e9675e5af0610ebfa8a2598e86cca7c7952d5/tests/assets/reportREAD.xls -------------------------------------------------------------------------------- /tests/assets/report_name_fix.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeCavePro/OpenMCDF/564e9675e5af0610ebfa8a2598e86cca7c7952d5/tests/assets/report_name_fix.xls -------------------------------------------------------------------------------- /tests/assets/testbad.ole: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeCavePro/OpenMCDF/564e9675e5af0610ebfa8a2598e86cca7c7952d5/tests/assets/testbad.ole --------------------------------------------------------------------------------