fpspreadsheet: Fix crash when reading a BIFF5 file written by Excel97 (it has data (the directory) beyound the workbook stream which is not expected by the BIFF5/8 readers).

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3897 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2015-01-24 22:19:55 +00:00
parent 9d89f1dcf3
commit 3113794fab
7 changed files with 65 additions and 43 deletions

View File

@ -67,7 +67,7 @@
<Unit1> <Unit1>
<Filename Value="main.pas"/> <Filename Value="main.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
<ComponentName Value="Form1"/> <ComponentName Value="MainForm"/>
<HasResources Value="True"/> <HasResources Value="True"/>
<ResourceBaseClass Value="Form"/> <ResourceBaseClass Value="Form"/>
<UnitName Value="main"/> <UnitName Value="main"/>

View File

@ -15,7 +15,7 @@ uses
begin begin
RequireDerivedFormResource := True; RequireDerivedFormResource := True;
Application.Initialize; Application.Initialize;
Application.CreateForm(TForm1, Form1); Application.CreateForm(TMainForm, MainForm);
Application.Run; Application.Run;
end. end.

View File

@ -1,9 +1,9 @@
object Form1: TForm1 object MainForm: TMainForm
Left = 495 Left = 495
Height = 600 Height = 600
Top = 132 Top = 132
Width = 929 Width = 929
Caption = 'Form1' Caption = 'demo_ctrls'
ClientHeight = 580 ClientHeight = 580
ClientWidth = 929 ClientWidth = 929
Menu = MainMenu Menu = MainMenu

View File

