fpspreadsheet: Improved password handling and format detection for the decryption readers.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@8913 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2023-08-05 22:20:06 +00:00
parent e1291123ef
commit 5464272445
8 changed files with 200 additions and 51 deletions

View File

@ -156,7 +156,7 @@ type
var AFontColor: TsColor); var AFontColor: TsColor);
function ReadHeaderFooterText(ANode: TDOMNode): String; function ReadHeaderFooterText(ANode: TDOMNode): String;
procedure ReadMetaData(ANode: TDOMNode); procedure ReadMetaData(ANode: TDOMNode);
procedure ReadMetaInfManifest(ANode: TDOMNode; out IsEncrypted: Boolean); procedure ReadMetaInfManifest(ANode: TDOMNode);
procedure ReadPictures(AStream: TStream); procedure ReadPictures(AStream: TStream);
procedure ReadPrintRanges(ATableNode: TDOMNode; ASheet: TsBasicWorksheet); procedure ReadPrintRanges(ATableNode: TDOMNode; ASheet: TsBasicWorksheet);
procedure ReadRowsAndCells(ATableNode: TDOMNode); procedure ReadRowsAndCells(ATableNode: TDOMNode);
@ -177,6 +177,7 @@ type
protected protected
FPointSeparatorSettings: TFormatSettings; FPointSeparatorSettings: TFormatSettings;
procedure AddBuiltinNumFormats; override; procedure AddBuiltinNumFormats; override;
function NeedsPassword(AStream: TStream): Boolean; override;
procedure ReadAutomaticStyles(AStylesNode: TDOMNode); procedure ReadAutomaticStyles(AStylesNode: TDOMNode);
procedure ReadMasterStyles(AStylesNode: TDOMNode); procedure ReadMasterStyles(AStylesNode: TDOMNode);
procedure ReadNumFormats(AStylesNode: TDOMNode); procedure ReadNumFormats(AStylesNode: TDOMNode);
@ -203,7 +204,6 @@ type
function Decrypt(AStream: TStream; ADecryptionInfo: TsOpenDocManifestFileEntry; function Decrypt(AStream: TStream; ADecryptionInfo: TsOpenDocManifestFileEntry;
APassword: String; ADestStream: TStream; out AErrorMsg: String): Boolean; virtual; APassword: String; ADestStream: TStream; out AErrorMsg: String): Boolean; virtual;
function SupportsDecryption: Boolean; virtual;
function UnzipToStream(AStream: TStream; AZippedFile: String; function UnzipToStream(AStream: TStream; AZippedFile: String;
ADestStream: TStream): Boolean; virtual; ADestStream: TStream): Boolean; virtual;
function UnzipToStream(AStream: TStream; AZippedFile: String; function UnzipToStream(AStream: TStream; AZippedFile: String;
@ -1630,6 +1630,22 @@ begin
Result := -1; Result := -1;
end; end;
{ Returns true if the file is password-protected, i.e. if there are entries
in the META-INF/manifest.xml while contain decryption information. }
function TsSpreadOpenDocReader.NeedsPassword(AStream: TStream): Boolean;
var
i: Integer;
begin
Unused(AStream);
for i := 0 to FManifestFileEntries.Count-1 do
if TsOpenDocManifestFileEntry(FManifestFileEntries[i]).Encrypted then
begin
Result := true;
exit;
end;
Result := false;
end;
function TsSpreadOpenDocReader.NodeIsEmptyCell(ACellNode: TDOMNode): Boolean; function TsSpreadOpenDocReader.NodeIsEmptyCell(ACellNode: TDOMNode): Boolean;
var var
valuestr: String; valuestr: String;
@ -2062,8 +2078,7 @@ end;
// Reads the file META-INF/manifest.xml. It is never encrypted and contains // Reads the file META-INF/manifest.xml. It is never encrypted and contains
// decryption information. // decryption information.
procedure TsSpreadOpenDocReader.ReadMetaInfManifest(ANode: TDOMNode; procedure TsSpreadOpenDocReader.ReadMetaInfManifest(ANode: TDOMNode);
out IsEncrypted: Boolean);
function GetAlgorithmName(ASubNode: TDOMNode; AttrName: String): String; function GetAlgorithmName(ASubNode: TDOMNode; AttrName: String): String;
var var
@ -2093,7 +2108,6 @@ var
nodeName: String; nodeName: String;
entry: TsOpenDocManifestFileEntry; entry: TsOpenDocManifestFileEntry;
begin begin
IsEncrypted := false;
while ANode <> nil do while ANode <> nil do
begin begin
nodeName := ANode.NodeName; nodeName := ANode.NodeName;
@ -2107,7 +2121,6 @@ begin
nodeName := encryptionDataNode.NodeName; nodeName := encryptionDataNode.NodeName;
if nodeName = 'manifest:encryption-data' then if nodeName = 'manifest:encryption-data' then
begin begin
IsEncrypted := true;
entry.Encrypted := true; entry.Encrypted := true;
entry.EncryptionData_ChecksumType := GetAlgorithmName(encryptionDataNode, 'manifest:checksum-type'); entry.EncryptionData_ChecksumType := GetAlgorithmName(encryptionDataNode, 'manifest:checksum-type');
entry.EncryptionData_Checksum := GetAttrValue(encryptionDataNode, 'manifest:checksum'); entry.EncryptionData_Checksum := GetAttrValue(encryptionDataNode, 'manifest:checksum');
@ -2924,9 +2937,8 @@ begin
ReadXMLStream(Doc, XMLStream); ReadXMLStream(Doc, XMLStream);
if Assigned(Doc) then if Assigned(Doc) then
begin begin
ReadMetaInfManifest(Doc.DocumentElement.FindNode('manifest:file-entry'), isEncrypted); ReadMetaInfManifest(Doc.DocumentElement.FindNode('manifest:file-entry'));
if isEncrypted and not SupportsDecryption then CheckPassword(XMLStream, APassword);
raise EFpSpreadsheetReader.Create('File is encrypted.');
end; end;
end; end;
finally finally
@ -5612,13 +5624,6 @@ begin
Result := false; Result := false;
end; 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; function TsSpreadOpenDocReader.UnzipToStream(AStream: TStream;
AZippedFile: String; ADestStream: TStream): Boolean; AZippedFile: String; ADestStream: TStream): Boolean;
begin begin

View File

@ -48,12 +48,16 @@ type
public public
{ File format detection } { File format detection }
class function CheckFileFormat(AStream: TStream): boolean; virtual; abstract; class function CheckFileFormat(AStream: TStream): boolean; virtual; abstract;
{ General writing methods } { General reading methods }
procedure ReadFromFile(AFileName: string; APassword: String = ''; procedure ReadFromFile(AFileName: string; APassword: String = '';
AParams: TsStreamParams = []); virtual; abstract; AParams: TsStreamParams = []); virtual; abstract;
procedure ReadFromStream(AStream: TStream; APassword: String = ''; procedure ReadFromStream(AStream: TStream; APassword: String = '';
AParams: TsStreamParams = []); virtual; abstract; AParams: TsStreamParams = []); virtual; abstract;
procedure ReadFromStrings(AStrings: TStrings; AParams: TsStreamParams = []); virtual; abstract; procedure ReadFromStrings(AStrings: TStrings; AParams: TsStreamParams = []); virtual; abstract;
{ Related to password-protected files }
procedure CheckPassword(AStream: TStream; var APassword: String); virtual;
function NeedsPassword(AStream: TStream): Boolean; virtual;
function SupportsDecryption: Boolean; virtual;
end; end;
{ TsBasicSpreadWriter } { TsBasicSpreadWriter }
@ -263,6 +267,53 @@ begin
end; end;
{------------------------------------------------------------------------------}
{ TsBasicSpreadReader }
{------------------------------------------------------------------------------}
{@@ ----------------------------------------------------------------------------
Checks whether the currently processed stream is password-protected.
If true, it checks whether the reader class supports decryption and makes
sure that a password is provided to the calling routine (ReadFromStream).
Exceptions are raised in the error cases.
Must be called at the beginning of ReadFromStream when the stream potentially
can be decrypted in order to provide the user a reasonable error message.
-------------------------------------------------------------------------------}
procedure TsBasicSpreadReader.CheckPassword(AStream: TStream;
var APassword: String);
begin
if NeedsPassword(AStream) then
begin
if SupportsDecryption then
begin
if (APassword = '') and Assigned(FWorkbook.OnQueryPassword) then
APassword := FWorkbook.OnQueryPassword();
if (APassword = '') then
raise EFpSpreadsheetReader.Create('Password required to open this workbook.');
end else
raise EFpSpreadsheetReader.Create('File is encrypted.');
end;
end;
{@@ ----------------------------------------------------------------------------
Should return whether the workbook to be loaded is password-protected by
encryption.
-------------------------------------------------------------------------------}
function TsBasicSpreadReader.NeedsPassword(AStream: TStream): Boolean;
begin
Unused(AStream);
Result := false;
end;
{@@ ----------------------------------------------------------------------------
Returns true if this reader class is able to decrypt a password-protected
workbook file/stream.
-------------------------------------------------------------------------------}
function TsBasicSpreadReader.SupportsDecryption: Boolean;
begin
Result := false;
end;
{------------------------------------------------------------------------------} {------------------------------------------------------------------------------}
{ TsBasicSpreadWriter } { TsBasicSpreadWriter }
{------------------------------------------------------------------------------} {------------------------------------------------------------------------------}
@ -507,7 +558,9 @@ end;
Opens the file and calls ReadFromStream. Data are stored in the workbook Opens the file and calls ReadFromStream. Data are stored in the workbook
specified during construction. specified during construction.
@param AFileName The input file name. @param (AFileName The input file name.)
@param (APassword The password needed to open a password-protected workbook.
Note: Password-protected files are not supported by all readers.)
@see TsWorkbook @see TsWorkbook
-------------------------------------------------------------------------------} -------------------------------------------------------------------------------}
procedure TsCustomSpreadReader.ReadFromFile(AFileName: string; procedure TsCustomSpreadReader.ReadFromFile(AFileName: string;
@ -545,11 +598,9 @@ end;
Its basic implementation here assumes that the stream is a TStringStream and Its basic implementation here assumes that the stream is a TStringStream and
the data are provided by calling ReadFromStrings. This mechanism is valid the data are provided by calling ReadFromStrings. This mechanism is valid
for wikitables. for wiki-tables.
Data will be stored in the workbook defined at construction. Data will be stored in the workbook defined at construction.
@param AData Workbook which is filled by the data from the stream.
-------------------------------------------------------------------------------} -------------------------------------------------------------------------------}
procedure TsCustomSpreadReader.ReadFromStream(AStream: TStream; procedure TsCustomSpreadReader.ReadFromStream(AStream: TStream;
APassword: String; AParams: TsStreamParams = []); APassword: String; AParams: TsStreamParams = []);
@ -573,7 +624,7 @@ end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Reads workbook data from a string list. This abstract implementation does Reads workbook data from a string list. This abstract implementation does
nothing and raises an exception. Must be overridden, like for wikitables. nothing and raises an exception. Must be overridden, like for wiki-tables.
-------------------------------------------------------------------------------} -------------------------------------------------------------------------------}
procedure TsCustomSpreadReader.ReadFromStrings(AStrings: TStrings; procedure TsCustomSpreadReader.ReadFromStrings(AStrings: TStrings;
AParams: TsStreamParams = []); AParams: TsStreamParams = []);
@ -593,8 +644,8 @@ end;
Creates an internal instance of the number format list according to the Creates an internal instance of the number format list according to the
file format being read/written. file format being read/written.
@param AWorkbook Workbook from with the file is written. This parameter is @param (AWorkbook Workbook from which the file is written. This parameter is
passed from the workbook which creates the writer. passed from the workbook which creates the writer.)
-------------------------------------------------------------------------------} -------------------------------------------------------------------------------}
constructor TsCustomSpreadWriter.Create(AWorkbook: TsBasicWorkbook); constructor TsCustomSpreadWriter.Create(AWorkbook: TsBasicWorkbook);
begin begin

