@webtypen/webframez-core 0.3.83 → 0.3.85
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -17,7 +17,7 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
17
17
|
const path_1 = __importDefault(require("path"));
|
|
18
18
|
const os_1 = __importDefault(require("os"));
|
|
19
19
|
const crypto_1 = __importDefault(require("crypto"));
|
|
20
|
-
const
|
|
20
|
+
const ZipReader_1 = require("./Utils/ZipReader");
|
|
21
21
|
const Config_1 = require("../Config");
|
|
22
22
|
const DBConnection_1 = require("../Database/DBConnection");
|
|
23
23
|
const BackupOutputDrivers_1 = require("./BackupOutputDrivers");
|
|
@@ -705,7 +705,7 @@ class BackupManager {
|
|
|
705
705
|
}
|
|
706
706
|
extractArtifact(artifactPath, targetDir) {
|
|
707
707
|
return __awaiter(this, void 0, void 0, function* () {
|
|
708
|
-
yield (0,
|
|
708
|
+
yield (0, ZipReader_1.extractBackupZip)(artifactPath, targetDir);
|
|
709
709
|
fs_1.default.rmSync(path_1.default.join(targetDir, ".webframez-backup"), { recursive: true, force: true });
|
|
710
710
|
});
|
|
711
711
|
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.extractBackupZip = void 0;
|
|
16
|
+
const fs_1 = __importDefault(require("fs"));
|
|
17
|
+
const path_1 = __importDefault(require("path"));
|
|
18
|
+
const stream_1 = require("stream");
|
|
19
|
+
const promises_1 = require("stream/promises");
|
|
20
|
+
const yauzl_1 = require("yauzl");
|
|
21
|
+
function ensureDirectory(directory) {
|
|
22
|
+
const parent = path_1.default.dirname(directory);
|
|
23
|
+
if (parent !== directory) {
|
|
24
|
+
ensureDirectory(parent);
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
fs_1.default.mkdirSync(directory, { mode: 0o700 });
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (error.code !== "EEXIST") {
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const info = fs_1.default.lstatSync(directory);
|
|
35
|
+
if (!info.isDirectory() || info.isSymbolicLink()) {
|
|
36
|
+
throw new Error(`Restore directory must not be a symlink or special file: ${directory}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function entryTarget(root, entry) {
|
|
40
|
+
const name = entry.fileName;
|
|
41
|
+
const parts = name.replace(/\/$/, "").split("/");
|
|
42
|
+
if (!name || name.includes("\\") || /[\x00-\x1f\x7f:]/.test(name) || parts.some((part) => !part || part === "." || part === "..")) {
|
|
43
|
+
throw new Error(`Unsafe ZIP entry path: ${JSON.stringify(name)}`);
|
|
44
|
+
}
|
|
45
|
+
const mode = (entry.externalFileAttributes >>> 16) & 0xffff;
|
|
46
|
+
const type = mode & 0o170000;
|
|
47
|
+
if (type !== 0 && type !== 0o100000 && type !== 0o040000) {
|
|
48
|
+
throw new Error(`ZIP symlinks and special files are forbidden: ${name}`);
|
|
49
|
+
}
|
|
50
|
+
const directory = name.endsWith("/") || type === 0o040000;
|
|
51
|
+
if (directory && entry.uncompressedSize !== 0) {
|
|
52
|
+
throw new Error(`ZIP directory contains file data: ${name}`);
|
|
53
|
+
}
|
|
54
|
+
const target = path_1.default.resolve(root, ...parts);
|
|
55
|
+
if (!target.startsWith(root + path_1.default.sep)) {
|
|
56
|
+
throw new Error(`ZIP entry escapes restore directory: ${name}`);
|
|
57
|
+
}
|
|
58
|
+
return { target, directory, mode: mode & 0o777 };
|
|
59
|
+
}
|
|
60
|
+
function extractEntry(zip, entry, target, mode) {
|
|
61
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
62
|
+
ensureDirectory(path_1.default.dirname(target));
|
|
63
|
+
try {
|
|
64
|
+
const existing = fs_1.default.lstatSync(target);
|
|
65
|
+
if (!existing.isFile() || existing.isSymbolicLink() || existing.nlink !== 1) {
|
|
66
|
+
throw new Error(`Restore destination is not a private regular file: ${target}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (error.code !== "ENOENT") {
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Do not truncate until the opened inode is verified. O_NOFOLLOW rejects
|
|
75
|
+
// destination symlinks; nlink prevents modifying files shared outside restore.
|
|
76
|
+
const output = yield fs_1.default.promises.open(target, fs_1.default.constants.O_WRONLY | fs_1.default.constants.O_CREAT | fs_1.default.constants.O_NOFOLLOW | fs_1.default.constants.O_NONBLOCK, 0o600);
|
|
77
|
+
try {
|
|
78
|
+
const info = yield output.stat();
|
|
79
|
+
if (!info.isFile() || info.nlink !== 1) {
|
|
80
|
+
throw new Error(`Restore destination is not a private regular file: ${target}`);
|
|
81
|
+
}
|
|
82
|
+
yield output.truncate(0);
|
|
83
|
+
yield new Promise((resolve, reject) => {
|
|
84
|
+
zip.openReadStream(entry, (error, input) => {
|
|
85
|
+
if (error) {
|
|
86
|
+
reject(error);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
// Attach all stream error handlers synchronously, before an
|
|
90
|
+
// inflater can report a corrupt entry on the next tick (Node 22).
|
|
91
|
+
const sink = new stream_1.Writable({
|
|
92
|
+
write(chunk, _encoding, callback) {
|
|
93
|
+
output.writeFile(chunk).then(() => callback(), callback);
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
(0, promises_1.pipeline)(input, sink).then(resolve, reject);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
yield output.chmod(mode || 0o600);
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
yield output.close();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/** Extract regular backup files only, sequentially, into an owner-controlled directory. */
|
|
107
|
+
function extractBackupZip(archive, destination) {
|
|
108
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
109
|
+
const root = path_1.default.resolve(destination);
|
|
110
|
+
ensureDirectory(root);
|
|
111
|
+
const zip = yield new Promise((resolve, reject) => {
|
|
112
|
+
(0, yauzl_1.open)(archive, { lazyEntries: true, validateEntrySizes: true, strictFileNames: true }, (error, file) => error ? reject(error) : resolve(file));
|
|
113
|
+
});
|
|
114
|
+
yield new Promise((resolve, reject) => {
|
|
115
|
+
const seen = new Set();
|
|
116
|
+
let finished = false;
|
|
117
|
+
const fail = (error) => {
|
|
118
|
+
if (!finished) {
|
|
119
|
+
finished = true;
|
|
120
|
+
zip.close();
|
|
121
|
+
reject(error);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
zip.on("error", fail);
|
|
125
|
+
zip.on("end", () => { finished = true; resolve(); });
|
|
126
|
+
zip.on("entry", (entry) => {
|
|
127
|
+
void (() => __awaiter(this, void 0, void 0, function* () {
|
|
128
|
+
const { target, directory, mode } = entryTarget(root, entry);
|
|
129
|
+
if (seen.has(target)) {
|
|
130
|
+
throw new Error(`Duplicate ZIP entry: ${entry.fileName}`);
|
|
131
|
+
}
|
|
132
|
+
seen.add(target);
|
|
133
|
+
if (directory) {
|
|
134
|
+
ensureDirectory(target);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
yield extractEntry(zip, entry, target, mode);
|
|
138
|
+
}
|
|
139
|
+
if (!finished) {
|
|
140
|
+
zip.readEntry();
|
|
141
|
+
}
|
|
142
|
+
}))().catch(fail);
|
|
143
|
+
});
|
|
144
|
+
zip.readEntry();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
exports.extractBackupZip = extractBackupZip;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webtypen/webframez-core",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.85",
|
|
4
4
|
"description": "The core package of the typescript based webframez (web-framework) by webtypen.de",
|
|
5
5
|
"homepage": "https://webtypen.de/",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,7 +47,8 @@
|
|
|
47
47
|
"@types/mime-types": "^2.1.4",
|
|
48
48
|
"@types/node": "^18.11.9",
|
|
49
49
|
"@types/qs": "^6.9.7",
|
|
50
|
-
"
|
|
50
|
+
"@types/yauzl": "^3.4.0",
|
|
51
|
+
"moment": "^2.31.0",
|
|
51
52
|
"ts-node": "^10.9.1",
|
|
52
53
|
"typescript": "^4.9.3"
|
|
53
54
|
},
|
|
@@ -55,11 +56,11 @@
|
|
|
55
56
|
"bcryptjs": "^3.0.3",
|
|
56
57
|
"busboy": "^1.6.0",
|
|
57
58
|
"dotenv": "^8.2.0",
|
|
58
|
-
"extract-zip": "^2.0.1",
|
|
59
59
|
"lodash": "^4.17.20",
|
|
60
60
|
"mime-types": "^2.1.35",
|
|
61
61
|
"moment-timezone": "^0.6.0",
|
|
62
|
-
"qs": "^6.
|
|
63
|
-
"querystring": "^0.2.1"
|
|
62
|
+
"qs": "^6.16.0",
|
|
63
|
+
"querystring": "^0.2.1",
|
|
64
|
+
"yauzl": "^3.4.0"
|
|
64
65
|
}
|
|
65
66
|
}
|