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.
Files changed (54) hide show
  1. package/lib/commands/ext-configure.js +58 -4
  2. package/lib/commands/ext-export.js +4 -9
  3. package/lib/commands/ext-install.js +63 -4
  4. package/lib/commands/ext-uninstall.js +9 -0
  5. package/lib/commands/ext-update.js +55 -2
  6. package/lib/config.js +6 -3
  7. package/lib/deploy/extensions/planner.js +6 -6
  8. package/lib/deploy/functions/backend.js +10 -1
  9. package/lib/deploy/functions/checkIam.js +4 -4
  10. package/lib/deploy/functions/prepare.js +2 -1
  11. package/lib/deploy/functions/release/fabricator.js +4 -4
  12. package/lib/deploy/functions/release/planner.js +34 -20
  13. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +6 -1
  14. package/lib/deploy/functions/runtimes/node/index.js +27 -0
  15. package/lib/deploy/functions/runtimes/node/parseTriggers.js +24 -8
  16. package/lib/deploy/functions/services/firebaseAlerts.js +30 -0
  17. package/lib/deploy/functions/services/index.js +9 -1
  18. package/lib/deploy/functions/services/storage.js +10 -4
  19. package/lib/deploy/functions/triggerRegionHelper.js +1 -1
  20. package/lib/emulator/constants.js +1 -0
  21. package/lib/emulator/controller.js +9 -7
  22. package/lib/emulator/extensions/validation.js +37 -2
  23. package/lib/emulator/extensionsEmulator.js +44 -9
  24. package/lib/emulator/functionsEmulator.js +13 -8
  25. package/lib/emulator/functionsEmulatorShared.js +17 -10
  26. package/lib/emulator/storage/apis/firebase.js +312 -335
  27. package/lib/emulator/storage/apis/gcloud.js +238 -113
  28. package/lib/emulator/storage/crc.js +5 -1
  29. package/lib/emulator/storage/errors.js +9 -0
  30. package/lib/emulator/storage/files.js +161 -304
  31. package/lib/emulator/storage/index.js +27 -73
  32. package/lib/emulator/storage/metadata.js +63 -49
  33. package/lib/emulator/storage/multipart.js +62 -0
  34. package/lib/emulator/storage/persistence.js +78 -0
  35. package/lib/emulator/storage/rules/config.js +33 -0
  36. package/lib/emulator/storage/rules/manager.js +81 -0
  37. package/lib/emulator/storage/rules/utils.js +48 -0
  38. package/lib/emulator/storage/server.js +2 -2
  39. package/lib/emulator/storage/upload.js +106 -0
  40. package/lib/extensions/emulator/optionsHelper.js +35 -3
  41. package/lib/extensions/extensionsHelper.js +19 -10
  42. package/lib/extensions/manifest.js +109 -13
  43. package/lib/extensions/paramHelper.js +5 -4
  44. package/lib/functions/env.js +4 -6
  45. package/lib/functions/events/v2.js +11 -0
  46. package/lib/gcp/cloudfunctions.js +18 -6
  47. package/lib/gcp/cloudfunctionsv2.js +30 -12
  48. package/lib/gcp/resourceManager.js +4 -4
  49. package/lib/serve/functions.js +2 -1
  50. package/lib/utils.js +14 -1
  51. package/npm-shrinkwrap.json +2 -2
  52. package/package.json +1 -1
  53. package/lib/deploy/extensions/params.js +0 -42
  54. 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 types_2 = require("../rules/types");