View File

@ -1228,6 +1228,8 @@ type
property Keywords: TStrings read FKeywords write FKeywords; property Keywords: TStrings read FKeywords write FKeywords;
end; end;
TsOnQueryPassword = function: String of object;
{@@ Basic worksheet class to avoid circular unit references. It has only those {@@ Basic worksheet class to avoid circular unit references. It has only those
properties and methods which do not require any other unit than fpstypes. } properties and methods which do not require any other unit than fpstypes. }
TsBasicWorksheet = class TsBasicWorksheet = class
@ -1256,6 +1258,7 @@ type
TsBasicWorkbook = class TsBasicWorkbook = class
private private
FLog: TStringList; FLog: TStringList;
FOnQueryPassword: TsOnQueryPassword;
function GetErrorMsg: String; function GetErrorMsg: String;
protected protected
FFileName: String; FFileName: String;
@ -1292,6 +1295,8 @@ type
property Protection: TsWorkbookProtections read FProtection write FProtection; property Protection: TsWorkbookProtections read FProtection write FProtection;
{@@ Units of row heights and column widths } {@@ Units of row heights and column widths }
property Units: TsSizeUnits read FUnits; property Units: TsSizeUnits read FUnits;
{@@ Event returning the password to open a password-protected workbook }
property OnQueryPassword: TsOnQueryPassword read FOnQueryPassword write FOnQueryPassword;
end; end;
{@@ Ancestor of the fpSpreadsheet exceptions } {@@ Ancestor of the fpSpreadsheet exceptions }