@ -11,9 +11,9 @@ uses
type type
{ TForm1 } { TMainForm }
TForm1 = class(TForm) TMainForm = class(TForm)
AcRowDelete: TAction; AcRowDelete: TAction;
AcColDelete: TAction; AcColDelete: TAction;
AcRowAdd: TAction; AcRowAdd: TAction;
@ -286,21 +286,22 @@ type
procedure InspectorTabControlChange(Sender: TObject); procedure InspectorTabControlChange(Sender: TObject);
private private
{ private declarations } { private declarations }
procedure UpdateCaption;
public public
{ public declarations } { public declarations }
end; end;
var var
Form1: TForm1; MainForm: TMainForm;
implementation implementation
{$R *.lfm} {$R *.lfm}
{ TForm1 } { TMainForm }
{ Loads the spreadsheet file selected by the AcFileOpen action } { Loads the spreadsheet file selected by the AcFileOpen action }
procedure TForm1.AcFileOpenAccept(Sender: TObject); procedure TMainForm.AcFileOpenAccept(Sender: TObject);
begin begin
WorkbookSource.AutodetectFormat := false; WorkbookSource.AutodetectFormat := false;
case AcFileOpen.Dialog.FilterIndex of case AcFileOpen.Dialog.FilterIndex of
@ -314,10 +315,11 @@ begin
8: WorkbookSource.FileFormat := sfCSV; // Text files 8: WorkbookSource.FileFormat := sfCSV; // Text files
end; end;
WorkbookSource.FileName := AcFileOpen.Dialog.FileName; // this loads the file WorkbookSource.FileName := AcFileOpen.Dialog.FileName; // this loads the file
UpdateCaption;
end; end;
{ Saves the spreadsheet to the file selected by the AcFileSaveAs action } { Saves the spreadsheet to the file selected by the AcFileSaveAs action }
procedure TForm1.AcFileSaveAsAccept(Sender: TObject); procedure TMainForm.AcFileSaveAsAccept(Sender: TObject);
var var
fmt: TsSpreadsheetFormat; fmt: TsSpreadsheetFormat;
begin begin
@ -333,20 +335,21 @@ begin
7: fmt := sfWikiTable_WikiMedia; 7: fmt := sfWikiTable_WikiMedia;
end; end;
WorkbookSource.SaveToSpreadsheetFile(AcFileSaveAs.Dialog.FileName, fmt); WorkbookSource.SaveToSpreadsheetFile(AcFileSaveAs.Dialog.FileName, fmt);
UpdateCaption;
finally finally
Screen.Cursor := crDefault; Screen.Cursor := crDefault;
end; end;
end; end;
{ Adds a column before the active cell } { Adds a column before the active cell }
procedure TForm1.AcColAddExecute(Sender: TObject); procedure TMainForm.AcColAddExecute(Sender: TObject);
begin begin
WorksheetGrid.InsertCol(WorksheetGrid.Col); WorksheetGrid.InsertCol(WorksheetGrid.Col);
WorksheetGrid.Col := WorksheetGrid.Col + 1; WorksheetGrid.Col := WorksheetGrid.Col + 1;
end; end;
{ Deletes the column with the active cell } { Deletes the column with the active cell }
procedure TForm1.AcColDeleteExecute(Sender: TObject); procedure TMainForm.AcColDeleteExecute(Sender: TObject);
var var
c: Integer; c: Integer;
begin begin
@ -356,14 +359,14 @@ begin
end; end;
{ Adds a row before the active cell } { Adds a row before the active cell }
procedure TForm1.AcRowAddExecute(Sender: TObject); procedure TMainForm.AcRowAddExecute(Sender: TObject);
begin begin
WorksheetGrid.InsertRow(WorksheetGrid.Row); WorksheetGrid.InsertRow(WorksheetGrid.Row);
WorksheetGrid.Row := WorksheetGrid.Row + 1; WorksheetGrid.Row := WorksheetGrid.Row + 1;
end; end;
{ Deletes the row with the active cell } { Deletes the row with the active cell }
procedure TForm1.AcRowDeleteExecute(Sender: TObject); procedure TMainForm.AcRowDeleteExecute(Sender: TObject);
var var
r: Integer; r: Integer;
begin begin
@ -373,17 +376,28 @@ begin
end; end;
{ Toggles the spreadsheet inspector on and off } { Toggles the spreadsheet inspector on and off }
procedure TForm1.AcViewInspectorExecute(Sender: TObject); procedure TMainForm.AcViewInspectorExecute(Sender: TObject);
begin begin
InspectorTabControl.Visible := AcViewInspector.Checked; InspectorTabControl.Visible := AcViewInspector.Checked;
end; end;
{ Event handler to synchronize the mode of the spreadsheet inspector with the { Event handler to synchronize the mode of the spreadsheet inspector with the
selected tab of the TabControl } selected tab of the TabControl }
procedure TForm1.InspectorTabControlChange(Sender: TObject); procedure TMainForm.InspectorTabControlChange(Sender: TObject);
begin begin
Inspector.Mode := TsInspectorMode(InspectorTabControl.TabIndex); Inspector.Mode := TsInspectorMode(InspectorTabControl.TabIndex);
end; end;
procedure TMainForm.UpdateCaption;
begin
if WorkbookSource = nil then
Caption := 'demo_ctrls'
else
Caption := Format('demo_ctrls - "%s" [%s]', [
WorkbookSource.Filename,
GetFileFormatName(WorkbookSource.Workbook.FileFormat)
]);
end;
end. end.

View File

