extractors.js
6.41 KB
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
var stream = require('stream');
if (!stream.Readable) {
var stream = require('readable-stream');
}
var fs = require('graceful-fs');
var Q = require('q');
var path = require('path');
var zlib = require('zlib');
var touch = Q.denodeify(require('touch'));
var mkpath = Q.denodeify(require('mkpath'));
var writeFile = Q.denodeify(fs.writeFile);
var inflateRaw = Q.denodeify(zlib.inflateRaw);
var symlink = Q.denodeify(fs.symlink);
var stat = Q.denodeify(fs.stat);
// Use a cache of promises for building the directory tree. This allows us to
// correctly queue up file extractions for after their path has been created,
// avoid trying to create the path twice and still be async.
var mkdir = function (dir, cache, mode) {
dir = path.normalize(path.resolve(process.cwd(), dir) + path.sep);
if (mode === undefined) {
mode = parseInt('777', 8) & (~process.umask());
}
if (!cache[dir]) {
var parent;
if (fs.existsSync(dir)) {
parent = new Q();
} else {
parent = mkdir(path.dirname(dir), cache, mode);
}
cache[dir] = parent.then(function () {
return mkpath(dir, mode);
});
}
return cache[dir];
};
// Utility methods for writing output files
var extractors = {
folder: function (folder, destination, zip) {
return mkdir(destination, zip.dirCache, folder.mode)
.then(function () {
return {folder: folder.path};
});
},
store: function (file, destination, zip) {
var writer;
if (file.uncompressedSize === 0) {
writer = touch.bind(null, destination);
} else if (file.uncompressedSize <= zip.chunkSize) {
writer = function () {
return zip.getBuffer(file._offset, file._offset + file.uncompressedSize)
.then(function (buffer) {
return writeFile(destination, buffer, { mode: file.mode });
});
};
} else {
var input = new stream.Readable();
input.wrap(fs.createReadStream(zip.filename, {start: file._offset, end: file._offset + file.uncompressedSize - 1}));
writer = pipePromise.bind(null, input, destination, { mode: file.mode });
}
return mkdir(path.dirname(destination), zip.dirCache)
.then(writer)
.then(function () {
return {stored: file.path};
});
},
deflate: function (file, destination, zip) {
// For Deflate you don't actually need to specify the end offset - and
// in fact many ZIP files don't include compressed file sizes for
// Deflated files so we don't even know what the end offset is.
return mkdir(path.dirname(destination), zip.dirCache)
.then(function () {
if (file._maxSize <= zip.chunkSize) {
return zip.getBuffer(file._offset, file._offset + file._maxSize)
.then(inflateRaw)
.then(function (buffer) {
return writeFile(destination, buffer, { mode: file.mode });
});
} else {
// For node 0.8 we need to create the Zlib stream and attach
// handlers in the same tick of the event loop, which is why we do
// the creation in here
var input = new stream.Readable();
input.wrap(fs.createReadStream(zip.filename, {start: file._offset}));
var inflater = input.pipe(zlib.createInflateRaw({highWaterMark: 32 * 1024}));
return pipePromise(inflater, destination, { mode: file.mode });
}
})
.then(function () {
return {deflated: file.path};
});
},
symlink: function (file, destination, zip, basePath) {
var parent = path.dirname(destination);
return mkdir(parent, zip.dirCache)
.then(function () {
return getLinkLocation(file, destination, zip, basePath);
})
.then(function (linkTo) {
return symlink(path.resolve(parent, linkTo), destination)
.then(function () {
return {symlink: file.path, linkTo: linkTo};
});
});
},
// Make a shallow copy of the file/directory this symlink points to instead
// of actually creating a link
copy: function (file, destination, zip, basePath) {
var type;
var parent = path.dirname(destination);
return mkdir(parent, zip.dirCache)
.then(function () {
return getLinkLocation(file, destination, zip, basePath);
})
.then(function (linkTo) {
return stat(path.resolve(parent, linkTo))
.then(function (stats) {
if (stats.isFile()) {
type = 'File';
var input = new stream.Readable();
input.wrap(fs.createReadStream(path.resolve(parent, linkTo)));
return pipePromise(input, destination);
} else if (stats.isDirectory()) {
type = 'Directory';
return mkdir(destination, zip.dirCache);
} else {
throw new Error('Could not follow symlink to unknown file type');
}
})
.then(function () {
return {copy: file.path, original: linkTo, type: type};
});
});
}
};
var getLinkLocation = function (file, destination, zip, basePath) {
var parent = path.dirname(destination);
return zip.getBuffer(file._offset, file._offset + file.uncompressedSize)
.then(function (buffer) {
var linkTo = buffer.toString();
var fullLink = path.resolve(parent, linkTo);
if (path.relative(basePath, fullLink).slice(0, 2) === '..') {
throw new Error('Symlink links outside archive');
}
return linkTo;
});
};
var pipePromise = function (input, destination, options) {
var deferred = Q.defer();
var output = fs.createWriteStream(destination, options);
var errorHandler = function (error) {
deferred.reject(error);
};
input.on('error', errorHandler);
output.on('error', errorHandler);
// For node 0.8 we can't just use the 'finish' event of the pipe
input.on('end', function () {
output.end(function () {
deferred.resolve();
});
});
input.pipe(output, {end: false});
return deferred.promise;
};
module.exports = extractors;