firebase-tools 10.2.2 → 10.3.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/ext-configure.js +58 -4
- package/lib/commands/ext-export.js +4 -9
- package/lib/commands/ext-install.js +63 -4
- package/lib/commands/ext-uninstall.js +9 -0
- package/lib/commands/ext-update.js +55 -2
- package/lib/config.js +6 -3
- package/lib/deploy/extensions/planner.js +6 -6
- 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 +24 -8
- 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/constants.js +1 -0
- package/lib/emulator/controller.js +9 -7
- package/lib/emulator/extensions/validation.js +37 -2
- package/lib/emulator/extensionsEmulator.js +44 -9
- package/lib/emulator/functionsEmulator.js +13 -8
- package/lib/emulator/functionsEmulatorShared.js +17 -10
- package/lib/emulator/storage/apis/firebase.js +312 -335
- 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 +27 -73
- 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 +33 -0
- package/lib/emulator/storage/rules/manager.js +81 -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/emulator/optionsHelper.js +35 -3
- package/lib/extensions/extensionsHelper.js +19 -10
- package/lib/extensions/manifest.js +109 -13
- package/lib/extensions/paramHelper.js +5 -4
- package/lib/functions/env.js +4 -6
- package/lib/functions/events/v2.js +11 -0
- package/lib/gcp/cloudfunctions.js +18 -6
- package/lib/gcp/cloudfunctionsv2.js +30 -12
- package/lib/gcp/resourceManager.js +4 -4
- 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
|
@@ -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--------------");
|
|
@@ -89,350 +69,352 @@ function createFirebaseEndpoints(emulator) {
|
|
|
89
69
|
next();
|
|
90
70
|
});
|
|
91
71
|
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;
|
|
72
|
+
var _a;
|
|
73
|
+
let metadata;
|
|
74
|
+
let data;
|
|
75
|
+
try {
|
|
76
|
+
({ metadata, data } = await storageLayer.handleGetObject({
|
|
77
|
+
bucketId: req.params.bucketId,
|
|
78
|
+
decodedObjectId: decodeURIComponent(req.params.objectId),
|
|
79
|
+
authorization: req.header("authorization"),
|
|
80
|
+
downloadToken: (_a = req.query.token) === null || _a === void 0 ? void 0 : _a.toString(),
|
|
81
|
+
}));
|
|
111
82
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
83
|
+
catch (err) {
|
|
84
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
85
|
+
return res.sendStatus(404);
|
|
86
|
+
}
|
|
87
|
+
else if (err instanceof errors_1.ForbiddenError) {
|
|
88
|
+
return res.status(403).json({
|
|
89
|
+
error: {
|
|
90
|
+
code: 403,
|
|
91
|
+
message: `Permission denied. No READ permission.`,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
throw err;
|
|
115
96
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
isGZipped = true;
|
|
97
|
+
if (!metadata.downloadTokens.length) {
|
|
98
|
+
metadata.addDownloadToken();
|
|
119
99
|
}
|
|
120
100
|
if (req.query.alt === "media") {
|
|
121
|
-
|
|
122
|
-
if (!data) {
|
|
123
|
-
res.sendStatus(404);
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
101
|
+
const isGZipped = metadata.contentEncoding === "gzip";
|
|
126
102
|
if (isGZipped) {
|
|
127
103
|
data = (0, zlib_1.gunzipSync)(data);
|
|
128
104
|
}
|
|
129
105
|
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));
|
|
106
|
+
res.setHeader("Content-Type", metadata.contentType);
|
|
107
|
+
setObjectHeaders(res, metadata, { "Content-Encoding": isGZipped ? "identity" : undefined });
|
|
108
|
+
const byteRange = req.range(data.byteLength, { combine: true });
|
|
109
|
+
if (Array.isArray(byteRange) && byteRange.type === "bytes" && byteRange.length > 0) {
|
|
110
|
+
const range = byteRange[0];
|
|
111
|
+
res.setHeader("Content-Range", `${byteRange.type} ${range.start}-${range.end}/${data.byteLength}`);
|
|
112
|
+
res.status(206).end(data.slice(range.start, range.end + 1));
|
|
141
113
|
}
|
|
142
114
|
else {
|
|
143
115
|
res.end(data);
|
|
144
116
|
}
|
|
145
117
|
return;
|
|
146
118
|
}
|
|
147
|
-
|
|
148
|
-
md.addDownloadToken();
|
|
149
|
-
}
|
|
150
|
-
res.json(new metadata_1.OutgoingFirebaseMetadata(md));
|
|
119
|
+
return res.json(new metadata_1.OutgoingFirebaseMetadata(metadata));
|
|
151
120
|
});
|
|
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
121
|
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
|
-
},
|
|
122
|
+
var _a, _b;
|
|
123
|
+
const maxResults = (_a = req.query.maxResults) === null || _a === void 0 ? void 0 : _a.toString();
|
|
124
|
+
let response;
|
|
125
|
+
try {
|
|
126
|
+
response = await storageLayer.handleListObjects({
|
|
127
|
+
bucketId: req.params.bucketId,
|
|
128
|
+
prefix: req.query.prefix ? req.query.prefix.toString() : "",
|
|
129
|
+
delimiter: req.query.delimiter ? req.query.delimiter.toString() : "",
|
|
130
|
+
pageToken: (_b = req.query.pageToken) === null || _b === void 0 ? void 0 : _b.toString(),
|
|
131
|
+
maxResults: maxResults ? +maxResults : undefined,
|
|
132
|
+
authorization: req.header("authorization"),
|
|
204
133
|
});
|
|
205
134
|
}
|
|
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
|
-
}))) {
|
|
135
|
+
catch (err) {
|
|
136
|
+
if (err instanceof errors_1.ForbiddenError) {
|
|
222
137
|
return res.status(403).json({
|
|
223
138
|
error: {
|
|
224
139
|
code: 403,
|
|
225
|
-
message: `Permission denied. No
|
|
140
|
+
message: `Permission denied. No LIST permission.`,
|
|
226
141
|
},
|
|
227
142
|
});
|
|
228
143
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
146
|
+
return res.json(response);
|
|
147
|
+
});
|
|
148
|
+
const reqBodyToBuffer = async (req) => {
|
|
149
|
+
if (req.body instanceof Buffer) {
|
|
150
|
+
return Buffer.from(req.body);
|
|
151
|
+
}
|
|
152
|
+
const bufs = [];
|
|
153
|
+
req.on("data", (data) => {
|
|
154
|
+
bufs.push(data);
|
|
155
|
+
});
|
|
156
|
+
await new Promise((resolve) => {
|
|
157
|
+
req.on("end", () => {
|
|
158
|
+
resolve();
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
return Buffer.concat(bufs);
|
|
162
|
+
};
|
|
163
|
+
const handleUpload = async (req, res) => {
|
|
164
|
+
if (!req.query.name) {
|
|
165
|
+
res.sendStatus(400);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const bucketId = req.params.bucketId;
|
|
169
|
+
const objectId = req.query.name.toString();
|
|
170
|
+
const uploadType = req.header("x-goog-upload-protocol");
|
|
171
|
+
if (uploadType === "multipart") {
|
|
172
|
+
const contentTypeHeader = req.header("content-type");
|
|
173
|
+
if (!contentTypeHeader) {
|
|
174
|
+
return res.sendStatus(400);
|
|
236
175
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
176
|
+
let metadataRaw;
|
|
177
|
+
let dataRaw;
|
|
178
|
+
try {
|
|
179
|
+
({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await reqBodyToBuffer(req)));
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
if (err instanceof Error) {
|
|
183
|
+
return res.status(400).json({
|
|
184
|
+
error: {
|
|
185
|
+
code: 400,
|
|
186
|
+
message: err.toString(),
|
|
187
|
+
},
|
|
188
|
+
});
|
|
244
189
|
}
|
|
245
|
-
|
|
190
|
+
throw err;
|
|
246
191
|
}
|
|
247
|
-
|
|
248
|
-
|
|
192
|
+
const upload = uploadService.multipartUpload({
|
|
193
|
+
bucketId,
|
|
194
|
+
objectId,
|
|
195
|
+
metadataRaw,
|
|
196
|
+
dataRaw: dataRaw,
|
|
197
|
+
authorization: req.header("authorization"),
|
|
198
|
+
});
|
|
199
|
+
let metadata;
|
|
200
|
+
try {
|
|
201
|
+
metadata = await storageLayer.handleUploadObject(upload);
|
|
249
202
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
203
|
+
catch (err) {
|
|
204
|
+
if (err instanceof errors_1.ForbiddenError) {
|
|
205
|
+
return res.status(403).json({
|
|
206
|
+
error: {
|
|
207
|
+
code: 403,
|
|
208
|
+
message: "Permission denied. No WRITE permission.",
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
throw err;
|
|
253
213
|
}
|
|
254
|
-
|
|
255
|
-
return res.json(new metadata_1.OutgoingFirebaseMetadata(
|
|
214
|
+
metadata.addDownloadToken();
|
|
215
|
+
return res.status(200).json(new metadata_1.OutgoingFirebaseMetadata(metadata));
|
|
256
216
|
}
|
|
257
|
-
|
|
217
|
+
const uploadCommand = req.header("x-goog-upload-command");
|
|
218
|
+
if (!uploadCommand) {
|
|
258
219
|
res.sendStatus(400);
|
|
259
220
|
return;
|
|
260
221
|
}
|
|
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,
|
|
222
|
+
if (uploadCommand === "start") {
|
|
223
|
+
const upload = uploadService.startResumableUpload({
|
|
224
|
+
bucketId,
|
|
225
|
+
objectId,
|
|
226
|
+
metadataRaw: JSON.stringify(req.body),
|
|
300
227
|
authorization: req.header("authorization"),
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
228
|
+
});
|
|
229
|
+
res.header("x-goog-upload-chunk-granularity", "10000");
|
|
230
|
+
res.header("x-goog-upload-control-url", "");
|
|
231
|
+
res.header("x-goog-upload-status", "active");
|
|
232
|
+
const emulatorInfo = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE);
|
|
233
|
+
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`);
|
|
234
|
+
res.header("x-gupload-uploadid", upload.id);
|
|
235
|
+
return res.sendStatus(200);
|
|
236
|
+
}
|
|
237
|
+
if (!req.query.upload_id) {
|
|
238
|
+
return res.sendStatus(400);
|
|
239
|
+
}
|
|
240
|
+
const uploadId = req.query.upload_id.toString();
|
|
241
|
+
if (uploadCommand === "query") {
|
|
242
|
+
let upload;
|
|
243
|
+
try {
|
|
244
|
+
upload = uploadService.getResumableUpload(uploadId);
|
|
312
245
|
}
|
|
313
|
-
|
|
314
|
-
|
|
246
|
+
catch (err) {
|
|
247
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
248
|
+
return res.sendStatus(404);
|
|
249
|
+
}
|
|
250
|
+
throw err;
|
|
315
251
|
}
|
|
316
|
-
res.
|
|
317
|
-
return;
|
|
252
|
+
res.header("X-Goog-Upload-Size-Received", upload.size.toString());
|
|
253
|
+
return res.sendStatus(200);
|
|
318
254
|
}
|
|
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
|
-
}
|
|
255
|
+
if (uploadCommand === "cancel") {
|
|
256
|
+
try {
|
|
257
|
+
uploadService.cancelResumableUpload(uploadId);
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
261
|
+
return res.sendStatus(404);
|
|
337
262
|
}
|
|
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;
|
|
263
|
+
else if (err instanceof upload_1.NotCancellableError) {
|
|
264
|
+
return res.sendStatus(400);
|
|
359
265
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
266
|
+
throw err;
|
|
267
|
+
}
|
|
268
|
+
return res.sendStatus(200);
|
|
269
|
+
}
|
|
270
|
+
if (uploadCommand.includes("upload")) {
|
|
271
|
+
let upload;
|
|
272
|
+
try {
|
|
273
|
+
upload = uploadService.continueResumableUpload(uploadId, await reqBodyToBuffer(req));
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
277
|
+
return res.sendStatus(404);
|
|
369
278
|
}
|
|
370
|
-
else {
|
|
371
|
-
res.sendStatus(
|
|
279
|
+
else if (err instanceof upload_1.UploadNotActiveError) {
|
|
280
|
+
return res.sendStatus(400);
|
|
372
281
|
}
|
|
373
|
-
|
|
282
|
+
throw err;
|
|
283
|
+
}
|
|
284
|
+
if (!uploadCommand.includes("finalize")) {
|
|
285
|
+
res.header("x-goog-upload-status", "active");
|
|
286
|
+
res.header("x-gupload-uploadid", upload.id);
|
|
287
|
+
return res.sendStatus(200);
|
|
374
288
|
}
|
|
289
|
+
}
|
|
290
|
+
if (uploadCommand.includes("finalize")) {
|
|
375
291
|
let upload;
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
292
|
+
try {
|
|
293
|
+
upload = uploadService.finalizeResumableUpload(uploadId);
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
297
|
+
return res.sendStatus(404);
|
|
298
|
+
}
|
|
299
|
+
else if (err instanceof upload_1.UploadNotActiveError) {
|
|
300
|
+
return res.sendStatus(400);
|
|
301
|
+
}
|
|
302
|
+
throw err;
|
|
303
|
+
}
|
|
304
|
+
let metadata;
|
|
305
|
+
try {
|
|
306
|
+
metadata = await storageLayer.handleUploadObject(upload);
|
|
307
|
+
}
|
|
308
|
+
catch (err) {
|
|
309
|
+
if (err instanceof errors_1.ForbiddenError) {
|
|
310
|
+
return res.status(403).json({
|
|
311
|
+
error: {
|
|
312
|
+
code: 403,
|
|
313
|
+
message: `Permission denied. No WRITE permission.`,
|
|
314
|
+
},
|
|
381
315
|
});
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
316
|
+
}
|
|
317
|
+
throw err;
|
|
318
|
+
}
|
|
319
|
+
metadata.addDownloadToken();
|
|
320
|
+
return res.status(200).json(new metadata_1.OutgoingFirebaseMetadata(metadata));
|
|
321
|
+
}
|
|
322
|
+
return res.sendStatus(400);
|
|
323
|
+
};
|
|
324
|
+
const handleTokenRequest = (req, res) => {
|
|
325
|
+
var _a, _b;
|
|
326
|
+
if (!req.query.create_token && !req.query.delete_token) {
|
|
327
|
+
return res.sendStatus(400);
|
|
328
|
+
}
|
|
329
|
+
const bucketId = req.params.bucketId;
|
|
330
|
+
const decodedObjectId = decodeURIComponent(req.params.objectId);
|
|
331
|
+
const authorization = req.header("authorization");
|
|
332
|
+
let metadata;
|
|
333
|
+
if (req.query.create_token) {
|
|
334
|
+
if (req.query.create_token !== "true") {
|
|
335
|
+
return res.sendStatus(400);
|
|
336
|
+
}
|
|
337
|
+
try {
|
|
338
|
+
metadata = storageLayer.handleCreateDownloadToken({
|
|
339
|
+
bucketId,
|
|
340
|
+
decodedObjectId,
|
|
341
|
+
authorization,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
catch (err) {
|
|
345
|
+
if (err instanceof errors_1.ForbiddenError) {
|
|
346
|
+
return res.status(403).json({
|
|
347
|
+
error: {
|
|
348
|
+
code: 403,
|
|
349
|
+
message: `Missing admin credentials.`,
|
|
350
|
+
},
|
|
387
351
|
});
|
|
388
352
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
res.sendStatus(400);
|
|
392
|
-
return;
|
|
353
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
354
|
+
return res.sendStatus(404);
|
|
393
355
|
}
|
|
394
|
-
|
|
395
|
-
res.header("x-gupload-uploadid", upload.uploadId);
|
|
356
|
+
throw err;
|
|
396
357
|
}
|
|
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);
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
try {
|
|
361
|
+
metadata = storageLayer.handleDeleteDownloadToken({
|
|
362
|
+
bucketId,
|
|
363
|
+
decodedObjectId,
|
|
364
|
+
token: (_b = (_a = req.query["delete_token"]) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : "",
|
|
365
|
+
authorization,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
catch (err) {
|
|
369
|
+
if (err instanceof errors_1.ForbiddenError) {
|
|
413
370
|
return res.status(403).json({
|
|
414
371
|
error: {
|
|
415
372
|
code: 403,
|
|
416
|
-
message: `
|
|
373
|
+
message: `Missing admin credentials.`,
|
|
417
374
|
},
|
|
418
375
|
});
|
|
419
376
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const md = uploadedFile.metadata;
|
|
423
|
-
if (md.downloadTokens.length === 0) {
|
|
424
|
-
md.addDownloadToken();
|
|
377
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
378
|
+
return res.sendStatus(404);
|
|
425
379
|
}
|
|
426
|
-
|
|
380
|
+
throw err;
|
|
427
381
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
382
|
+
}
|
|
383
|
+
setObjectHeaders(res, metadata);
|
|
384
|
+
return res.json(new metadata_1.OutgoingFirebaseMetadata(metadata));
|
|
385
|
+
};
|
|
386
|
+
const handleObjectPostRequest = async (req, res) => {
|
|
387
|
+
if (req.query.create_token || req.query.delete_token) {
|
|
388
|
+
return handleTokenRequest(req, res);
|
|
389
|
+
}
|
|
390
|
+
return handleUpload(req, res);
|
|
391
|
+
};
|
|
392
|
+
const handleMetadataUpdate = async (req, res) => {
|
|
393
|
+
let metadata;
|
|
394
|
+
try {
|
|
395
|
+
metadata = await storageLayer.handleUpdateObjectMetadata({
|
|
396
|
+
bucketId: req.params.bucketId,
|
|
397
|
+
decodedObjectId: decodeURIComponent(req.params.objectId),
|
|
398
|
+
metadata: req.body,
|
|
399
|
+
authorization: req.header("authorization"),
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
catch (err) {
|
|
403
|
+
if (err instanceof errors_1.ForbiddenError) {
|
|
404
|
+
return res.status(403).json({
|
|
405
|
+
error: {
|
|
406
|
+
code: 403,
|
|
407
|
+
message: `Permission denied. No WRITE permission.`,
|
|
408
|
+
},
|
|
409
|
+
});
|
|
431
410
|
}
|
|
432
|
-
|
|
433
|
-
res.sendStatus(
|
|
411
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
412
|
+
return res.sendStatus(404);
|
|
434
413
|
}
|
|
414
|
+
throw err;
|
|
435
415
|
}
|
|
416
|
+
setObjectHeaders(res, metadata);
|
|
417
|
+
return res.json(new metadata_1.OutgoingFirebaseMetadata(metadata));
|
|
436
418
|
};
|
|
437
419
|
firebaseStorageAPI.patch("/b/:bucketId/o/:objectId", handleMetadataUpdate);
|
|
438
420
|
firebaseStorageAPI.put("/b/:bucketId/o/:objectId?", async (req, res) => {
|
|
@@ -441,38 +423,33 @@ function createFirebaseEndpoints(emulator) {
|
|
|
441
423
|
case "patch":
|
|
442
424
|
return handleMetadataUpdate(req, res);
|
|
443
425
|
default:
|
|
444
|
-
return
|
|
426
|
+
return handleObjectPostRequest(req, res);
|
|
445
427
|
}
|
|
446
428
|
});
|
|
447
|
-
firebaseStorageAPI.post("/b/:bucketId/o/:objectId?",
|
|
429
|
+
firebaseStorageAPI.post("/b/:bucketId/o/:objectId?", handleObjectPostRequest);
|
|
448
430
|
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
|
-
},
|
|
431
|
+
try {
|
|
432
|
+
await storageLayer.handleDeleteObject({
|
|
433
|
+
bucketId: req.params.bucketId,
|
|
434
|
+
decodedObjectId: decodeURIComponent(req.params.objectId),
|
|
435
|
+
authorization: req.header("authorization"),
|
|
468
436
|
});
|
|
469
437
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
438
|
+
catch (err) {
|
|
439
|
+
if (err instanceof errors_1.ForbiddenError) {
|
|
440
|
+
return res.status(403).json({
|
|
441
|
+
error: {
|
|
442
|
+
code: 403,
|
|
443
|
+
message: `Permission denied. No WRITE permission.`,
|
|
444
|
+
},
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
448
|
+
return res.sendStatus(404);
|
|
449
|
+
}
|
|
450
|
+
throw err;
|
|
473
451
|
}
|
|
474
|
-
|
|
475
|
-
res.sendStatus(200);
|
|
452
|
+
res.sendStatus(204);
|
|
476
453
|
});
|
|
477
454
|
firebaseStorageAPI.get("/", (req, res) => {
|
|
478
455
|
res.json({ emulator: "storage" });
|