@@ -14,8 +14,10 @@ package excelize
1414import (
1515 "archive/zip"
1616 "bytes"
17+ "encoding/binary"
1718 "encoding/xml"
1819 "io"
20+ "math"
1921 "os"
2022 "path/filepath"
2123 "sort"
@@ -123,17 +125,11 @@ func (f *File) WriteTo(w io.Writer, opts ...Options) (int64, error) {
123125 return 0 , err
124126 }
125127 }
126- if f .options != nil && f .options .Password != "" {
127- buf , err := f .WriteToBuffer ()
128- if err != nil {
129- return 0 , err
130- }
131- return buf .WriteTo (w )
132- }
133- if err := f .writeDirectToWriter (w ); err != nil {
128+ buf , err := f .WriteToBuffer ()
129+ if err != nil {
134130 return 0 , err
135131 }
136- return 0 , nil
132+ return buf . WriteTo ( w )
137133}
138134
139135// WriteToBuffer provides a function to get bytes.Buffer from the saved file,
@@ -143,32 +139,22 @@ func (f *File) WriteToBuffer() (*bytes.Buffer, error) {
143139 zw := zip .NewWriter (buf )
144140
145141 if err := f .writeToZip (zw ); err != nil {
146- return buf , zw .Close ()
142+ _ = zw .Close ()
143+ return buf , err
147144 }
148-
145+ if err := zw .Close (); err != nil {
146+ return buf , err
147+ }
148+ f .writeZip64LFH (buf )
149149 if f .options != nil && f .options .Password != "" {
150- if err := zw .Close (); err != nil {
151- return buf , err
152- }
153150 b , err := Encrypt (buf .Bytes (), f .options )
154151 if err != nil {
155152 return buf , err
156153 }
157154 buf .Reset ()
158155 buf .Write (b )
159- return buf , nil
160- }
161- return buf , zw .Close ()
162- }
163-
164- // writeDirectToWriter provides a function to write to io.Writer.
165- func (f * File ) writeDirectToWriter (w io.Writer ) error {
166- zw := zip .NewWriter (w )
167- if err := f .writeToZip (zw ); err != nil {
168- _ = zw .Close ()
169- return err
170156 }
171- return zw . Close ()
157+ return buf , nil
172158}
173159
174160// writeToZip provides a function to write to zip.Writer
@@ -197,11 +183,16 @@ func (f *File) writeToZip(zw *zip.Writer) error {
197183 _ = stream .rawData .Close ()
198184 return err
199185 }
200- if _ , err = io .Copy (fi , from ); err != nil {
186+ written , err := io .Copy (fi , from )
187+ if err != nil {
201188 return err
202189 }
190+ if written > math .MaxUint32 {
191+ f .zip64Entries = append (f .zip64Entries , path )
192+ }
203193 }
204194 var (
195+ n int
205196 err error
206197 files , tempFiles []string
207198 )
@@ -219,7 +210,9 @@ func (f *File) writeToZip(zw *zip.Writer) error {
219210 break
220211 }
221212 content , _ := f .Pkg .Load (path )
222- _ , err = fi .Write (content .([]byte ))
213+ if n , err = fi .Write (content .([]byte )); n > math .MaxUint32 {
214+ f .zip64Entries = append (f .zip64Entries , path )
215+ }
223216 }
224217 f .tempFiles .Range (func (path , content interface {}) bool {
225218 if _ , ok := f .Pkg .Load (path ); ok {
@@ -234,7 +227,46 @@ func (f *File) writeToZip(zw *zip.Writer) error {
234227 if fi , err = zw .Create (path ); err != nil {
235228 break
236229 }
237- _ , err = fi .Write (f .readBytes (path ))
230+ if n , err = fi .Write (f .readBytes (path )); n > math .MaxUint32 {
231+ f .zip64Entries = append (f .zip64Entries , path )
232+ }
238233 }
239234 return err
240235}
236+
237+ // writeZip64LFH function sets the ZIP version to 0x2D (45) in the Local File
238+ // Header (LFH). Excel strictly enforces ZIP64 format validation rules. When any
239+ // file within the workbook (OCP) exceeds 4GB in size, the ZIP64 format must be
240+ // used according to the PKZIP specification. However, ZIP files generated using
241+ // Go's standard archive/zip library always set the version in the local file
242+ // header to 20 (ZIP version 2.0) by default, as defined in the internal
243+ // 'writeHeader' function during ZIP creation. The archive/zip package only sets
244+ // the 'ReaderVersion' to 45 (ZIP64 version 4.5) in the central directory for
245+ // entries larger than 4GB. This results in a version mismatch between the
246+ // central directory and the local file header. As a result, opening the
247+ // generated workbook with spreadsheet application will prompt file corruption.
248+ func (f * File ) writeZip64LFH (buf * bytes.Buffer ) error {
249+ if len (f .zip64Entries ) == 0 {
250+ return nil
251+ }
252+ data , offset := buf .Bytes (), 0
253+ for offset < len (data ) {
254+ idx := bytes .Index (data [offset :], []byte {0x50 , 0x4b , 0x03 , 0x04 })
255+ if idx == - 1 {
256+ break
257+ }
258+ idx += offset
259+ if idx + 30 > len (data ) {
260+ break
261+ }
262+ filenameLen := int (binary .LittleEndian .Uint16 (data [idx + 26 : idx + 28 ]))
263+ if idx + 30 + filenameLen > len (data ) {
264+ break
265+ }
266+ if inStrSlice (f .zip64Entries , string (data [idx + 30 :idx + 30 + filenameLen ]), true ) != - 1 {
267+ binary .LittleEndian .PutUint16 (data [idx + 4 :idx + 6 ], 45 )
268+ }
269+ offset = idx + 1
270+ }
271+ return nil
272+ }
0 commit comments