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.
Files changed (76) hide show
  1. package/lib/commands/deploy.js +1 -1
  2. package/lib/commands/experimental-functions-shell.js +1 -1
  3. package/lib/commands/ext-configure.js +68 -7
  4. package/lib/commands/ext-export.js +10 -9
  5. package/lib/commands/ext-install.js +73 -9
  6. package/lib/commands/ext-uninstall.js +9 -0
  7. package/lib/commands/ext-update.js +58 -3
  8. package/lib/commands/functions-config-export.js +2 -2
  9. package/lib/commands/functions-shell.js +1 -1
  10. package/lib/commands/hosting-channel-create.js +2 -2
  11. package/lib/commands/hosting-channel-delete.js +2 -2
  12. package/lib/commands/hosting-channel-deploy.js +2 -2
  13. package/lib/commands/hosting-channel-list.js +2 -2
  14. package/lib/commands/hosting-channel-open.js +2 -2
  15. package/lib/commands/hosting-sites-delete.js +2 -2
  16. package/lib/commands/serve.js +1 -1
  17. package/lib/commands/target-apply.js +2 -2
  18. package/lib/commands/target-clear.js +2 -2
  19. package/lib/commands/target-remove.js +2 -2
  20. package/lib/commands/target.js +2 -2
  21. package/lib/config.js +9 -3
  22. package/lib/deploy/extensions/planner.js +15 -9
  23. package/lib/deploy/functions/backend.js +10 -1
  24. package/lib/deploy/functions/checkIam.js +4 -4
  25. package/lib/deploy/functions/prepare.js +2 -1
  26. package/lib/deploy/functions/release/fabricator.js +4 -4
  27. package/lib/deploy/functions/release/planner.js +34 -20
  28. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +6 -1
  29. package/lib/deploy/functions/runtimes/node/index.js +27 -0
  30. package/lib/deploy/functions/runtimes/node/parseTriggers.js +36 -13
  31. package/lib/deploy/functions/services/firebaseAlerts.js +30 -0
  32. package/lib/deploy/functions/services/index.js +9 -1
  33. package/lib/deploy/functions/services/storage.js +10 -4
  34. package/lib/deploy/functions/triggerRegionHelper.js +1 -1
  35. package/lib/emulator/auth/apiSpec.js +37 -0
  36. package/lib/emulator/commandUtils.js +2 -2
  37. package/lib/emulator/constants.js +1 -0
  38. package/lib/emulator/controller.js +9 -7
  39. package/lib/emulator/extensions/validation.js +37 -2
  40. package/lib/emulator/extensionsEmulator.js +47 -9
  41. package/lib/emulator/functionsEmulator.js +17 -12
  42. package/lib/emulator/functionsEmulatorShared.js +34 -11
  43. package/lib/emulator/storage/apis/firebase.js +316 -341
  44. package/lib/emulator/storage/apis/gcloud.js +238 -113
  45. package/lib/emulator/storage/crc.js +5 -1
  46. package/lib/emulator/storage/errors.js +9 -0
  47. package/lib/emulator/storage/files.js +161 -304
  48. package/lib/emulator/storage/index.js +25 -74
  49. package/lib/emulator/storage/metadata.js +63 -49
  50. package/lib/emulator/storage/multipart.js +62 -0
  51. package/lib/emulator/storage/persistence.js +78 -0
  52. package/lib/emulator/storage/rules/config.js +34 -0
  53. package/lib/emulator/storage/rules/manager.js +98 -0
  54. package/lib/emulator/storage/rules/runtime.js +4 -0
  55. package/lib/emulator/storage/rules/utils.js +48 -0
  56. package/lib/emulator/storage/server.js +2 -2
  57. package/lib/emulator/storage/upload.js +106 -0
  58. package/lib/extensions/askUserForParam.js +77 -28
  59. package/lib/extensions/emulator/optionsHelper.js +35 -3
  60. package/lib/extensions/extensionsHelper.js +19 -10
  61. package/lib/extensions/manifest.js +142 -14
  62. package/lib/extensions/paramHelper.js +32 -9
  63. package/lib/fsutils.js +14 -1
  64. package/lib/functions/env.js +4 -6
  65. package/lib/functions/events/v2.js +11 -0
  66. package/lib/gcp/cloudfunctions.js +20 -7
  67. package/lib/gcp/cloudfunctionsv2.js +30 -12
  68. package/lib/gcp/resourceManager.js +4 -4
  69. package/lib/requireConfig.js +11 -9
  70. package/lib/serve/functions.js +2 -1
  71. package/lib/utils.js +14 -1
  72. package/npm-shrinkwrap.json +2 -2
  73. package/package.json +1 -1
  74. package/lib/deploy/extensions/params.js +0 -42
  75. package/lib/deploy/functions/eventTypes.js +0 -10
  76. 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 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--------------");
