fpspreadsheet: Add ods reader for cell, sheet and workbook protection. Simplification in xlsx reader for sheet protection. Remove field HashValue from TsCryptoInfo (it is PasswordHash now)

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@5791 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2017-03-05 23:05:03 +00:00
parent b04fd3f0b5
commit 16118290c5
8 changed files with 137 additions and 66 deletions

View File

@ -33,6 +33,7 @@ end;
function StrToAlgorithm(const AName: String): TsCryptoAlgorithm; function StrToAlgorithm(const AName: String): TsCryptoAlgorithm;
begin begin
case AName of case AName of
// Excel
'MD2' : Result := caMD2; 'MD2' : Result := caMD2;
'MD4' : Result := caMD4; 'MD4' : Result := caMD4;
'MD5' : Result := caMD5; 'MD5' : Result := caMD5;
@ -43,7 +44,15 @@ begin
'SHA-384' : Result := caSHA384; 'SHA-384' : Result := caSHA384;
'SHA-512' : Result := caSHA512; 'SHA-512' : Result := caSHA512;
'WHIRLPOOL' : Result := caWHIRLPOOL; 'WHIRLPOOL' : Result := caWHIRLPOOL;
else Result := caUnknown; else
// Libre/OpenOffice
if pos('sha1', AName) > 0 then // http://www.w3.org/2000/09/xmldsig#sha1
Result := caSHA1
else
if pos('sha256', AName) > 0 then // http://www.w3.org/2000/09/xmldsig#sha256
Result := caSHA256
else
Result := caUnknown;
end; end;
end; end;

View File

