Skip to content

Commit 48c16de

Browse files
committed
Improve security and simplify code
- Make variable name more semantic - Reduce cyclomatic complexities for the formula calculate function - Support specified unzip size limit on open file options, avoid zip bombs vulnerability attack - Typo fix for documentation and error message
1 parent f6f14f5 commit 48c16de

21 files changed

+165
-120
lines changed

Diff for: calc.go

+35-24
Original file line numberDiff line numberDiff line change
@@ -6026,6 +6026,39 @@ func (fn *formulaFuncs) DATE(argsList *list.List) formulaArg {
60266026
return newStringFormulaArg(timeFromExcelTime(daysBetween(excelMinTime1900.Unix(), d)+1, false).String())
60276027
}
60286028

6029+
// calcDateDif is an implementation of the formula function DATEDIF,
6030+
// calculation difference between two dates.
6031+
func calcDateDif(unit string, diff float64, seq []int, startArg, endArg formulaArg) float64 {
6032+
ey, sy, em, sm, ed, sd := seq[0], seq[1], seq[2], seq[3], seq[4], seq[5]
6033+
switch unit {
6034+
case "d":
6035+
diff = endArg.Number - startArg.Number
6036+
case "md":
6037+
smMD := em
6038+
if ed < sd {
6039+
smMD--
6040+
}
6041+
diff = endArg.Number - daysBetween(excelMinTime1900.Unix(), makeDate(ey, time.Month(smMD), sd)) - 1
6042+
case "ym":
6043+
diff = float64(em - sm)
6044+
if ed < sd {
6045+
diff--
6046+
}
6047+
if diff < 0 {
6048+
diff += 12
6049+
}
6050+
case "yd":
6051+
syYD := sy
6052+
if em < sm || (em == sm && ed < sd) {
6053+
syYD++
6054+
}
6055+
s := daysBetween(excelMinTime1900.Unix(), makeDate(syYD, time.Month(em), ed))
6056+
e := daysBetween(excelMinTime1900.Unix(), makeDate(sy, time.Month(sm), sd))
6057+
diff = s - e
6058+
}
6059+
return diff
6060+
}
6061+
60296062
// DATEDIF function calculates the number of days, months, or years between
60306063
// two dates. The syntax of the function is:
60316064
//
@@ -6051,8 +6084,6 @@ func (fn *formulaFuncs) DATEDIF(argsList *list.List) formulaArg {
60516084
ey, emm, ed := endDate.Date()
60526085
sm, em, diff := int(smm), int(emm), 0.0
60536086
switch unit {
6054-
case "d":
6055-
return newNumberFormulaArg(endArg.Number - startArg.Number)
60566087
case "y":
60576088
diff = float64(ey - sy)
60586089
if em < sm || (em == sm && ed < sd) {
@@ -6069,28 +6100,8 @@ func (fn *formulaFuncs) DATEDIF(argsList *list.List) formulaArg {
60696100
mdiff += 12
60706101
}
60716102
diff = float64(ydiff*12 + mdiff)
6072-
case "md":
6073-
smMD := em
6074-
if ed < sd {
6075-
smMD--
6076-
}
6077-
diff = endArg.Number - daysBetween(excelMinTime1900.Unix(), makeDate(ey, time.Month(smMD), sd)) - 1
6078-
case "ym":
6079-
diff = float64(em - sm)
6080-
if ed < sd {
6081-
diff--
6082-
}
6083-
if diff < 0 {
6084-
diff += 12
6085-
}
6086-
case "yd":
6087-
syYD := sy
6088-
if em < sm || (em == sm && ed < sd) {
6089-
syYD++
6090-
}
6091-
s := daysBetween(excelMinTime1900.Unix(), makeDate(syYD, time.Month(em), ed))
6092-
e := daysBetween(excelMinTime1900.Unix(), makeDate(sy, time.Month(sm), sd))
6093-
diff = s - e
6103+
case "d", "md", "ym", "yd":
6104+
diff = calcDateDif(unit, diff, []int{ey, sy, em, sm, ed, sd}, startArg, endArg)
60946105
default:
60956106
return newErrorFormulaArg(formulaErrorVALUE, "DATEDIF has invalid unit")
60966107
}

Diff for: comment_test.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
// the LICENSE file.
44
//
55
// Package excelize providing a set of functions that allow you to write to
6-
// and read from XLSX files. Support reads and writes XLSX file generated by
7-
// Microsoft Excel™ 2007 and later. Support save file without losing original
8-
// charts of XLSX. This library needs Go version 1.15 or later.
6+
// and read from XLSX / XLSM / XLTM files. Supports reading and writing
7+
// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
8+
// complex components by high compatibility, and provided streaming API for
9+
// generating or reading data from a worksheet with huge amounts of data. This
10+
// library needs Go version 1.15 or later.
911

1012
package excelize
1113

Diff for: crypt.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
// the LICENSE file.
44
//
55
// Package excelize providing a set of functions that allow you to write to
6-
// and read from XLSX files. Support reads and writes XLSX file generated by
7-
// Microsoft Excel™ 2007 and later. Support save file without losing original
8-
// charts of XLSX. This library needs Go version 1.15 or later.
6+
// and read from XLSX / XLSM / XLTM files. Supports reading and writing
7+
// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
8+
// complex components by high compatibility, and provided streaming API for
9+
// generating or reading data from a worksheet with huge amounts of data. This
10+
// library needs Go version 1.15 or later.
911

1012
package excelize
1113

@@ -15,14 +17,14 @@ import (
1517
"crypto/cipher"
1618
"crypto/hmac"
1719
"crypto/md5"
20+
"crypto/rand"
1821
"crypto/sha1"
1922
"crypto/sha256"
2023
"crypto/sha512"
2124
"encoding/base64"
2225
"encoding/binary"
2326
"encoding/xml"
2427
"hash"
25-
"math/rand"
2628
"reflect"
2729
"strings"
2830

Diff for: crypt_test.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
// the LICENSE file.
44
//
55
// Package excelize providing a set of functions that allow you to write to
6-
// and read from XLSX files. Support reads and writes XLSX file generated by
7-
// Microsoft Excel™ 2007 and later. Support save file without losing original
8-
// charts of XLSX. This library needs Go version 1.15 or later.
6+
// and read from XLSX / XLSM / XLTM files. Supports reading and writing
7+
// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
8+
// complex components by high compatibility, and provided streaming API for
9+
// generating or reading data from a worksheet with huge amounts of data. This
10+
// library needs Go version 1.15 or later.
911

1012
package excelize
1113

Diff for: datavalidation_test.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
// the LICENSE file.
44
//
55
// Package excelize providing a set of functions that allow you to write to
6-
// and read from XLSX files. Support reads and writes XLSX file generated by
7-
// Microsoft Excel™ 2007 and later. Support save file without losing original
8-
// charts of XLSX. This library needs Go version 1.15 or later.
6+
// and read from XLSX / XLSM / XLTM files. Supports reading and writing
7+
// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
8+
// complex components by high compatibility, and provided streaming API for
9+
// generating or reading data from a worksheet with huge amounts of data. This
10+
// library needs Go version 1.15 or later.
911

1012
package excelize
1113

Diff for: date_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,5 @@ func TestExcelDateToTime(t *testing.T) {
8585
}
8686
// Check error case
8787
_, err := ExcelDateToTime(-1, false)
88-
assert.EqualError(t, err, "invalid date value -1.000000, negative values are not supported supported")
88+
assert.EqualError(t, err, "invalid date value -1.000000, negative values are not supported")
8989
}

Diff for: errors.go

+11-1
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,36 @@ import (
1616
"fmt"
1717
)
1818

19+
// newInvalidColumnNameError defined the error message on receiving the invalid column name.
1920
func newInvalidColumnNameError(col string) error {
2021
return fmt.Errorf("invalid column name %q", col)
2122
}
2223

24+
// newInvalidRowNumberError defined the error message on receiving the invalid row number.
2325
func newInvalidRowNumberError(row int) error {
2426
return fmt.Errorf("invalid row number %d", row)
2527
}
2628

29+
// newInvalidCellNameError defined the error message on receiving the invalid cell name.
2730
func newInvalidCellNameError(cell string) error {
2831
return fmt.Errorf("invalid cell name %q", cell)
2932
}
3033

34+
// newInvalidExcelDateError defined the error message on receiving the data with negative values.
3135
func newInvalidExcelDateError(dateValue float64) error {
32-
return fmt.Errorf("invalid date value %f, negative values are not supported supported", dateValue)
36+
return fmt.Errorf("invalid date value %f, negative values are not supported", dateValue)
3337
}
3438

39+
// newUnsupportChartType defined the error message on receiving the chart type are unsupported.
3540
func newUnsupportChartType(chartType string) error {
3641
return fmt.Errorf("unsupported chart type %s", chartType)
3742
}
3843

44+
// newUnzipSizeLimitError defined the error message on unzip size exceeds the limit.
45+
func newUnzipSizeLimitError(unzipSizeLimit int64) error {
46+
return fmt.Errorf("unzip size exceeds the %d bytes limit", unzipSizeLimit)
47+
}
48+
3949
var (
4050
// ErrStreamSetColWidth defined the error message on set column width in
4151
// stream writing mode.

Diff for: errors_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@ func TestNewInvalidCellNameError(t *testing.T) {
2121
}
2222

2323
func TestNewInvalidExcelDateError(t *testing.T) {
24-
assert.EqualError(t, newInvalidExcelDateError(-1), "invalid date value -1.000000, negative values are not supported supported")
24+
assert.EqualError(t, newInvalidExcelDateError(-1), "invalid date value -1.000000, negative values are not supported")
2525
}

Diff for: excelize.go

+27-17
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"io/ioutil"
2222
"os"
2323
"path"
24+
"path/filepath"
2425
"strconv"
2526
"strings"
2627
"sync"
@@ -59,21 +60,27 @@ type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, e
5960

6061
// Options define the options for open spreadsheet.
6162
type Options struct {
62-
Password string
63+
Password string
64+
UnzipSizeLimit int64
6365
}
6466

65-
// OpenFile take the name of an spreadsheet file and returns a populated spreadsheet file struct
66-
// for it. For example, open spreadsheet with password protection:
67+
// OpenFile take the name of an spreadsheet file and returns a populated
68+
// spreadsheet file struct for it. For example, open spreadsheet with
69+
// password protection:
6770
//
6871
// f, err := excelize.OpenFile("Book1.xlsx", excelize.Options{Password: "password"})
6972
// if err != nil {
7073
// return
7174
// }
7275
//
73-
// Note that the excelize just support decrypt and not support encrypt currently, the spreadsheet
74-
// saved by Save and SaveAs will be without password unprotected.
76+
// Note that the excelize just support decrypt and not support encrypt
77+
// currently, the spreadsheet saved by Save and SaveAs will be without
78+
// password unprotected.
79+
//
80+
// UnzipSizeLimit specified the unzip size limit in bytes on open the
81+
// spreadsheet, the default size limit is 16GB.
7582
func OpenFile(filename string, opt ...Options) (*File, error) {
76-
file, err := os.Open(filename)
83+
file, err := os.Open(filepath.Clean(filename))
7784
if err != nil {
7885
return nil, err
7986
}
@@ -89,6 +96,7 @@ func OpenFile(filename string, opt ...Options) (*File, error) {
8996
// newFile is object builder
9097
func newFile() *File {
9198
return &File{
99+
options: &Options{UnzipSizeLimit: UnzipSizeLimit},
92100
xmlAttr: make(map[string][]xml.Attr),
93101
checked: make(map[string]bool),
94102
sheetMap: make(map[string]string),
@@ -111,10 +119,13 @@ func OpenReader(r io.Reader, opt ...Options) (*File, error) {
111119
return nil, err
112120
}
113121
f := newFile()
114-
if bytes.Contains(b, oleIdentifier) && len(opt) > 0 {
115-
for _, o := range opt {
116-
f.options = &o
122+
for i := range opt {
123+
f.options = &opt[i]
124+
if f.options.UnzipSizeLimit == 0 {
125+
f.options.UnzipSizeLimit = UnzipSizeLimit
117126
}
127+
}
128+
if bytes.Contains(b, oleIdentifier) {
118129
b, err = Decrypt(b, f.options)
119130
if err != nil {
120131
return nil, fmt.Errorf("decrypted file failed")
@@ -124,8 +135,7 @@ func OpenReader(r io.Reader, opt ...Options) (*File, error) {
124135
if err != nil {
125136
return nil, err
126137
}
127-
128-
file, sheetCount, err := ReadZipReader(zr)
138+
file, sheetCount, err := ReadZipReader(zr, f.options)
129139
if err != nil {
130140
return nil, err
131141
}
@@ -316,18 +326,18 @@ func (f *File) UpdateLinkedValue() error {
316326
// recalculate formulas
317327
wb.CalcPr = nil
318328
for _, name := range f.GetSheetList() {
319-
xlsx, err := f.workSheetReader(name)
329+
ws, err := f.workSheetReader(name)
320330
if err != nil {
321331
if err.Error() == fmt.Sprintf("sheet %s is chart sheet", trimSheetName(name)) {
322332
continue
323333
}
324334
return err
325335
}
326-
for indexR := range xlsx.SheetData.Row {
327-
for indexC, col := range xlsx.SheetData.Row[indexR].C {
336+
for indexR := range ws.SheetData.Row {
337+
for indexC, col := range ws.SheetData.Row[indexR].C {
328338
if col.F != nil && col.V != "" {
329-
xlsx.SheetData.Row[indexR].C[indexC].V = ""
330-
xlsx.SheetData.Row[indexR].C[indexC].T = ""
339+
ws.SheetData.Row[indexR].C[indexC].V = ""
340+
ws.SheetData.Row[indexR].C[indexC].T = ""
331341
}
332342
}
333343
}
@@ -381,7 +391,7 @@ func (f *File) AddVBAProject(bin string) error {
381391
Type: SourceRelationshipVBAProject,
382392
})
383393
}
384-
file, _ := ioutil.ReadFile(bin)
394+
file, _ := ioutil.ReadFile(filepath.Clean(bin))
385395
f.Pkg.Store("xl/vbaProject.bin", file)
386396
return err
387397
}

Diff for: excelize_test.go

+8-7
Original file line numberDiff line numberDiff line change
@@ -184,13 +184,9 @@ func TestSaveFile(t *testing.T) {
184184

185185
func TestSaveAsWrongPath(t *testing.T) {
186186
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
187-
if assert.NoError(t, err) {
188-
// Test write file to not exist directory.
189-
err = f.SaveAs("")
190-
if assert.Error(t, err) {
191-
assert.True(t, os.IsNotExist(err), "Error: %v: Expected os.IsNotExists(err) == true", err)
192-
}
193-
}
187+
assert.NoError(t, err)
188+
// Test write file to not exist directory.
189+
assert.EqualError(t, f.SaveAs(""), "open .: is a directory")
194190
}
195191

196192
func TestCharsetTranscoder(t *testing.T) {
@@ -204,6 +200,10 @@ func TestOpenReader(t *testing.T) {
204200
_, err = OpenReader(bytes.NewReader(oleIdentifier), Options{Password: "password"})
205201
assert.EqualError(t, err, "decrypted file failed")
206202

203+
// Test open spreadsheet with unzip size limit.
204+
_, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipSizeLimit: 100})
205+
assert.EqualError(t, err, newUnzipSizeLimitError(100).Error())
206+
207207
// Test open password protected spreadsheet created by Microsoft Office Excel 2010.
208208
f, err := OpenFile(filepath.Join("test", "encryptSHA1.xlsx"), Options{Password: "password"})
209209
assert.NoError(t, err)
@@ -1226,6 +1226,7 @@ func TestWorkSheetReader(t *testing.T) {
12261226
f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
12271227
_, err := f.workSheetReader("Sheet1")
12281228
assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8")
1229+
assert.EqualError(t, f.UpdateLinkedValue(), "xml decode error: XML syntax error on line 1: invalid UTF-8")
12291230

12301231
// Test on no checked worksheet.
12311232
f = NewFile()

0 commit comments

Comments
 (0)