Skip to content

Commit

Permalink
spreadsheet: add support for protecting/unprotecting workbooks/sheets
Browse files Browse the repository at this point in the history
  • Loading branch information
tbaliance committed Sep 20, 2017
1 parent 981b0b2 commit a8d0385
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 3 deletions.
29 changes: 29 additions & 0 deletions spreadsheet/password.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 2017 Baliance. All rights reserved.
//
// Use of this source code is governed by the terms of the Affero GNU General
// Public License version 3.0 as published by the Free Software Foundation and
// appearing in the file LICENSE included in the packaging of this file. A
// commercial license can be purchased by contacting [email protected].

package spreadsheet

import (
"fmt"
)

// PasswordHash returns the password hash for a workbook using the modified
// spreadsheetML password hash that is compatible with Excel.
func PasswordHash(s string) string {
hash := uint16(0)
if len(s) > 0 {
for i := len(s) - 1; i >= 0; i-- {
c := s[i]
hash = ((hash >> 14) & 0x01) | ((hash << 1) & 0x7fff)
hash ^= uint16(c)
}
hash = ((hash >> 14) & 0x01) | ((hash << 1) & 0x7fff)
hash ^= uint16(len(s))
hash ^= (0x8000 | ('N' << 8) | 'K')
}
return fmt.Sprintf("%04X", uint64(hash))
}
29 changes: 29 additions & 0 deletions spreadsheet/password_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 2017 Baliance. All rights reserved.
//
// Use of this source code is governed by the terms of the Affero GNU General
// Public License version 3.0 as published by the Free Software Foundation and
// appearing in the file LICENSE included in the packaging of this file. A
// commercial license can be purchased by contacting [email protected].

package spreadsheet_test

import (
"testing"

"baliance.com/gooxml/spreadsheet"
)

