From 1c9d111a6029fbb10db35435674b99e2e2838f08 Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Thu, 27 Jul 2023 11:00:29 +0000 Subject: [PATCH] fpspreadsheet: Prepare ODS reader for supporting decryption. git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@8901 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- .../source/common/fpsopendocument.pas | 111 ++++++++++++++---- .../source/common/fpsreaderwriter.pas | 14 +++ 2 files changed, 100 insertions(+), 25 deletions(-) diff --git a/components/fpspreadsheet/source/common/fpsopendocument.pas b/components/fpspreadsheet/source/common/fpsopendocument.pas index 918e2fb05..b95af97d5 100644 --- a/components/fpspreadsheet/source/common/fpsopendocument.pas +++ b/components/fpspreadsheet/source/common/fpsopendocument.pas @@ -76,6 +76,28 @@ type function BuildXMLAsString(AFormatName: String): String; end; + { Information contained in META-INF, in particular decryption parameters. } + + TsOpenDocManifestFileEntry = class + // General + FileName: String; + Encrypted: Boolean; + // Checksum of encrypted data + EncryptionData_Checksum: String; + EncryptionData_ChecksumType: String; + // Encrypting the data with the pwd hash ("key") + AlgorithmName: String; + InitializationVector: String; + IterationCount: Integer; + // Start key generation + StartKeyGenerationName: String; + StartKeySize: Integer; + // Key derivation (encrypting and salting the start key) + KeyDerivationName: String; + KeySize: Integer; + Salt: String; + end; + { TsSpreadOpenDocReader } TsSpreadOpenDocReader = class(TsSpreadXMLReader) @@ -179,6 +201,11 @@ type procedure ReadNumber(ARow, ACol: Cardinal; AStyleIndex: Integer; ACellNode: TDOMNode); reintroduce; + function Decrypt(AStream: TStream; ADecryptionInfo: TsOpenDocManifestFileEntry; + APassword: RawByteString; ADestStream: TStream): Boolean; virtual; + function SupportsDecryption: Boolean; virtual; + function UnzipToStream(AStream: TStream; AZippedFile: String; + APassword: RawByteString; ADestStream: TStream): Boolean; public constructor Create(AWorkbook: TsBasicWorkbook); override; destructor Destroy; override; @@ -575,22 +602,6 @@ type end; *) -type - TManifestFileEntry = class - FileName: String; - Encrypted: Boolean; - EncryptionData_Checksum: String; - EncryptionData_ChecksumType: String; - AlgorithmName: String; - InitializationVector: String; - StartKeyGenerationName: String; - StartKeySize: Integer; - IterationCount: Integer; - KeyDerivationName: String; - KeySize: Integer; - Salt: String; - end; - {******************************************************************************} { Clipboard utility } {******************************************************************************} @@ -2078,7 +2089,7 @@ procedure TsSpreadOpenDocReader.ReadMetaInfManifest(ANode: TDOMNode; var encryptionDataNode, childNode: TDOMNode; nodeName: String; - entry: TManifestFileEntry; + entry: TsOpenDocManifestFileEntry; begin IsEncrypted := false; while ANode <> nil do @@ -2086,7 +2097,7 @@ begin nodeName := ANode.NodeName; if nodeName = 'manifest:file-entry' then begin - entry := TManifestFileEntry.Create; + entry := TsOpenDocManifestFileEntry.Create; entry.FileName := GetAttrValue(ANode, 'manifest:full-path'); encryptionDataNode := ANode.FirstChild; while encryptionDataNode <> nil do @@ -2898,20 +2909,20 @@ var end; begin - Unused(APassword, AParams); + Unused(AParams); Doc := nil; try // Read the META-INF/manifest.xml file to learn about encryption XMLStream := CreateXMLStream; try - if UnzipToStream(AStream, 'META-INF/manifest.xml', XMLStream) then + if fpsXMLCommon.UnzipToStream(AStream, 'META-INF/manifest.xml', XMLStream) then begin ReadXMLStream(Doc, XMLStream); if Assigned(Doc) then begin ReadMetaInfManifest(Doc.DocumentElement.FindNode('manifest:file-entry'), isEncrypted); - if isEncrypted then + if isEncrypted and not SupportsDecryption then raise EFpSpreadsheetReader.Create('File is encrypted.'); end; end; @@ -2926,7 +2937,7 @@ begin // process the styles.xml file XMLStream := CreateXMLStream; try - if UnzipToStream(AStream, 'styles.xml', XMLStream) then + if UnzipToStream(AStream, 'styles.xml', APassword, XMLStream) then ReadXMLStream(Doc, XMLStream); finally XMLStream.Free; @@ -2946,7 +2957,7 @@ begin // process the content.xml file XMLStream := CreateXMLStream; try - if UnzipToStream(AStream, 'content.xml', XMLStream) then + if UnzipToStream(AStream, 'content.xml', APassword, XMLStream) then ReadXMLStream(Doc, XMLStream) else raise EFPSpreadsheetReader.CreateFmt(rsDefectiveInternalFileStructure, ['ods']); @@ -3037,7 +3048,7 @@ begin // process the meta.xml file XMLStream := CreateXMLStream; try - if UnzipToStream(AStream, 'meta.xml', XMLStream) then + if UnzipToStream(AStream, 'meta.xml', APassword, XMLStream) then begin ReadXMLStream(Doc, XMLStream); try @@ -3053,7 +3064,7 @@ begin // process the settings.xml file (Note: it does not always exist!) XMLStream := CreateXMLStream; try - if UnzipToStream(AStream, 'settings.xml', XMLStream) then + if UnzipToStream(AStream, 'settings.xml', APassword, XMLStream) then begin ReadXMLStream(Doc, XMLStream); try @@ -5584,6 +5595,56 @@ begin FTableStyleList.Add(tablestyle); end; +{ The standard ods reader does not support decryption. Must be overridden in a + descendant reader class which implements decryption taking advantage of the + provided password and the parameters in ADecryptionInfo. } +function TsSpreadOpenDocReader.Decrypt(AStream: TStream; + ADecryptionInfo: TsOpenDocManifestFileEntry; APassword: RawByteString; + ADestStream: TStream): Boolean; +begin + Result := false; +end; + +{ If a descendant reader class supports decryption it must return true + here. The standard ods reader is not able to read encrypted file content. } +function TsSpreadOpenDocReader.SupportsDecryption: Boolean; +begin + Result := false; +end; + +function TsSpreadOpenDocReader.UnzipToStream(AStream: TStream; AZippedFile: String; + APassword: RawByteString; ADestStream: TStream): Boolean; +var + i: Integer; + mfe: TsOpenDocManifestFileEntry; + isEncrypted: Boolean; + tmpStream: TStream; +begin + mfe := nil; + + // Find the requested file among the manifest file entries containing + // parameters needed for decryption. + for i := 0 to FManifestFileEntries.Count-1 do + if TsOpenDocManifestFileEntry(FManifestFileEntries[i]).FileName = AZippedFile then + begin + mfe := TsOpenDocManifestFileEntry(FManifestFileEntries[i]); + isEncrypted := mfe.Encrypted; + break; + end; + + if isEncrypted then begin + tmpStream := TMemoryStream.Create; + try + // Read the encrypted file from the input stream + Result := fpsXMLCommon.UnzipToStream(AStream, AZippedFile, tmpStream); + // Decrypt the file into the destination stream + Result := Result and Decrypt(tmpStream, mfe, APassword, ADestStream); + finally + tmpStream.Free; + end; + end else + Result := fpsXMLCommon.UnzipToStream(AStream, AZippedFile, ADestStream); +end; { TsSpreadOpenDocWriter } diff --git a/components/fpspreadsheet/source/common/fpsreaderwriter.pas b/components/fpspreadsheet/source/common/fpsreaderwriter.pas index e7c9c87af..6eab237ac 100644 --- a/components/fpspreadsheet/source/common/fpsreaderwriter.pas +++ b/components/fpspreadsheet/source/common/fpsreaderwriter.pas @@ -204,6 +204,8 @@ function RegisterSpreadFormat( AFormatName, ATechnicalName: String; const AFileExtensions: array of String): TsSpreadFormatID; +procedure UnregisterSpreadFormat(AFormatID: TsSpreadFormatID); + function GetFileFormatFilter(AListSeparator, AExtSeparator: Char; AFileAccess: TsSpreadFileAccess; const APriorityFormats: array of TsSpreadFormatID; AllSpreadFormats: Boolean = false; AllExcelFormats: Boolean = false): String; @@ -1326,6 +1328,18 @@ begin Result := fmt.FormatID; end; +procedure UnregisterSpreadFormat(AFormatID: TsSpreadFormatID); +var + n: Integer; +begin + n := SpreadFormatRegistry.IndexOf(AFormatID); + if n <> -1 then + begin + TObject(SpreadFormatRegistry.FList[n]).Free; + SpreadFormatRegistry.FList.Delete(n); + end; +end; + function GetFileFormatFilter(AListSeparator, AExtSeparator: Char; AFileAccess: TsSpreadFileAccess; const APriorityFormats: array of TsSpreadFormatID; AllSpreadFormats: Boolean = false; AllExcelFormats: Boolean = false): String;