View File

@ -141,6 +141,8 @@ type
protected protected
FFirstNumFormatIndexInFile: Integer; FFirstNumFormatIndexInFile: Integer;
procedure AddBuiltinNumFormats; override; procedure AddBuiltinNumFormats; override;
class function IsEncrypted(AStream: TStream): Boolean;
function NeedsPassword(AStream: TStream): Boolean; override;
public public
constructor Create(AWorkbook: TsBasicWorkbook); override; constructor Create(AWorkbook: TsBasicWorkbook); override;
destructor Destroy; override; destructor Destroy; override;
@ -359,6 +361,8 @@ const
LAST_PALETTE_INDEX = 63; LAST_PALETTE_INDEX = 63;
CFB_SIGNATURE = $E11AB1A1E011CFD0; // Compound File Binary Signature
type type
TFillListData = class TFillListData = class
PatternType: String; PatternType: String;
@ -951,6 +955,25 @@ begin
Result := TMemoryStream.Create; Result := TMemoryStream.Create;
end; end;
{ Checks the file header for the signature of the decrypted file format. }
class function TsSpreadOOXMLReader.IsEncrypted(AStream: TStream): Boolean;
var
p: Int64;
buf: Cardinal;
begin
p := AStream.Position;
AStream.Position := 0;
AStream.Read(buf, SizeOf(buf));
Result := (buf = Cardinal(CFB_SIGNATURE));
AStream.Position := p;
end;
{ Returns TRUE if the file is encrypted and requires a password. }
function TsSpreadOOXMLReader.NeedsPassword(AStream: TStream): Boolean;
begin
Result := IsEncrypted(AStream);
end;
procedure TsSpreadOOXMLReader.ReadActiveSheet(ANode: TDOMNode; procedure TsSpreadOOXMLReader.ReadActiveSheet(ANode: TDOMNode;
out ActiveSheetIndex: Integer); out ActiveSheetIndex: Integer);
var var
@ -4411,6 +4434,8 @@ begin
Unused(APassword, AParams); Unused(APassword, AParams);
Doc := nil; Doc := nil;
CheckPassword(AStream, APassword);
try try
// Retrieve theme colors // Retrieve theme colors
XMLStream := CreateXMLStream; XMLStream := CreateXMLStream;