@ -6399,8 +6399,8 @@ class function TsWorkbook.GetFormatFromFileHeader(const AFileName: TFileName;
const const
BIFF2_HEADER: array[0..15] of byte = ( BIFF2_HEADER: array[0..15] of byte = (
$09,$00, $04,$00, $00,$00, $10,$00, $31,$00, $0A,$00, $C8,$00, $00,$00); $09,$00, $04,$00, $00,$00, $10,$00, $31,$00, $0A,$00, $C8,$00, $00,$00);
BIFF58_HEADER: array[0..15] of byte = ( BIFF58_HEADER: array[0..7] of byte = (
$D0,$CF, $11,$E0, $A1,$B1, $1A,$E1, $00,$00, $00,$00, $00,$00, $00,$00); $D0,$CF, $11,$E0, $A1,$B1, $1A,$E1);
BIFF5_MARKER: array[0..7] of widechar = ( BIFF5_MARKER: array[0..7] of widechar = (
'B', 'o', 'o', 'k', #0, #0, #0, #0); 'B', 'o', 'o', 'k', #0, #0, #0, #0);
BIFF8_MARKER:array[0..7] of widechar = ( BIFF8_MARKER:array[0..7] of widechar = (
@ -6419,7 +6419,7 @@ begin
// Read first 16 bytes // Read first 16 bytes
stream.ReadBuffer(buf, 16); stream.ReadBuffer(buf, 16);
// Check for Excel 2# // Check for Excel 2
ok := true; ok := true;
for i:=0 to 15 do for i:=0 to 15 do
if buf[i] <> BIFF2_HEADER[i] then if buf[i] <> BIFF2_HEADER[i] then
@ -6434,7 +6434,7 @@ begin
end; end;
// Check for Excel 5 or 8 // Check for Excel 5 or 8
for i:=0 to 15 do for i:=0 to 7 do
if buf[i] <> BIFF58_HEADER[i] then if buf[i] <> BIFF58_HEADER[i] then
exit; exit;

View File

@ -387,30 +387,29 @@ begin
CurStreamPos := AStream.Position; CurStreamPos := AStream.Position;
case RecordType of case RecordType of
INT_EXCEL_ID_BLANK : ReadBlank(AStream);
INT_EXCEL_ID_BOOLERROR : ReadBool(AStream);
INT_EXCEL_ID_MULBLANK : ReadMulBlank(AStream);
INT_EXCEL_ID_NUMBER : ReadNumber(AStream);
INT_EXCEL_ID_LABEL : ReadLabel(AStream);
INT_EXCEL_ID_RSTRING : ReadRichString(AStream); //(RSTRING) This record stores a formatted text cell (Rich-Text). In BIFF8 it is usually replaced by the LABELSST record. Excel still uses this record, if it copies formatted text cells to the clipboard.
INT_EXCEL_ID_RK : ReadRKValue(AStream); //(RK) This record represents a cell that contains an RK value (encoded integer or floating-point value). If a floating-point value cannot be encoded to an RK value, a NUMBER record will be written. This record replaces the record INTEGER written in BIFF2.
INT_EXCEL_ID_MULRK : ReadMulRKValues(AStream);
INT_EXCEL_ID_COLINFO : ReadColInfo(AStream);
INT_EXCEL_ID_STANDARDWIDTH : ReadStandardWidth(AStream, FWorksheet);
INT_EXCEL_ID_DEFCOLWIDTH : ReadDefColWidth(AStream);
INT_EXCEL_ID_ROW : ReadRowInfo(AStream);
INT_EXCEL_ID_FORMULA : ReadFormula(AStream);
INT_EXCEL_ID_SHAREDFMLA : ReadSharedFormula(AStream);
INT_EXCEL_ID_STRING : ReadStringRecord(AStream);
INT_EXCEL_ID_WINDOW2 : ReadWindow2(AStream);
INT_EXCEL_ID_PANE : ReadPane(AStream);
INT_EXCEL_ID_BOF : ;
INT_EXCEL_ID_EOF : SectionEOF := True;
INT_EXCEL_ID_BLANK : ReadBlank(AStream); {$IFDEF FPSPREADDEBUG} // Only write out if debugging
INT_EXCEL_ID_BOOLERROR : ReadBool(AStream);
INT_EXCEL_ID_MULBLANK : ReadMulBlank(AStream);
INT_EXCEL_ID_NUMBER : ReadNumber(AStream);
INT_EXCEL_ID_LABEL : ReadLabel(AStream);
INT_EXCEL_ID_RSTRING : ReadRichString(AStream); //(RSTRING) This record stores a formatted text cell (Rich-Text). In BIFF8 it is usually replaced by the LABELSST record. Excel still uses this record, if it copies formatted text cells to the clipboard.
INT_EXCEL_ID_RK : ReadRKValue(AStream); //(RK) This record represents a cell that contains an RK value (encoded integer or floating-point value). If a floating-point value cannot be encoded to an RK value, a NUMBER record will be written. This record replaces the record INTEGER written in BIFF2.
INT_EXCEL_ID_MULRK : ReadMulRKValues(AStream);
INT_EXCEL_ID_COLINFO : ReadColInfo(AStream);
INT_EXCEL_ID_STANDARDWIDTH : ReadStandardWidth(AStream, FWorksheet);
INT_EXCEL_ID_DEFCOLWIDTH : ReadDefColWidth(AStream);
INT_EXCEL_ID_ROW : ReadRowInfo(AStream);
INT_EXCEL_ID_FORMULA : ReadFormula(AStream);
INT_EXCEL_ID_SHAREDFMLA : ReadSharedFormula(AStream);
INT_EXCEL_ID_STRING : ReadStringRecord(AStream);
INT_EXCEL_ID_WINDOW2 : ReadWindow2(AStream);
INT_EXCEL_ID_PANE : ReadPane(AStream);
INT_EXCEL_ID_BOF : ;
INT_EXCEL_ID_EOF : SectionEOF := True;
{$IFDEF FPSPREADDEBUG} // Only write out if debugging
else else
// Show unsupported record types to console. // Show unsupported record types to console.
case RecordType of case RecordType of
$000C: ; //(CALCCOUNT) This record is part of the Calculation Settings Block. It specifies the maximum number of times the formulas should be iteratively calculated. This is a fail-safe against mutually recursive formulas locking up a spreadsheet application. $000C: ; //(CALCCOUNT) This record is part of the Calculation Settings Block. It specifies the maximum number of times the formulas should be iteratively calculated. This is a fail-safe against mutually recursive formulas locking up a spreadsheet application.
$000D: ; //(CALCMODE) This record is part of the Calculation Settings Block. It specifies whether to calculate formulas manually, automatically or automatically except for multiple table operations. $000D: ; //(CALCMODE) This record is part of the Calculation Settings Block. It specifies whether to calculate formulas manually, automatically or automatically except for multiple table operations.
@ -445,7 +444,7 @@ begin
else else
WriteLn(format('Record type: %.4X Record Size: %.4X',[RecordType,RecordSize])); WriteLn(format('Record type: %.4X Record Size: %.4X',[RecordType,RecordSize]));
end; end;
{$ENDIF} {$ENDIF}
end; end;
// Make sure we are in the right position for the next record // Make sure we are in the right position for the next record
@ -720,6 +719,7 @@ end;
procedure TsSpreadBIFF5Reader.ReadFromStream(AStream: TStream; AData: TsWorkbook); procedure TsSpreadBIFF5Reader.ReadFromStream(AStream: TStream; AData: TsWorkbook);
var var
BIFF5EOF: Boolean; BIFF5EOF: Boolean;
p,s: Int64;
begin begin
{ Initializations } { Initializations }
@ -742,10 +742,15 @@ begin
ReadWorksheet(AStream, AData); ReadWorksheet(AStream, AData);
// Check for the end of the file // Check for the end of the file
p := AStream.Position;
s := AStream.Size;
if AStream.Position >= AStream.Size then BIFF5EOF := True; if AStream.Position >= AStream.Size then BIFF5EOF := True;
// Final preparations // Final preparations
Inc(FCurrentWorksheet); Inc(FCurrentWorksheet);
if FCurrentWorksheet = FWorksheetNames.Count then BIFF5EOF := True;
// It can happen in files written by Office97 that the OLE directory is
// at the end of the file.
end; end;
if not FPaletteFound then if not FPaletteFound then

View File

@ -638,6 +638,9 @@ begin
// Final preparations // Final preparations
Inc(FCurrentWorksheet); Inc(FCurrentWorksheet);
if FCurrentWorksheet = FWorksheetNames.Count then BIFF8EOF := True;
// It can happen in files written by Office97 that the OLE directory is
// at the end of the file.
end; end;
if not FPaletteFound then if not FPaletteFound then