Skip to content

Commit 87a00e4

Browse files
committed
This closed qax-os#1680, fixing a potential issue that stream reader temporary files can not be clear
- Delete image files from the workbook internally when deleting pictures to reduce generated workbook size and resolve potential security issues
1 parent 99df1a7 commit 87a00e4

7 files changed

+150
-18
lines changed

chart.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -1109,7 +1109,8 @@ func (f *File) DeleteChart(sheet, cell string) error {
11091109
return err
11101110
}
11111111
drawingXML := strings.ReplaceAll(f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID), "..", "xl")
1112-
return f.deleteDrawing(col, row, drawingXML, "Chart")
1112+
_, err = f.deleteDrawing(col, row, drawingXML, "Chart")
1113+
return err
11131114
}
11141115

11151116
// countCharts provides a function to get chart files count storage in the

chart_test.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,15 @@ func TestDeleteDrawing(t *testing.T) {
120120
f := NewFile()
121121
path := "xl/drawings/drawing1.xml"
122122
f.Pkg.Store(path, MacintoshCyrillicCharset)
123-
assert.EqualError(t, f.deleteDrawing(0, 0, path, "Chart"), "XML syntax error on line 1: invalid UTF-8")
124-
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
123+
_, err := f.deleteDrawing(0, 0, path, "Chart")
124+
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
125+
f, err = OpenFile(filepath.Join("test", "Book1.xlsx"))
125126
assert.NoError(t, err)
126127
f.Drawings.Store(path, &xlsxWsDr{TwoCellAnchor: []*xdrCellAnchor{{
127128
GraphicFrame: string(MacintoshCyrillicCharset),
128129
}}})
129-
assert.EqualError(t, f.deleteDrawing(0, 0, path, "Chart"), "XML syntax error on line 1: invalid UTF-8")
130+
_, err = f.deleteDrawing(0, 0, path, "Chart")
131+
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
130132
}
131133