View File

@ -10,7 +10,7 @@ uses
{$IFDEF UNZIP_ABBREVIA} {$IFDEF UNZIP_ABBREVIA}
ABUnzper, ABUnzper,
{$ENDIF} {$ENDIF}
fpsTypes, fpsOpenDocument; fpsTypes, fpsUtils, fpsOpenDocument;
type type
TsSpreadOpenDocReaderCrypto = class(TsSpreadOpenDocReader) TsSpreadOpenDocReaderCrypto = class(TsSpreadOpenDocReader)
@ -141,7 +141,6 @@ begin
raise EFpSpreadsheetReader.Create('Unsupported key generation method ' + ADecryptionInfo.KeyDerivationName); raise EFpSpreadsheetReader.Create('Unsupported key generation method ' + ADecryptionInfo.KeyDerivationName);
end; end;
{ Tells the calling routine that this reader is able to decrypt ods files. } { Tells the calling routine that this reader is able to decrypt ods files. }
function TsSpreadOpenDocReaderCrypto.SupportsDecryption: Boolean; function TsSpreadOpenDocReaderCrypto.SupportsDecryption: Boolean;
begin begin

View File

@ -18,7 +18,7 @@ uses
CFB_Signature = $E11AB1A1E011CFD0; // Compound File Binary Signature CFB_Signature = $E11AB1A1E011CFD0; // Compound File Binary Signature
// Weird is the documentation is equal to // Weird is the documentation is equal to
// $D0CF11E0A1B11AE1, but here is inversed // $D0CF11E0A1B11AE1, but here is inversed
// maybe related to litle endian thing?!! // maybe related to little endian thing?!!
// EncryptionHeaderFlags as defined in 2.3.1 [MS-OFFCRYPTO] // EncryptionHeaderFlags as defined in 2.3.1 [MS-OFFCRYPTO]
ehfAES = $00000004; ehfAES = $00000004;