@@ -72,8 +52,10 @@ function createFirebaseEndpoints(emulator) {
72
52
  next();
73
53
  });
74
54
  }
75
- firebaseStorageAPI.use((req, res, next) => {
76
- if (!emulator.rules) {
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
- 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;
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
- if (!md) {
113
- res.sendStatus(404);
114
- return;
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
- let isGZipped = false;
117
- if (md.contentEncoding === "gzip") {
118
- isGZipped = true;
95
+ if (!metadata.downloadTokens.length) {
96
+ metadata.addDownloadToken();
119
97
  }
120
98
  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
- }
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", 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));
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
- if (!md.downloadTokens.length) {
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
- 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
- },
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
- 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
- }))) {
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 WRITE permission.`,
138
+ message: `Permission denied. No LIST permission.`,
226
139
  },
227
140
  });
228
141
  }
229
- if (!mdBefore) {
230
- return res.status(404).json({
231
- error: {
232
- code: 404,
233
- message: `Request object can not be found`,
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
- 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;
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
- md = storageLayer.addDownloadToken(req.params.bucketId, req.params.objectId);
188
+ throw err;
246
189
  }
247
- else if (deleteTokenParam) {
248
- md = storageLayer.deleteDownloadToken(req.params.bucketId, req.params.objectId, deleteTokenParam.toString());
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
- if (!md) {
251
- res.sendStatus(404);
252
- return;
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
- setObjectHeaders(res, md);
255
- return res.json(new metadata_1.OutgoingFirebaseMetadata(md));
212
+ metadata.addDownloadToken();
213
+ return res.status(200).json(new metadata_1.OutgoingFirebaseMetadata(metadata));
256
214
  }
257
- if (!req.query.name) {
215
+ const uploadCommand = req.header("x-goog-upload-command");
216
+ if (!uploadCommand) {
258
217
  res.sendStatus(400);
259
218
  return;
260
219
  }
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,
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
- 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
- });
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
- if (md.downloadTokens.length === 0) {
314
- md.addDownloadToken();
244
+ catch (err) {
245
+ if (err instanceof errors_1.NotFoundError) {
246
+ return res.sendStatus(404);
247
+ }
248
+ throw err;
315
249
  }
316
- res.json(new metadata_1.OutgoingFirebaseMetadata(md));
317
- return;
250
+ res.header("X-Goog-Upload-Size-Received", upload.size.toString());
251
+ return res.sendStatus(200);
318
252
  }
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
- }
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
- 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;
261
+ else if (err instanceof upload_1.NotCancellableError) {
262
+ return res.sendStatus(400);
359
263
  }
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);
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(404);
277
+ else if (err instanceof upload_1.UploadNotActiveError) {
278
+ return res.sendStatus(400);
372
279
  }
373
- return;
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
- if (uploadCommand.includes("upload")) {
377
- if (!(req.body instanceof Buffer)) {
378
- const bufs = [];
379
- req.on("data", (data) => {
380
- bufs.push(data);
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
- await new Promise((resolve) => {
383
- req.on("end", () => {
384
- req.body = Buffer.concat(bufs);
385
- resolve();
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
- upload = storageLayer.uploadBytes(uploadId, req.body);
390
- if (!upload) {
391
- res.sendStatus(400);
392
- return;
351
+ if (err instanceof errors_1.NotFoundError) {
352
+ return res.sendStatus(404);
393
353
  }
394
- res.header("x-goog-upload-status", "active");
395
- res.header("x-gupload-uploadid", upload.uploadId);
354
+ throw err;
396
355
  }
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);
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: `Permission denied. No WRITE permission.`,
371
+ message: `Missing admin credentials.`,
417
372
  },
418
373
  });
419
374
  }
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();
375
+ if (err instanceof errors_1.NotFoundError) {
376
+ return res.sendStatus(404);
425
377
  }
426
- res.json(new metadata_1.OutgoingFirebaseMetadata(uploadedFile.metadata));
378
+ throw err;
427
379
  }
428
- else if (!upload) {
429
- res.sendStatus(400);
430
- return;
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
- else {
433
- res.sendStatus(200);
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 handleUpload(req, res);
424
+ return handleObjectPostRequest(req, res);
445
425
  }
446
426
  });
447
- firebaseStorageAPI.post("/b/:bucketId/o/:objectId?", handleUpload);
427
+ firebaseStorageAPI.post("/b/:bucketId/o/:objectId?", handleObjectPostRequest);
448
428
  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
- },
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
- if (!md) {
471
- res.sendStatus(404);
472
- return;
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
- storageLayer.deleteFile(req.params.bucketId, req.params.objectId);
475
- res.sendStatus(200);
450
+ res.sendStatus(204);
476
451
  });
477
452
  firebaseStorageAPI.get("/", (req, res) => {
478
453
  res.json({ emulator: "storage" });