@@ -14,8 +14,10 @@ package excelize
14
14
import (
15
15
"archive/zip"
16
16
"bytes"
17
+ "encoding/binary"
17
18
"encoding/xml"
18
19
"io"
20
+ "math"
19
21
"os"
20
22
"path/filepath"
21
23
"sort"
@@ -123,17 +125,11 @@ func (f *File) WriteTo(w io.Writer, opts ...Options) (int64, error) {
123
125
return 0 , err
124
126
}
125
127
}
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 {
134
130
return 0 , err
135
131
}
136
- return 0 , nil
132
+ return buf . WriteTo ( w )
137
133
}
138
134
139
135
// WriteToBuffer provides a function to get bytes.Buffer from the saved file,
@@ -143,32 +139,22 @@ func (f *File) WriteToBuffer() (*bytes.Buffer, error) {
143
139
zw := zip .NewWriter (buf )
144
140
145
141
if err := f .writeToZip (zw ); err != nil {
146
- return buf , zw .Close ()
142
+ _ = zw .Close ()
143
+ return buf , err
147
144
}
148
-
145
+ if err := zw .Close (); err != nil {
146
+ return buf , err
147
+ }
148
+ f .writeZip64LFH (buf )
149
149
if f .options != nil && f .options .Password != "" {
150
- if err := zw .Close (); err != nil {
151
- return buf , err
152
- }
153
150
b , err := Encrypt (buf .Bytes (), f .options )
154
151
if err != nil {
155
152
return buf , err
156
153
}
157
154
buf .Reset ()
158
155
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
170
156
}
171
- return zw . Close ()
157
+ return buf , nil
172
158
}
173
159
174
160
// writeToZip provides a function to write to zip.Writer
@@ -197,11 +183,16 @@ func (f *File) writeToZip(zw *zip.Writer) error {
197
183
_ = stream .rawData .Close ()
198
184
return err
199
185
}
200
- if _ , err = io .Copy (fi , from ); err != nil {
186
+ written , err := io .Copy (fi , from )
187
+ if err != nil {
201
188
return err
202
189
}
190
+ if written > math .MaxUint32 {
191
+ f .zip64Entries = append (f .zip64Entries , path )
192
+ }
203
193
}
204
194
var (
195
+ n int
205
196
err error
206
197
files , tempFiles []string
207
198
)
@@ -219,7 +210,9 @@ func (f *File) writeToZip(zw *zip.Writer) error {
219
210
break
220
211
}
221
212
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
+ }
223
216
}
224
217
f .tempFiles .Range (func (path , content interface {}) bool {
225
218
if _ , ok := f .Pkg .Load (path ); ok {
@@ -234,7 +227,46 @@ func (f *File) writeToZip(zw *zip.Writer) error {
234
227
if fi , err = zw .Create (path ); err != nil {
235
228
break
236
229
}
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
+ }
238
233
}
239
234
return err
240
235
}
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