132134
func TestAddChart(t *testing.T) {

drawing.go

+52-7
Original file line numberDiff line numberDiff line change
@@ -1391,11 +1391,14 @@ func (f *File) addSheetDrawingChart(drawingXML string, rID int, opts *GraphicOpt
13911391
return err
13921392
}
13931393

1394-
// deleteDrawing provides a function to delete chart graphic frame by given by
1394+
// deleteDrawing provides a function to delete the chart graphic frame and
1395+
// returns deleted embed relationships ID (for unique picture cell anchor) by
13951396
// given coordinates and graphic type.
1396-
func (f *File) deleteDrawing(col, row int, drawingXML, drawingType string) error {
1397+
func (f *File) deleteDrawing(col, row int, drawingXML, drawingType string) (string, error) {
13971398
var (
13981399
err error
1400+
rID string
1401+
rIDs []string
13991402
wsDr *xlsxWsDr
14001403
deTwoCellAnchor *decodeCellAnchor
14011404
)
@@ -1407,32 +1410,74 @@ func (f *File) deleteDrawing(col, row int, drawingXML, drawingType string) error
14071410
"Chart": func(anchor *decodeCellAnchor) bool { return anchor.Pic == nil },
14081411
"Pic": func(anchor *decodeCellAnchor) bool { return anchor.Pic != nil },
14091412
}
1413+
onAnchorCell := func(c, r int) bool { return c == col && r == row }
14101414
if wsDr, _, err = f.drawingParser(drawingXML); err != nil {
1411-
return err
1415+
return rID, err
14121416
}
14131417
for idx := 0; idx < len(wsDr.TwoCellAnchor); idx++ {
14141418
if err = nil; wsDr.TwoCellAnchor[idx].From != nil && xdrCellAnchorFuncs[drawingType](wsDr.TwoCellAnchor[idx]) {
1415-
if wsDr.TwoCellAnchor[idx].From.Col == col && wsDr.TwoCellAnchor[idx].From.Row == row {
1419+
if onAnchorCell(wsDr.TwoCellAnchor[idx].From.Col, wsDr.TwoCellAnchor[idx].From.Row) {
1420+
rID, _ = extractEmbedRID(wsDr.TwoCellAnchor[idx].Pic, nil, rIDs)
14161421
wsDr.TwoCellAnchor = append(wsDr.TwoCellAnchor[:idx], wsDr.TwoCellAnchor[idx+1:]...)
14171422
idx--
1423+
continue
14181424
}
1425+
_, rIDs = extractEmbedRID(wsDr.TwoCellAnchor[idx].Pic, nil, rIDs)
14191426
}
14201427
}
14211428
for idx := 0; idx < len(wsDr.TwoCellAnchor); idx++ {
14221429
deTwoCellAnchor = new(decodeCellAnchor)
14231430
if err = f.xmlNewDecoder(strings.NewReader("<decodeCellAnchor>" + wsDr.TwoCellAnchor[idx].GraphicFrame + "</decodeCellAnchor>")).
14241431
Decode(deTwoCellAnchor); err != nil && err != io.EOF {
1425-
return err
1432+
return rID, err
14261433
}
14271434
if err = nil; deTwoCellAnchor.From != nil && decodeCellAnchorFuncs[drawingType](deTwoCellAnchor) {
1428-
if deTwoCellAnchor.From.Col == col && deTwoCellAnchor.From.Row == row {
1435+
if onAnchorCell(deTwoCellAnchor.From.Col, deTwoCellAnchor.From.Row) {
1436+
rID, _ = extractEmbedRID(nil, deTwoCellAnchor.Pic, rIDs)
14291437
wsDr.TwoCellAnchor = append(wsDr.TwoCellAnchor[:idx], wsDr.TwoCellAnchor[idx+1:]...)
14301438
idx--
1439+
continue
14311440
}
1441+
_, rIDs = extractEmbedRID(nil, deTwoCellAnchor.Pic, rIDs)
14321442
}
14331443
}
1444+
if inStrSlice(rIDs, rID, true) != -1 {
1445+
rID = ""
1446+
}
14341447
f.Drawings.Store(drawingXML, wsDr)
1435-
return err
1448+
return rID, err
1449+
}
1450+
1451+
// extractEmbedRID returns embed relationship ID and all relationship ID lists
1452+
// for giving cell anchor.
1453+
func extractEmbedRID(pic *xlsxPic, decodePic *decodePic, rIDs []string) (string, []string) {
1454+
if pic != nil {
1455+
rIDs = append(rIDs, pic.BlipFill.Blip.Embed)
1456+
return pic.BlipFill.Blip.Embed, rIDs
1457+
}
1458+
if decodePic != nil {
1459+
rIDs = append(rIDs, decodePic.BlipFill.Blip.Embed)
1460+
return decodePic.BlipFill.Blip.Embed, rIDs
1461+
}
1462+
return "", rIDs
1463+
}
1464+
1465+
// deleteDrawingRels provides a function to delete relationships in
1466+
// xl/drawings/_rels/drawings%d.xml.rels by giving drawings relationships path
1467+
// and relationship ID.
1468+
func (f *File) deleteDrawingRels(rels, rID string) {
1469+
drawingRels, _ := f.relsReader(rels)
1470+
if drawingRels == nil {
1471+
drawingRels = &xlsxRelationships{}
1472+
}
1473+
drawingRels.mu.Lock()
1474+
defer drawingRels.mu.Unlock()
1475+
for k, v := range drawingRels.Relationships {
1476+
if v.ID == rID {
1477+
drawingRels.Relationships = append(drawingRels.Relationships[:k], drawingRels.Relationships[k+1:]...)
1478+
}
1479+
}
1480+
f.Relationships.Store(rels, drawingRels)
14361481
}
14371482

14381483
// genAxID provides a function to generate ID for primary and secondary

drawing_test.go

+9
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,12 @@ func TestDrawingParser(t *testing.T) {
3838
_, _, err = f.drawingParser("wsDr")
3939
assert.NoError(t, err)
4040
}
41+
42+
func TestDeleteDrawingRels(t *testing.T) {
43+
f := NewFile()
44+
// Test delete drawing relationships with unsupported charset
45+
rels := "xl/drawings/_rels/drawing1.xml.rels"
46+
f.Relationships.Delete(rels)
47+
f.Pkg.Store(rels, MacintoshCyrillicCharset)
48+
f.deleteDrawingRels(rels, "")
49+
}

lib.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,22 @@ func (f *File) ReadZipReader(r *zip.Reader) (map[string][]byte, int, error) {
4848
fileName = partName
4949
}
5050
if strings.EqualFold(fileName, defaultXMLPathSharedStrings) && fileSize > f.options.UnzipXMLSizeLimit {
51-
if tempFile, err := f.unzipToTemp(v); err == nil {
51+
tempFile, err := f.unzipToTemp(v)
52+
if tempFile != "" {
5253
f.tempFiles.Store(fileName, tempFile)
54+
}
55+
if err == nil {
5356
continue
5457
}
5558
}
5659
if strings.HasPrefix(strings.ToLower(fileName), "xl/worksheets/sheet") {
5760
worksheets++
5861
if fileSize > f.options.UnzipXMLSizeLimit && !v.FileInfo().IsDir() {
59-
if tempFile, err := f.unzipToTemp(v); err == nil {
62+
tempFile, err := f.unzipToTemp(v)
63+
if tempFile != "" {
6064
f.tempFiles.Store(fileName, tempFile)
65+
}
66+
if err == nil {
6167
continue
6268
}
6369
}

picture.go

+31-3
Original file line numberDiff line numberDiff line change
@@ -468,8 +468,7 @@ func (f *File) GetPictures(sheet, cell string) ([]Picture, error) {
468468
}
469469

470470
// DeletePicture provides a function to delete all pictures in a cell by given
471-
// worksheet name and cell reference. Note that the image file won't be deleted
472-
// from the document currently.
471+
// worksheet name and cell reference.
473472
func (f *File) DeletePicture(sheet, cell string) error {
474473
col, row, err := CellNameToCoordinates(cell)
475474
if err != nil {
@@ -485,7 +484,36 @@ func (f *File) DeletePicture(sheet, cell string) error {
485484
return err
486485
}
487486
drawingXML := strings.ReplaceAll(f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID), "..", "xl")
488-
return f.deleteDrawing(col, row, drawingXML, "Pic")
487+
drawingRels := "xl/drawings/_rels/" + filepath.Base(drawingXML) + ".rels"
488+
rID, err := f.deleteDrawing(col, row, drawingXML, "Pic")
489+
if err != nil {
490+
return err
491+
}
492+
rels := f.getDrawingRelationships(drawingRels, rID)
493+
if rels == nil {
494+
return err
495+
}
496+
var used bool
497+
f.Pkg.Range(func(k, v interface{}) bool {
498+
if strings.Contains(k.(string), "xl/drawings/_rels/") {
499+
r, err := f.relsReader(k.(string))
500+
if err != nil {
501+
return true
502+
}
503+
for _, rel := range r.Relationships {
504+
if rel.ID != rels.ID && rel.Type == SourceRelationshipImage &&
505+
filepath.Base(rel.Target) == filepath.Base(rels.Target) {
506+
used = true
507+
}
508+
}
509+
}
510+
return true
511+
})
512+
if !used {
513+
f.Pkg.Delete(strings.Replace(rels.Target, "../", "xl/", -1))
514+
}
515+
f.deleteDrawingRels(drawingRels, rID)
516+
return err
489517
}
490518

491519
// getPicture provides a function to get picture base name and raw content

picture_test.go

+43-2
Original file line numberDiff line numberDiff line change
@@ -240,10 +240,25 @@ func TestAddPictureFromBytes(t *testing.T) {
240240
func TestDeletePicture(t *testing.T) {
241241
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
242242
assert.NoError(t, err)
243+
// Test delete picture on a worksheet which does not contains any pictures
243244
assert.NoError(t, f.DeletePicture("Sheet1", "A1"))
244-
assert.NoError(t, f.AddPicture("Sheet1", "P1", filepath.Join("test", "images", "excel.jpg"), nil))
245-
assert.NoError(t, f.DeletePicture("Sheet1", "P1"))
245+
// Add same pictures on different worksheets
246+
assert.NoError(t, f.AddPicture("Sheet1", "F20", filepath.Join("test", "images", "excel.jpg"), nil))
247+
assert.NoError(t, f.AddPicture("Sheet1", "I20", filepath.Join("test", "images", "excel.jpg"), nil))
248+
assert.NoError(t, f.AddPicture("Sheet2", "F1", filepath.Join("test", "images", "excel.jpg"), nil))
249+
// Test delete picture on a worksheet, the images should be preserved
250+
assert.NoError(t, f.DeletePicture("Sheet1", "F20"))
246251
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeletePicture.xlsx")))
252+
assert.NoError(t, f.Close())
253+
254+
f, err = OpenFile(filepath.Join("test", "TestDeletePicture.xlsx"))
255+
assert.NoError(t, err)
256+
// Test delete same picture on different worksheet, the images should be removed
257+
assert.NoError(t, f.DeletePicture("Sheet1", "F10"))
258+
assert.NoError(t, f.DeletePicture("Sheet2", "F1"))
259+
assert.NoError(t, f.DeletePicture("Sheet1", "I20"))
260+
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeletePicture2.xlsx")))
261+
247262
// Test delete picture on not exists worksheet
248263
assert.EqualError(t, f.DeletePicture("SheetN", "A1"), "sheet SheetN does not exist")
249264
// Test delete picture with invalid sheet name
@@ -253,6 +268,32 @@ func TestDeletePicture(t *testing.T) {
253268
assert.NoError(t, f.Close())
254269
// Test delete picture on no chart worksheet
255270
assert.NoError(t, NewFile().DeletePicture("Sheet1", "A1"))
271+
272+
f, err = OpenFile(filepath.Join("test", "TestDeletePicture.xlsx"))
273+
assert.NoError(t, err)
274+
// Test delete picture with unsupported charset drawing
275+
f.Pkg.Store("xl/drawings/drawing1.xml", MacintoshCyrillicCharset)
276+
assert.EqualError(t, f.DeletePicture("Sheet1", "F10"), "XML syntax error on line 1: invalid UTF-8")
277+
assert.NoError(t, f.Close())
278+
279+
f, err = OpenFile(filepath.Join("test", "TestDeletePicture.xlsx"))
280+
assert.NoError(t, err)
281+
// Test delete picture with unsupported charset drawing relationships
282+
f.Relationships.Delete("xl/drawings/_rels/drawing1.xml.rels")
283+
f.Pkg.Store("xl/drawings/_rels/drawing1.xml.rels", MacintoshCyrillicCharset)
284+
assert.NoError(t, f.DeletePicture("Sheet2", "F1"))
285+
assert.NoError(t, f.Close())
286+
287+
f = NewFile()
288+
assert.NoError(t, err)
289+
assert.NoError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.jpg"), nil))
290+
assert.NoError(t, f.AddPicture("Sheet1", "G1", filepath.Join("test", "images", "excel.jpg"), nil))
291+
drawing, ok := f.Drawings.Load("xl/drawings/drawing1.xml")
292+
assert.True(t, ok)
293+
// Made two picture reference the same drawing relationship ID
294+
drawing.(*xlsxWsDr).TwoCellAnchor[1].Pic.BlipFill.Blip.Embed = "rId1"
295+
assert.NoError(t, f.DeletePicture("Sheet1", "A1"))
296+
assert.NoError(t, f.Close())
256297
}
257298

258299
func TestDrawingResize(t *testing.T) {

0 commit comments

Comments
 (0)