@ -115,6 +115,7 @@ type
procedure ReadColumns(ATableNode: TDOMNode); procedure ReadColumns(ATableNode: TDOMNode);
procedure ReadColumnStyle(AStyleNode: TDOMNode); procedure ReadColumnStyle(AStyleNode: TDOMNode);
procedure ReadDateMode(SpreadSheetNode: TDOMNode); procedure ReadDateMode(SpreadSheetNode: TDOMNode);
procedure ReadDocumentProtection(ANode: TDOMNode);
procedure ReadFont(ANode: TDOMNode; var AFontName: String; procedure ReadFont(ANode: TDOMNode; var AFontName: String;
var AFontSize: Single; var AFontStyle: TsFontStyles; var AFontColor: TsColor; var AFontSize: Single; var AFontStyle: TsFontStyles; var AFontColor: TsColor;
var AFontPosition: TsFontPosition); var AFontPosition: TsFontPosition);
@ -129,6 +130,7 @@ type
procedure ReadRowsAndCells(ATableNode: TDOMNode); procedure ReadRowsAndCells(ATableNode: TDOMNode);
procedure ReadRowStyle(AStyleNode: TDOMNode); procedure ReadRowStyle(AStyleNode: TDOMNode);
procedure ReadShapes(ATableNode: TDOMNode); procedure ReadShapes(ATableNode: TDOMNode);
procedure ReadSheetProtection(ANode: TDOMNode; ASheet: TsWorksheet);
procedure ReadTableStyle(AStyleNode: TDOMNode); procedure ReadTableStyle(AStyleNode: TDOMNode);
protected protected
@ -285,8 +287,7 @@ uses
{$IFDEF FPS_VARISBOOL} {$IFDEF FPS_VARISBOOL}
fpsPatches, fpsPatches,
{$ENDIF} {$ENDIF}
fpsStrings, fpsStreams, fpsClasses, fpsExprParser, fpsStrings, fpsStreams, fpsCrypto, fpsClasses, fpsExprParser, fpsImages;
fpsImages;
const const
{ OpenDocument general XML constants } { OpenDocument general XML constants }
@ -2060,6 +2061,25 @@ begin
raise Exception.CreateFmt('Spreadsheet file corrupt: cannot handle null-date format %s', [NullDateSetting]); raise Exception.CreateFmt('Spreadsheet file corrupt: cannot handle null-date format %s', [NullDateSetting]);
end; end;
procedure TsSpreadOpenDocReader.ReadDocumentProtection(ANode: TDOMNode);
var
s: String;
cinfo: TsCryptoInfo;
begin
if ANode = nil then
exit;
if GetAttrValue(ANode, 'table:structure-protected') = 'true' then
Workbook.Protection := Workbook.Protection + [bpLockStructure]
else
exit;
InitCryptoInfo(cinfo);
cinfo.PasswordHash := GetAttrValue(ANode, 'table:protection-key');
cinfo.Algorithm := StrToAlgorithm(GetAttrValue(ANode, 'table:protection-key-digest-algorithm'));
Workbook.CryptoInfo := cinfo;
end;
{ Reads font data from an xml node and returns the font elements. } { Reads font data from an xml node and returns the font elements. }
procedure TsSpreadOpenDocReader.ReadFont(ANode: TDOMNode; var AFontName: String; procedure TsSpreadOpenDocReader.ReadFont(ANode: TDOMNode; var AFontName: String;
var AFontSize: Single; var AFontStyle: TsFontStyles; var AFontColor: TsColor; var AFontSize: Single; var AFontStyle: TsFontStyles; var AFontColor: TsColor;
@ -2468,6 +2488,7 @@ begin
if not Assigned(SpreadSheetNode) then if not Assigned(SpreadSheetNode) then
raise Exception.Create('[TsSpreadOpenDocReader.ReadFromStream] Node "office:spreadsheet" not found.'); raise Exception.Create('[TsSpreadOpenDocReader.ReadFromStream] Node "office:spreadsheet" not found.');
ReadDocumentProtection(SpreadsheetNode);
ReadDateMode(SpreadSheetNode); ReadDateMode(SpreadSheetNode);
//process each table (sheet) //process each table (sheet)
@ -2485,6 +2506,8 @@ begin
end; end;
FWorkSheet := FWorkbook.AddWorksheet(GetAttrValue(TableNode, 'table:name'), true); FWorkSheet := FWorkbook.AddWorksheet(GetAttrValue(TableNode, 'table:name'), true);
tablestyleName := GetAttrValue(TableNode, 'table:style-name'); tablestyleName := GetAttrValue(TableNode, 'table:style-name');
// Read protection
ReadSheetProtection(TableNode, FWorksheet);
// Collect embedded images // Collect embedded images
ReadShapes(TableNode); ReadShapes(TableNode);
// Collect column styles used // Collect column styles used
@ -3882,6 +3905,50 @@ begin
end; end;
end; end;
procedure TsSpreadOpenDocReader.ReadSheetProtection(ANode: TDOMNode;
ASheet: TsWorksheet);
var
s: String;
sp: TsWorksheetProtections;
cinfo: TsCryptoInfo;
childNode: TDOMNode;
nodeName: String;
begin
if ANode = nil then
exit;
s := GetAttrValue(ANode, 'table:protected');
if s = 'true' then begin
sp := DEFAULT_SHEET_PROTECTION;
Include(sp, spCells);
// These items are ALLOWED (unlike Excel where they are FORBIDDEN).
// <loext:table-protection loext:select-unprotected-cells="true" />
// <loext:table-protection loext:select-protected-cells="true" />
// <loext:table-protection />
childNode := ANode.FirstChild;
while childNode <> nil do
begin
nodeName := childnode.NodeName;
if nodeName = 'loext:table-protection' then begin
s := GetAttrValue(childnode, 'loext:select-unprotected-cells');
if s='true' then Exclude(sp, spSelectUnlockedCells)
else Include(sp, spSelectUnlockedCells);
if s='false' then Exclude(sp, spSelectLockedCells)
else Include(sp, spSelectLockedCells);
end;
childNode := childNode.NextSibling;
end;
ASheet.Protection := sp;
ASheet.Protect(true);
InitCryptoInfo(cinfo);
cinfo.PasswordHash := GetAttrValue(ANode, 'table:protection-key');
cinfo.Algorithm := StrToAlgorithm(GetAttrValue(ANode, 'table:protection-key-digest-algorithm'));
ASheet.CryptoInfo := cinfo;
end else
ASheet.Protect(false);
end;
procedure TsSpreadOpenDocReader.ReadStyles(AStylesNode: TDOMNode); procedure TsSpreadOpenDocReader.ReadStyles(AStylesNode: TDOMNode);
var var
styleNode: TDOMNode; styleNode: TDOMNode;
@ -4220,6 +4287,17 @@ begin
fmt.VertAlignment := vaBottom; fmt.VertAlignment := vaBottom;
if fmt.VertAlignment <> vaDefault then if fmt.VertAlignment <> vaDefault then
Include(fmt.UsedFormattingFields, uffVertAlign); Include(fmt.UsedFormattingFields, uffVertAlign);
// Protection
s := GetAttrValue(styleChildNode, 'style:cell-protect');
if s = 'none' then
fmt.Protection := []
else if s = 'hidden-and-protected' then
fmt.Protection := [cpLockCell, cpHideFormulas]
else if s = 'protected' then
fmt.Protection := [cpLockCell]
else if s = 'formula-hidden' then
fmt.Protection := [cpHideFormulas];
end end
else else
if nodeName = 'style:paragraph-properties' then if nodeName = 'style:paragraph-properties' then

View File

@ -1189,7 +1189,8 @@ begin
FActiveCellRow := UNASSIGNED_ROW_COL_INDEX; FActiveCellRow := UNASSIGNED_ROW_COL_INDEX;
FActiveCellCol := UNASSIGNED_ROW_COL_INDEX; FActiveCellCol := UNASSIGNED_ROW_COL_INDEX;
FProtection := DEFAULT_SHEET_PROTECTIONS; FProtection := DEFAULT_SHEET_PROTECTION;
InitCryptoInfo(FCryptoInfo);
FOptions := [soShowGridLines, soShowHeaders]; FOptions := [soShowGridLines, soShowHeaders];
end; end;
@ -7981,6 +7982,10 @@ begin
// Add default cell format // Add default cell format
InitFormatRecord(fmt); InitFormatRecord(fmt);
AddCellFormat(fmt); AddCellFormat(fmt);
// Protection
InitCryptoInfo(FCryptoInfo);
FProtection := [];
end; end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------

View File

@ -650,7 +650,6 @@ type
TsCryptoInfo = record TsCryptoInfo = record
PasswordHash: String; PasswordHash: String;
Algorithm: TsCryptoAlgorithm; Algorithm: TsCryptoAlgorithm;
HashValue: string;
SaltValue: string; SaltValue: string;
SpinCount: Integer; SpinCount: Integer;
end; end;
@ -680,7 +679,7 @@ const
spCells, spSort, spSelectLockedCells, spSelectUnlockedCells spCells, spSort, spSelectLockedCells, spSelectUnlockedCells
{, spObjects, spPivotTables, spScenarios} ]; {, spObjects, spPivotTables, spScenarios} ];
DEFAULT_SHEET_PROTECTIONS = ALL_SHEET_PROTECTIONS - [spSelectLockedCells, spSelectUnlockedcells]; DEFAULT_SHEET_PROTECTION = ALL_SHEET_PROTECTIONS - [spSelectLockedCells, spSelectUnlockedcells];
DEFAULT_CELL_PROTECTION = [cpLockCell]; DEFAULT_CELL_PROTECTION = [cpLockCell];