View File

@ -6,11 +6,17 @@ interface
uses uses
Classes, Classes,
fpstypes, xlsxooxml, xlsxdecrypter; fpstypes, fpsUtils, xlsxooxml, xlsxdecrypter;
type type
TsSpreadOOXMLReaderCrypto = class(TsSpreadOOXMLReader) TsSpreadOOXMLReaderCrypto = class(TsSpreadOOXMLReader)
private
FNeedsPassword: Boolean;
protected
function NeedsPassword(AStream: TStream): Boolean; override;
function SupportsDecryption: Boolean; override;
public public
class function CheckFileFormat(AStream: TStream): boolean; override;
procedure ReadFromStream(AStream: TStream; APassword: String = ''; procedure ReadFromStream(AStream: TStream; APassword: String = '';
AParams: TsStreamParams = []); override; AParams: TsStreamParams = []); override;
end; end;
@ -24,17 +30,34 @@ implementation
uses uses
fpsReaderWriter; fpsReaderWriter;
class function TsSpreadOOXMLReaderCrypto.CheckFileFormat(AStream: TStream): boolean;
begin
Result := inherited; // This checks for a normal xlsx format ...
if not Result then
Result := IsEncrypted(AStream); // ... and this for a decrypted one.
end;
function TsSpreadOOXMLReaderCrypto.NeedsPassword(AStream: TStream): Boolean;
begin
Unused(AStream);
Result := FNeedsPassword;
end;
procedure TsSpreadOOXMLReaderCrypto.ReadFromStream(AStream: TStream; procedure TsSpreadOOXMLReaderCrypto.ReadFromStream(AStream: TStream;
APassword: String = ''; AParams: TsStreamParams = []); APassword: String = ''; AParams: TsStreamParams = []);
var var
ExcelDecrypt : TExcelFileDecryptor; ExcelDecrypt : TExcelFileDecryptor;
DecryptedStream: TStream; DecryptedStream: TStream;
begin begin
FNeedsPassword := false;
ExcelDecrypt := TExcelFileDecryptor.Create; ExcelDecrypt := TExcelFileDecryptor.Create;
try try
AStream.Position := 0; AStream.Position := 0;
if ExcelDecrypt.isEncryptedAndSupported(AStream) then if ExcelDecrypt.isEncryptedAndSupported(AStream) then
begin begin
FNeedsPassword := true;
CheckPassword(AStream, APassword);
DecryptedStream := TMemoryStream.Create; DecryptedStream := TMemoryStream.Create;
try try
ExcelDecrypt.Decrypt(AStream, DecryptedStream, UnicodeString(APassword)); ExcelDecrypt.Decrypt(AStream, DecryptedStream, UnicodeString(APassword));
@ -42,12 +65,9 @@ begin
AStream.Free; AStream.Free;
AStream := TMemoryStream.Create; AStream := TMemoryStream.Create;
DecryptedStream.Position := 0; DecryptedStream.Position := 0;
TMemoryStream(decryptedStream).SaveToFile('decr.zip');
DecryptedStream.Position := 0;
AStream.CopyFrom(DecryptedStream, DecryptedStream.Size); AStream.CopyFrom(DecryptedStream, DecryptedStream.Size);
AStream.Position := 0; AStream.Position := 0;
FNeedsPassword := false; // AStream is not encrypted any more.
finally finally
DecryptedStream.Free; DecryptedStream.Free;
end; end;
@ -60,6 +80,11 @@ begin
inherited; inherited;
end; end;
function TsSpreadOOXMLReaderCrypto.SupportsDecryption: Boolean;
begin
Result := true;
end;
initialization initialization

