diff --git a/components/fpspreadsheet/source/common/fpscrypto.pas b/components/fpspreadsheet/source/common/fpscrypto.pas index b652b0147..9fd4bbcd6 100644 --- a/components/fpspreadsheet/source/common/fpscrypto.pas +++ b/components/fpspreadsheet/source/common/fpscrypto.pas @@ -33,6 +33,7 @@ end; function StrToAlgorithm(const AName: String): TsCryptoAlgorithm; begin case AName of + // Excel 'MD2' : Result := caMD2; 'MD4' : Result := caMD4; 'MD5' : Result := caMD5; @@ -43,7 +44,15 @@ begin 'SHA-384' : Result := caSHA384; 'SHA-512' : Result := caSHA512; '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; diff --git a/components/fpspreadsheet/source/common/fpsopendocument.pas b/components/fpspreadsheet/source/common/fpsopendocument.pas index 0d351fdd1..e3d7c327e 100644 --- a/components/fpspreadsheet/source/common/fpsopendocument.pas +++ b/components/fpspreadsheet/source/common/fpsopendocument.pas @@ -115,6 +115,7 @@ type procedure ReadColumns(ATableNode: TDOMNode); procedure ReadColumnStyle(AStyleNode: TDOMNode); procedure ReadDateMode(SpreadSheetNode: TDOMNode); + procedure ReadDocumentProtection(ANode: TDOMNode); procedure ReadFont(ANode: TDOMNode; var AFontName: String; var AFontSize: Single; var AFontStyle: TsFontStyles; var AFontColor: TsColor; var AFontPosition: TsFontPosition); @@ -129,6 +130,7 @@ type procedure ReadRowsAndCells(ATableNode: TDOMNode); procedure ReadRowStyle(AStyleNode: TDOMNode); procedure ReadShapes(ATableNode: TDOMNode); + procedure ReadSheetProtection(ANode: TDOMNode; ASheet: TsWorksheet); procedure ReadTableStyle(AStyleNode: TDOMNode); protected @@ -285,8 +287,7 @@ uses {$IFDEF FPS_VARISBOOL} fpsPatches, {$ENDIF} - fpsStrings, fpsStreams, fpsClasses, fpsExprParser, - fpsImages; + fpsStrings, fpsStreams, fpsCrypto, fpsClasses, fpsExprParser, fpsImages; const { OpenDocument general XML constants } @@ -2060,6 +2061,25 @@ begin raise Exception.CreateFmt('Spreadsheet file corrupt: cannot handle null-date format %s', [NullDateSetting]); 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. } procedure TsSpreadOpenDocReader.ReadFont(ANode: TDOMNode; var AFontName: String; var AFontSize: Single; var AFontStyle: TsFontStyles; var AFontColor: TsColor; @@ -2468,6 +2488,7 @@ begin if not Assigned(SpreadSheetNode) then raise Exception.Create('[TsSpreadOpenDocReader.ReadFromStream] Node "office:spreadsheet" not found.'); + ReadDocumentProtection(SpreadsheetNode); ReadDateMode(SpreadSheetNode); //process each table (sheet) @@ -2485,6 +2506,8 @@ begin end; FWorkSheet := FWorkbook.AddWorksheet(GetAttrValue(TableNode, 'table:name'), true); tablestyleName := GetAttrValue(TableNode, 'table:style-name'); + // Read protection + ReadSheetProtection(TableNode, FWorksheet); // Collect embedded images ReadShapes(TableNode); // Collect column styles used @@ -3882,6 +3905,50 @@ begin 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). + // + // + // + 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); var styleNode: TDOMNode; @@ -4220,6 +4287,17 @@ begin fmt.VertAlignment := vaBottom; if fmt.VertAlignment <> vaDefault then 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 else if nodeName = 'style:paragraph-properties' then diff --git a/components/fpspreadsheet/source/common/fpspreadsheet.pas b/components/fpspreadsheet/source/common/fpspreadsheet.pas index 4d4549bc4..076e5ee12 100644 --- a/components/fpspreadsheet/source/common/fpspreadsheet.pas +++ b/components/fpspreadsheet/source/common/fpspreadsheet.pas @@ -1189,7 +1189,8 @@ begin FActiveCellRow := UNASSIGNED_ROW_COL_INDEX; FActiveCellCol := UNASSIGNED_ROW_COL_INDEX; - FProtection := DEFAULT_SHEET_PROTECTIONS; + FProtection := DEFAULT_SHEET_PROTECTION; + InitCryptoInfo(FCryptoInfo); FOptions := [soShowGridLines, soShowHeaders]; end; @@ -7981,6 +7982,10 @@ begin // Add default cell format InitFormatRecord(fmt); AddCellFormat(fmt); + + // Protection + InitCryptoInfo(FCryptoInfo); + FProtection := []; end; {@@ ---------------------------------------------------------------------------- diff --git a/components/fpspreadsheet/source/common/fpstypes.pas b/components/fpspreadsheet/source/common/fpstypes.pas index 9d0ad2bf2..d3a1641e3 100644 --- a/components/fpspreadsheet/source/common/fpstypes.pas +++ b/components/fpspreadsheet/source/common/fpstypes.pas @@ -650,7 +650,6 @@ type TsCryptoInfo = record PasswordHash: String; Algorithm: TsCryptoAlgorithm; - HashValue: string; SaltValue: string; SpinCount: Integer; end; @@ -680,7 +679,7 @@ const spCells, spSort, spSelectLockedCells, spSelectUnlockedCells {, spObjects, spPivotTables, spScenarios} ]; - DEFAULT_SHEET_PROTECTIONS = ALL_SHEET_PROTECTIONS - [spSelectLockedCells, spSelectUnlockedcells]; + DEFAULT_SHEET_PROTECTION = ALL_SHEET_PROTECTIONS - [spSelectLockedCells, spSelectUnlockedcells]; DEFAULT_CELL_PROTECTION = [cpLockCell]; diff --git a/components/fpspreadsheet/source/common/fpsutils.pas b/components/fpspreadsheet/source/common/fpsutils.pas index a1b8234f4..6900849a0 100644 --- a/components/fpspreadsheet/source/common/fpsutils.pas +++ b/components/fpspreadsheet/source/common/fpsutils.pas @@ -2087,7 +2087,6 @@ procedure InitCryptoInfo(out AValue: TsCryptoInfo); begin AValue.PasswordHash := ''; AValue.Algorithm := caUnknown; - AValue.HashValue := ''; AValue.SaltValue := ''; AValue.SpinCount := 0; end; diff --git a/components/fpspreadsheet/source/common/xlscommon.pas b/components/fpspreadsheet/source/common/xlscommon.pas index f8d87a70c..c4f24a7ce 100644 --- a/components/fpspreadsheet/source/common/xlscommon.pas +++ b/components/fpspreadsheet/source/common/xlscommon.pas @@ -2043,14 +2043,14 @@ begin if AWorksheet = nil then begin // Password for workbook protection - cinfo := FWorkbook.CryptoInfo; + InitCryptoInfo(cinfo); cinfo.PasswordHash := Format('%.4x', [hash]); cinfo.Algorithm := caExcel; FWorkbook.CryptoInfo := cinfo; end else begin // Password for worksheet protection - cinfo := AWorksheet.CryptoInfo; + InitCryptoInfo(cinfo); cinfo.PasswordHash := Format('%.4x', [hash]); cinfo.Algorithm := caExcel; AWorksheet.CryptoInfo := cinfo; diff --git a/components/fpspreadsheet/source/common/xlsxooxml.pas b/components/fpspreadsheet/source/common/xlsxooxml.pas index 96f4df9ba..91bbca3f3 100644 --- a/components/fpspreadsheet/source/common/xlsxooxml.pas +++ b/components/fpspreadsheet/source/common/xlsxooxml.pas @@ -2006,7 +2006,7 @@ procedure TsSpreadOOXMLReader.ReadSheetProtection(ANode: TDOMNode; var s: String; shc: TsCryptoInfo; - shp0, shp1: TsWorksheetProtections; + shp: TsWorksheetProtections; begin if ANode = nil then exit; @@ -2036,100 +2036,83 @@ begin end; AWorksheet.CryptoInfo := shc; - shp1 := []; // will get "1" to include - shp0 := ALL_SHEET_PROTECTIONS; // will get "0" to exclude + shp := DEFAULT_SHEET_PROTECTION; // Attribute not found -> property = false s := GetAttrValue(ANode, 'sheet'); - if (s = '1') then - Include(shp1, spCells) else - Exclude(shp0, spCells); + if (s = '1') then Include(shp, spCells) else + if (s = '0') or (s = '') then Exclude(shp, spCells); s := GetAttrValue(ANode, 'selectLockedCells'); - if (s = '1') then - Include(shp1, spSelectLockedCells) else - Exclude(shp0, spSelectLockedCells); + if (s = '1') then Include(shp, spSelectLockedCells) else + if (s = '0') or (s = '') then Exclude(shp, spSelectLockedCells); s := GetAttrValue(ANode, 'selectUnlockedCells'); - if (s = '1') then - Include(shp1, spSelectUnlockedCells) else - Exclude(shp0, spSelectUnlockedCells); + if (s = '1') then Include(shp, spSelectUnlockedCells) else + if (s = '') or (s = '0') then Exclude(shp, spSelectUnlockedCells); // these options are currently not supported by fpspreadsheet { s := GetAttrValue(ANode, 'objects'); - if (s = '1') then - Include(shp1, spObjects) else - Exclude(shp0, spObjects); + if (s = '1') then Include(shp, spObjects) else + if (s = '') or (s = '0') then Exclude(shp, spObjects); s := GetAttrValue(ANode, 'scenarios'); - if (s = '1') then - Include(shp1, spScenarios) else - Exclude(shp0, spScenarios); + if (s = '1') then Include(shp, spScenarios) else + if (s = '') or (s = '0') then Exclude(shp, spScenarios); } // Attribute not found -> property = true { s := GetAttrValue(ANode, 'autoFilter'); - if (s = '0') then - Exclude(shp1, spAutoFilter) else - Include(shp0, spAutoFilter); + if (s = '0') then Exclude(shp, spAutoFilter) else + if (s = '') or (s = '1') then Include(shp, spAutoFilter); } s := GetAttrValue(ANode, 'deleteColumns'); - if (s = '0') then - Exclude(shp0, spDeleteColumns) else - Include(shp1, spDeleteColumns); + if (s = '0') then Exclude(shp, spDeleteColumns) else + if (s = '') or (s = '1') then Include(shp, spDeleteColumns); s := GetAttrValue(ANode, 'deleteRows'); - if (s = '0') then - Exclude(shp0, spDeleteRows) else - Include(shp1, spDeleteRows); + if (s = '0') then Exclude(shp, spDeleteRows) else + if (s = '') or (s = '1') then Include(shp, spDeleteRows); s := GetAttrValue(ANode, 'formatCells'); - if (s = '0') then - Exclude(shp0, spFormatCells) else - Include(shp1, spFormatCells); + if (s = '0') then Exclude(shp, spFormatCells) else + if (s = '') or (s = '1') then Include(shp, spFormatCells); s := GetAttrValue(ANode, 'formatColumns'); - if (s = '0') then - Exclude(shp0, spFormatColumns) else - Include(shp1, spFormatColumns); + if (s = '0') then Exclude(shp, spFormatColumns) else + if (s = '') or (s = '1') then Include(shp, spFormatColumns); s := GetAttrValue(ANode, 'formatRows'); - if (s = '0') then - Exclude(shp0, spFormatRows) else - Include(shp1, spFormatRows); + if (s = '0') then Exclude(shp, spFormatRows) else + if (s = '') or (s = '1') then Include(shp, spFormatRows); s := GetAttrValue(ANode, 'insertColumns'); - if (s = '0') then - Exclude(shp0, spInsertColumns) else - Include(shp1, spInsertColumns); + if (s = '0') then Exclude(shp, spInsertColumns) else + if (s = '') or (s = '1') then Include(shp, spInsertColumns); s := GetAttrValue(ANode, 'insertHyperlinks'); - if (s = '0') then - Exclude(shp0, spInsertHyperlinks) else - Include(shp1, spInsertHyperlinks); + if (s = '0') then Exclude(shp, spInsertHyperlinks) else + if (s = '') or (s = '1') then Include(shp, spInsertHyperlinks); s := GetAttrValue(ANode, 'insertRows'); - if (s = '0') then - Exclude(shp0, spInsertRows) else - Include(shp1, spInsertRows); + if (s = '0') then Exclude(shp, spInsertRows) else + if (s = '') or (s = '1') then Include(shp, spInsertRows); s := GetAttrValue(ANode, 'sort'); - if (s = '0') then - Exclude(shp0, spSort) else - Include(shp1, spSort); + if (s = '0') then Exclude(shp, spSort) else + if (s = '') or (s = '1') then Include(shp, spSort); // Currently no pivottable support in fpspreadsheet { s := GetAttrValue(ANode, 'pivotTables'); - if (s = '0') then - Exclude(shp0, spPivotTables) else - Include(shp1, spPivotTables); + if (s = '0') then Exclude(shp, spPivotTables) else + if (s = '') or (s = '1') then Include(shp, spPivotTables); } - AWorksheet.Protection := shp0 + shp1; + AWorksheet.Protection := shp; AWorksheet.Protect(true); end; @@ -3449,7 +3432,7 @@ begin s := s + ' password="' + AWorksheet.CryptoInfo.PasswordHash + '"' else begin - s := s + ' hashValue="' + AWorksheet.CryptoInfo.HashValue + '"'; + s := s + ' hashValue="' + AWorksheet.CryptoInfo.PasswordHash + '"'; if AWorksheet.CryptoInfo.Algorithm <> caUnknown then s := s + ' algorithmName="' + AlgorithmToStr(AWorksheet.CryptoInfo.Algorithm) + '"'; diff --git a/components/fpspreadsheet/source/visual/fpspreadsheetctrls.pas b/components/fpspreadsheet/source/visual/fpspreadsheetctrls.pas index 622a7b995..c93d77ef0 100644 --- a/components/fpspreadsheet/source/visual/fpspreadsheetctrls.pas +++ b/components/fpspreadsheet/source/visual/fpspreadsheetctrls.pas @@ -3655,9 +3655,8 @@ begin AStrings.Add('(-) CryptoInfo='); AStrings.Add(Format(' PasswordHash=%s', [Workbook.CryptoInfo.PasswordHash])); 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(' SplinCount=%d', [Workbook.CryptoInfo.SpinCount])); + AStrings.Add(Format(' SpinCount=%d', [Workbook.CryptoInfo.SpinCount])); end else AStrings.Add('(+) CryptoInfo=(dblclick for more...)'); @@ -3839,9 +3838,8 @@ begin AStrings.Add('(-) CryptoInfo='); AStrings.Add(Format(' PasswordHash=%s', [Worksheet.CryptoInfo.PasswordHash])); 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(' SplinCount=%d', [Worksheet.CryptoInfo.SpinCount])); + AStrings.Add(Format(' SpinCount=%d', [Worksheet.CryptoInfo.SpinCount])); end else AStrings.Add('(+) CryptoInfo=(dblclick for more...)');