View File

@ -2087,7 +2087,6 @@ procedure InitCryptoInfo(out AValue: TsCryptoInfo);
begin begin
AValue.PasswordHash := ''; AValue.PasswordHash := '';
AValue.Algorithm := caUnknown; AValue.Algorithm := caUnknown;
AValue.HashValue := '';
AValue.SaltValue := ''; AValue.SaltValue := '';
AValue.SpinCount := 0; AValue.SpinCount := 0;
end; end;

View File

@ -2043,14 +2043,14 @@ begin
if AWorksheet = nil then begin if AWorksheet = nil then begin
// Password for workbook protection // Password for workbook protection
cinfo := FWorkbook.CryptoInfo; InitCryptoInfo(cinfo);
cinfo.PasswordHash := Format('%.4x', [hash]); cinfo.PasswordHash := Format('%.4x', [hash]);
cinfo.Algorithm := caExcel; cinfo.Algorithm := caExcel;
FWorkbook.CryptoInfo := cinfo; FWorkbook.CryptoInfo := cinfo;
end else end else
begin begin
// Password for worksheet protection // Password for worksheet protection
cinfo := AWorksheet.CryptoInfo; InitCryptoInfo(cinfo);
cinfo.PasswordHash := Format('%.4x', [hash]); cinfo.PasswordHash := Format('%.4x', [hash]);
cinfo.Algorithm := caExcel; cinfo.Algorithm := caExcel;
AWorksheet.CryptoInfo := cinfo; AWorksheet.CryptoInfo := cinfo;