12
- async function isPermitted(opts) {
13
- if (!opts.ruleset) {
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
- const decodedObjectId = decodeURIComponent(req.params.objectId);
93
- const operationPath = ["b", req.params.bucketId, "o", decodedObjectId].join("/");
94
- const md = storageLayer.getMetadata(req.params.bucketId, decodedObjectId);
95
- const rulesFiles = {};
96
- if (md) {
97
- rulesFiles.before = md.asRulesResource();
98
- }
99
- const isPermittedViaHeader = await isPermitted({
100
- ruleset: emulator.rules,
101
- method: types_2.RulesetOperationMethod.GET,
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
- if (!md) {
113
- res.sendStatus(404);
114
- return;
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
- let isGZipped = false;
117
- if (md.contentEncoding === "gzip") {
118
- isGZipped = true;
97
+ if (!metadata.downloadTokens.length) {
98
+ metadata.addDownloadToken();
119
99
  }
120
100
  if (req.query.alt === "media") {
121
- let data = storageLayer.getBytes(req.params.bucketId, req.params.objectId);
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", md.contentType);
131
- setObjectHeaders(res, md, { "Content-Encoding": isGZipped ? "identity" : undefined });
132
- const byteRange = [...(req.header("range") || "").split("bytes="), "", ""];
133
- const [rangeStart, rangeEnd] = byteRange[1].split("-");
134
- if (rangeStart) {
135
- const range = {
136
- start: parseInt(rangeStart),
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
- if (!md.downloadTokens.length) {
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
- let maxRes = undefined;
185
- if (req.query.maxResults) {
186
- maxRes = +req.query.maxResults.toString();
187
- }
188
- const delimiter = req.query.delimiter ? req.query.delimiter.toString() : "/";
189
- const pageToken = req.query.pageToken ? req.query.pageToken.toString() : undefined;
190
- const prefix = req.query.prefix ? req.query.prefix.toString() : "";
191
- const operationPath = ["b", req.params.bucketId, "o", prefix].join("/");
192
- if (!(await isPermitted({
193
- ruleset: emulator.rules,
194
- method: types_2.RulesetOperationMethod.LIST,
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
- res.json(storageLayer.listItemsAndPrefixes(req.params.bucketId, prefix, delimiter, pageToken, maxRes));
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 WRITE permission.`,
140
+ message: `Permission denied. No LIST permission.`,
226
141
  },
227
142
  });
228
143
  }
229
- if (!mdBefore) {
230
- return res.status(404).json({
231
- error: {
232
- code: 404,
233
- message: `Request object can not be found`,
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
- const createTokenParam = req.query["create_token"];
238
- const deleteTokenParam = req.query["delete_token"];
239
- let md;
240
- if (createTokenParam) {
241
- if (createTokenParam !== "true") {
242
- res.sendStatus(400);
243
- return;
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
- md = storageLayer.addDownloadToken(req.params.bucketId, req.params.objectId);
190
+ throw err;
246
191
  }
247
- else if (deleteTokenParam) {
248
- md = storageLayer.deleteDownloadToken(req.params.bucketId, req.params.objectId, deleteTokenParam.toString());
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
- if (!md) {
251
- res.sendStatus(404);
252
- return;
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
- setObjectHeaders(res, md);
255
- return res.json(new metadata_1.OutgoingFirebaseMetadata(md));
214
+ metadata.addDownloadToken();
215
+ return res.status(200).json(new metadata_1.OutgoingFirebaseMetadata(metadata));
256
216
  }
257
- if (!req.query.name) {
217
+ const uploadCommand = req.header("x-goog-upload-command");
218
+ if (!uploadCommand) {
258
219
  res.sendStatus(400);
259
220
  return;
260
221
  }
261
- const name = req.query.name.toString();
262
- const uploadType = req.header("x-goog-upload-protocol");
263
- if (uploadType === "multipart") {
264
- const contentType = req.header("content-type");
265
- if (!contentType || !contentType.startsWith("multipart/related")) {
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
- file: {
302
- after: md === null || md === void 0 ? void 0 : md.asRulesResource(),
303
- },
304
- }))) {
305
- storageLayer.deleteFile(md === null || md === void 0 ? void 0 : md.bucket, md === null || md === void 0 ? void 0 : md.name);
306
- return res.status(403).json({
307
- error: {
308
- code: 403,
309
- message: `Permission denied. No WRITE permission.`,
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
- if (md.downloadTokens.length === 0) {
314
- md.addDownloadToken();
246
+ catch (err) {
247
+ if (err instanceof errors_1.NotFoundError) {
248
+ return res.sendStatus(404);
249
+ }
250
+ throw err;
315
251
  }
316
- res.json(new metadata_1.OutgoingFirebaseMetadata(md));
317
- return;
252
+ res.header("X-Goog-Upload-Size-Received", upload.size.toString());
253
+ return res.sendStatus(200);
318
254
  }
319
- else {
320
- const operationPath = ["b", req.params.bucketId, "o", name].join("/");
321
- const uploadCommand = req.header("x-goog-upload-command");
322
- if (!uploadCommand) {
323
- res.sendStatus(400);
324
- return;
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
- const upload = storageLayer.startUpload(req.params.bucketId, name, objectContentType, req.body, req.header("authorization"));
339
- storageLayer.uploadBytes(upload.uploadId, Buffer.alloc(0));
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
- res.header("X-Goog-Upload-Size-Received", upload.currentBytesUploaded.toString());
361
- res.sendStatus(200);
362
- return;
363
- }
364
- if (uploadCommand === "cancel") {
365
- const upload = storageLayer.queryUpload(uploadId);
366
- if (upload) {
367
- const cancelled = storageLayer.cancelUpload(upload);
368
- res.sendStatus(cancelled ? 200 : 400);
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(404);
279
+ else if (err instanceof upload_1.UploadNotActiveError) {
280
+ return res.sendStatus(400);
372
281
  }
373
- return;
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
- if (uploadCommand.includes("upload")) {
377
- if (!(req.body instanceof Buffer)) {
378
- const bufs = [];
379
- req.on("data", (data) => {
380
- bufs.push(data);
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
- await new Promise((resolve) => {
383
- req.on("end", () => {
384
- req.body = Buffer.concat(bufs);
385
- resolve();
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
- upload = storageLayer.uploadBytes(uploadId, req.body);
390
- if (!upload) {
391
- res.sendStatus(400);
392
- return;
353
+ if (err instanceof errors_1.NotFoundError) {
354
+ return res.sendStatus(404);
393
355
  }
394
- res.header("x-goog-upload-status", "active");
395
- res.header("x-gupload-uploadid", upload.uploadId);
356
+ throw err;
396
357
  }
397
- if (uploadCommand.includes("finalize")) {
398
- upload = storageLayer.queryUpload(uploadId);
399
- if (!upload) {
400
- res.sendStatus(400);
401
- return;
402
- }
403
- if (!(await isPermitted({
404
- ruleset: emulator.rules,
405
- method: types_2.RulesetOperationMethod.CREATE,
406
- path: operationPath,
407
- authorization: upload.authorization,
408
- file: {
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: `Permission denied. No WRITE permission.`,
373
+ message: `Missing admin credentials.`,
417
374
  },
418
375
  });
419
376
  }
420
- res.header("x-goog-upload-status", "final");
421
- const uploadedFile = storageLayer.finalizeUpload(upload);
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
- res.json(new metadata_1.OutgoingFirebaseMetadata(uploadedFile.metadata));
380
+ throw err;
427
381
  }
428
- else if (!upload) {
429
- res.sendStatus(400);
430
- return;
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
- else {
433
- res.sendStatus(200);
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 handleUpload(req, res);
426
+ return handleObjectPostRequest(req, res);
445
427
  }
446
428
  });
447
- firebaseStorageAPI.post("/b/:bucketId/o/:objectId?", handleUpload);
429
+ firebaseStorageAPI.post("/b/:bucketId/o/:objectId?", handleObjectPostRequest);
448
430
  firebaseStorageAPI.delete("/b/:bucketId/o/:objectId", async (req, res) => {
449
- const decodedObjectId = decodeURIComponent(req.params.objectId);
450
- const operationPath = ["b", req.params.bucketId, "o", decodedObjectId].join("/");
451
- const md = storageLayer.getMetadata(req.params.bucketId, decodedObjectId);
452
- const rulesFiles = {};
453
- if (md) {
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
- if (!md) {
471
- res.sendStatus(404);
472
- return;
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
- storageLayer.deleteFile(req.params.bucketId, req.params.objectId);
475
- res.sendStatus(200);
452
+ res.sendStatus(204);
476
453
  });
477
454
  firebaseStorageAPI.get("/", (req, res) => {
478
455
  res.json({ emulator: "storage" });