forked from p30928647/excelize
feat: implement SHA-512 algorithm to ProtectSheet (#1115)
This commit is contained in:
parent
9e64df6a96
commit
af5c4d00e8
4
chart.go
4
chart.go
|
@ -980,12 +980,12 @@ func (f *File) getFormatChart(format string, combo []string) (*formatChart, []*f
|
|||
return formatSet, comboCharts, err
|
||||
}
|
||||
if _, ok := chartValAxNumFmtFormatCode[comboChart.Type]; !ok {
|
||||
return formatSet, comboCharts, newUnsupportChartType(comboChart.Type)
|
||||
return formatSet, comboCharts, newUnsupportedChartType(comboChart.Type)
|
||||
}
|
||||
comboCharts = append(comboCharts, comboChart)
|
||||
}
|
||||
if _, ok := chartValAxNumFmtFormatCode[formatSet.Type]; !ok {
|
||||
return formatSet, comboCharts, newUnsupportChartType(formatSet.Type)
|
||||
return formatSet, comboCharts, newUnsupportedChartType(formatSet.Type)
|
||||
}
|
||||
return formatSet, comboCharts, err
|
||||
}
|
||||
|
|
64
crypt.go
64
crypt.go
|
@ -43,6 +43,7 @@ var (
|
|||
packageOffset = 8 // First 8 bytes are the size of the stream
|
||||
packageEncryptionChunkSize = 4096
|
||||
iterCount = 50000
|
||||
sheetProtectionSpinCount = 1e5
|
||||
oleIdentifier = []byte{
|
||||
0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1,
|
||||
}
|
||||
|
@ -146,7 +147,7 @@ func Decrypt(raw []byte, opt *Options) (packageBuf []byte, err error) {
|
|||
case "standard":
|
||||
return standardDecrypt(encryptionInfoBuf, encryptedPackageBuf, opt)
|
||||
default:
|
||||
err = ErrUnsupportEncryptMechanism
|
||||
err = ErrUnsupportedEncryptMechanism
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -307,7 +308,7 @@ func encryptionMechanism(buffer []byte) (mechanism string, err error) {
|
|||
} else if (versionMajor == 3 || versionMajor == 4) && versionMinor == 3 {
|
||||
mechanism = "extensible"
|
||||
}
|
||||
err = ErrUnsupportEncryptMechanism
|
||||
err = ErrUnsupportedEncryptMechanism
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -387,14 +388,14 @@ func standardConvertPasswdToKey(header StandardEncryptionHeader, verifier Standa
|
|||
key = hashing("sha1", iterator, key)
|
||||
}
|
||||
var block int
|
||||
hfinal := hashing("sha1", key, createUInt32LEBuffer(block, 4))
|
||||
hFinal := hashing("sha1", key, createUInt32LEBuffer(block, 4))
|
||||
cbRequiredKeyLength := int(header.KeySize) / 8
|
||||
cbHash := sha1.Size
|
||||
buf1 := bytes.Repeat([]byte{0x36}, 64)
|
||||
buf1 = append(standardXORBytes(hfinal, buf1[:cbHash]), buf1[cbHash:]...)
|
||||
buf1 = append(standardXORBytes(hFinal, buf1[:cbHash]), buf1[cbHash:]...)
|
||||
x1 := hashing("sha1", buf1)
|
||||
buf2 := bytes.Repeat([]byte{0x5c}, 64)
|
||||
buf2 = append(standardXORBytes(hfinal, buf2[:cbHash]), buf2[cbHash:]...)
|
||||
buf2 = append(standardXORBytes(hFinal, buf2[:cbHash]), buf2[cbHash:]...)
|
||||
x2 := hashing("sha1", buf2)
|
||||
x3 := append(x1, x2...)
|
||||
keyDerived := x3[:cbRequiredKeyLength]
|
||||
|
@ -417,7 +418,8 @@ func standardXORBytes(a, b []byte) []byte {
|
|||
// ECMA-376 Agile Encryption
|
||||
|
||||
// agileDecrypt decrypt the CFB file format with ECMA-376 agile encryption.
|
||||
// Support cryptographic algorithm: MD4, MD5, RIPEMD-160, SHA1, SHA256, SHA384 and SHA512.
|
||||
// Support cryptographic algorithm: MD4, MD5, RIPEMD-160, SHA1, SHA256,
|
||||
// SHA384 and SHA512.
|
||||
func agileDecrypt(encryptionInfoBuf, encryptedPackageBuf []byte, opt *Options) (packageBuf []byte, err error) {
|
||||
var encryptionInfo Encryption
|
||||
if encryptionInfo, err = parseEncryptionInfo(encryptionInfoBuf[8:]); err != nil {
|
||||
|
@ -605,11 +607,55 @@ func createIV(blockKey interface{}, encryption Encryption) ([]byte, error) {
|
|||
return iv, nil
|
||||
}
|
||||
|
||||
// randomBytes returns securely generated random bytes. It will return an error if the system's
|
||||
// secure random number generator fails to function correctly, in which case the caller should not
|
||||
// continue.
|
||||
// randomBytes returns securely generated random bytes. It will return an
|
||||
// error if the system's secure random number generator fails to function
|
||||
// correctly, in which case the caller should not continue.
|
||||
func randomBytes(n int) ([]byte, error) {
|
||||
b := make([]byte, n)
|
||||
_, err := rand.Read(b)
|
||||
return b, err
|
||||
}
|
||||
|
||||
// ISO Write Protection Method
|
||||
|
||||
// genISOPasswdHash implements the ISO password hashing algorithm by given
|
||||
// plaintext password, name of the cryptographic hash algorithm, salt value
|
||||
// and spin count.
|
||||
func genISOPasswdHash(passwd, hashAlgorithm, salt string, spinCount int) (hashValue, saltValue string, err error) {
|
||||
if len(passwd) < 1 || len(passwd) > MaxFieldLength {
|
||||
err = ErrPasswordLengthInvalid
|
||||
return
|
||||
}
|
||||
hash, ok := map[string]string{
|
||||
"MD4": "md4",
|
||||
"MD5": "md5",
|
||||
"SHA-1": "sha1",
|
||||
"SHA-256": "sha256",
|
||||
"SHA-384": "sha384",
|
||||
"SHA-512": "sha512",
|
||||
}[hashAlgorithm]
|
||||
if !ok {
|
||||
err = ErrUnsupportedHashAlgorithm
|
||||
return
|
||||
}
|
||||
var b bytes.Buffer
|
||||
s, _ := randomBytes(16)
|
||||
if salt != "" {
|
||||
if s, err = base64.StdEncoding.DecodeString(salt); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
b.Write(s)
|
||||
encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder()
|
||||
passwordBuffer, _ := encoder.Bytes([]byte(passwd))
|
||||
b.Write(passwordBuffer)
|
||||
// Generate the initial hash.
|
||||
key := hashing(hash, b.Bytes())
|
||||
// Now regenerate until spin count.
|
||||
for i := 0; i < spinCount; i++ {
|
||||
iterator := createUInt32LEBuffer(i, 4)
|
||||
key = hashing(hash, key, iterator)
|
||||
}
|
||||
hashValue, saltValue = base64.StdEncoding.EncodeToString(key), base64.StdEncoding.EncodeToString(s)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -28,11 +28,27 @@ func TestEncrypt(t *testing.T) {
|
|||
func TestEncryptionMechanism(t *testing.T) {
|
||||
mechanism, err := encryptionMechanism([]byte{3, 0, 3, 0})
|
||||
assert.Equal(t, mechanism, "extensible")
|
||||
assert.EqualError(t, err, ErrUnsupportEncryptMechanism.Error())
|
||||
assert.EqualError(t, err, ErrUnsupportedEncryptMechanism.Error())
|
||||
_, err = encryptionMechanism([]byte{})
|
||||
assert.EqualError(t, err, ErrUnknownEncryptMechanism.Error())
|
||||
}
|
||||
|
||||
func TestHashing(t *testing.T) {
|
||||
assert.Equal(t, hashing("unsupportHashAlgorithm", []byte{}), []uint8([]byte(nil)))
|
||||
assert.Equal(t, hashing("unsupportedHashAlgorithm", []byte{}), []uint8([]byte(nil)))
|
||||
}
|
||||
|
||||
func TestGenISOPasswdHash(t *testing.T) {
|
||||
for hashAlgorithm, expected := range map[string][]string{
|
||||
"MD4": {"2lZQZUubVHLm/t6KsuHX4w==", "TTHjJdU70B/6Zq83XGhHVA=="},
|
||||
"MD5": {"HWbqyd4dKKCjk1fEhk2kuQ==", "8ADyorkumWCayIukRhlVKQ=="},
|
||||
"SHA-1": {"XErQIV3Ol+nhXkyCxrLTEQm+mSc=", "I3nDtyf59ASaNX1l6KpFnA=="},
|
||||
"SHA-256": {"7oqMFyfED+mPrzRIBQ+KpKT4SClMHEPOZldliP15xAA=", "ru1R/w3P3Jna2Qo+EE8QiA=="},
|
||||
"SHA-384": {"nMODLlxsC8vr0btcq0kp/jksg5FaI3az5Sjo1yZk+/x4bFzsuIvpDKUhJGAk/fzo", "Zjq9/jHlgOY6MzFDSlVNZg=="},
|
||||
"SHA-512": {"YZ6jrGOFQgVKK3rDK/0SHGGgxEmFJglQIIRamZc2PkxVtUBp54fQn96+jVXEOqo6dtCSanqksXGcm/h3KaiR4Q==", "p5s/bybHBPtusI7EydTIrg=="},
|
||||
} {
|
||||
hashValue, saltValue, err := genISOPasswdHash("password", hashAlgorithm, expected[1], int(sheetProtectionSpinCount))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected[0], hashValue)
|
||||
assert.Equal(t, expected[1], saltValue)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ const (
|
|||
DataValidationTypeDate
|
||||
DataValidationTypeDecimal
|
||||
typeList // inline use
|
||||
DataValidationTypeTextLeng
|
||||
DataValidationTypeTextLength
|
||||
DataValidationTypeTime
|
||||
// DataValidationTypeWhole Integer
|
||||
DataValidationTypeWhole
|
||||
|
@ -116,7 +116,7 @@ func (dd *DataValidation) SetInput(title, msg string) {
|
|||
func (dd *DataValidation) SetDropList(keys []string) error {
|
||||
formula := strings.Join(keys, ",")
|
||||
if MaxFieldLength < len(utf16.Encode([]rune(formula))) {
|
||||
return ErrDataValidationFormulaLenth
|
||||
return ErrDataValidationFormulaLength
|
||||
}
|
||||
dd.Formula1 = fmt.Sprintf(`<formula1>"%s"</formula1>`, formulaEscaper.Replace(formula))
|
||||
dd.Type = convDataValidationType(typeList)
|
||||
|
@ -155,7 +155,7 @@ func (dd *DataValidation) SetRange(f1, f2 interface{}, t DataValidationType, o D
|
|||
}
|
||||
dd.Formula1, dd.Formula2 = formula1, formula2
|
||||
dd.Type = convDataValidationType(t)
|
||||
dd.Operator = convDataValidationOperatior(o)
|
||||
dd.Operator = convDataValidationOperator(o)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -192,22 +192,22 @@ func (dd *DataValidation) SetSqref(sqref string) {
|
|||
// convDataValidationType get excel data validation type.
|
||||
func convDataValidationType(t DataValidationType) string {
|
||||
typeMap := map[DataValidationType]string{
|
||||
typeNone: "none",
|
||||
DataValidationTypeCustom: "custom",
|
||||
DataValidationTypeDate: "date",
|
||||
DataValidationTypeDecimal: "decimal",
|
||||
typeList: "list",
|
||||
DataValidationTypeTextLeng: "textLength",
|
||||
DataValidationTypeTime: "time",
|
||||
DataValidationTypeWhole: "whole",
|
||||
typeNone: "none",
|
||||
DataValidationTypeCustom: "custom",
|
||||
DataValidationTypeDate: "date",
|
||||
DataValidationTypeDecimal: "decimal",
|
||||
typeList: "list",
|
||||
DataValidationTypeTextLength: "textLength",
|
||||
DataValidationTypeTime: "time",
|
||||
DataValidationTypeWhole: "whole",
|
||||
}
|
||||
|
||||
return typeMap[t]
|
||||
|
||||
}
|
||||
|
||||
// convDataValidationOperatior get excel data validation operator.
|
||||
func convDataValidationOperatior(o DataValidationOperator) string {
|
||||
// convDataValidationOperator get excel data validation operator.
|
||||
func convDataValidationOperator(o DataValidationOperator) string {
|
||||
typeMap := map[DataValidationOperator]string{
|
||||
DataValidationOperatorBetween: "between",
|
||||
DataValidationOperatorEqual: "equal",
|
||||
|
|
|
@ -94,7 +94,7 @@ func TestDataValidationError(t *testing.T) {
|
|||
t.Errorf("data validation error. Formula1 must be empty!")
|
||||
return
|
||||
}
|
||||
assert.EqualError(t, err, ErrDataValidationFormulaLenth.Error())
|
||||
assert.EqualError(t, err, ErrDataValidationFormulaLength.Error())
|
||||
assert.EqualError(t, dvRange.SetRange(nil, 20, DataValidationTypeWhole, DataValidationOperatorBetween), ErrParameterInvalid.Error())
|
||||
assert.EqualError(t, dvRange.SetRange(10, nil, DataValidationTypeWhole, DataValidationOperatorBetween), ErrParameterInvalid.Error())
|
||||
assert.NoError(t, dvRange.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorGreaterThan))
|
||||
|
@ -114,7 +114,7 @@ func TestDataValidationError(t *testing.T) {
|
|||
err = dvRange.SetDropList(keys)
|
||||
assert.Equal(t, prevFormula1, dvRange.Formula1,
|
||||
"Formula1 should be unchanged for invalid input %v", keys)
|
||||
assert.EqualError(t, err, ErrDataValidationFormulaLenth.Error())
|
||||
assert.EqualError(t, err, ErrDataValidationFormulaLength.Error())
|
||||
}
|
||||
assert.NoError(t, f.AddDataValidation("Sheet1", dvRange))
|
||||
assert.NoError(t, dvRange.SetRange(
|
||||
|
|
50
errors.go
50
errors.go
|
@ -16,42 +16,50 @@ import (
|
|||
"fmt"
|
||||
)
|
||||
|
||||
// newInvalidColumnNameError defined the error message on receiving the invalid column name.
|
||||
// newInvalidColumnNameError defined the error message on receiving the
|
||||
// invalid column name.
|
||||
func newInvalidColumnNameError(col string) error {
|
||||
return fmt.Errorf("invalid column name %q", col)
|
||||
}
|
||||
|
||||
// newInvalidRowNumberError defined the error message on receiving the invalid row number.
|
||||
// newInvalidRowNumberError defined the error message on receiving the invalid
|
||||
// row number.
|
||||
func newInvalidRowNumberError(row int) error {
|
||||
return fmt.Errorf("invalid row number %d", row)
|
||||
}
|
||||
|
||||
// newInvalidCellNameError defined the error message on receiving the invalid cell name.
|
||||
// newInvalidCellNameError defined the error message on receiving the invalid
|
||||
// cell name.
|
||||
func newInvalidCellNameError(cell string) error {
|
||||
return fmt.Errorf("invalid cell name %q", cell)
|
||||
}
|
||||
|
||||
// newInvalidExcelDateError defined the error message on receiving the data with negative values.
|
||||
// newInvalidExcelDateError defined the error message on receiving the data
|
||||
// with negative values.
|
||||
func newInvalidExcelDateError(dateValue float64) error {
|
||||
return fmt.Errorf("invalid date value %f, negative values are not supported", dateValue)
|
||||
}
|
||||
|
||||
// newUnsupportChartType defined the error message on receiving the chart type are unsupported.
|
||||
func newUnsupportChartType(chartType string) error {
|
||||
// newUnsupportedChartType defined the error message on receiving the chart
|
||||
// type are unsupported.
|
||||
func newUnsupportedChartType(chartType string) error {
|
||||
return fmt.Errorf("unsupported chart type %s", chartType)
|
||||
}
|
||||
|
||||
// newUnzipSizeLimitError defined the error message on unzip size exceeds the limit.
|
||||
// newUnzipSizeLimitError defined the error message on unzip size exceeds the
|
||||
// limit.
|
||||
func newUnzipSizeLimitError(unzipSizeLimit int64) error {
|
||||
return fmt.Errorf("unzip size exceeds the %d bytes limit", unzipSizeLimit)
|
||||
}
|
||||
|
||||
// newInvalidStyleID defined the error message on receiving the invalid style ID.
|
||||
// newInvalidStyleID defined the error message on receiving the invalid style
|
||||
// ID.
|
||||
func newInvalidStyleID(styleID int) error {
|
||||
return fmt.Errorf("invalid style ID %d, negative values are not supported", styleID)
|
||||
}
|
||||
|
||||
// newFieldLengthError defined the error message on receiving the field length overflow.
|
||||
// newFieldLengthError defined the error message on receiving the field length
|
||||
// overflow.
|
||||
func newFieldLengthError(name string) error {
|
||||
return fmt.Errorf("field %s must be less or equal than 255 characters", name)
|
||||
}
|
||||
|
@ -103,12 +111,18 @@ var (
|
|||
ErrMaxFileNameLength = errors.New("file name length exceeds maximum limit")
|
||||
// ErrEncrypt defined the error message on encryption spreadsheet.
|
||||
ErrEncrypt = errors.New("not support encryption currently")
|
||||
// ErrUnknownEncryptMechanism defined the error message on unsupport
|
||||
// ErrUnknownEncryptMechanism defined the error message on unsupported
|
||||
// encryption mechanism.
|
||||
ErrUnknownEncryptMechanism = errors.New("unknown encryption mechanism")
|
||||
// ErrUnsupportEncryptMechanism defined the error message on unsupport
|
||||
// ErrUnsupportedEncryptMechanism defined the error message on unsupported
|
||||
// encryption mechanism.
|
||||
ErrUnsupportEncryptMechanism = errors.New("unsupport encryption mechanism")
|
||||
ErrUnsupportedEncryptMechanism = errors.New("unsupported encryption mechanism")
|
||||
// ErrUnsupportedHashAlgorithm defined the error message on unsupported
|
||||
// hash algorithm.
|
||||
ErrUnsupportedHashAlgorithm = errors.New("unsupported hash algorithm")
|
||||
// ErrPasswordLengthInvalid defined the error message on invalid password
|
||||
// length.
|
||||
ErrPasswordLengthInvalid = errors.New("password length invalid")
|
||||
// ErrParameterRequired defined the error message on receive the empty
|
||||
// parameter.
|
||||
ErrParameterRequired = errors.New("parameter is required")
|
||||
|
@ -131,11 +145,17 @@ var (
|
|||
// ErrSheetIdx defined the error message on receive the invalid worksheet
|
||||
// index.
|
||||
ErrSheetIdx = errors.New("invalid worksheet index")
|
||||
// ErrUnprotectSheet defined the error message on worksheet has set no
|
||||
// protection.
|
||||
ErrUnprotectSheet = errors.New("worksheet has set no protect")
|
||||
// ErrUnprotectSheetPassword defined the error message on remove sheet
|
||||
// protection with password verification failed.
|
||||
ErrUnprotectSheetPassword = errors.New("worksheet protect password not match")
|
||||
// ErrGroupSheets defined the error message on group sheets.
|
||||
ErrGroupSheets = errors.New("group worksheet must contain an active worksheet")
|
||||
// ErrDataValidationFormulaLenth defined the error message for receiving a
|
||||
// ErrDataValidationFormulaLength defined the error message for receiving a
|
||||
// data validation formula length that exceeds the limit.
|
||||
ErrDataValidationFormulaLenth = errors.New("data validation must be 0-255 characters")
|
||||
ErrDataValidationFormulaLength = errors.New("data validation must be 0-255 characters")
|
||||
// ErrDataValidationRange defined the error message on set decimal range
|
||||
// exceeds limit.
|
||||
ErrDataValidationRange = errors.New("data validation range exceeds limit")
|
||||
|
@ -164,5 +184,5 @@ var (
|
|||
ErrSparkline = errors.New("must have the same number of 'Location' and 'Range' parameters")
|
||||
// ErrSparklineStyle defined the error message on receive the invalid
|
||||
// sparkline Style parameters.
|
||||
ErrSparklineStyle = errors.New("parameter 'Style' must betweent 0-35")
|
||||
ErrSparklineStyle = errors.New("parameter 'Style' must between 0-35")
|
||||
)
|
||||
|
|
|
@ -1160,13 +1160,44 @@ func TestHSL(t *testing.T) {
|
|||
|
||||
func TestProtectSheet(t *testing.T) {
|
||||
f := NewFile()
|
||||
assert.NoError(t, f.ProtectSheet("Sheet1", nil))
|
||||
assert.NoError(t, f.ProtectSheet("Sheet1", &FormatSheetProtection{
|
||||
sheetName := f.GetSheetName(0)
|
||||
assert.NoError(t, f.ProtectSheet(sheetName, nil))
|
||||
// Test protect worksheet with XOR hash algorithm
|
||||
assert.NoError(t, f.ProtectSheet(sheetName, &FormatSheetProtection{
|
||||
Password: "password",
|
||||
EditScenarios: false,
|
||||
}))
|
||||
|
||||
ws, err := f.workSheetReader(sheetName)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "83AF", ws.SheetProtection.Password)
|
||||
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestProtectSheet.xlsx")))
|
||||
// Test protect worksheet with SHA-512 hash algorithm
|
||||
assert.NoError(t, f.ProtectSheet(sheetName, &FormatSheetProtection{
|
||||
AlgorithmName: "SHA-512",
|
||||
Password: "password",
|
||||
}))
|
||||
ws, err = f.workSheetReader(sheetName)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 24, len(ws.SheetProtection.SaltValue))
|
||||
assert.Equal(t, 88, len(ws.SheetProtection.HashValue))
|
||||
assert.Equal(t, int(sheetProtectionSpinCount), ws.SheetProtection.SpinCount)
|
||||
// Test remove sheet protection with an incorrect password
|
||||
assert.EqualError(t, f.UnprotectSheet(sheetName, "wrongPassword"), ErrUnprotectSheetPassword.Error())
|
||||
// Test remove sheet protection with password verification
|
||||
assert.NoError(t, f.UnprotectSheet(sheetName, "password"))
|
||||
// Test protect worksheet with empty password
|
||||
assert.NoError(t, f.ProtectSheet(sheetName, &FormatSheetProtection{}))
|
||||
assert.Equal(t, "", ws.SheetProtection.Password)
|
||||
// Test protect worksheet with password exceeds the limit length
|
||||
assert.EqualError(t, f.ProtectSheet(sheetName, &FormatSheetProtection{
|
||||
AlgorithmName: "MD4",
|
||||
Password: strings.Repeat("s", MaxFieldLength+1),
|
||||
}), ErrPasswordLengthInvalid.Error())
|
||||
// Test protect worksheet with unsupported hash algorithm
|
||||
assert.EqualError(t, f.ProtectSheet(sheetName, &FormatSheetProtection{
|
||||
AlgorithmName: "RIPEMD-160",
|
||||
Password: "password",
|
||||
}), ErrUnsupportedHashAlgorithm.Error())
|
||||
// Test protect not exists worksheet.
|
||||
assert.EqualError(t, f.ProtectSheet("SheetN", nil), "sheet SheetN is not exist")
|
||||
}
|
||||
|
@ -1176,12 +1207,30 @@ func TestUnprotectSheet(t *testing.T) {
|
|||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
// Test unprotect not exists worksheet.
|
||||
// Test remove protection on not exists worksheet.
|
||||
assert.EqualError(t, f.UnprotectSheet("SheetN"), "sheet SheetN is not exist")
|
||||
|
||||
assert.NoError(t, f.UnprotectSheet("Sheet1"))
|
||||
assert.EqualError(t, f.UnprotectSheet("Sheet1", "password"), ErrUnprotectSheet.Error())
|
||||
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestUnprotectSheet.xlsx")))
|
||||
assert.NoError(t, f.Close())
|
||||
|
||||
f = NewFile()
|
||||
sheetName := f.GetSheetName(0)
|
||||
assert.NoError(t, f.ProtectSheet(sheetName, &FormatSheetProtection{Password: "password"}))
|
||||
// Test remove sheet protection with an incorrect password
|
||||
assert.EqualError(t, f.UnprotectSheet(sheetName, "wrongPassword"), ErrUnprotectSheetPassword.Error())
|
||||
// Test remove sheet protection with password verification
|
||||
assert.NoError(t, f.UnprotectSheet(sheetName, "password"))
|
||||
// Test with invalid salt value
|
||||
assert.NoError(t, f.ProtectSheet(sheetName, &FormatSheetProtection{
|
||||
AlgorithmName: "SHA-512",
|
||||
Password: "password",
|
||||
}))
|
||||
ws, err := f.workSheetReader(sheetName)
|
||||
assert.NoError(t, err)
|
||||
ws.SheetProtection.SaltValue = "YWJjZA====="
|
||||
assert.EqualError(t, f.UnprotectSheet(sheetName, "wrongPassword"), "illegal base64 data at input byte 8")
|
||||
}
|
||||
|
||||
func TestSetDefaultTimeStyle(t *testing.T) {
|
||||
|
|
|
@ -222,7 +222,7 @@ func TestAddPivotTable(t *testing.T) {
|
|||
PivotTableRange: "Sheet1!$G$2:$M$34",
|
||||
Rows: []PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
|
||||
Columns: []PivotTableField{{Data: "Type", DefaultSubtotal: true}},
|
||||
Data: []PivotTableField{{Data: "Sales", Subtotal: "-", Name: strings.Repeat("s", 256)}},
|
||||
Data: []PivotTableField{{Data: "Sales", Subtotal: "-", Name: strings.Repeat("s", MaxFieldLength+1)}},
|
||||
}))
|
||||
|
||||
// Test adjust range with invalid range
|
||||
|
|
49
sheet.go
49
sheet.go
|
@ -1129,10 +1129,14 @@ func (f *File) SetHeaderFooter(sheet string, settings *FormatHeaderFooter) error
|
|||
}
|
||||
|
||||
// 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:
|
||||
// or deliberately changing, moving, or deleting data in a worksheet. The
|
||||
// optional field AlgorithmName specified hash algorithm, support XOR, MD4,
|
||||
// MD5, SHA1, SHA256, SHA384, and SHA512 currently, if no hash algorithm
|
||||
// specified, will be using the XOR algorithm as default. For example,
|
||||
// protect Sheet1 with protection settings:
|
||||
//
|
||||
// err := f.ProtectSheet("Sheet1", &excelize.FormatSheetProtection{
|
||||
// AlgorithmName: "SHA-512",
|
||||
// Password: "password",
|
||||
// EditScenarios: false,
|
||||
// })
|
||||
|
@ -1168,22 +1172,55 @@ func (f *File) ProtectSheet(sheet string, settings *FormatSheetProtection) error
|
|||
Sort: settings.Sort,
|
||||
}
|
||||
if settings.Password != "" {
|
||||
ws.SheetProtection.Password = genSheetPasswd(settings.Password)
|
||||
if settings.AlgorithmName == "" {
|
||||
ws.SheetProtection.Password = genSheetPasswd(settings.Password)
|
||||
return err
|
||||
}
|
||||
hashValue, saltValue, err := genISOPasswdHash(settings.Password, settings.AlgorithmName, "", int(sheetProtectionSpinCount))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ws.SheetProtection.Password = ""
|
||||
ws.SheetProtection.AlgorithmName = settings.AlgorithmName
|
||||
ws.SheetProtection.SaltValue = saltValue
|
||||
ws.SheetProtection.HashValue = hashValue
|
||||
ws.SheetProtection.SpinCount = int(sheetProtectionSpinCount)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// UnprotectSheet provides a function to unprotect an Excel worksheet.
|
||||
func (f *File) UnprotectSheet(sheet string) error {
|
||||
// UnprotectSheet provides a function to remove protection for a sheet,
|
||||
// specified the second optional password parameter to remove sheet
|
||||
// protection with password verification.
|
||||
func (f *File) UnprotectSheet(sheet string, password ...string) error {
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// password verification
|
||||
if len(password) > 0 {
|
||||
if ws.SheetProtection == nil {
|
||||
return ErrUnprotectSheet
|
||||
}
|
||||
if ws.SheetProtection.AlgorithmName == "" && ws.SheetProtection.Password != genSheetPasswd(password[0]) {
|
||||
return ErrUnprotectSheetPassword
|
||||
}
|
||||
if ws.SheetProtection.AlgorithmName != "" {
|
||||
// check with given salt value
|
||||
hashValue, _, err := genISOPasswdHash(password[0], ws.SheetProtection.AlgorithmName, ws.SheetProtection.SaltValue, ws.SheetProtection.SpinCount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ws.SheetProtection.HashValue != hashValue {
|
||||
return ErrUnprotectSheetPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
ws.SheetProtection = nil
|
||||
return err
|
||||
}
|
||||
|
||||
// trimSheetName provides a function to trim invaild characters by given worksheet
|
||||
// trimSheetName provides a function to trim invalid characters by given worksheet
|
||||
// name.
|
||||
func trimSheetName(name string) string {
|
||||
if strings.ContainsAny(name, ":\\/?*[]") || utf8.RuneCountInString(name) > 31 {
|
||||
|
|
|
@ -838,6 +838,7 @@ type formatConditional struct {
|
|||
|
||||
// FormatSheetProtection directly maps the settings of worksheet protection.
|
||||
type FormatSheetProtection struct {
|
||||
AlgorithmName string
|
||||
AutoFilter bool
|
||||
DeleteColumns bool
|
||||
DeleteRows bool
|
||||
|
|
Loading…
Reference in New Issue