View File

@ -2006,7 +2006,7 @@ procedure TsSpreadOOXMLReader.ReadSheetProtection(ANode: TDOMNode;
var var
s: String; s: String;
shc: TsCryptoInfo; shc: TsCryptoInfo;
shp0, shp1: TsWorksheetProtections; shp: TsWorksheetProtections;
begin begin
if ANode = nil then if ANode = nil then
exit; exit;
@ -2036,100 +2036,83 @@ begin
end; end;
AWorksheet.CryptoInfo := shc; AWorksheet.CryptoInfo := shc;
shp1 := []; // will get "1" to include shp := DEFAULT_SHEET_PROTECTION;
shp0 := ALL_SHEET_PROTECTIONS; // will get "0" to exclude
// Attribute not found -> property = false // Attribute not found -> property = false
s := GetAttrValue(ANode, 'sheet'); s := GetAttrValue(ANode, 'sheet');
if (s = '1') then if (s = '1') then Include(shp, spCells) else
Include(shp1, spCells) else if (s = '0') or (s = '') then Exclude(shp, spCells);
Exclude(shp0, spCells);
s := GetAttrValue(ANode, 'selectLockedCells'); s := GetAttrValue(ANode, 'selectLockedCells');
if (s = '1') then if (s = '1') then Include(shp, spSelectLockedCells) else
Include(shp1, spSelectLockedCells) else if (s = '0') or (s = '') then Exclude(shp, spSelectLockedCells);
Exclude(shp0, spSelectLockedCells);
s := GetAttrValue(ANode, 'selectUnlockedCells'); s := GetAttrValue(ANode, 'selectUnlockedCells');
if (s = '1') then if (s = '1') then Include(shp, spSelectUnlockedCells) else
Include(shp1, spSelectUnlockedCells) else if (s = '') or (s = '0') then Exclude(shp, spSelectUnlockedCells);
Exclude(shp0, spSelectUnlockedCells);
// these options are currently not supported by fpspreadsheet // these options are currently not supported by fpspreadsheet
{ {
s := GetAttrValue(ANode, 'objects'); s := GetAttrValue(ANode, 'objects');
if (s = '1') then if (s = '1') then Include(shp, spObjects) else
Include(shp1, spObjects) else if (s = '') or (s = '0') then Exclude(shp, spObjects);
Exclude(shp0, spObjects);
s := GetAttrValue(ANode, 'scenarios'); s := GetAttrValue(ANode, 'scenarios');
if (s = '1') then if (s = '1') then Include(shp, spScenarios) else
Include(shp1, spScenarios) else if (s = '') or (s = '0') then Exclude(shp, spScenarios);
Exclude(shp0, spScenarios);
} }
// Attribute not found -> property = true // Attribute not found -> property = true
{ {
s := GetAttrValue(ANode, 'autoFilter'); s := GetAttrValue(ANode, 'autoFilter');
if (s = '0') then if (s = '0') then Exclude(shp, spAutoFilter) else
Exclude(shp1, spAutoFilter) else if (s = '') or (s = '1') then Include(shp, spAutoFilter);
Include(shp0, spAutoFilter);
} }
s := GetAttrValue(ANode, 'deleteColumns'); s := GetAttrValue(ANode, 'deleteColumns');
if (s = '0') then if (s = '0') then Exclude(shp, spDeleteColumns) else
Exclude(shp0, spDeleteColumns) else if (s = '') or (s = '1') then Include(shp, spDeleteColumns);
Include(shp1, spDeleteColumns);
s := GetAttrValue(ANode, 'deleteRows'); s := GetAttrValue(ANode, 'deleteRows');
if (s = '0') then if (s = '0') then Exclude(shp, spDeleteRows) else
Exclude(shp0, spDeleteRows) else if (s = '') or (s = '1') then Include(shp, spDeleteRows);
Include(shp1, spDeleteRows);
s := GetAttrValue(ANode, 'formatCells'); s := GetAttrValue(ANode, 'formatCells');
if (s = '0') then if (s = '0') then Exclude(shp, spFormatCells) else
Exclude(shp0, spFormatCells) else if (s = '') or (s = '1') then Include(shp, spFormatCells);
Include(shp1, spFormatCells);
s := GetAttrValue(ANode, 'formatColumns'); s := GetAttrValue(ANode, 'formatColumns');
if (s = '0') then if (s = '0') then Exclude(shp, spFormatColumns) else
Exclude(shp0, spFormatColumns) else if (s = '') or (s = '1') then Include(shp, spFormatColumns);
Include(shp1, spFormatColumns);
s := GetAttrValue(ANode, 'formatRows'); s := GetAttrValue(ANode, 'formatRows');
if (s = '0') then if (s = '0') then Exclude(shp, spFormatRows) else
Exclude(shp0, spFormatRows) else if (s = '') or (s = '1') then Include(shp, spFormatRows);
Include(shp1, spFormatRows);
s := GetAttrValue(ANode, 'insertColumns'); s := GetAttrValue(ANode, 'insertColumns');
if (s = '0') then if (s = '0') then Exclude(shp, spInsertColumns) else
Exclude(shp0, spInsertColumns) else if (s = '') or (s = '1') then Include(shp, spInsertColumns);
Include(shp1, spInsertColumns);
s := GetAttrValue(ANode, 'insertHyperlinks'); s := GetAttrValue(ANode, 'insertHyperlinks');
if (s = '0') then if (s = '0') then Exclude(shp, spInsertHyperlinks) else
Exclude(shp0, spInsertHyperlinks) else if (s = '') or (s = '1') then Include(shp, spInsertHyperlinks);
Include(shp1, spInsertHyperlinks);
s := GetAttrValue(ANode, 'insertRows'); s := GetAttrValue(ANode, 'insertRows');
if (s = '0') then if (s = '0') then Exclude(shp, spInsertRows) else
Exclude(shp0, spInsertRows) else if (s = '') or (s = '1') then Include(shp, spInsertRows);
Include(shp1, spInsertRows);
s := GetAttrValue(ANode, 'sort'); s := GetAttrValue(ANode, 'sort');
if (s = '0') then if (s = '0') then Exclude(shp, spSort) else
Exclude(shp0, spSort) else if (s = '') or (s = '1') then Include(shp, spSort);
Include(shp1, spSort);
// Currently no pivottable support in fpspreadsheet // Currently no pivottable support in fpspreadsheet
{ {
s := GetAttrValue(ANode, 'pivotTables'); s := GetAttrValue(ANode, 'pivotTables');
if (s = '0') then if (s = '0') then Exclude(shp, spPivotTables) else
Exclude(shp0, spPivotTables) else if (s = '') or (s = '1') then Include(shp, spPivotTables);
Include(shp1, spPivotTables);
} }
AWorksheet.Protection := shp0 + shp1; AWorksheet.Protection := shp;
AWorksheet.Protect(true); AWorksheet.Protect(true);
end; end;
@ -3449,7 +3432,7 @@ begin
s := s + ' password="' + AWorksheet.CryptoInfo.PasswordHash + '"' s := s + ' password="' + AWorksheet.CryptoInfo.PasswordHash + '"'
else else
begin begin
s := s + ' hashValue="' + AWorksheet.CryptoInfo.HashValue + '"'; s := s + ' hashValue="' + AWorksheet.CryptoInfo.PasswordHash + '"';
if AWorksheet.CryptoInfo.Algorithm <> caUnknown then if AWorksheet.CryptoInfo.Algorithm <> caUnknown then
s := s + ' algorithmName="' + AlgorithmToStr(AWorksheet.CryptoInfo.Algorithm) + '"'; s := s + ' algorithmName="' + AlgorithmToStr(AWorksheet.CryptoInfo.Algorithm) + '"';

View File

@ -3655,9 +3655,8 @@ begin
AStrings.Add('(-) CryptoInfo='); AStrings.Add('(-) CryptoInfo=');
AStrings.Add(Format(' PasswordHash=%s', [Workbook.CryptoInfo.PasswordHash])); AStrings.Add(Format(' PasswordHash=%s', [Workbook.CryptoInfo.PasswordHash]));
AStrings.Add(Format(' Algorithm=%s', [AlgorithmToStr(Workbook.CryptoInfo.Algorithm)])); AStrings.Add(Format(' Algorithm=%s', [AlgorithmToStr(Workbook.CryptoInfo.Algorithm)]));
AStrings.Add(Format(' HashValue=%s', [Workbook.CryptoInfo.HashValue]));
AStrings.Add(Format(' SaltValue=%s', [Workbook.CryptoInfo.SaltValue])); AStrings.Add(Format(' SaltValue=%s', [Workbook.CryptoInfo.SaltValue]));
AStrings.Add(Format(' SplinCount=%d', [Workbook.CryptoInfo.SpinCount])); AStrings.Add(Format(' SpinCount=%d', [Workbook.CryptoInfo.SpinCount]));
end else end else
AStrings.Add('(+) CryptoInfo=(dblclick for more...)'); AStrings.Add('(+) CryptoInfo=(dblclick for more...)');
@ -3839,9 +3838,8 @@ begin
AStrings.Add('(-) CryptoInfo='); AStrings.Add('(-) CryptoInfo=');
AStrings.Add(Format(' PasswordHash=%s', [Worksheet.CryptoInfo.PasswordHash])); AStrings.Add(Format(' PasswordHash=%s', [Worksheet.CryptoInfo.PasswordHash]));
AStrings.Add(Format(' Algorithm=%s', [AlgorithmToStr(Worksheet.CryptoInfo.Algorithm)])); AStrings.Add(Format(' Algorithm=%s', [AlgorithmToStr(Worksheet.CryptoInfo.Algorithm)]));
AStrings.Add(Format(' HashValue=%s', [Worksheet.CryptoInfo.HashValue]));
AStrings.Add(Format(' SaltValue=%s', [Worksheet.CryptoInfo.SaltValue])); AStrings.Add(Format(' SaltValue=%s', [Worksheet.CryptoInfo.SaltValue]));
AStrings.Add(Format(' SplinCount=%d', [Worksheet.CryptoInfo.SpinCount])); AStrings.Add(Format(' SpinCount=%d', [Worksheet.CryptoInfo.SpinCount]));
end else end else
AStrings.Add('(+) CryptoInfo=(dblclick for more...)'); AStrings.Add('(+) CryptoInfo=(dblclick for more...)');