summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/Unknwon/cae/zip/zip.go
blob: 21086f825e74b352bea4791e436d4e20e9b71838 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
// Copyright 2013 Unknown
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

// Package zip enables you to transparently read or write ZIP compressed archives and the files inside them.
package zip

import (
	"archive/zip"
	"errors"
	"io"
	"os"
	"path"
	"strings"

	"github.com/Unknwon/cae"
)

// A File represents a file or directory entry in archive.
type File struct {
	*zip.FileHeader
	oldName    string // NOTE: unused, for future change name feature.
	oldComment string // NOTE: unused, for future change comment feature.
	absPath    string // Absolute path of local file system.
	tmpPath    string
}

// A ZipArchive represents a file archive, compressed with Zip.
type ZipArchive struct {
	*zip.ReadCloser
	FileName   string
	Comment    string
	NumFiles   int
	Flag       int
	Permission os.FileMode

	files        []*File
	isHasChanged bool

	// For supporting flushing to io.Writer.
	writer      io.Writer
	isHasWriter bool
}

// OpenFile is the generalized open call; most users will use Open
// instead. It opens the named zip file with specified flag
// (O_RDONLY etc.) if applicable. If successful,
// methods on the returned ZipArchive can be used for I/O.
// If there is an error, it will be of type *PathError.
func OpenFile(name string, flag int, perm os.FileMode) (*ZipArchive, error) {
	z := new(ZipArchive)
	err := z.Open(name, flag, perm)
	return z, err
}

// Create creates the named zip file, truncating
// it if it already exists. If successful, methods on the returned
// ZipArchive can be used for I/O; the associated file descriptor has mode
// O_RDWR.
// If there is an error, it will be of type *PathError.
func Create(name string) (*ZipArchive, error) {
	os.MkdirAll(path.Dir(name), os.ModePerm)
	return OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
}

// Open opens the named zip file for reading. If successful, methods on
// the returned ZipArchive can be used for reading; the associated file
// descriptor has mode O_RDONLY.
// If there is an error, it will be of type *PathError.
func Open(name string) (*ZipArchive, error) {
	return OpenFile(name, os.O_RDONLY, 0)
}

// New accepts a variable that implemented interface io.Writer
// for write-only purpose operations.
func New(w io.Writer) *ZipArchive {
	return &ZipArchive{
		writer:      w,
		isHasWriter: true,
	}
}

// List returns a string slice of files' name in ZipArchive.
// Specify prefixes will be used as filters.
func (z *ZipArchive) List(prefixes ...string) []string {
	isHasPrefix := len(prefixes) > 0
	names := make([]string, 0, z.NumFiles)
	for _, f := range z.files {
		if isHasPrefix && !cae.HasPrefix(f.Name, prefixes) {
			continue
		}
		names = append(names, f.Name)
	}
	return names
}

// AddEmptyDir adds a raw directory entry to ZipArchive,
// it returns false if same directory enry already existed.
func (z *ZipArchive) AddEmptyDir(dirPath string) bool {
	dirPath = strings.Replace(dirPath, "\\", "/", -1)

	if !strings.HasSuffix(dirPath, "/") {
		dirPath += "/"
	}

	for _, f := range z.files {
		if dirPath == f.Name {
			return false
		}
	}

	dirPath = strings.TrimSuffix(dirPath, "/")
	if strings.Contains(dirPath, "/") {
		// Auto add all upper level directories.
		z.AddEmptyDir(path.Dir(dirPath))
	}
	z.files = append(z.files, &File{
		FileHeader: &zip.FileHeader{
			Name:             dirPath + "/",
			UncompressedSize: 0,
		},
	})
	z.updateStat()
	return true
}

// AddDir adds a directory and subdirectories entries to ZipArchive.
func (z *ZipArchive) AddDir(dirPath, absPath string) error {
	dir, err := os.Open(absPath)
	if err != nil {
		return err
	}
	defer dir.Close()

	// Make sure we have all upper level directories.
	z.AddEmptyDir(dirPath)

	fis, err := dir.Readdir(0)
	if err != nil {
		return err
	}
	for _, fi := range fis {
		curPath := absPath + "/" + fi.Name()
		tmpRecPath := path.Join(dirPath, fi.Name())
		if fi.IsDir() {
			if err = z.AddDir(tmpRecPath, curPath); err != nil {
				return err
			}
		} else {
			if err = z.AddFile(tmpRecPath, curPath); err != nil {
				return err
			}
		}
	}
	return nil
}

// updateStat should be called after every change for rebuilding statistic.
func (z *ZipArchive) updateStat() {
	z.NumFiles = len(z.files)
	z.isHasChanged = true
}

// AddFile adds a file entry to ZipArchive.
func (z *ZipArchive) AddFile(fileName, absPath string) error {
	fileName = strings.Replace(fileName, "\\", "/", -1)
	absPath = strings.Replace(absPath, "\\", "/", -1)

	if cae.IsFilter(absPath) {
		return nil
	}

	f, err := os.Open(absPath)
	if err != nil {
		return err
	}
	defer f.Close()

	fi, err := f.Stat()
	if err != nil {
		return err
	}

	file := new(File)
	file.FileHeader, err = zip.FileInfoHeader(fi)
	if err != nil {
		return err
	}
	file.Name = fileName
	file.absPath = absPath

	z.AddEmptyDir(path.Dir(fileName))

	isExist := false
	for _, f := range z.files {
		if fileName == f.Name {
			f = file
			isExist = true
			break
		}
	}
	if !isExist {
		z.files = append(z.files, file)
	}

	z.updateStat()
	return nil
}

// DeleteIndex deletes an entry in the archive by its index.
func (z *ZipArchive) DeleteIndex(idx int) error {
	if idx >= z.NumFiles {
		return errors.New("index out of range of number of files")
	}

	z.files = append(z.files[:idx], z.files[idx+1:]...)
	return nil
}

// DeleteName deletes an entry in the archive by its name.
func (z *ZipArchive) DeleteName(name string) error {
	for i, f := range z.files {
		if f.Name == name {
			return z.DeleteIndex(i)
		}
	}
	return errors.New("entry with given name not found")
}