diff --git a/excelize_test.go b/excelize_test.go old mode 100644 new mode 100755 index d021b55..f7a70d9 --- a/excelize_test.go +++ b/excelize_test.go @@ -1208,6 +1208,19 @@ func TestSearchSheet(t *testing.T) { t.Log(xlsx.SearchSheet("Sheet1", "A")) } +func TestProtectSheet(t *testing.T) { + xlsx := NewFile() + xlsx.ProtectSheet("Sheet1", nil) + xlsx.ProtectSheet("Sheet1", &FormatSheetProtection{ + Password: "password", + EditScenarios: false, + }) + err := xlsx.SaveAs("./test/Book_protect_sheet.xlsx") + if err != nil { + t.Error(err) + } +} + func trimSliceSpace(s []string) []string { for { if len(s) > 0 && s[len(s)-1] == "" { diff --git a/lib.go b/lib.go index 8e63da9..cf43dc9 100644 --- a/lib.go +++ b/lib.go @@ -15,6 +15,8 @@ import ( "io" "log" "math" + "strconv" + "strings" "unicode" ) @@ -199,3 +201,29 @@ func namespaceStrictToTransitional(content []byte) []byte { } return content } + +// genSheetPasswd provides a method to generate password for worksheet +// protection by given plaintext. When an Excel sheet is being protected with +// a password, a 16-bit (two byte) long hash is generated. To verify a +// password, it is compared to the hash. Obviously, if the input data volume +// is great, numerous passwords will match the same hash. Here is the +// algorithm to create the hash value: +// +// take the ASCII values of all characters shift left the first character 1 bit, the second 2 bits and so on (use only the lower 15 bits and rotate all higher bits, the highest bit of the 16-bit value is always 0 [signed short]) +// XOR all these values +// XOR the count of characters +// XOR the constant 0xCE4B +func genSheetPasswd(plaintext string) string { + var password int64 = 0x0000 + var charPos uint = 1 + for _, v := range plaintext { + value := int64(v) << charPos + charPos++ + rotatedBits := value >> 15 // rotated bits beyond bit 15 + value &= 0x7fff // first 15 bits + password ^= (value | rotatedBits) + } + password ^= int64(len(plaintext)) + password ^= 0xCE4B + return strings.ToUpper(strconv.FormatInt(password, 16)) +} diff --git a/sheet.go b/sheet.go index 522d112..8ddb8c9 100644 --- a/sheet.go +++ b/sheet.go @@ -711,6 +711,47 @@ func (f *File) SearchSheet(sheet, value string) []string { return result } +// ProtectSheet provides a function to prevent other users from accidentally +// or deliberately changing, moving, or deleting data in a worksheet. For +// example protect Sheet1 with protection settings: +// +// xlsx.ProtectSheet("Sheet1", &excelize.FormatSheetProtection{ +// Password: "password", +// EditScenarios: false, +// }) +// +func (f *File) ProtectSheet(sheet string, settings *FormatSheetProtection) { + xlsx := f.workSheetReader(sheet) + if settings == nil { + settings = &FormatSheetProtection{ + EditObjects: true, + EditScenarios: true, + SelectLockedCells: true, + } + } + xlsx.SheetProtection = &xlsxSheetProtection{ + AutoFilter: settings.AutoFilter, + DeleteColumns: settings.DeleteColumns, + DeleteRows: settings.DeleteRows, + FormatCells: settings.FormatCells, + FormatColumns: settings.FormatColumns, + FormatRows: settings.FormatRows, + InsertColumns: settings.InsertColumns, + InsertHyperlinks: settings.InsertHyperlinks, + InsertRows: settings.InsertRows, + Objects: settings.EditObjects, + PivotTables: settings.PivotTables, + Scenarios: settings.EditScenarios, + SelectLockedCells: settings.SelectLockedCells, + SelectUnlockedCells: settings.SelectUnlockedCells, + Sheet: true, + Sort: settings.Sort, + } + if settings.Password != "" { + xlsx.SheetProtection.Password = genSheetPasswd(settings.Password) + } +} + // trimSheetName provides a function to trim invaild characters by given worksheet // name. func trimSheetName(name string) string { diff --git a/test/Book1.xlsx b/test/Book1.xlsx old mode 100755 new mode 100644 index 7d9886e..84c43d1 Binary files a/test/Book1.xlsx and b/test/Book1.xlsx differ diff --git a/xmlWorksheet.go b/xmlWorksheet.go index 072ecce..42f8ddb 100644 --- a/xmlWorksheet.go +++ b/xmlWorksheet.go @@ -377,26 +377,27 @@ type xlsxF struct { // xlsxSheetProtection collection expresses the sheet protection options to // enforce when the sheet is protected. type xlsxSheetProtection struct { - AlgorithmName string `xml:"algorithmName,attr,omitempty"` - AutoFilter int `xml:"autoFilter,attr,omitempty"` - DeleteColumns int `xml:"deleteColumns,attr,omitempty"` - DeleteRows int `xml:"deleteRows,attr,omitempty"` - FormatCells int `xml:"formatCells,attr,omitempty"` - FormatColumns int `xml:"formatColumns,attr,omitempty"` - FormatRows int `xml:"formatRows,attr,omitempty"` - HashValue string `xml:"hashValue,attr,omitempty"` - InsertColumns int `xml:"insertColumns,attr,omitempty"` - InsertHyperlinks int `xml:"insertHyperlinks,attr,omitempty"` - InsertRows int `xml:"insertRows,attr,omitempty"` - Objects int `xml:"objects,attr,omitempty"` - PivotTables int `xml:"pivotTables,attr,omitempty"` - SaltValue string `xml:"saltValue,attr,omitempty"` - Scenarios int `xml:"scenarios,attr,omitempty"` - SelectLockedCells int `xml:"selectLockedCells,attr,omitempty"` - SelectUnlockedCell int `xml:"selectUnlockedCell,attr,omitempty"` - Sheet int `xml:"sheet,attr,omitempty"` - Sort int `xml:"sort,attr,omitempty"` - SpinCount int `xml:"spinCount,attr,omitempty"` + AlgorithmName string `xml:"algorithmName,attr,omitempty"` + AutoFilter bool `xml:"autoFilter,attr,omitempty"` + DeleteColumns bool `xml:"deleteColumns,attr,omitempty"` + DeleteRows bool `xml:"deleteRows,attr,omitempty"` + FormatCells bool `xml:"formatCells,attr,omitempty"` + FormatColumns bool `xml:"formatColumns,attr,omitempty"` + FormatRows bool `xml:"formatRows,attr,omitempty"` + HashValue string `xml:"hashValue,attr,omitempty"` + InsertColumns bool `xml:"insertColumns,attr,omitempty"` + InsertHyperlinks bool `xml:"insertHyperlinks,attr,omitempty"` + InsertRows bool `xml:"insertRows,attr,omitempty"` + Objects bool `xml:"objects,attr,omitempty"` + Password string `xml:"password,attr,omitempty"` + PivotTables bool `xml:"pivotTables,attr,omitempty"` + SaltValue string `xml:"saltValue,attr,omitempty"` + Scenarios bool `xml:"scenarios,attr,omitempty"` + SelectLockedCells bool `xml:"selectLockedCells,attr,omitempty"` + SelectUnlockedCells bool `xml:"selectUnlockedCells,attr,omitempty"` + Sheet bool `xml:"sheet,attr,omitempty"` + Sort bool `xml:"sort,attr,omitempty"` + SpinCount int `xml:"spinCount,attr,omitempty"` } // xlsxPhoneticPr (Phonetic Properties) represents a collection of phonetic @@ -599,3 +600,23 @@ type formatConditional struct { MultiRange string `json:"multi_range,omitempty"` BarColor string `json:"bar_color,omitempty"` } + +// FormatSheetProtection directly maps the settings of worksheet protection. +type FormatSheetProtection struct { + AutoFilter bool + DeleteColumns bool + DeleteRows bool + EditObjects bool + EditScenarios bool + FormatCells bool + FormatColumns bool + FormatRows bool + InsertColumns bool + InsertHyperlinks bool + InsertRows bool + Password string + PivotTables bool + SelectLockedCells bool + SelectUnlockedCells bool + Sort bool +}