firebase-tools 10.2.2 → 10.4.0
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.
- package/lib/commands/deploy.js +1 -1
- package/lib/commands/experimental-functions-shell.js +1 -1
- package/lib/commands/ext-configure.js +68 -7
- package/lib/commands/ext-export.js +10 -9
- package/lib/commands/ext-install.js +73 -9
- package/lib/commands/ext-uninstall.js +9 -0
- package/lib/commands/ext-update.js +58 -3
- package/lib/commands/functions-config-export.js +2 -2
- package/lib/commands/functions-shell.js +1 -1
- package/lib/commands/hosting-channel-create.js +2 -2
- package/lib/commands/hosting-channel-delete.js +2 -2
- package/lib/commands/hosting-channel-deploy.js +2 -2
- package/lib/commands/hosting-channel-list.js +2 -2
- package/lib/commands/hosting-channel-open.js +2 -2
- package/lib/commands/hosting-sites-delete.js +2 -2
- package/lib/commands/serve.js +1 -1
- package/lib/commands/target-apply.js +2 -2
- package/lib/commands/target-clear.js +2 -2
- package/lib/commands/target-remove.js +2 -2
- package/lib/commands/target.js +2 -2
- package/lib/config.js +9 -3
- package/lib/deploy/extensions/planner.js +15 -9
- package/lib/deploy/functions/backend.js +10 -1
- package/lib/deploy/functions/checkIam.js +4 -4
- package/lib/deploy/functions/prepare.js +2 -1
- package/lib/deploy/functions/release/fabricator.js +4 -4
- package/lib/deploy/functions/release/planner.js +34 -20
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +6 -1
- package/lib/deploy/functions/runtimes/node/index.js +27 -0
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +36 -13
- package/lib/deploy/functions/services/firebaseAlerts.js +30 -0
- package/lib/deploy/functions/services/index.js +9 -1
- package/lib/deploy/functions/services/storage.js +10 -4
- package/lib/deploy/functions/triggerRegionHelper.js +1 -1
- package/lib/emulator/auth/apiSpec.js +37 -0
- package/lib/emulator/commandUtils.js +2 -2
- package/lib/emulator/constants.js +1 -0
- package/lib/emulator/controller.js +9 -7
- package/lib/emulator/extensions/validation.js +37 -2
- package/lib/emulator/extensionsEmulator.js +47 -9
- package/lib/emulator/functionsEmulator.js +17 -12
- package/lib/emulator/functionsEmulatorShared.js +34 -11
- package/lib/emulator/storage/apis/firebase.js +316 -341
- package/lib/emulator/storage/apis/gcloud.js +238 -113
- package/lib/emulator/storage/crc.js +5 -1
- package/lib/emulator/storage/errors.js +9 -0
- package/lib/emulator/storage/files.js +161 -304
- package/lib/emulator/storage/index.js +25 -74
- package/lib/emulator/storage/metadata.js +63 -49
- package/lib/emulator/storage/multipart.js +62 -0
- package/lib/emulator/storage/persistence.js +78 -0
- package/lib/emulator/storage/rules/config.js +34 -0
- package/lib/emulator/storage/rules/manager.js +98 -0
- package/lib/emulator/storage/rules/runtime.js +4 -0
- package/lib/emulator/storage/rules/utils.js +48 -0
- package/lib/emulator/storage/server.js +2 -2
- package/lib/emulator/storage/upload.js +106 -0
- package/lib/extensions/askUserForParam.js +77 -28
- package/lib/extensions/emulator/optionsHelper.js +35 -3
- package/lib/extensions/extensionsHelper.js +19 -10
- package/lib/extensions/manifest.js +142 -14
- package/lib/extensions/paramHelper.js +32 -9
- package/lib/fsutils.js +14 -1
- package/lib/functions/env.js +4 -6
- package/lib/functions/events/v2.js +11 -0
- package/lib/gcp/cloudfunctions.js +20 -7
- package/lib/gcp/cloudfunctionsv2.js +30 -12
- package/lib/gcp/resourceManager.js +4 -4
- package/lib/requireConfig.js +11 -9
- package/lib/serve/functions.js +2 -1
- package/lib/utils.js +14 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/lib/deploy/extensions/params.js +0 -42
- package/lib/deploy/functions/eventTypes.js +0 -10
- package/lib/prepareUpload.js +0 -44
|
@@ -5,34 +5,14 @@ const emulatorLogger_1 = require("../../emulatorLogger");
|
|
|
5
5
|
const types_1 = require("../../types");
|
|
6
6
|
const zlib_1 = require("zlib");
|
|
7
7
|
const metadata_1 = require("../metadata");
|
|
8
|
-
const mime = require("mime");
|
|
9
8
|
const express_1 = require("express");
|
|
10
9
|
const registry_1 = require("../../registry");
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE).log("WARN", `Can not process SDK request with no loaded ruleset`);
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
if (["Bearer owner", "Firebase owner"].includes(opts.authorization || "")) {
|
|
18
|
-
return true;
|
|
19
|
-
}
|
|
20
|
-
const { permitted, issues } = await opts.ruleset.verify({
|
|
21
|
-
method: opts.method,
|
|
22
|
-
path: opts.path,
|
|
23
|
-
file: opts.file,
|
|
24
|
-
token: opts.authorization ? opts.authorization.split(" ")[1] : undefined,
|
|
25
|
-
});
|
|
26
|
-
if (issues.exist()) {
|
|
27
|
-
issues.all.forEach((warningOrError) => {
|
|
28
|
-
emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE).log("WARN", warningOrError);
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
return !!permitted;
|
|
32
|
-
}
|
|
10
|
+
const multipart_1 = require("../multipart");
|
|
11
|
+
const errors_1 = require("../errors");
|
|
12
|
+
const upload_1 = require("../upload");
|
|
33
13
|
function createFirebaseEndpoints(emulator) {
|
|
34
14
|
const firebaseStorageAPI = (0, express_1.Router)();
|
|
35
|
-
const { storageLayer } = emulator;
|
|
15
|
+
const { storageLayer, uploadService } = emulator;
|
|
36
16
|
if (process.env.STORAGE_EMULATOR_DEBUG) {
|
|
37
17
|
firebaseStorageAPI.use((req, res, next) => {
|
|
38
18
|
console.log("--------------INCOMING REQUEST--------------");
|
|
@@ -72,8 +52,10 @@ function createFirebaseEndpoints(emulator) {
|
|
|
72
52
|
next();
|
|
73
53
|
});
|
|
74
54
|
}
|
|
75
|
-
firebaseStorageAPI.use((req, res, next) => {
|
|
76
|
-
|
|
55
|
+
firebaseStorageAPI.use(/.*\/b\/(.+?)\/.*/, (req, res, next) => {
|
|
56
|
+
const bucketId = req.params[0];
|
|
57
|
+
storageLayer.createBucket(bucketId);
|
|
58
|
+
if (!emulator.rulesManager.getRuleset(bucketId)) {
|
|
77
59
|
emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE).log("WARN", "Permission denied because no Storage ruleset is currently loaded, check your rules for syntax errors.");
|
|
78
60
|
return res.status(403).json({
|
|
79
61
|
error: {
|
|
@@ -84,355 +66,353 @@ function createFirebaseEndpoints(emulator) {
|
|
|
84
66
|
}
|
|
85
67
|
next();
|
|
86
68
|
});
|
|
87
|
-
firebaseStorageAPI.use(/.*\/b\/(.+?)\/.*/, (req, res, next) => {
|
|
88
|
-
storageLayer.createBucket(req.params[0]);
|
|
89
|
-
next();
|
|
90
|
-
});
|
|
91
69
|
firebaseStorageAPI.get("/b/:bucketId/o/:objectId", async (req, res) => {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
path: operationPath,
|
|
103
|
-
file: rulesFiles,
|
|
104
|
-
authorization: req.header("authorization"),
|
|
105
|
-
});
|
|
106
|
-
const isPermittedViaToken = req.query.token && md && md.downloadTokens.includes(req.query.token.toString());
|
|
107
|
-
const isRequestPermitted = isPermittedViaHeader || !!isPermittedViaToken;
|
|
108
|
-
if (!isRequestPermitted) {
|
|
109
|
-
res.sendStatus(403);
|
|
110
|
-
return;
|
|
70
|
+
var _a;
|
|
71
|
+
let metadata;
|
|
72
|
+
let data;
|
|
73
|
+
try {
|
|
74
|
+
({ metadata, data } = await storageLayer.handleGetObject({
|
|
75
|
+
bucketId: req.params.bucketId,
|
|
76
|
+
decodedObjectId: decodeURIComponent(req.params.objectId),
|
|
77
|
+
authorization: req.header("authorization"),
|
|
78
|
+
downloadToken: (_a = req.query.token) === null || _a === void 0 ? void 0 : _a.toString(),
|
|
79
|
+
}));
|
|
111
80
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
81
|
+
catch (err) {
|
|
82
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
83
|
+
return res.sendStatus(404);
|
|
84
|
+
}
|
|
85
|
+
else if (err instanceof errors_1.ForbiddenError) {
|
|
86
|
+
return res.status(403).json({
|
|
87
|
+
error: {
|
|
88
|
+
code: 403,
|
|
89
|
+
message: `Permission denied. No READ permission.`,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
throw err;
|
|
115
94
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
isGZipped = true;
|
|
95
|
+
if (!metadata.downloadTokens.length) {
|
|
96
|
+
metadata.addDownloadToken();
|
|
119
97
|
}
|
|
120
98
|
if (req.query.alt === "media") {
|
|
121
|
-
|
|
122
|
-
if (!data) {
|
|
123
|
-
res.sendStatus(404);
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
99
|
+
const isGZipped = metadata.contentEncoding === "gzip";
|
|
126
100
|
if (isGZipped) {
|
|
127
101
|
data = (0, zlib_1.gunzipSync)(data);
|
|
128
102
|
}
|
|
129
103
|
res.setHeader("Accept-Ranges", "bytes");
|
|
130
|
-
res.setHeader("Content-Type",
|
|
131
|
-
setObjectHeaders(res,
|
|
132
|
-
const byteRange =
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
end: rangeEnd ? parseInt(rangeEnd) : data.byteLength,
|
|
138
|
-
};
|
|
139
|
-
res.setHeader("Content-Range", `bytes ${range.start}-${range.end - 1}/${data.byteLength}`);
|
|
140
|
-
res.status(206).end(data.slice(range.start, range.end));
|
|
104
|
+
res.setHeader("Content-Type", metadata.contentType);
|
|
105
|
+
setObjectHeaders(res, metadata, { "Content-Encoding": isGZipped ? "identity" : undefined });
|
|
106
|
+
const byteRange = req.range(data.byteLength, { combine: true });
|
|
107
|
+
if (Array.isArray(byteRange) && byteRange.type === "bytes" && byteRange.length > 0) {
|
|
108
|
+
const range = byteRange[0];
|
|
109
|
+
res.setHeader("Content-Range", `${byteRange.type} ${range.start}-${range.end}/${data.byteLength}`);
|
|
110
|
+
res.status(206).end(data.slice(range.start, range.end + 1));
|
|
141
111
|
}
|
|
142
112
|
else {
|
|
143
113
|
res.end(data);
|
|
144
114
|
}
|
|
145
115
|
return;
|
|
146
116
|
}
|
|
147
|
-
|
|
148
|
-
md.addDownloadToken();
|
|
149
|
-
}
|
|
150
|
-
res.json(new metadata_1.OutgoingFirebaseMetadata(md));
|
|
117
|
+
return res.json(new metadata_1.OutgoingFirebaseMetadata(metadata));
|
|
151
118
|
});
|
|
152
|
-
const handleMetadataUpdate = async (req, res) => {
|
|
153
|
-
const md = storageLayer.getMetadata(req.params.bucketId, req.params.objectId);
|
|
154
|
-
if (!md) {
|
|
155
|
-
res.sendStatus(404);
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
const decodedObjectId = decodeURIComponent(req.params.objectId);
|
|
159
|
-
const operationPath = ["b", req.params.bucketId, "o", decodedObjectId].join("/");
|
|
160
|
-
if (!(await isPermitted({
|
|
161
|
-
ruleset: emulator.rules,
|
|
162
|
-
method: types_2.RulesetOperationMethod.UPDATE,
|
|
163
|
-
path: operationPath,
|
|
164
|
-
authorization: req.header("authorization"),
|
|
165
|
-
file: {
|
|
166
|
-
before: md.asRulesResource(),
|
|
167
|
-
after: md.asRulesResource(req.body),
|
|
168
|
-
},
|
|
169
|
-
}))) {
|
|
170
|
-
return res.status(403).json({
|
|
171
|
-
error: {
|
|
172
|
-
code: 403,
|
|
173
|
-
message: `Permission denied. No WRITE permission.`,
|
|
174
|
-
},
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
md.update(req.body);
|
|
178
|
-
setObjectHeaders(res, md);
|
|
179
|
-
const outgoingMetadata = new metadata_1.OutgoingFirebaseMetadata(md);
|
|
180
|
-
res.json(outgoingMetadata);
|
|
181
|
-
return;
|
|
182
|
-
};
|
|
183
119
|
firebaseStorageAPI.get("/b/:bucketId/o", async (req, res) => {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
path: operationPath,
|
|
196
|
-
file: {},
|
|
197
|
-
authorization: req.header("authorization"),
|
|
198
|
-
}))) {
|
|
199
|
-
return res.status(403).json({
|
|
200
|
-
error: {
|
|
201
|
-
code: 403,
|
|
202
|
-
message: `Permission denied. No LIST permission.`,
|
|
203
|
-
},
|
|
120
|
+
var _a, _b;
|
|
121
|
+
const maxResults = (_a = req.query.maxResults) === null || _a === void 0 ? void 0 : _a.toString();
|
|
122
|
+
let response;
|
|
123
|
+
try {
|
|
124
|
+
response = await storageLayer.handleListObjects({
|
|
125
|
+
bucketId: req.params.bucketId,
|
|
126
|
+
prefix: req.query.prefix ? req.query.prefix.toString() : "",
|
|
127
|
+
delimiter: req.query.delimiter ? req.query.delimiter.toString() : "",
|
|
128
|
+
pageToken: (_b = req.query.pageToken) === null || _b === void 0 ? void 0 : _b.toString(),
|
|
129
|
+
maxResults: maxResults ? +maxResults : undefined,
|
|
130
|
+
authorization: req.header("authorization"),
|
|
204
131
|
});
|
|
205
132
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const handleUpload = async (req, res) => {
|
|
209
|
-
if (req.query.create_token || req.query.delete_token) {
|
|
210
|
-
const decodedObjectId = decodeURIComponent(req.params.objectId);
|
|
211
|
-
const operationPath = ["b", req.params.bucketId, "o", decodedObjectId].join("/");
|
|
212
|
-
const mdBefore = storageLayer.getMetadata(req.params.bucketId, req.params.objectId);
|
|
213
|
-
if (!(await isPermitted({
|
|
214
|
-
ruleset: emulator.rules,
|
|
215
|
-
method: types_2.RulesetOperationMethod.UPDATE,
|
|
216
|
-
path: operationPath,
|
|
217
|
-
authorization: req.header("authorization"),
|
|
218
|
-
file: {
|
|
219
|
-
before: mdBefore === null || mdBefore === void 0 ? void 0 : mdBefore.asRulesResource(),
|
|
220
|
-
},
|
|
221
|
-
}))) {
|
|
133
|
+
catch (err) {
|
|
134
|
+
if (err instanceof errors_1.ForbiddenError) {
|
|
222
135
|
return res.status(403).json({
|
|
223
136
|
error: {
|
|
224
137
|
code: 403,
|
|
225
|
-
message: `Permission denied. No
|
|
138
|
+
message: `Permission denied. No LIST permission.`,
|
|
226
139
|
},
|
|
227
140
|
});
|
|
228
141
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
142
|
+
throw err;
|
|
143
|
+
}
|
|
144
|
+
return res.json(response);
|
|
145
|
+
});
|
|
146
|
+
const reqBodyToBuffer = async (req) => {
|
|
147
|
+
if (req.body instanceof Buffer) {
|
|
148
|
+
return Buffer.from(req.body);
|
|
149
|
+
}
|
|
150
|
+
const bufs = [];
|
|
151
|
+
req.on("data", (data) => {
|
|
152
|
+
bufs.push(data);
|
|
153
|
+
});
|
|
154
|
+
await new Promise((resolve) => {
|
|
155
|
+
req.on("end", () => {
|
|
156
|
+
resolve();
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
return Buffer.concat(bufs);
|
|
160
|
+
};
|
|
161
|
+
const handleUpload = async (req, res) => {
|
|
162
|
+
if (!req.query.name) {
|
|
163
|
+
res.sendStatus(400);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const bucketId = req.params.bucketId;
|
|
167
|
+
const objectId = req.query.name.toString();
|
|
168
|
+
const uploadType = req.header("x-goog-upload-protocol");
|
|
169
|
+
if (uploadType === "multipart") {
|
|
170
|
+
const contentTypeHeader = req.header("content-type");
|
|
171
|
+
if (!contentTypeHeader) {
|
|
172
|
+
return res.sendStatus(400);
|
|
236
173
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
174
|
+
let metadataRaw;
|
|
175
|
+
let dataRaw;
|
|
176
|
+
try {
|
|
177
|
+
({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await reqBodyToBuffer(req)));
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
if (err instanceof Error) {
|
|
181
|
+
return res.status(400).json({
|
|
182
|
+
error: {
|
|
183
|
+
code: 400,
|
|
184
|
+
message: err.toString(),
|
|
185
|
+
},
|
|
186
|
+
});
|
|
244
187
|
}
|
|
245
|
-
|
|
188
|
+
throw err;
|
|
246
189
|
}
|
|
247
|
-
|
|
248
|
-
|
|
190
|
+
const upload = uploadService.multipartUpload({
|
|
191
|
+
bucketId,
|
|
192
|
+
objectId,
|
|
193
|
+
metadataRaw,
|
|
194
|
+
dataRaw: dataRaw,
|
|
195
|
+
authorization: req.header("authorization"),
|
|
196
|
+
});
|
|
197
|
+
let metadata;
|
|
198
|
+
try {
|
|
199
|
+
metadata = await storageLayer.handleUploadObject(upload);
|
|
249
200
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
201
|
+
catch (err) {
|
|
202
|
+
if (err instanceof errors_1.ForbiddenError) {
|
|
203
|
+
return res.status(403).json({
|
|
204
|
+
error: {
|
|
205
|
+
code: 403,
|
|
206
|
+
message: "Permission denied. No WRITE permission.",
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
throw err;
|
|
253
211
|
}
|
|
254
|
-
|
|
255
|
-
return res.json(new metadata_1.OutgoingFirebaseMetadata(
|
|
212
|
+
metadata.addDownloadToken();
|
|
213
|
+
return res.status(200).json(new metadata_1.OutgoingFirebaseMetadata(metadata));
|
|
256
214
|
}
|
|
257
|
-
|
|
215
|
+
const uploadCommand = req.header("x-goog-upload-command");
|
|
216
|
+
if (!uploadCommand) {
|
|
258
217
|
res.sendStatus(400);
|
|
259
218
|
return;
|
|
260
219
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
res.sendStatus(400);
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
const boundary = `--${contentType.split("boundary=")[1]}`;
|
|
270
|
-
const bodyString = req.body.toString();
|
|
271
|
-
const bodyStringParts = bodyString.split(boundary).filter((v) => v);
|
|
272
|
-
const metadataString = bodyStringParts[0].split("\r\n")[3];
|
|
273
|
-
const blobParts = bodyStringParts[1].split("\r\n");
|
|
274
|
-
const blobContentTypeString = blobParts[1];
|
|
275
|
-
if (!blobContentTypeString || !blobContentTypeString.startsWith("Content-Type: ")) {
|
|
276
|
-
res.sendStatus(400);
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
const blobContentType = blobContentTypeString.slice("Content-Type: ".length);
|
|
280
|
-
const bodyBuffer = req.body;
|
|
281
|
-
const metadataSegment = `${boundary}${bodyString.split(boundary)[1]}`;
|
|
282
|
-
const dataSegment = `${boundary}${bodyString.split(boundary).slice(2)[0]}`;
|
|
283
|
-
const dataSegmentHeader = (dataSegment.match(/.+Content-Type:.+?\r\n\r\n/s) || [])[0];
|
|
284
|
-
if (!dataSegmentHeader) {
|
|
285
|
-
res.sendStatus(400);
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
const bufferOffset = metadataSegment.length + dataSegmentHeader.length;
|
|
289
|
-
const blobBytes = Buffer.from(bodyBuffer.slice(bufferOffset, -`\r\n${boundary}--`.length));
|
|
290
|
-
const md = storageLayer.oneShotUpload(req.params.bucketId, name, blobContentType, JSON.parse(metadataString), Buffer.from(blobBytes));
|
|
291
|
-
if (!md) {
|
|
292
|
-
res.sendStatus(400);
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
const operationPath = ["b", req.params.bucketId, "o", name].join("/");
|
|
296
|
-
if (!(await isPermitted({
|
|
297
|
-
ruleset: emulator.rules,
|
|
298
|
-
method: types_2.RulesetOperationMethod.CREATE,
|
|
299
|
-
path: operationPath,
|
|
220
|
+
if (uploadCommand === "start") {
|
|
221
|
+
const upload = uploadService.startResumableUpload({
|
|
222
|
+
bucketId,
|
|
223
|
+
objectId,
|
|
224
|
+
metadataRaw: JSON.stringify(req.body),
|
|
300
225
|
authorization: req.header("authorization"),
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
226
|
+
});
|
|
227
|
+
res.header("x-goog-upload-chunk-granularity", "10000");
|
|
228
|
+
res.header("x-goog-upload-control-url", "");
|
|
229
|
+
res.header("x-goog-upload-status", "active");
|
|
230
|
+
const emulatorInfo = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE);
|
|
231
|
+
res.header("x-goog-upload-url", `http://${req.hostname}:${emulatorInfo === null || emulatorInfo === void 0 ? void 0 : emulatorInfo.port}/v0/b/${bucketId}/o?name=${objectId}&upload_id=${upload.id}&upload_protocol=resumable`);
|
|
232
|
+
res.header("x-gupload-uploadid", upload.id);
|
|
233
|
+
return res.sendStatus(200);
|
|
234
|
+
}
|
|
235
|
+
if (!req.query.upload_id) {
|
|
236
|
+
return res.sendStatus(400);
|
|
237
|
+
}
|
|
238
|
+
const uploadId = req.query.upload_id.toString();
|
|
239
|
+
if (uploadCommand === "query") {
|
|
240
|
+
let upload;
|
|
241
|
+
try {
|
|
242
|
+
upload = uploadService.getResumableUpload(uploadId);
|
|
312
243
|
}
|
|
313
|
-
|
|
314
|
-
|
|
244
|
+
catch (err) {
|
|
245
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
246
|
+
return res.sendStatus(404);
|
|
247
|
+
}
|
|
248
|
+
throw err;
|
|
315
249
|
}
|
|
316
|
-
res.
|
|
317
|
-
return;
|
|
250
|
+
res.header("X-Goog-Upload-Size-Received", upload.size.toString());
|
|
251
|
+
return res.sendStatus(200);
|
|
318
252
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
if (uploadCommand === "start") {
|
|
327
|
-
let objectContentType = req.header("x-goog-upload-header-content-type") ||
|
|
328
|
-
req.header("x-goog-upload-content-type");
|
|
329
|
-
if (!objectContentType) {
|
|
330
|
-
const mimeTypeFromName = mime.getType(name);
|
|
331
|
-
if (!mimeTypeFromName) {
|
|
332
|
-
objectContentType = "application/octet-stream";
|
|
333
|
-
}
|
|
334
|
-
else {
|
|
335
|
-
objectContentType = mimeTypeFromName;
|
|
336
|
-
}
|
|
253
|
+
if (uploadCommand === "cancel") {
|
|
254
|
+
try {
|
|
255
|
+
uploadService.cancelResumableUpload(uploadId);
|
|
256
|
+
}
|
|
257
|
+
catch (err) {
|
|
258
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
259
|
+
return res.sendStatus(404);
|
|
337
260
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
const emulatorInfo = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE);
|
|
341
|
-
res.header("x-goog-upload-chunk-granularity", "10000");
|
|
342
|
-
res.header("x-goog-upload-control-url", "");
|
|
343
|
-
res.header("x-goog-upload-status", "active");
|
|
344
|
-
res.header("x-goog-upload-url", `http://${req.hostname}:${emulatorInfo === null || emulatorInfo === void 0 ? void 0 : emulatorInfo.port}/v0/b/${req.params.bucketId}/o?name=${req.query.name}&upload_id=${upload.uploadId}&upload_protocol=resumable`);
|
|
345
|
-
res.header("x-gupload-uploadid", upload.uploadId);
|
|
346
|
-
res.status(200).send();
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
if (!req.query.upload_id) {
|
|
350
|
-
res.sendStatus(400);
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
const uploadId = req.query.upload_id.toString();
|
|
354
|
-
if (uploadCommand === "query") {
|
|
355
|
-
const upload = storageLayer.queryUpload(uploadId);
|
|
356
|
-
if (!upload) {
|
|
357
|
-
res.sendStatus(400);
|
|
358
|
-
return;
|
|
261
|
+
else if (err instanceof upload_1.NotCancellableError) {
|
|
262
|
+
return res.sendStatus(400);
|
|
359
263
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
264
|
+
throw err;
|
|
265
|
+
}
|
|
266
|
+
return res.sendStatus(200);
|
|
267
|
+
}
|
|
268
|
+
if (uploadCommand.includes("upload")) {
|
|
269
|
+
let upload;
|
|
270
|
+
try {
|
|
271
|
+
upload = uploadService.continueResumableUpload(uploadId, await reqBodyToBuffer(req));
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
275
|
+
return res.sendStatus(404);
|
|
369
276
|
}
|
|
370
|
-
else {
|
|
371
|
-
res.sendStatus(
|
|
277
|
+
else if (err instanceof upload_1.UploadNotActiveError) {
|
|
278
|
+
return res.sendStatus(400);
|
|
372
279
|
}
|
|
373
|
-
|
|
280
|
+
throw err;
|
|
281
|
+
}
|
|
282
|
+
if (!uploadCommand.includes("finalize")) {
|
|
283
|
+
res.header("x-goog-upload-status", "active");
|
|
284
|
+
res.header("x-gupload-uploadid", upload.id);
|
|
285
|
+
return res.sendStatus(200);
|
|
374
286
|
}
|
|
287
|
+
}
|
|
288
|
+
if (uploadCommand.includes("finalize")) {
|
|
375
289
|
let upload;
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
290
|
+
try {
|
|
291
|
+
upload = uploadService.finalizeResumableUpload(uploadId);
|
|
292
|
+
}
|
|
293
|
+
catch (err) {
|
|
294
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
295
|
+
return res.sendStatus(404);
|
|
296
|
+
}
|
|
297
|
+
else if (err instanceof upload_1.UploadNotActiveError) {
|
|
298
|
+
return res.sendStatus(400);
|
|
299
|
+
}
|
|
300
|
+
throw err;
|
|
301
|
+
}
|
|
302
|
+
let metadata;
|
|
303
|
+
try {
|
|
304
|
+
metadata = await storageLayer.handleUploadObject(upload);
|
|
305
|
+
}
|
|
306
|
+
catch (err) {
|
|
307
|
+
if (err instanceof errors_1.ForbiddenError) {
|
|
308
|
+
return res.status(403).json({
|
|
309
|
+
error: {
|
|
310
|
+
code: 403,
|
|
311
|
+
message: `Permission denied. No WRITE permission.`,
|
|
312
|
+
},
|
|
381
313
|
});
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
314
|
+
}
|
|
315
|
+
throw err;
|
|
316
|
+
}
|
|
317
|
+
metadata.addDownloadToken();
|
|
318
|
+
return res.status(200).json(new metadata_1.OutgoingFirebaseMetadata(metadata));
|
|
319
|
+
}
|
|
320
|
+
return res.sendStatus(400);
|
|
321
|
+
};
|
|
322
|
+
const handleTokenRequest = (req, res) => {
|
|
323
|
+
var _a, _b;
|
|
324
|
+
if (!req.query.create_token && !req.query.delete_token) {
|
|
325
|
+
return res.sendStatus(400);
|
|
326
|
+
}
|
|
327
|
+
const bucketId = req.params.bucketId;
|
|
328
|
+
const decodedObjectId = decodeURIComponent(req.params.objectId);
|
|
329
|
+
const authorization = req.header("authorization");
|
|
330
|
+
let metadata;
|
|
331
|
+
if (req.query.create_token) {
|
|
332
|
+
if (req.query.create_token !== "true") {
|
|
333
|
+
return res.sendStatus(400);
|
|
334
|
+
}
|
|
335
|
+
try {
|
|
336
|
+
metadata = storageLayer.handleCreateDownloadToken({
|
|
337
|
+
bucketId,
|
|
338
|
+
decodedObjectId,
|
|
339
|
+
authorization,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
catch (err) {
|
|
343
|
+
if (err instanceof errors_1.ForbiddenError) {
|
|
344
|
+
return res.status(403).json({
|
|
345
|
+
error: {
|
|
346
|
+
code: 403,
|
|
347
|
+
message: `Missing admin credentials.`,
|
|
348
|
+
},
|
|
387
349
|
});
|
|
388
350
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
res.sendStatus(400);
|
|
392
|
-
return;
|
|
351
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
352
|
+
return res.sendStatus(404);
|
|
393
353
|
}
|
|
394
|
-
|
|
395
|
-
res.header("x-gupload-uploadid", upload.uploadId);
|
|
354
|
+
throw err;
|
|
396
355
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
after: storageLayer.createMetadata(upload).asRulesResource(),
|
|
410
|
-
},
|
|
411
|
-
}))) {
|
|
412
|
-
storageLayer.deleteFile(upload.bucketId, name);
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
try {
|
|
359
|
+
metadata = storageLayer.handleDeleteDownloadToken({
|
|
360
|
+
bucketId,
|
|
361
|
+
decodedObjectId,
|
|
362
|
+
token: (_b = (_a = req.query["delete_token"]) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : "",
|
|
363
|
+
authorization,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
catch (err) {
|
|
367
|
+
if (err instanceof errors_1.ForbiddenError) {
|
|
413
368
|
return res.status(403).json({
|
|
414
369
|
error: {
|
|
415
370
|
code: 403,
|
|
416
|
-
message: `
|
|
371
|
+
message: `Missing admin credentials.`,
|
|
417
372
|
},
|
|
418
373
|
});
|
|
419
374
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const md = uploadedFile.metadata;
|
|
423
|
-
if (md.downloadTokens.length === 0) {
|
|
424
|
-
md.addDownloadToken();
|
|
375
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
376
|
+
return res.sendStatus(404);
|
|
425
377
|
}
|
|
426
|
-
|
|
378
|
+
throw err;
|
|
427
379
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
380
|
+
}
|
|
381
|
+
setObjectHeaders(res, metadata);
|
|
382
|
+
return res.json(new metadata_1.OutgoingFirebaseMetadata(metadata));
|
|
383
|
+
};
|
|
384
|
+
const handleObjectPostRequest = async (req, res) => {
|
|
385
|
+
if (req.query.create_token || req.query.delete_token) {
|
|
386
|
+
return handleTokenRequest(req, res);
|
|
387
|
+
}
|
|
388
|
+
return handleUpload(req, res);
|
|
389
|
+
};
|
|
390
|
+
const handleMetadataUpdate = async (req, res) => {
|
|
391
|
+
let metadata;
|
|
392
|
+
try {
|
|
393
|
+
metadata = await storageLayer.handleUpdateObjectMetadata({
|
|
394
|
+
bucketId: req.params.bucketId,
|
|
395
|
+
decodedObjectId: decodeURIComponent(req.params.objectId),
|
|
396
|
+
metadata: req.body,
|
|
397
|
+
authorization: req.header("authorization"),
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
catch (err) {
|
|
401
|
+
if (err instanceof errors_1.ForbiddenError) {
|
|
402
|
+
return res.status(403).json({
|
|
403
|
+
error: {
|
|
404
|
+
code: 403,
|
|
405
|
+
message: `Permission denied. No WRITE permission.`,
|
|
406
|
+
},
|
|
407
|
+
});
|
|
431
408
|
}
|
|
432
|
-
|
|
433
|
-
res.sendStatus(
|
|
409
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
410
|
+
return res.sendStatus(404);
|
|
434
411
|
}
|
|
412
|
+
throw err;
|
|
435
413
|
}
|
|
414
|
+
setObjectHeaders(res, metadata);
|
|
415
|
+
return res.json(new metadata_1.OutgoingFirebaseMetadata(metadata));
|
|
436
416
|
};
|
|
437
417
|
firebaseStorageAPI.patch("/b/:bucketId/o/:objectId", handleMetadataUpdate);
|
|
438
418
|
firebaseStorageAPI.put("/b/:bucketId/o/:objectId?", async (req, res) => {
|
|
@@ -441,38 +421,33 @@ function createFirebaseEndpoints(emulator) {
|
|
|
441
421
|
case "patch":
|
|
442
422
|
return handleMetadataUpdate(req, res);
|
|
443
423
|
default:
|
|
444
|
-
return
|
|
424
|
+
return handleObjectPostRequest(req, res);
|
|
445
425
|
}
|
|
446
426
|
});
|
|
447
|
-
firebaseStorageAPI.post("/b/:bucketId/o/:objectId?",
|
|
427
|
+
firebaseStorageAPI.post("/b/:bucketId/o/:objectId?", handleObjectPostRequest);
|
|
448
428
|
firebaseStorageAPI.delete("/b/:bucketId/o/:objectId", async (req, res) => {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
rulesFiles.before = md.asRulesResource();
|
|
455
|
-
}
|
|
456
|
-
if (!(await isPermitted({
|
|
457
|
-
ruleset: emulator.rules,
|
|
458
|
-
method: types_2.RulesetOperationMethod.DELETE,
|
|
459
|
-
path: operationPath,
|
|
460
|
-
authorization: req.header("authorization"),
|
|
461
|
-
file: rulesFiles,
|
|
462
|
-
}))) {
|
|
463
|
-
return res.status(403).json({
|
|
464
|
-
error: {
|
|
465
|
-
code: 403,
|
|
466
|
-
message: `Permission denied. No WRITE permission.`,
|
|
467
|
-
},
|
|
429
|
+
try {
|
|
430
|
+
await storageLayer.handleDeleteObject({
|
|
431
|
+
bucketId: req.params.bucketId,
|
|
432
|
+
decodedObjectId: decodeURIComponent(req.params.objectId),
|
|
433
|
+
authorization: req.header("authorization"),
|
|
468
434
|
});
|
|
469
435
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
436
|
+
catch (err) {
|
|
437
|
+
if (err instanceof errors_1.ForbiddenError) {
|
|
438
|
+
return res.status(403).json({
|
|
439
|
+
error: {
|
|
440
|
+
code: 403,
|
|
441
|
+
message: `Permission denied. No WRITE permission.`,
|
|
442
|
+
},
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
446
|
+
return res.sendStatus(404);
|
|
447
|
+
}
|
|
448
|
+
throw err;
|
|
473
449
|
}
|
|
474
|
-
|
|
475
|
-
res.sendStatus(200);
|
|
450
|
+
res.sendStatus(204);
|
|
476
451
|
});
|
|
477
452
|
firebaseStorageAPI.get("/", (req, res) => {
|
|
478
453
|
res.json({ emulator: "storage" });
|