View File

@ -27,8 +27,8 @@ unit fpspreadsheetctrls;
interface interface
uses uses
LMessages, LResources, LCLVersion, LCLType, LCLIntf, LCLProc, LCLVersion, LMessages, LResources,
Classes, Graphics, SysUtils, Controls, StdCtrls, ComCtrls, ValEdit, ActnList, Classes, Types, Graphics, SysUtils, Controls, StdCtrls, ComCtrls, ValEdit, ActnList,
fpstypes, fpspreadsheet; fpstypes, fpspreadsheet;
const const
@ -70,6 +70,7 @@ type
FPendingOperation: TsCopyOperation; FPendingOperation: TsCopyOperation;
FOptions: TsWorkbookOptions; FOptions: TsWorkbookOptions;
FOnError: TsWorkbookSourceErrorEvent; FOnError: TsWorkbookSourceErrorEvent;
FOnQueryPassword: TsOnQueryPassword;
// Getters / setters // Getters / setters
function GetFileFormat: TsSpreadsheetFormat; function GetFileFormat: TsSpreadsheetFormat;
@ -96,10 +97,11 @@ type
protected protected
procedure AbortSelection; procedure AbortSelection;
function DoQueryPassword: String;
procedure DoShowError(const AErrorMsg: String); procedure DoShowError(const AErrorMsg: String);
procedure InternalCreateNewWorkbook(AWorkbook: TsWorkbook = nil); procedure InternalCreateNewWorkbook(AWorkbook: TsWorkbook = nil);
procedure InternalLoadFromFile(AFileName: string; AAutoDetect: Boolean; procedure InternalLoadFromFile(AFileName: string; AAutoDetect: Boolean;
AFormatID: TsSpreadFormatID; AWorksheetIndex: Integer = -1); AFormatID: TsSpreadFormatID; AWorksheetIndex: Integer; APassword: String);
procedure InternalLoadFromWorkbook(AWorkbook: TsWorkbook; procedure InternalLoadFromWorkbook(AWorkbook: TsWorkbook;
AWorksheetIndex: Integer = -1); AWorksheetIndex: Integer = -1);
procedure Loaded; override; procedure Loaded; override;
@ -116,15 +118,13 @@ type
public public
procedure CreateNewWorkbook; procedure CreateNewWorkbook;
procedure LoadFromProtectedSpreadsheetFile(AFileName: String;
AFormatID: TsSpreadFormatID; APassword: String; AWorksheetIndex: Integer = -1);
procedure LoadFromSpreadsheetFile(AFileName: string; procedure LoadFromSpreadsheetFile(AFileName: string;
AFormat: TsSpreadsheetFormat; AWorksheetIndex: Integer = -1); overload; AFormat: TsSpreadsheetFormat; AWorksheetIndex: Integer = -1); overload;
procedure LoadFromSpreadsheetFile(AFileName: string; procedure LoadFromSpreadsheetFile(AFileName: string;
AFormatID: TsSpreadFormatID = sfidUnknown; AWorksheetIndex: Integer = -1); overload; AFormatID: TsSpreadFormatID = sfidUnknown; AWorksheetIndex: Integer = -1); overload;
procedure LoadFromWorkbook(AWorkbook: TsWorkbook; AWorksheetIndex: Integer = -1); procedure LoadFromWorkbook(AWorkbook: TsWorkbook; AWorksheetIndex: Integer = -1);
{
procedure LoadFromSpreadsheetFile(AFileName: string;
AWorksheetIndex: Integer = -1); overload;
}
procedure SaveToSpreadsheetFile(AFileName: string; procedure SaveToSpreadsheetFile(AFileName: string;
AOverwriteExisting: Boolean = true); overload; AOverwriteExisting: Boolean = true); overload;
@ -133,9 +133,6 @@ type
procedure SaveToSpreadsheetFile(AFileName: string; AFormatID: TsSpreadFormatID; procedure SaveToSpreadsheetFile(AFileName: string; AFormatID: TsSpreadFormatID;
AOverwriteExisting: Boolean = true); overload; AOverwriteExisting: Boolean = true); overload;
// procedure DisableControls;
// procedure EnableControls;
procedure SelectCell(ASheetRow, ASheetCol: Cardinal); procedure SelectCell(ASheetRow, ASheetCol: Cardinal);
procedure SelectWorksheet(AWorkSheet: TsWorksheet); procedure SelectWorksheet(AWorkSheet: TsWorksheet);
@ -183,6 +180,8 @@ type
{@@ A message box is displayey if an error occurs during loading of a {@@ A message box is displayey if an error occurs during loading of a
spreadsheet. This behavior can be replaced by means of the event OnError. } spreadsheet. This behavior can be replaced by means of the event OnError. }
property OnError: TsWorkbookSourceErrorEvent read FOnError write FOnError; property OnError: TsWorkbookSourceErrorEvent read FOnError write FOnError;
{@@ Event fired when a password is required. Handler must return the pwd. }
property OnQueryPassword: TsOnQueryPassword read FOnQueryPassword write FOnQueryPassword;
end; end;
@ -710,8 +709,7 @@ function ScalePPI(ALength: Integer): Integer;
implementation implementation
uses uses
Types, Math, StrUtils, TypInfo, LCLType, LCLIntf, LCLProc, Math, StrUtils, TypInfo, Dialogs, Forms, Clipbrd,
Dialogs, Forms, Clipbrd,
fpsStrings, fpsCrypto, fpsReaderWriter, fpsUtils, fpsNumFormat, fpsImages, fpsStrings, fpsCrypto, fpsReaderWriter, fpsUtils, fpsNumFormat, fpsImages,
fpsHTMLUtils, fpsExprParser; fpsHTMLUtils, fpsExprParser;
@ -1016,6 +1014,22 @@ begin
SelectWorksheet(FWorksheet); SelectWorksheet(FWorksheet);
end; end;
function TsWorkbookSource.DoQueryPassword: String;
var
crs: TCursor;
begin
crs := Screen.Cursor;
Screen.Cursor := crDefault;
try
if Assigned(FOnQueryPassword) then
Result := FOnQueryPassword()
else
Result := InputBox('Password required to open workbook', 'Password', '');
finally
Screen.Cursor := crs;
end;
end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
An error has occured during loading of the workbook. Shows a message box by An error has occured during loading of the workbook. Shows a message box by
default. But a different behavior can be obtained by means of the OnError default. But a different behavior can be obtained by means of the OnError
@ -1137,20 +1151,23 @@ end;
for the loader. for the loader.
Is ignored when AAutoDetect is @false.) Is ignored when AAutoDetect is @false.)
@param(AWorksheetIndex Index of the worksheet to be selected after loading.) @param(AWorksheetIndex Index of the worksheet to be selected after loading.)
@param(APassword Password to open encrypted workbook. Note: this is
supported only by ods and xlsx readers.)
-------------------------------------------------------------------------------} -------------------------------------------------------------------------------}
procedure TsWorkbookSource.InternalLoadFromFile(AFileName: string; procedure TsWorkbookSource.InternalLoadFromFile(AFileName: string;
AAutoDetect: Boolean; AFormatID: TsSpreadFormatID; AAutoDetect: Boolean; AFormatID: TsSpreadFormatID; AWorksheetIndex: Integer;
AWorksheetIndex: Integer = -1); APassword: String);
var var
book: TsWorkbook; book: TsWorkbook;
begin begin
book := TsWorkbook.Create; book := TsWorkbook.Create;
try try
book.Options := FOptions; book.Options := FOptions;
book.OnQueryPassword := @DoQueryPassword;
if AAutoDetect then if AAutoDetect then
book.ReadfromFile(AFileName) book.ReadfromFile(AFileName, APassword)
else else
book.ReadFromFile(AFileName, AFormatID); book.ReadFromFile(AFileName, AFormatID, APassword);
InternalLoadFromWorkbook(book, AWorksheetIndex); InternalLoadFromWorkbook(book, AWorksheetIndex);
except except
// book is normally used as current workbook. But it must be destroyed // book is normally used as current workbook. But it must be destroyed
@ -1220,9 +1237,11 @@ begin
end; end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Public spreadsheet loader to be used if file format is known. Public loader of a spreadsheet file.
Call this methdd for both built-in and user-provided file formats. Call this method for both built-in and user-provided file formats.
If the workbook is password-protected the password is prompted by a dialog.
@param(AFilename Name of the spreadsheet file to be loaded.) @param(AFilename Name of the spreadsheet file to be loaded.)
@param(AFormatID Identifier of the spreadsheet file format assumed @param(AFormatID Identifier of the spreadsheet file format assumed
@ -1236,7 +1255,7 @@ var
autodetect: Boolean; autodetect: Boolean;
begin begin
autodetect := (AFormatID = sfidUnknown); autodetect := (AFormatID = sfidUnknown);
InternalLoadFromFile(AFileName, autodetect, AFormatID, AWorksheetIndex); InternalLoadFromFile(AFileName, autodetect, AFormatID, AWorksheetIndex, '');
end; end;
(* (*
{@@ ------------------------------------------------------------------------------ {@@ ------------------------------------------------------------------------------
@ -1272,6 +1291,26 @@ begin
InternalLoadFromWorkbook(AWorkbook, AWorksheetIndex); InternalLoadFromWorkbook(AWorkbook, AWorksheetIndex);
end; end;
{@@ ----------------------------------------------------------------------------
Public loader of a spreadsheet file.
Should be called in case of password-protected files when the password is
already known and no password dialog should appear.
Call this method for both built-in and user-provided file formats.
@param(AFilename Name of the spreadsheet file to be loaded.)
@param(AFormatID Identifier of the spreadsheet file format assumed
for the file.)
@param(AWorksheetIndex Index of the worksheet to be selected after loading.
(If empty then the active worksheet is loaded) )
-------------------------------------------------------------------------------}
procedure TsWorkbookSource.LoadFromProtectedSpreadsheetFile(AFileName: String;
AFormatID: TsSpreadFormatID; APassword: String; AWorksheetIndex: Integer = -1);
begin
InternalLoadFromFile(AFileName, false, AFormatID, AWorksheetIndex, APassword);
end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Notifies listeners of workbook, worksheet, cell, or selection changes. Notifies listeners of workbook, worksheet, cell, or selection changes.
The changed item is identified by the parameter AChangedItems. The changed item is identified by the parameter AChangedItems.