| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 1 | // Copyright 2016 Google Inc. All rights reserved. | 
|  | 2 | // | 
|  | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | 4 | // you may not use this file except in compliance with the License. | 
|  | 5 | // You may obtain a copy of the License at | 
|  | 6 | // | 
|  | 7 | //     http://www.apache.org/licenses/LICENSE-2.0 | 
|  | 8 | // | 
|  | 9 | // Unless required by applicable law or agreed to in writing, software | 
|  | 10 | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | 12 | // See the License for the specific language governing permissions and | 
|  | 13 | // limitations under the License. | 
|  | 14 |  | 
|  | 15 | package zip | 
|  | 16 |  | 
|  | 17 | import ( | 
| Dan Willemsen | 017d893 | 2016-08-04 15:43:03 -0700 | [diff] [blame] | 18 | "errors" | 
| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 19 | "io" | 
|  | 20 | ) | 
|  | 21 |  | 
| Jeff Gaston | c5eb66d | 2017-08-24 14:11:27 -0700 | [diff] [blame] | 22 | const DataDescriptorFlag = 0x8 | 
| Nan Zhang | 10e8e93 | 2017-09-19 17:13:11 -0700 | [diff] [blame] | 23 | const ExtendedTimeStampTag = 0x5455 | 
| Jeff Gaston | c5eb66d | 2017-08-24 14:11:27 -0700 | [diff] [blame] | 24 |  | 
| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 25 | func (w *Writer) CopyFrom(orig *File, newName string) error { | 
|  | 26 | if w.last != nil && !w.last.closed { | 
|  | 27 | if err := w.last.close(); err != nil { | 
|  | 28 | return err | 
|  | 29 | } | 
|  | 30 | w.last = nil | 
|  | 31 | } | 
|  | 32 |  | 
|  | 33 | fileHeader := orig.FileHeader | 
|  | 34 | fileHeader.Name = newName | 
|  | 35 | fh := &fileHeader | 
| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 36 |  | 
| Nan Zhang | 10e8e93 | 2017-09-19 17:13:11 -0700 | [diff] [blame] | 37 | // In some cases, we need strip the extras if it change between Central Directory | 
|  | 38 | // and Local File Header. | 
|  | 39 | fh.Extra = stripExtras(fh.Extra) | 
| Dan Willemsen | a1354b3 | 2017-02-17 13:14:43 -0800 | [diff] [blame] | 40 |  | 
| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 41 | h := &header{ | 
|  | 42 | FileHeader: fh, | 
|  | 43 | offset:     uint64(w.cw.count), | 
|  | 44 | } | 
|  | 45 | w.dir = append(w.dir, h) | 
| Kelvin Zhang | 45e2f14 | 2020-07-30 17:12:38 -0400 | [diff] [blame] | 46 | if !fh.isZip64() { | 
|  | 47 | // Some writers will generate 64 bit sizes and set 32 bit fields to | 
|  | 48 | // uint32max even if the actual size fits in 32 bit. So we should | 
|  | 49 | // make sure CompressedSize contains the correct value in such | 
|  | 50 | // cases. With out the two lines below we would be writing invalid(-1) | 
|  | 51 | // sizes in such case. | 
|  | 52 | fh.CompressedSize = uint32(fh.CompressedSize64) | 
|  | 53 | fh.UncompressedSize = uint32(fh.UncompressedSize64) | 
|  | 54 | } | 
| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 55 |  | 
|  | 56 | if err := writeHeader(w.cw, fh); err != nil { | 
|  | 57 | return err | 
|  | 58 | } | 
| Colin Cross | c2a62d4 | 2023-08-22 14:22:28 -0700 | [diff] [blame] | 59 |  | 
|  | 60 | // Strip the extras again in case writeHeader added the local file header extras that are incorrect for the | 
|  | 61 | // central directory. | 
|  | 62 | fh.Extra = stripExtras(fh.Extra) | 
|  | 63 |  | 
| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 64 | dataOffset, err := orig.DataOffset() | 
|  | 65 | if err != nil { | 
|  | 66 | return err | 
|  | 67 | } | 
|  | 68 | io.Copy(w.cw, io.NewSectionReader(orig.zipr, dataOffset, int64(orig.CompressedSize64))) | 
|  | 69 |  | 
| Nan Zhang | d5998cc | 2017-09-13 13:17:43 -0700 | [diff] [blame] | 70 | if orig.hasDataDescriptor() { | 
|  | 71 | // Write data descriptor. | 
|  | 72 | var buf []byte | 
|  | 73 | if fh.isZip64() { | 
|  | 74 | buf = make([]byte, dataDescriptor64Len) | 
|  | 75 | } else { | 
|  | 76 | buf = make([]byte, dataDescriptorLen) | 
|  | 77 | } | 
|  | 78 | b := writeBuf(buf) | 
|  | 79 | b.uint32(dataDescriptorSignature) | 
|  | 80 | b.uint32(fh.CRC32) | 
|  | 81 | if fh.isZip64() { | 
|  | 82 | b.uint64(fh.CompressedSize64) | 
|  | 83 | b.uint64(fh.UncompressedSize64) | 
|  | 84 | } else { | 
|  | 85 | b.uint32(fh.CompressedSize) | 
|  | 86 | b.uint32(fh.UncompressedSize) | 
|  | 87 | } | 
|  | 88 | _, err = w.cw.Write(buf) | 
| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 89 | } | 
| Dan Willemsen | 3bf1a08 | 2016-08-03 00:35:25 -0700 | [diff] [blame] | 90 | return err | 
|  | 91 | } | 
| Dan Willemsen | 017d893 | 2016-08-04 15:43:03 -0700 | [diff] [blame] | 92 |  | 
| Nan Zhang | 10e8e93 | 2017-09-19 17:13:11 -0700 | [diff] [blame] | 93 | // The zip64 extras change between the Central Directory and Local File Header, while we use | 
|  | 94 | // the same structure for both. The Local File Haeder is taken care of by us writing a data | 
|  | 95 | // descriptor with the zip64 values. The Central Directory Entry is written by Close(), where | 
|  | 96 | // the zip64 extra is automatically created and appended when necessary. | 
|  | 97 | // | 
|  | 98 | // The extended-timestamp extra block changes between the Central Directory Header and Local | 
|  | 99 | // File Header. | 
|  | 100 | // Extended-Timestamp extra(LFH): <tag-size-flag-modtime-actime-changetime> | 
|  | 101 | // Extended-Timestamp extra(CDH): <tag-size-flag-modtime> | 
|  | 102 | func stripExtras(input []byte) []byte { | 
| Dan Willemsen | a1354b3 | 2017-02-17 13:14:43 -0800 | [diff] [blame] | 103 | ret := []byte{} | 
|  | 104 |  | 
|  | 105 | for len(input) >= 4 { | 
|  | 106 | r := readBuf(input) | 
|  | 107 | tag := r.uint16() | 
|  | 108 | size := r.uint16() | 
|  | 109 | if int(size) > len(r) { | 
|  | 110 | break | 
|  | 111 | } | 
| Nan Zhang | 10e8e93 | 2017-09-19 17:13:11 -0700 | [diff] [blame] | 112 | if tag != zip64ExtraId && tag != ExtendedTimeStampTag { | 
| Dan Willemsen | a1354b3 | 2017-02-17 13:14:43 -0800 | [diff] [blame] | 113 | ret = append(ret, input[:4+size]...) | 
|  | 114 | } | 
|  | 115 | input = input[4+size:] | 
|  | 116 | } | 
|  | 117 |  | 
|  | 118 | // Keep any trailing data | 
|  | 119 | ret = append(ret, input...) | 
|  | 120 |  | 
|  | 121 | return ret | 
|  | 122 | } | 
|  | 123 |  | 
| Dan Willemsen | 017d893 | 2016-08-04 15:43:03 -0700 | [diff] [blame] | 124 | // CreateCompressedHeader adds a file to the zip file using the provied | 
|  | 125 | // FileHeader for the file metadata. | 
|  | 126 | // It returns a Writer to which the already compressed file contents | 
|  | 127 | // should be written. | 
|  | 128 | // | 
|  | 129 | // The UncompressedSize64 and CRC32 entries in the FileHeader must be filled | 
|  | 130 | // out already. | 
|  | 131 | // | 
|  | 132 | // The file's contents must be written to the io.Writer before the next | 
|  | 133 | // call to Create, CreateHeader, CreateCompressedHeader, or Close. The | 
|  | 134 | // provided FileHeader fh must not be modified after a call to | 
|  | 135 | // CreateCompressedHeader | 
|  | 136 | func (w *Writer) CreateCompressedHeader(fh *FileHeader) (io.WriteCloser, error) { | 
|  | 137 | if w.last != nil && !w.last.closed { | 
|  | 138 | if err := w.last.close(); err != nil { | 
|  | 139 | return nil, err | 
|  | 140 | } | 
|  | 141 | } | 
|  | 142 | if len(w.dir) > 0 && w.dir[len(w.dir)-1].FileHeader == fh { | 
|  | 143 | // See https://golang.org/issue/11144 confusion. | 
|  | 144 | return nil, errors.New("archive/zip: invalid duplicate FileHeader") | 
|  | 145 | } | 
|  | 146 |  | 
| Jeff Gaston | c5eb66d | 2017-08-24 14:11:27 -0700 | [diff] [blame] | 147 | fh.Flags |= DataDescriptorFlag // we will write a data descriptor | 
| Dan Willemsen | 017d893 | 2016-08-04 15:43:03 -0700 | [diff] [blame] | 148 |  | 
|  | 149 | fh.CreatorVersion = fh.CreatorVersion&0xff00 | zipVersion20 // preserve compatibility byte | 
|  | 150 | fh.ReaderVersion = zipVersion20 | 
|  | 151 |  | 
|  | 152 | fw := &compressedFileWriter{ | 
|  | 153 | fileWriter{ | 
|  | 154 | zipw:      w.cw, | 
|  | 155 | compCount: &countWriter{w: w.cw}, | 
|  | 156 | }, | 
|  | 157 | } | 
|  | 158 |  | 
|  | 159 | h := &header{ | 
|  | 160 | FileHeader: fh, | 
|  | 161 | offset:     uint64(w.cw.count), | 
|  | 162 | } | 
|  | 163 | w.dir = append(w.dir, h) | 
|  | 164 | fw.header = h | 
|  | 165 |  | 
|  | 166 | if err := writeHeader(w.cw, fh); err != nil { | 
|  | 167 | return nil, err | 
|  | 168 | } | 
|  | 169 |  | 
|  | 170 | w.last = &fw.fileWriter | 
|  | 171 | return fw, nil | 
|  | 172 | } | 
|  | 173 |  | 
| Jeff Gaston | c5eb66d | 2017-08-24 14:11:27 -0700 | [diff] [blame] | 174 | // Updated version of CreateHeader that doesn't enforce writing a data descriptor | 
|  | 175 | func (w *Writer) CreateHeaderAndroid(fh *FileHeader) (io.Writer, error) { | 
|  | 176 | writeDataDescriptor := fh.Method != Store | 
|  | 177 | if writeDataDescriptor { | 
| Colin Cross | 7592d5a | 2023-07-18 15:57:09 -0700 | [diff] [blame] | 178 | fh.Flags |= DataDescriptorFlag | 
| Jeff Gaston | c5eb66d | 2017-08-24 14:11:27 -0700 | [diff] [blame] | 179 | } else { | 
|  | 180 | fh.Flags &= ^uint16(DataDescriptorFlag) | 
|  | 181 | } | 
|  | 182 | return w.createHeaderImpl(fh) | 
|  | 183 | } | 
|  | 184 |  | 
| Dan Willemsen | 017d893 | 2016-08-04 15:43:03 -0700 | [diff] [blame] | 185 | type compressedFileWriter struct { | 
|  | 186 | fileWriter | 
|  | 187 | } | 
|  | 188 |  | 
|  | 189 | func (w *compressedFileWriter) Write(p []byte) (int, error) { | 
|  | 190 | if w.closed { | 
|  | 191 | return 0, errors.New("zip: write to closed file") | 
|  | 192 | } | 
|  | 193 | return w.compCount.Write(p) | 
|  | 194 | } | 
|  | 195 |  | 
|  | 196 | func (w *compressedFileWriter) Close() error { | 
|  | 197 | if w.closed { | 
|  | 198 | return errors.New("zip: file closed twice") | 
|  | 199 | } | 
|  | 200 | w.closed = true | 
|  | 201 |  | 
|  | 202 | // update FileHeader | 
|  | 203 | fh := w.header.FileHeader | 
|  | 204 | fh.CompressedSize64 = uint64(w.compCount.count) | 
|  | 205 |  | 
|  | 206 | if fh.isZip64() { | 
|  | 207 | fh.CompressedSize = uint32max | 
|  | 208 | fh.UncompressedSize = uint32max | 
|  | 209 | fh.ReaderVersion = zipVersion45 // requires 4.5 - File uses ZIP64 format extensions | 
|  | 210 | } else { | 
|  | 211 | fh.CompressedSize = uint32(fh.CompressedSize64) | 
|  | 212 | fh.UncompressedSize = uint32(fh.UncompressedSize64) | 
|  | 213 | } | 
|  | 214 |  | 
|  | 215 | // Write data descriptor. This is more complicated than one would | 
|  | 216 | // think, see e.g. comments in zipfile.c:putextended() and | 
|  | 217 | // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7073588. | 
|  | 218 | // The approach here is to write 8 byte sizes if needed without | 
|  | 219 | // adding a zip64 extra in the local header (too late anyway). | 
|  | 220 | var buf []byte | 
|  | 221 | if fh.isZip64() { | 
|  | 222 | buf = make([]byte, dataDescriptor64Len) | 
|  | 223 | } else { | 
|  | 224 | buf = make([]byte, dataDescriptorLen) | 
|  | 225 | } | 
|  | 226 | b := writeBuf(buf) | 
|  | 227 | b.uint32(dataDescriptorSignature) // de-facto standard, required by OS X | 
|  | 228 | b.uint32(fh.CRC32) | 
|  | 229 | if fh.isZip64() { | 
|  | 230 | b.uint64(fh.CompressedSize64) | 
|  | 231 | b.uint64(fh.UncompressedSize64) | 
|  | 232 | } else { | 
|  | 233 | b.uint32(fh.CompressedSize) | 
|  | 234 | b.uint32(fh.UncompressedSize) | 
|  | 235 | } | 
|  | 236 | _, err := w.zipw.Write(buf) | 
|  | 237 | return err | 
|  | 238 | } |