已验证 提交 48c16de8 编写于 作者: xurime's avatar xurime

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
上级 f6f14f50
...@@ -6026,6 +6026,39 @@ func (fn *formulaFuncs) DATE(argsList *list.List) formulaArg { ...@@ -6026,6 +6026,39 @@ func (fn *formulaFuncs) DATE(argsList *list.List) formulaArg {
return newStringFormulaArg(timeFromExcelTime(daysBetween(excelMinTime1900.Unix(), d)+1, false).String()) return newStringFormulaArg(timeFromExcelTime(daysBetween(excelMinTime1900.Unix(), d)+1, false).String())
} }
// calcDateDif is an implementation of the formula function DATEDIF,
// calculation difference between two dates.
func calcDateDif(unit string, diff float64, seq []int, startArg, endArg formulaArg) float64 {
ey, sy, em, sm, ed, sd := seq[0], seq[1], seq[2], seq[3], seq[4], seq[5]
switch unit {
case "d":
diff = endArg.Number - startArg.Number
case "md":
smMD := em
if ed < sd {
smMD--
}
diff = endArg.Number - daysBetween(excelMinTime1900.Unix(), makeDate(ey, time.Month(smMD), sd)) - 1
case "ym":
diff = float64(em - sm)
if ed < sd {
diff--
}
if diff < 0 {
diff += 12
}
case "yd":
syYD := sy
if em < sm || (em == sm && ed < sd) {
syYD++
}
s := daysBetween(excelMinTime1900.Unix(), makeDate(syYD, time.Month(em), ed))
e := daysBetween(excelMinTime1900.Unix(), makeDate(sy, time.Month(sm), sd))
diff = s - e
}
return diff
}
// DATEDIF function calculates the number of days, months, or years between // DATEDIF function calculates the number of days, months, or years between
// two dates. The syntax of the function is: // two dates. The syntax of the function is:
// //
...@@ -6051,8 +6084,6 @@ func (fn *formulaFuncs) DATEDIF(argsList *list.List) formulaArg { ...@@ -6051,8 +6084,6 @@ func (fn *formulaFuncs) DATEDIF(argsList *list.List) formulaArg {
ey, emm, ed := endDate.Date() ey, emm, ed := endDate.Date()
sm, em, diff := int(smm), int(emm), 0.0 sm, em, diff := int(smm), int(emm), 0.0
switch unit { switch unit {
case "d":
return newNumberFormulaArg(endArg.Number - startArg.Number)
case "y": case "y":
diff = float64(ey - sy) diff = float64(ey - sy)
if em < sm || (em == sm && ed < sd) { if em < sm || (em == sm && ed < sd) {
...@@ -6069,28 +6100,8 @@ func (fn *formulaFuncs) DATEDIF(argsList *list.List) formulaArg { ...@@ -6069,28 +6100,8 @@ func (fn *formulaFuncs) DATEDIF(argsList *list.List) formulaArg {
mdiff += 12 mdiff += 12
} }
diff = float64(ydiff*12 + mdiff) diff = float64(ydiff*12 + mdiff)
case "md": case "d", "md", "ym", "yd":
smMD := em diff = calcDateDif(unit, diff, []int{ey, sy, em, sm, ed, sd}, startArg, endArg)
if ed < sd {
smMD--
}
diff = endArg.Number - daysBetween(excelMinTime1900.Unix(), makeDate(ey, time.Month(smMD), sd)) - 1
case "ym":
diff = float64(em - sm)
if ed < sd {
diff--
}
if diff < 0 {
diff += 12
}
case "yd":
syYD := sy
if em < sm || (em == sm && ed < sd) {
syYD++
}
s := daysBetween(excelMinTime1900.Unix(), makeDate(syYD, time.Month(em), ed))
e := daysBetween(excelMinTime1900.Unix(), makeDate(sy, time.Month(sm), sd))
diff = s - e
default: default:
return newErrorFormulaArg(formulaErrorVALUE, "DATEDIF has invalid unit") return newErrorFormulaArg(formulaErrorVALUE, "DATEDIF has invalid unit")
} }
......
...@@ -3,9 +3,11 @@ ...@@ -3,9 +3,11 @@
// the LICENSE file. // the LICENSE file.
// //
// Package excelize providing a set of functions that allow you to write to // Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by // and read from XLSX / XLSM / XLTM files. Supports reading and writing
// Microsoft Excel™ 2007 and later. Support save file without losing original // spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
// charts of XLSX. This library needs Go version 1.15 or later. // complex components by high compatibility, and provided streaming API for
// generating or reading data from a worksheet with huge amounts of data. This
// library needs Go version 1.15 or later.
package excelize package excelize
......
...@@ -3,9 +3,11 @@ ...@@ -3,9 +3,11 @@
// the LICENSE file. // the LICENSE file.
// //
// Package excelize providing a set of functions that allow you to write to // Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by // and read from XLSX / XLSM / XLTM files. Supports reading and writing
// Microsoft Excel™ 2007 and later. Support save file without losing original // spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
// charts of XLSX. This library needs Go version 1.15 or later. // complex components by high compatibility, and provided streaming API for
// generating or reading data from a worksheet with huge amounts of data. This
// library needs Go version 1.15 or later.
package excelize package excelize
...@@ -15,6 +17,7 @@ import ( ...@@ -15,6 +17,7 @@ import (
"crypto/cipher" "crypto/cipher"
"crypto/hmac" "crypto/hmac"
"crypto/md5" "crypto/md5"
"crypto/rand"
"crypto/sha1" "crypto/sha1"
"crypto/sha256" "crypto/sha256"
"crypto/sha512" "crypto/sha512"
...@@ -22,7 +25,6 @@ import ( ...@@ -22,7 +25,6 @@ import (
"encoding/binary" "encoding/binary"
"encoding/xml" "encoding/xml"
"hash" "hash"
"math/rand"
"reflect" "reflect"
"strings" "strings"
......
...@@ -3,9 +3,11 @@ ...@@ -3,9 +3,11 @@
// the LICENSE file. // the LICENSE file.
// //
// Package excelize providing a set of functions that allow you to write to // Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by // and read from XLSX / XLSM / XLTM files. Supports reading and writing
// Microsoft Excel™ 2007 and later. Support save file without losing original // spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
// charts of XLSX. This library needs Go version 1.15 or later. // complex components by high compatibility, and provided streaming API for
// generating or reading data from a worksheet with huge amounts of data. This
// library needs Go version 1.15 or later.
package excelize package excelize
......
...@@ -3,9 +3,11 @@ ...@@ -3,9 +3,11 @@
// the LICENSE file. // the LICENSE file.
// //
// Package excelize providing a set of functions that allow you to write to // Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by // and read from XLSX / XLSM / XLTM files. Supports reading and writing
// Microsoft Excel™ 2007 and later. Support save file without losing original // spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
// charts of XLSX. This library needs Go version 1.15 or later. // complex components by high compatibility, and provided streaming API for
// generating or reading data from a worksheet with huge amounts of data. This
// library needs Go version 1.15 or later.
package excelize package excelize
......
...@@ -85,5 +85,5 @@ func TestExcelDateToTime(t *testing.T) { ...@@ -85,5 +85,5 @@ func TestExcelDateToTime(t *testing.T) {
} }
// Check error case // Check error case
_, err := ExcelDateToTime(-1, false) _, err := ExcelDateToTime(-1, false)
assert.EqualError(t, err, "invalid date value -1.000000, negative values are not supported supported") assert.EqualError(t, err, "invalid date value -1.000000, negative values are not supported")
} }
...@@ -16,26 +16,36 @@ import ( ...@@ -16,26 +16,36 @@ import (
"fmt" "fmt"
) )
// newInvalidColumnNameError defined the error message on receiving the invalid column name.
func newInvalidColumnNameError(col string) error { func newInvalidColumnNameError(col string) error {
return fmt.Errorf("invalid column name %q", col) return fmt.Errorf("invalid column name %q", col)
} }
// newInvalidRowNumberError defined the error message on receiving the invalid row number.
func newInvalidRowNumberError(row int) error { func newInvalidRowNumberError(row int) error {
return fmt.Errorf("invalid row number %d", row) return fmt.Errorf("invalid row number %d", row)
} }
// newInvalidCellNameError defined the error message on receiving the invalid cell name.
func newInvalidCellNameError(cell string) error { func newInvalidCellNameError(cell string) error {
return fmt.Errorf("invalid cell name %q", cell) return fmt.Errorf("invalid cell name %q", cell)
} }
// newInvalidExcelDateError defined the error message on receiving the data with negative values.
func newInvalidExcelDateError(dateValue float64) error { func newInvalidExcelDateError(dateValue float64) error {
return fmt.Errorf("invalid date value %f, negative values are not supported supported", dateValue) 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 { func newUnsupportChartType(chartType string) error {
return fmt.Errorf("unsupported chart type %s", chartType) return fmt.Errorf("unsupported chart type %s", chartType)
} }
// 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)
}
var ( var (
// ErrStreamSetColWidth defined the error message on set column width in // ErrStreamSetColWidth defined the error message on set column width in
// stream writing mode. // stream writing mode.
......
...@@ -21,5 +21,5 @@ func TestNewInvalidCellNameError(t *testing.T) { ...@@ -21,5 +21,5 @@ func TestNewInvalidCellNameError(t *testing.T) {
} }
func TestNewInvalidExcelDateError(t *testing.T) { func TestNewInvalidExcelDateError(t *testing.T) {
assert.EqualError(t, newInvalidExcelDateError(-1), "invalid date value -1.000000, negative values are not supported supported") assert.EqualError(t, newInvalidExcelDateError(-1), "invalid date value -1.000000, negative values are not supported")
} }
...@@ -21,6 +21,7 @@ import ( ...@@ -21,6 +21,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path" "path"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
...@@ -59,21 +60,27 @@ type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, e ...@@ -59,21 +60,27 @@ type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, e
// Options define the options for open spreadsheet. // Options define the options for open spreadsheet.
type Options struct { type Options struct {
Password string Password string
UnzipSizeLimit int64
} }
// OpenFile take the name of an spreadsheet file and returns a populated spreadsheet file struct // OpenFile take the name of an spreadsheet file and returns a populated
// for it. For example, open spreadsheet with password protection: // spreadsheet file struct for it. For example, open spreadsheet with
// password protection:
// //
// f, err := excelize.OpenFile("Book1.xlsx", excelize.Options{Password: "password"}) // f, err := excelize.OpenFile("Book1.xlsx", excelize.Options{Password: "password"})
// if err != nil { // if err != nil {
// return // return
// } // }
// //
// Note that the excelize just support decrypt and not support encrypt currently, the spreadsheet // Note that the excelize just support decrypt and not support encrypt
// saved by Save and SaveAs will be without password unprotected. // currently, the spreadsheet saved by Save and SaveAs will be without
// password unprotected.
//
// UnzipSizeLimit specified the unzip size limit in bytes on open the
// spreadsheet, the default size limit is 16GB.
func OpenFile(filename string, opt ...Options) (*File, error) { func OpenFile(filename string, opt ...Options) (*File, error) {
file, err := os.Open(filename) file, err := os.Open(filepath.Clean(filename))
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -89,6 +96,7 @@ func OpenFile(filename string, opt ...Options) (*File, error) { ...@@ -89,6 +96,7 @@ func OpenFile(filename string, opt ...Options) (*File, error) {
// newFile is object builder // newFile is object builder
func newFile() *File { func newFile() *File {
return &File{ return &File{
options: &Options{UnzipSizeLimit: UnzipSizeLimit},
xmlAttr: make(map[string][]xml.Attr), xmlAttr: make(map[string][]xml.Attr),
checked: make(map[string]bool), checked: make(map[string]bool),
sheetMap: make(map[string]string), sheetMap: make(map[string]string),
...@@ -111,10 +119,13 @@ func OpenReader(r io.Reader, opt ...Options) (*File, error) { ...@@ -111,10 +119,13 @@ func OpenReader(r io.Reader, opt ...Options) (*File, error) {
return nil, err return nil, err
} }
f := newFile() f := newFile()
if bytes.Contains(b, oleIdentifier) && len(opt) > 0 { for i := range opt {
for _, o := range opt { f.options = &opt[i]
f.options = &o if f.options.UnzipSizeLimit == 0 {
f.options.UnzipSizeLimit = UnzipSizeLimit
} }
}
if bytes.Contains(b, oleIdentifier) {
b, err = Decrypt(b, f.options) b, err = Decrypt(b, f.options)
if err != nil { if err != nil {
return nil, fmt.Errorf("decrypted file failed") return nil, fmt.Errorf("decrypted file failed")
...@@ -124,8 +135,7 @@ func OpenReader(r io.Reader, opt ...Options) (*File, error) { ...@@ -124,8 +135,7 @@ func OpenReader(r io.Reader, opt ...Options) (*File, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
file, sheetCount, err := ReadZipReader(zr, f.options)
file, sheetCount, err := ReadZipReader(zr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -316,18 +326,18 @@ func (f *File) UpdateLinkedValue() error { ...@@ -316,18 +326,18 @@ func (f *File) UpdateLinkedValue() error {
// recalculate formulas // recalculate formulas
wb.CalcPr = nil wb.CalcPr = nil
for _, name := range f.GetSheetList() { for _, name := range f.GetSheetList() {
xlsx, err := f.workSheetReader(name) ws, err := f.workSheetReader(name)
if err != nil { if err != nil {
if err.Error() == fmt.Sprintf("sheet %s is chart sheet", trimSheetName(name)) { if err.Error() == fmt.Sprintf("sheet %s is chart sheet", trimSheetName(name)) {
continue continue
} }
return err return err
} }
for indexR := range xlsx.SheetData.Row { for indexR := range ws.SheetData.Row {
for indexC, col := range xlsx.SheetData.Row[indexR].C { for indexC, col := range ws.SheetData.Row[indexR].C {
if col.F != nil && col.V != "" { if col.F != nil && col.V != "" {
xlsx.SheetData.Row[indexR].C[indexC].V = "" ws.SheetData.Row[indexR].C[indexC].V = ""
xlsx.SheetData.Row[indexR].C[indexC].T = "" ws.SheetData.Row[indexR].C[indexC].T = ""
} }
} }
} }
...@@ -381,7 +391,7 @@ func (f *File) AddVBAProject(bin string) error { ...@@ -381,7 +391,7 @@ func (f *File) AddVBAProject(bin string) error {
Type: SourceRelationshipVBAProject, Type: SourceRelationshipVBAProject,
}) })
} }
file, _ := ioutil.ReadFile(bin) file, _ := ioutil.ReadFile(filepath.Clean(bin))
f.Pkg.Store("xl/vbaProject.bin", file) f.Pkg.Store("xl/vbaProject.bin", file)
return err return err
} }
......
...@@ -184,13 +184,9 @@ func TestSaveFile(t *testing.T) { ...@@ -184,13 +184,9 @@ func TestSaveFile(t *testing.T) {
func TestSaveAsWrongPath(t *testing.T) { func TestSaveAsWrongPath(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if assert.NoError(t, err) { assert.NoError(t, err)
// Test write file to not exist directory. // Test write file to not exist directory.
err = f.SaveAs("") assert.EqualError(t, f.SaveAs(""), "open .: is a directory")
if assert.Error(t, err) {
assert.True(t, os.IsNotExist(err), "Error: %v: Expected os.IsNotExists(err) == true", err)
}
}
} }
func TestCharsetTranscoder(t *testing.T) { func TestCharsetTranscoder(t *testing.T) {
...@@ -204,6 +200,10 @@ func TestOpenReader(t *testing.T) { ...@@ -204,6 +200,10 @@ func TestOpenReader(t *testing.T) {
_, err = OpenReader(bytes.NewReader(oleIdentifier), Options{Password: "password"}) _, err = OpenReader(bytes.NewReader(oleIdentifier), Options{Password: "password"})
assert.EqualError(t, err, "decrypted file failed") assert.EqualError(t, err, "decrypted file failed")
// Test open spreadsheet with unzip size limit.
_, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipSizeLimit: 100})
assert.EqualError(t, err, newUnzipSizeLimitError(100).Error())
// Test open password protected spreadsheet created by Microsoft Office Excel 2010. // Test open password protected spreadsheet created by Microsoft Office Excel 2010.
f, err := OpenFile(filepath.Join("test", "encryptSHA1.xlsx"), Options{Password: "password"}) f, err := OpenFile(filepath.Join("test", "encryptSHA1.xlsx"), Options{Password: "password"})
assert.NoError(t, err) assert.NoError(t, err)
...@@ -1226,6 +1226,7 @@ func TestWorkSheetReader(t *testing.T) { ...@@ -1226,6 +1226,7 @@ func TestWorkSheetReader(t *testing.T) {
f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset) f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
_, err := f.workSheetReader("Sheet1") _, err := f.workSheetReader("Sheet1")
assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8") assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8")
assert.EqualError(t, f.UpdateLinkedValue(), "xml decode error: XML syntax error on line 1: invalid UTF-8")
// Test on no checked worksheet. // Test on no checked worksheet.
f = NewFile() f = NewFile()
......
...@@ -17,6 +17,7 @@ import ( ...@@ -17,6 +17,7 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"path/filepath"
"sync" "sync"
) )
...@@ -69,14 +70,14 @@ func (f *File) SaveAs(name string, opt ...Options) error { ...@@ -69,14 +70,14 @@ func (f *File) SaveAs(name string, opt ...Options) error {
return ErrMaxFileNameLength return ErrMaxFileNameLength
} }
f.Path = name f.Path = name
file, err := os.OpenFile(name, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666) file, err := os.OpenFile(filepath.Clean(name), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600)
if err != nil { if err != nil {
return err return err
} }
defer file.Close() defer file.Close()
f.options = nil f.options = nil
for _, o := range opt { for i := range opt {
f.options = &o f.options = &opt[i]
} }
return f.Write(file) return f.Write(file)
} }
...@@ -102,7 +103,8 @@ func (f *File) WriteTo(w io.Writer) (int64, error) { ...@@ -102,7 +103,8 @@ func (f *File) WriteTo(w io.Writer) (int64, error) {
return 0, nil return 0, nil
} }
// WriteToBuffer provides a function to get bytes.Buffer from the saved file. And it allocate space in memory. Be careful when the file size is large. // WriteToBuffer provides a function to get bytes.Buffer from the saved file,
// and it allocates space in memory. Be careful when the file size is large.
func (f *File) WriteToBuffer() (*bytes.Buffer, error) { func (f *File) WriteToBuffer() (*bytes.Buffer, error) {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
zw := zip.NewWriter(buf) zw := zip.NewWriter(buf)
...@@ -130,7 +132,7 @@ func (f *File) WriteToBuffer() (*bytes.Buffer, error) { ...@@ -130,7 +132,7 @@ func (f *File) WriteToBuffer() (*bytes.Buffer, error) {
func (f *File) writeDirectToWriter(w io.Writer) error { func (f *File) writeDirectToWriter(w io.Writer) error {
zw := zip.NewWriter(w) zw := zip.NewWriter(w)
if err := f.writeToZip(zw); err != nil { if err := f.writeToZip(zw); err != nil {
zw.Close() _ = zw.Close()
return err return err
} }
return zw.Close() return zw.Close()
...@@ -157,14 +159,14 @@ func (f *File) writeToZip(zw *zip.Writer) error { ...@@ -157,14 +159,14 @@ func (f *File) writeToZip(zw *zip.Writer) error {
var from io.Reader var from io.Reader
from, err = stream.rawData.Reader() from, err = stream.rawData.Reader()
if err != nil { if err != nil {
stream.rawData.Close() _ = stream.rawData.Close()
return err return err
} }
_, err = io.Copy(fi, from) _, err = io.Copy(fi, from)
if err != nil { if err != nil {
return err return err
} }
stream.rawData.Close() _ = stream.rawData.Close()
} }
var err error var err error
f.Pkg.Range(func(path, content interface{}) bool { f.Pkg.Range(func(path, content interface{}) bool {
......
...@@ -37,9 +37,7 @@ func BenchmarkWrite(b *testing.B) { ...@@ -37,9 +37,7 @@ func BenchmarkWrite(b *testing.B) {
func TestWriteTo(t *testing.T) { func TestWriteTo(t *testing.T) {
// Test WriteToBuffer err // Test WriteToBuffer err
{ {
f := File{} f, buf := File{Pkg: sync.Map{}}, bytes.Buffer{}
buf := bytes.Buffer{}
f.Pkg = sync.Map{}
f.Pkg.Store("/d/", []byte("s")) f.Pkg.Store("/d/", []byte("s"))
_, err := f.WriteTo(bufio.NewWriter(&buf)) _, err := f.WriteTo(bufio.NewWriter(&buf))
assert.EqualError(t, err, "zip: write to directory") assert.EqualError(t, err, "zip: write to directory")
...@@ -47,9 +45,7 @@ func TestWriteTo(t *testing.T) { ...@@ -47,9 +45,7 @@ func TestWriteTo(t *testing.T) {
} }
// Test file path overflow // Test file path overflow
{ {
f := File{} f, buf := File{Pkg: sync.Map{}}, bytes.Buffer{}
buf := bytes.Buffer{}
f.Pkg = sync.Map{}
const maxUint16 = 1<<16 - 1 const maxUint16 = 1<<16 - 1
f.Pkg.Store(strings.Repeat("s", maxUint16+1), nil) f.Pkg.Store(strings.Repeat("s", maxUint16+1), nil)
_, err := f.WriteTo(bufio.NewWriter(&buf)) _, err := f.WriteTo(bufio.NewWriter(&buf))
...@@ -57,9 +53,7 @@ func TestWriteTo(t *testing.T) { ...@@ -57,9 +53,7 @@ func TestWriteTo(t *testing.T) {
} }
// Test StreamsWriter err // Test StreamsWriter err
{ {
f := File{} f, buf := File{Pkg: sync.Map{}}, bytes.Buffer{}
buf := bytes.Buffer{}
f.Pkg = sync.Map{}
f.Pkg.Store("s", nil) f.Pkg.Store("s", nil)
f.streams = make(map[string]*StreamWriter) f.streams = make(map[string]*StreamWriter)
file, _ := os.Open("123") file, _ := os.Open("123")
......
...@@ -26,15 +26,22 @@ import ( ...@@ -26,15 +26,22 @@ import (
// ReadZipReader can be used to read the spreadsheet in memory without touching the // ReadZipReader can be used to read the spreadsheet in memory without touching the
// filesystem. // filesystem.
func ReadZipReader(r *zip.Reader) (map[string][]byte, int, error) { func ReadZipReader(r *zip.Reader, o *Options) (map[string][]byte, int, error) {
var err error var (
var docPart = map[string]string{ err error
"[content_types].xml": "[Content_Types].xml", docPart = map[string]string{
"xl/sharedstrings.xml": "xl/sharedStrings.xml", "[content_types].xml": "[Content_Types].xml",
} "xl/sharedstrings.xml": "xl/sharedStrings.xml",
fileList := make(map[string][]byte, len(r.File)) }
worksheets := 0 fileList = make(map[string][]byte, len(r.File))
worksheets int
unzipSize int64
)
for _, v := range r.File { for _, v := range r.File {
unzipSize += v.FileInfo().Size()
if unzipSize > o.UnzipSizeLimit {
return fileList, worksheets, newUnzipSizeLimitError(o.UnzipSizeLimit)
}
fileName := strings.Replace(v.Name, "\\", "/", -1) fileName := strings.Replace(v.Name, "\\", "/", -1)
if partName, ok := docPart[strings.ToLower(fileName)]; ok { if partName, ok := docPart[strings.ToLower(fileName)]; ok {
fileName = partName fileName = partName
...@@ -61,7 +68,7 @@ func (f *File) readXML(name string) []byte { ...@@ -61,7 +68,7 @@ func (f *File) readXML(name string) []byte {
} }
// saveFileList provides a function to update given file content in file list // saveFileList provides a function to update given file content in file list
// of XLSX. // of spreadsheet.
func (f *File) saveFileList(name string, content []byte) { func (f *File) saveFileList(name string, content []byte) {
f.Pkg.Store(name, append([]byte(XMLHeader), content...)) f.Pkg.Store(name, append([]byte(XMLHeader), content...))
} }
...@@ -75,8 +82,7 @@ func readFile(file *zip.File) ([]byte, error) { ...@@ -75,8 +82,7 @@ func readFile(file *zip.File) ([]byte, error) {
dat := make([]byte, 0, file.FileInfo().Size()) dat := make([]byte, 0, file.FileInfo().Size())
buff := bytes.NewBuffer(dat) buff := bytes.NewBuffer(dat)
_, _ = io.Copy(buff, rc) _, _ = io.Copy(buff, rc)
rc.Close() return buff.Bytes(), rc.Close()
return buff.Bytes(), nil
} }
// SplitCellName splits cell name to column name and row number. // SplitCellName splits cell name to column name and row number.
......
...@@ -148,16 +148,16 @@ func TestUnmergeCell(t *testing.T) { ...@@ -148,16 +148,16 @@ func TestUnmergeCell(t *testing.T) {
} }
sheet1 := f.GetSheetName(0) sheet1 := f.GetSheetName(0)
xlsx, err := f.workSheetReader(sheet1) sheet, err := f.workSheetReader(sheet1)
assert.NoError(t, err) assert.NoError(t, err)
mergeCellNum := len(xlsx.MergeCells.Cells) mergeCellNum := len(sheet.MergeCells.Cells)
assert.EqualError(t, f.UnmergeCell("Sheet1", "A", "A"), `cannot convert cell "A" to coordinates: invalid cell name "A"`) assert.EqualError(t, f.UnmergeCell("Sheet1", "A", "A"), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
// unmerge the mergecell that contains A1 // unmerge the mergecell that contains A1
assert.NoError(t, f.UnmergeCell(sheet1, "A1", "A1")) assert.NoError(t, f.UnmergeCell(sheet1, "A1", "A1"))
if len(xlsx.MergeCells.Cells) != mergeCellNum-1 { if len(sheet.MergeCells.Cells) != mergeCellNum-1 {
t.FailNow() t.FailNow()
} }
......
...@@ -94,7 +94,7 @@ func (f *File) AddPicture(sheet, cell, picture, format string) error { ...@@ -94,7 +94,7 @@ func (f *File) AddPicture(sheet, cell, picture, format string) error {
if !ok { if !ok {
return ErrImgExt return ErrImgExt
} }
file, _ := ioutil.ReadFile(picture) file, _ := ioutil.ReadFile(filepath.Clean(picture))
_, name := filepath.Split(picture) _, name := filepath.Split(picture)
return f.AddPictureFromBytes(sheet, cell, format, name, ext, file) return f.AddPictureFromBytes(sheet, cell, format, name, ext, file)
} }
...@@ -199,8 +199,8 @@ func (f *File) deleteSheetRelationships(sheet, rID string) { ...@@ -199,8 +199,8 @@ func (f *File) deleteSheetRelationships(sheet, rID string) {
// addSheetLegacyDrawing provides a function to add legacy drawing element to // addSheetLegacyDrawing provides a function to add legacy drawing element to
// xl/worksheets/sheet%d.xml by given worksheet name and relationship index. // xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
func (f *File) addSheetLegacyDrawing(sheet string, rID int) { func (f *File) addSheetLegacyDrawing(sheet string, rID int) {
xlsx, _ := f.workSheetReader(sheet) ws, _ := f.workSheetReader(sheet)
xlsx.LegacyDrawing = &xlsxLegacyDrawing{ ws.LegacyDrawing = &xlsxLegacyDrawing{
RID: "rId" + strconv.Itoa(rID), RID: "rId" + strconv.Itoa(rID),
} }
} }
...@@ -208,8 +208,8 @@ func (f *File) addSheetLegacyDrawing(sheet string, rID int) { ...@@ -208,8 +208,8 @@ func (f *File) addSheetLegacyDrawing(sheet string, rID int) {
// addSheetDrawing provides a function to add drawing element to // addSheetDrawing provides a function to add drawing element to
// xl/worksheets/sheet%d.xml by given worksheet name and relationship index. // xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
func (f *File) addSheetDrawing(sheet string, rID int) { func (f *File) addSheetDrawing(sheet string, rID int) {
xlsx, _ := f.workSheetReader(sheet) ws, _ := f.workSheetReader(sheet)
xlsx.Drawing = &xlsxDrawing{ ws.Drawing = &xlsxDrawing{
RID: "rId" + strconv.Itoa(rID), RID: "rId" + strconv.Itoa(rID),
} }
} }
...@@ -217,8 +217,8 @@ func (f *File) addSheetDrawing(sheet string, rID int) { ...@@ -217,8 +217,8 @@ func (f *File) addSheetDrawing(sheet string, rID int) {
// addSheetPicture provides a function to add picture element to // addSheetPicture provides a function to add picture element to
// xl/worksheets/sheet%d.xml by given worksheet name and relationship index. // xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
func (f *File) addSheetPicture(sheet string, rID int) { func (f *File) addSheetPicture(sheet string, rID int) {
xlsx, _ := f.workSheetReader(sheet) ws, _ := f.workSheetReader(sheet)
xlsx.Picture = &xlsxPicture{ ws.Picture = &xlsxPicture{
RID: "rId" + strconv.Itoa(rID), RID: "rId" + strconv.Itoa(rID),
} }
} }
......
...@@ -71,24 +71,24 @@ func TestAddPicture(t *testing.T) { ...@@ -71,24 +71,24 @@ func TestAddPicture(t *testing.T) {
} }
func TestAddPictureErrors(t *testing.T) { func TestAddPictureErrors(t *testing.T) {
xlsx, err := OpenFile(filepath.Join("test", "Book1.xlsx")) f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err) assert.NoError(t, err)
// Test add picture to worksheet with invalid file path. // Test add picture to worksheet with invalid file path.
err = xlsx.AddPicture("Sheet1", "G21", filepath.Join("test", "not_exists_dir", "not_exists.icon"), "") err = f.AddPicture("Sheet1", "G21", filepath.Join("test", "not_exists_dir", "not_exists.icon"), "")
if assert.Error(t, err) { if assert.Error(t, err) {
assert.True(t, os.IsNotExist(err), "Expected os.IsNotExist(err) == true") assert.True(t, os.IsNotExist(err), "Expected os.IsNotExist(err) == true")
} }
// Test add picture to worksheet with unsupported file type. // Test add picture to worksheet with unsupported file type.
err = xlsx.AddPicture("Sheet1", "G21", filepath.Join("test", "Book1.xlsx"), "") err = f.AddPicture("Sheet1", "G21", filepath.Join("test", "Book1.xlsx"), "")
assert.EqualError(t, err, ErrImgExt.Error()) assert.EqualError(t, err, ErrImgExt.Error())
err = xlsx.AddPictureFromBytes("Sheet1", "G21", "", "Excel Logo", "jpg", make([]byte, 1)) err = f.AddPictureFromBytes("Sheet1", "G21", "", "Excel Logo", "jpg", make([]byte, 1))
assert.EqualError(t, err, ErrImgExt.Error()) assert.EqualError(t, err, ErrImgExt.Error())
// Test add picture to worksheet with invalid file data. // Test add picture to worksheet with invalid file data.
err = xlsx.AddPictureFromBytes("Sheet1", "G21", "", "Excel Logo", ".jpg", make([]byte, 1)) err = f.AddPictureFromBytes("Sheet1", "G21", "", "Excel Logo", ".jpg", make([]byte, 1))
assert.EqualError(t, err, "image: unknown format") assert.EqualError(t, err, "image: unknown format")
} }
......
...@@ -480,7 +480,7 @@ func (f *File) SetSheetBackground(sheet, picture string) error { ...@@ -480,7 +480,7 @@ func (f *File) SetSheetBackground(sheet, picture string) error {
if !ok { if !ok {
return ErrImgExt return ErrImgExt
} }
file, _ := ioutil.ReadFile(picture) file, _ := ioutil.ReadFile(filepath.Clean(picture))
name := f.addMedia(file, ext) name := f.addMedia(file, ext)
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels" sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels"
rID := f.addRels(sheetRels, SourceRelationshipImage, strings.Replace(name, "xl", "..", 1), "") rID := f.addRels(sheetRels, SourceRelationshipImage, strings.Replace(name, "xl", "..", 1), "")
...@@ -655,13 +655,13 @@ func (f *File) SetSheetVisible(name string, visible bool) error { ...@@ -655,13 +655,13 @@ func (f *File) SetSheetVisible(name string, visible bool) error {
} }
} }
for k, v := range content.Sheets.Sheet { for k, v := range content.Sheets.Sheet {
xlsx, err := f.workSheetReader(v.Name) ws, err := f.workSheetReader(v.Name)
if err != nil { if err != nil {
return err return err
} }
tabSelected := false tabSelected := false
if len(xlsx.SheetViews.SheetView) > 0 { if len(ws.SheetViews.SheetView) > 0 {
tabSelected = xlsx.SheetViews.SheetView[0].TabSelected tabSelected = ws.SheetViews.SheetView[0].TabSelected
} }
if v.Name == name && count > 1 && !tabSelected { if v.Name == name && count > 1 && !tabSelected {
content.Sheets.Sheet[k].State = "hidden" content.Sheets.Sheet[k].State = "hidden"
......
...@@ -182,14 +182,14 @@ func (o *AutoPageBreaks) getSheetPrOption(pr *xlsxSheetPr) { ...@@ -182,14 +182,14 @@ func (o *AutoPageBreaks) getSheetPrOption(pr *xlsxSheetPr) {
// AutoPageBreaks(bool) // AutoPageBreaks(bool)
// OutlineSummaryBelow(bool) // OutlineSummaryBelow(bool)
func (f *File) SetSheetPrOptions(name string, opts ...SheetPrOption) error { func (f *File) SetSheetPrOptions(name string, opts ...SheetPrOption) error {
sheet, err := f.workSheetReader(name) ws, err := f.workSheetReader(name)
if err != nil { if err != nil {
return err return err
} }
pr := sheet.SheetPr pr := ws.SheetPr
if pr == nil { if pr == nil {
pr = new(xlsxSheetPr) pr = new(xlsxSheetPr)
sheet.SheetPr = pr ws.SheetPr = pr
} }
for _, opt := range opts { for _, opt := range opts {
...@@ -208,11 +208,11 @@ func (f *File) SetSheetPrOptions(name string, opts ...SheetPrOption) error { ...@@ -208,11 +208,11 @@ func (f *File) SetSheetPrOptions(name string, opts ...SheetPrOption) error {
// AutoPageBreaks(bool) // AutoPageBreaks(bool)
// OutlineSummaryBelow(bool) // OutlineSummaryBelow(bool)
func (f *File) GetSheetPrOptions(name string, opts ...SheetPrOptionPtr) error { func (f *File) GetSheetPrOptions(name string, opts ...SheetPrOptionPtr) error {
sheet, err := f.workSheetReader(name) ws, err := f.workSheetReader(name)
if err != nil { if err != nil {
return err return err
} }
pr := sheet.SheetPr pr := ws.SheetPr
for _, opt := range opts { for _, opt := range opts {
opt.getSheetPrOption(pr) opt.getSheetPrOption(pr)
......
...@@ -346,8 +346,8 @@ func (sw *StreamWriter) SetRow(axis string, values []interface{}, opts ...RowOpt ...@@ -346,8 +346,8 @@ func (sw *StreamWriter) SetRow(axis string, values []interface{}, opts ...RowOpt
// marshalRowAttrs prepare attributes of the row by given options. // marshalRowAttrs prepare attributes of the row by given options.
func marshalRowAttrs(opts ...RowOpts) (attrs string, err error) { func marshalRowAttrs(opts ...RowOpts) (attrs string, err error) {
var opt *RowOpts var opt *RowOpts
for _, o := range opts { for i := range opts {
opt = &o opt = &opts[i]
} }
if opt == nil { if opt == nil {
return return
......
...@@ -5,9 +5,11 @@ ...@@ -5,9 +5,11 @@
// struct code generated by github.com/xuri/xgen // struct code generated by github.com/xuri/xgen
// //
// Package excelize providing a set of functions that allow you to write to // Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by // and read from XLSX / XLSM / XLTM files. Supports reading and writing
// Microsoft Excel™ 2007 and later. Support save file without losing original // spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
// charts of XLSX. This library needs Go version 1.15 or later. // complex components by high compatibility, and provided streaming API for
// generating or reading data from a worksheet with huge amounts of data. This
// library needs Go version 1.15 or later.
package excelize package excelize
......
...@@ -94,6 +94,7 @@ const ( ...@@ -94,6 +94,7 @@ const (
// Excel specifications and limits // Excel specifications and limits
const ( const (
UnzipSizeLimit = 1000 << 24
StreamChunkSize = 1 << 24 StreamChunkSize = 1 << 24
MaxFontFamilyLength = 31 MaxFontFamilyLength = 31
MaxFontSize = 409 MaxFontSize = 409
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册