func TestKnownHashes(t *testing.T) {
td := []struct {
Inp string
Exp string
}{
{"gooxml", "DD67"},
{"", "0000"},
}
for _, tc := range td {
if got := spreadsheet.PasswordHash(tc.Inp); got != tc.Exp {
t.Errorf("expected hash of %s = %s, got %s", tc.Inp, tc.Exp, got)
}
}
}
13 changes: 13 additions & 0 deletions spreadsheet/sheet.go
Original file line number Diff line number Diff line change
Expand Up @@ -634,3 +634,16 @@ func (s *Sheet) SetFrozen(firstRow, firstCol bool) {
func (s *Sheet) FormulaContext() formula.Context {
return newEvalContext(s)
}

// ClearProtection removes any protections applied to teh sheet.
func (s *Sheet) ClearProtection() {
s.x.SheetProtection = nil
}

// Protection controls the protection on an individual sheet.
func (s *Sheet) Protection() SheetProtection {
if s.x.SheetProtection == nil {
s.x.SheetProtection = sml.NewCT_SheetProtection()
}
return SheetProtection{s.x.SheetProtection}
}
68 changes: 68 additions & 0 deletions spreadsheet/sheetprotection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright 2017 Baliance. All rights reserved.
//
// Use of this source code is governed by the terms of the Affero GNU General
// Public License version 3.0 as published by the Free Software Foundation and
// appearing in the file LICENSE included in the packaging of this file. A
// commercial license can be purchased by contacting [email protected].

package spreadsheet

import (
"baliance.com/gooxml"
sml "baliance.com/gooxml/schema/schemas.openxmlformats.org/spreadsheetml"
)

type SheetProtection struct {
x *sml.CT_SheetProtection
}

// X returns the inner wrapped XML type.
func (p SheetProtection) X() *sml.CT_SheetProtection {
return p.x
}

// IsSheetLocked returns whether the sheet is locked.
func (p SheetProtection) IsSheetLocked() bool {
return p.x.SheetAttr != nil && *p.x.SheetAttr
}

// LockSheet controls the locking of the sheet.
func (p SheetProtection) LockSheet(b bool) {
if !b {
p.x.SheetAttr = nil
} else {
p.x.SheetAttr = gooxml.Bool(true)
}
}

// IsSheetLocked returns whether the sheet objects are locked.
func (p SheetProtection) IsObjectLocked() bool {
return p.x.ObjectsAttr != nil && *p.x.ObjectsAttr
}

// LockObject controls the locking of the sheet objects.
func (p SheetProtection) LockObject(b bool) {
if !b {
p.x.ObjectsAttr = nil
} else {
p.x.ObjectsAttr = gooxml.Bool(true)
}
}

// PasswordHash returns the hash of the workbook password.
func (p SheetProtection) PasswordHash() string {
if p.x.PasswordAttr == nil {
return ""
}
return *p.x.PasswordAttr
}

// SetPassword sets the password hash to a hash of the input password.
func (p SheetProtection) SetPassword(pw string) {
p.SetPasswordHash(PasswordHash(pw))
}

// SetPasswordHash sets the password hash to the input.
func (p SheetProtection) SetPasswordHash(pwHash string) {
p.x.PasswordAttr = gooxml.String(pwHash)
}
19 changes: 16 additions & 3 deletions spreadsheet/workbook.go
Original file line number Diff line number Diff line change
Expand Up @@ -525,13 +525,26 @@ func (wb *Workbook) SetActiveSheetIndex(idx uint32) {
}

// Tables returns a slice of all defined tables in the workbook.
func (w *Workbook) Tables() []Table {
if w.tables == nil {
func (wb *Workbook) Tables() []Table {
if wb.tables == nil {
return nil
}
ret := []Table{}
for _, t := range w.tables {
for _, t := range wb.tables {
ret = append(ret, Table{t})
}
return ret
}

// ClearProtection clears all workbook protections.
func (wb *Workbook) ClearProtection() {
wb.x.WorkbookProtection = nil
}

// Protection allows control over the workbook protections.
func (wb *Workbook) Protection() WorkbookProtection {
if wb.x.WorkbookProtection == nil {
wb.x.WorkbookProtection = sml.NewCT_WorkbookProtection()
}
return WorkbookProtection{wb.x.WorkbookProtection}
}
68 changes: 68 additions & 0 deletions spreadsheet/workbookprotection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright 2017 Baliance. All rights reserved.
//
// Use of this source code is governed by the terms of the Affero GNU General
// Public License version 3.0 as published by the Free Software Foundation and
// appearing in the file LICENSE included in the packaging of this file. A
// commercial license can be purchased by contacting [email protected].

package spreadsheet

import (
"baliance.com/gooxml"
sml "baliance.com/gooxml/schema/schemas.openxmlformats.org/spreadsheetml"
)

type WorkbookProtection struct {
x *sml.CT_WorkbookProtection
}

// X returns the inner wrapped XML type.
func (p WorkbookProtection) X() *sml.CT_WorkbookProtection {
return p.x
}

// IsStructureLocked returns whether the workbook structure is locked.
func (p WorkbookProtection) IsStructureLocked() bool {
return p.x.LockStructureAttr != nil && *p.x.LockStructureAttr
}

// LockStructure controls the locking of the workbook structure.
func (p WorkbookProtection) LockStructure(b bool) {
if !b {
p.x.LockStructureAttr = nil
} else {
p.x.LockStructureAttr = gooxml.Bool(true)
}
}

// IsWindowLocked returns whether the workbook windows are locked.
func (p WorkbookProtection) IsWindowLocked() bool {
return p.x.LockWindowsAttr != nil && *p.x.LockWindowsAttr
}

// LockWindow controls the locking of the workbook windows.
func (p WorkbookProtection) LockWindow(b bool) {
if !b {
p.x.LockWindowsAttr = nil
} else {
p.x.LockWindowsAttr = gooxml.Bool(true)
}
}

// PasswordHash returns the hash of the workbook password.
func (p WorkbookProtection) PasswordHash() string {
if p.x.WorkbookPasswordAttr == nil {
return ""
}
return *p.x.WorkbookPasswordAttr
}

// SetPassword sets the password hash to a hash of the input password.
func (p WorkbookProtection) SetPassword(pw string) {
p.SetPasswordHash(PasswordHash(pw))
}

// SetPasswordHash sets the password hash to the input.
func (p WorkbookProtection) SetPasswordHash(pwHash string) {
p.x.WorkbookPasswordAttr = gooxml.String(pwHash)
}

0 comments on commit a8d0385

Please sign in to comment.