firebase-tools 11.7.0 → 11.8.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.
@@ -60,12 +60,12 @@ exports.AllIngressSettings = [
60
60
  "ALLOW_INTERNAL_ONLY",
61
61
  "ALLOW_INTERNAL_AND_GCLB",
62
62
  ];
63
- async function resolveBackend(build, userEnvOpt, userEnvs) {
63
+ async function resolveBackend(build, userEnvOpt, userEnvs, nonInteractive) {
64
64
  var _a;
65
65
  const projectId = userEnvOpt.projectId;
66
66
  let paramValues = {};
67
67
  if (previews_1.previews.functionsparams) {
68
- paramValues = await params.resolveParams(build.params, projectId, envWithTypes(userEnvs));
68
+ paramValues = await params.resolveParams(build.params, projectId, envWithTypes(userEnvs), nonInteractive);
69
69
  const toWrite = {};
70
70
  for (const paramName of Object.keys(paramValues)) {
71
71
  if (userEnvs.hasOwnProperty(paramName)) {
@@ -102,7 +102,7 @@ function canSatisfyParam(param, value) {
102
102
  }
103
103
  (0, functional_1.assertExhaustive)(param);
104
104
  }
105
- async function resolveParams(params, projectId, userEnvs) {
105
+ async function resolveParams(params, projectId, userEnvs, nonInteractive) {
106
106
  const paramValues = {};
107
107
  const [provided, outstanding] = (0, functional_1.partition)(params, (param) => {
108
108
  return {}.hasOwnProperty.call(userEnvs, param.name);
@@ -117,6 +117,12 @@ async function resolveParams(params, projectId, userEnvs) {
117
117
  }
118
118
  paramValues[param.name] = userEnvs[param.name];
119
119
  }
120
+ if (nonInteractive && outstanding.length > 0) {
121
+ const envNames = outstanding.map((p) => p.name).join(", ");
122
+ throw new error_1.FirebaseError(`In non-interactive mode but have no value for the following environment variables: ${envNames}\n` +
123
+ "To continue, either run `firebase deploy` with an interactive terminal, or add values to a dotenv file. " +
124
+ "For information regarding how to use dotenv files, see https://firebase.google.com/docs/functions/config-env");
125
+ }
120
126
  for (const param of outstanding) {
121
127
  let paramDefault = param.default;
122
128
  if (paramDefault && isCEL(paramDefault)) {
@@ -78,7 +78,7 @@ async function prepare(context, options, payload) {
78
78
  const userEnvs = functionsEnv.loadUserEnvs(userEnvOpt);
79
79
  const envs = Object.assign(Object.assign({}, userEnvs), firebaseEnvs);
80
80
  const wantBuild = await runtimeDelegate.discoverBuild(runtimeConfig, firebaseEnvs);
81
- const wantBackend = await build.resolveBackend(wantBuild, userEnvOpt, userEnvs);
81
+ const wantBackend = await build.resolveBackend(wantBuild, userEnvOpt, userEnvs, options.nonInteractive);
82
82
  wantBackend.environmentVariables = envs;
83
83
  for (const endpoint of backend.allEndpoints(wantBackend)) {
84
84
  endpoint.environmentVariables = wantBackend.environmentVariables;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createFirebaseEndpoints = void 0;
4
4
  const emulatorLogger_1 = require("../../emulatorLogger");
5
5
  const types_1 = require("../../types");
6
+ const uuid = require("uuid");
6
7
  const zlib_1 = require("zlib");
7
8
  const metadata_1 = require("../metadata");
8
9
  const express_1 = require("express");
@@ -93,8 +94,8 @@ function createFirebaseEndpoints(emulator) {
93
94
  }
94
95
  throw err;
95
96
  }
96
- if (!metadata.downloadTokens.length) {
97
- metadata.addDownloadToken();
97
+ if (metadata.downloadTokens.length === 0) {
98
+ metadata.addDownloadToken(true);
98
99
  }
99
100
  if (req.query.alt === "media") {
100
101
  const isGZipped = metadata.contentEncoding === "gzip";
@@ -102,7 +103,8 @@ function createFirebaseEndpoints(emulator) {
102
103
  data = (0, zlib_1.gunzipSync)(data);
103
104
  }
104
105
  res.setHeader("Accept-Ranges", "bytes");
105
- res.setHeader("Content-Type", metadata.contentType);
106
+ res.setHeader("Content-Type", metadata.contentType || "application/octet-stream");
107
+ res.setHeader("Content-Disposition", metadata.contentDisposition || "inline");
106
108
  setObjectHeaders(res, metadata, { "Content-Encoding": isGZipped ? "identity" : undefined });
107
109
  const byteRange = req.range(data.byteLength, { combine: true });
108
110
  if (Array.isArray(byteRange) && byteRange.type === "bytes" && byteRange.length > 0) {
@@ -165,45 +167,26 @@ function createFirebaseEndpoints(emulator) {
165
167
  });
166
168
  });
167
169
  const handleUpload = async (req, res) => {
168
- var _a;
170
+ var _a, _b;
169
171
  const bucketId = req.params.bucketId;
170
172
  const objectId = req.params.objectId
171
173
  ? decodeURIComponent(req.params.objectId)
172
174
  : ((_a = req.query.name) === null || _a === void 0 ? void 0 : _a.toString()) || null;
173
- const uploadType = req.header("x-goog-upload-protocol");
174
- if (uploadType === "multipart") {
175
- if (!objectId) {
176
- res.sendStatus(400);
177
- return;
178
- }
179
- const contentTypeHeader = req.header("content-type");
180
- if (!contentTypeHeader) {
181
- return res.sendStatus(400);
182
- }
183
- let metadataRaw;
184
- let dataRaw;
185
- try {
186
- ({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await (0, request_1.reqBodyToBuffer)(req)));
175
+ const uploadType = (_b = req.header("x-goog-upload-protocol")) === null || _b === void 0 ? void 0 : _b.toString();
176
+ async function finalizeOneShotUpload(upload) {
177
+ var _a, _b, _c;
178
+ if (!((_b = (_a = upload.metadata) === null || _a === void 0 ? void 0 : _a.metadata) === null || _b === void 0 ? void 0 : _b.firebaseStorageDownloadTokens)) {
179
+ const customMetadata = Object.assign(Object.assign({}, (((_c = upload.metadata) === null || _c === void 0 ? void 0 : _c.metadata) || {})), { firebaseStorageDownloadTokens: uuid.v4() });
180
+ upload.metadata = Object.assign(Object.assign({}, (upload.metadata || {})), { metadata: customMetadata });
187
181
  }
188
- catch (err) {
189
- if (err instanceof Error) {
190
- return res.status(400).send(err.message);
191
- }
192
- throw err;
193
- }
194
- const upload = uploadService.multipartUpload({
195
- bucketId,
196
- objectId,
197
- metadataRaw,
198
- dataRaw: dataRaw,
199
- authorization: req.header("authorization"),
200
- });
201
182
  let metadata;
202
183
  try {
203
184
  metadata = await storageLayer.uploadObject(upload);
204
185
  }
205
186
  catch (err) {
206
187
  if (err instanceof errors_1.ForbiddenError) {
188
+ res.header("x-goog-upload-status", "final");
189
+ uploadService.setResponseCode(upload.id, 403);
207
190
  return res.status(403).json({
208
191
  error: {
209
192
  code: 403,
@@ -213,124 +196,153 @@ function createFirebaseEndpoints(emulator) {
213
196
  }
214
197
  throw err;
215
198
  }
216
- metadata.addDownloadToken(false);
199
+ if (!metadata.contentDisposition) {
200
+ metadata.contentDisposition = "inline";
201
+ }
217
202
  return res.status(200).json(new metadata_1.OutgoingFirebaseMetadata(metadata));
218
203
  }
219
- const uploadCommand = req.header("x-goog-upload-command");
220
- if (!uploadCommand) {
221
- res.sendStatus(400);
222
- return;
223
- }
224
- if (uploadCommand === "start") {
225
- if (!objectId) {
204
+ if (uploadType === "resumable") {
205
+ const uploadCommand = req.header("x-goog-upload-command");
206
+ if (!uploadCommand) {
226
207
  res.sendStatus(400);
227
208
  return;
228
209
  }
229
- const upload = uploadService.startResumableUpload({
230
- bucketId,
231
- objectId,
232
- metadataRaw: JSON.stringify(req.body),
233
- authorization: req.header("authorization"),
234
- });
235
- res.header("x-goog-upload-chunk-granularity", "10000");
236
- res.header("x-goog-upload-control-url", "");
237
- res.header("x-goog-upload-status", "active");
238
- res.header("x-gupload-uploadid", upload.id);
239
- const uploadUrl = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE, req);
240
- uploadUrl.pathname = `/v0/b/${bucketId}/o`;
241
- uploadUrl.searchParams.set("name", objectId);
242
- uploadUrl.searchParams.set("upload_id", upload.id);
243
- uploadUrl.searchParams.set("upload_protocol", "resumable");
244
- res.header("x-goog-upload-url", uploadUrl.toString());
245
- return res.sendStatus(200);
246
- }
247
- if (!req.query.upload_id) {
248
- return res.sendStatus(400);
249
- }
250
- const uploadId = req.query.upload_id.toString();
251
- if (uploadCommand === "query") {
252
- let upload;
253
- try {
254
- upload = uploadService.getResumableUpload(uploadId);
255
- }
256
- catch (err) {
257
- if (err instanceof errors_1.NotFoundError) {
258
- return res.sendStatus(404);
210
+ if (uploadCommand === "start") {
211
+ if (!objectId) {
212
+ res.sendStatus(400);
213
+ return;
259
214
  }
260
- throw err;
215
+ const upload = uploadService.startResumableUpload({
216
+ bucketId,
217
+ objectId,
218
+ metadataRaw: JSON.stringify(req.body),
219
+ authorization: req.header("authorization"),
220
+ });
221
+ res.header("x-goog-upload-chunk-granularity", "10000");
222
+ res.header("x-goog-upload-control-url", "");
223
+ res.header("x-goog-upload-status", "active");
224
+ res.header("x-gupload-uploadid", upload.id);
225
+ const uploadUrl = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE, req);
226
+ uploadUrl.pathname = `/v0/b/${bucketId}/o`;
227
+ uploadUrl.searchParams.set("name", objectId);
228
+ uploadUrl.searchParams.set("upload_id", upload.id);
229
+ uploadUrl.searchParams.set("upload_protocol", "resumable");
230
+ res.header("x-goog-upload-url", uploadUrl.toString());
231
+ return res.sendStatus(200);
261
232
  }
262
- res.header("X-Goog-Upload-Size-Received", upload.size.toString());
263
- return res.sendStatus(200);
264
- }
265
- if (uploadCommand === "cancel") {
266
- try {
267
- uploadService.cancelResumableUpload(uploadId);
233
+ if (!req.query.upload_id) {
234
+ return res.sendStatus(400);
268
235
  }
269
- catch (err) {
270
- if (err instanceof errors_1.NotFoundError) {
271
- return res.sendStatus(404);
236
+ const uploadId = req.query.upload_id.toString();
237
+ if (uploadCommand === "query") {
238
+ let upload;
239
+ try {
240
+ upload = uploadService.getResumableUpload(uploadId);
272
241
  }
273
- else if (err instanceof upload_1.NotCancellableError) {
274
- return res.sendStatus(400);
242
+ catch (err) {
243
+ if (err instanceof errors_1.NotFoundError) {
244
+ return res.sendStatus(404);
245
+ }
246
+ throw err;
275
247
  }
276
- throw err;
277
- }
278
- return res.sendStatus(200);
279
- }
280
- if (uploadCommand.includes("upload")) {
281
- let upload;
282
- try {
283
- upload = uploadService.continueResumableUpload(uploadId, await (0, request_1.reqBodyToBuffer)(req));
248
+ res.header("X-Goog-Upload-Size-Received", upload.size.toString());
249
+ return res.sendStatus(200);
284
250
  }
285
- catch (err) {
286
- if (err instanceof errors_1.NotFoundError) {
287
- return res.sendStatus(404);
251
+ if (uploadCommand === "cancel") {
252
+ try {
253
+ uploadService.cancelResumableUpload(uploadId);
288
254
  }
289
- else if (err instanceof upload_1.UploadNotActiveError) {
290
- return res.sendStatus(400);
255
+ catch (err) {
256
+ if (err instanceof errors_1.NotFoundError) {
257
+ return res.sendStatus(404);
258
+ }
259
+ else if (err instanceof upload_1.NotCancellableError) {
260
+ return res.sendStatus(400);
261
+ }
262
+ throw err;
291
263
  }
292
- throw err;
293
- }
294
- if (!uploadCommand.includes("finalize")) {
295
- res.header("x-goog-upload-status", "active");
296
- res.header("x-gupload-uploadid", upload.id);
297
264
  return res.sendStatus(200);
298
265
  }
299
- }
300
- if (uploadCommand.includes("finalize")) {
301
- let upload;
302
- try {
303
- upload = uploadService.finalizeResumableUpload(uploadId);
266
+ if (uploadCommand.includes("upload")) {
267
+ let upload;
268
+ try {
269
+ upload = uploadService.continueResumableUpload(uploadId, await (0, request_1.reqBodyToBuffer)(req));
270
+ }
271
+ catch (err) {
272
+ if (err instanceof errors_1.NotFoundError) {
273
+ return res.sendStatus(404);
274
+ }
275
+ else if (err instanceof upload_1.UploadNotActiveError) {
276
+ return res.sendStatus(400);
277
+ }
278
+ throw err;
279
+ }
280
+ if (!uploadCommand.includes("finalize")) {
281
+ res.header("x-goog-upload-status", "active");
282
+ res.header("x-gupload-uploadid", upload.id);
283
+ return res.sendStatus(200);
284
+ }
304
285
  }
305
- catch (err) {
306
- if (err instanceof errors_1.NotFoundError) {
307
- return res.sendStatus(404);
286
+ if (uploadCommand.includes("finalize")) {
287
+ let upload;
288
+ try {
289
+ upload = uploadService.finalizeResumableUpload(uploadId);
308
290
  }
309
- else if (err instanceof upload_1.UploadNotActiveError) {
310
- return res.sendStatus(400);
291
+ catch (err) {
292
+ if (err instanceof errors_1.NotFoundError) {
293
+ uploadService.setResponseCode(uploadId, 404);
294
+ return res.sendStatus(404);
295
+ }
296
+ else if (err instanceof upload_1.UploadNotActiveError) {
297
+ uploadService.setResponseCode(uploadId, 400);
298
+ return res.sendStatus(400);
299
+ }
300
+ else if (err instanceof upload_1.UploadPreviouslyFinalizedError) {
301
+ res.header("x-goog-upload-status", "final");
302
+ return res.sendStatus(uploadService.getPreviousResponseCode(uploadId));
303
+ }
304
+ throw err;
311
305
  }
312
- throw err;
306
+ res.header("x-goog-upload-status", "final");
307
+ return await finalizeOneShotUpload(upload);
308
+ }
309
+ }
310
+ if (!objectId) {
311
+ res.sendStatus(400);
312
+ return;
313
+ }
314
+ if (uploadType === "multipart") {
315
+ const contentTypeHeader = req.header("content-type");
316
+ if (!contentTypeHeader) {
317
+ return res.sendStatus(400);
313
318
  }
314
- let storedMetadata;
319
+ let metadataRaw;
320
+ let dataRaw;
315
321
  try {
316
- storedMetadata = await storageLayer.uploadObject(upload);
322
+ ({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await (0, request_1.reqBodyToBuffer)(req)));
317
323
  }
318
324
  catch (err) {
319
- if (err instanceof errors_1.ForbiddenError) {
320
- return res.status(403).json({
321
- error: {
322
- code: 403,
323
- message: `Permission denied. No WRITE permission.`,
324
- },
325
- });
325
+ if (err instanceof Error) {
326
+ return res.status(400).send(err.message);
326
327
  }
327
328
  throw err;
328
329
  }
329
- res.header("x-goog-upload-status", "final");
330
- storedMetadata.addDownloadToken(false);
331
- return res.status(200).json(new metadata_1.OutgoingFirebaseMetadata(storedMetadata));
330
+ const upload = uploadService.multipartUpload({
331
+ bucketId,
332
+ objectId,
333
+ metadataRaw,
334
+ dataRaw: dataRaw,
335
+ authorization: req.header("authorization"),
336
+ });
337
+ return await finalizeOneShotUpload(upload);
332
338
  }
333
- return res.sendStatus(400);
339
+ const upload = uploadService.mediaUpload({
340
+ bucketId: req.params.bucketId,
341
+ objectId: objectId,
342
+ dataRaw: await (0, request_1.reqBodyToBuffer)(req),
343
+ authorization: req.header("authorization"),
344
+ });
345
+ return await finalizeOneShotUpload(upload);
334
346
  };
335
347
  const handleTokenRequest = (req, res) => {
336
348
  var _a, _b;
@@ -469,11 +481,13 @@ function createFirebaseEndpoints(emulator) {
469
481
  }
470
482
  exports.createFirebaseEndpoints = createFirebaseEndpoints;
471
483
  function setObjectHeaders(res, metadata, headerOverride = { "Content-Encoding": undefined }) {
472
- res.setHeader("Content-Disposition", metadata.contentDisposition);
484
+ if (metadata.contentDisposition) {
485
+ res.setHeader("Content-Disposition", metadata.contentDisposition);
486
+ }
473
487
  if (headerOverride["Content-Encoding"]) {
474
488
  res.setHeader("Content-Encoding", headerOverride["Content-Encoding"]);
475
489
  }
476
- else {
490
+ else if (metadata.contentEncoding) {
477
491
  res.setHeader("Content-Encoding", metadata.contentEncoding);
478
492
  }
479
493
  if (metadata.cacheControl) {
@@ -15,6 +15,45 @@ const request_1 = require("../../shared/request");
15
15
  function createCloudEndpoints(emulator) {
16
16
  const gcloudStorageAPI = (0, express_1.Router)();
17
17
  const { adminStorageLayer, uploadService } = emulator;
18
+ if (process.env.STORAGE_EMULATOR_DEBUG) {
19
+ gcloudStorageAPI.use((req, res, next) => {
20
+ console.log("--------------INCOMING REQUEST--------------");
21
+ console.log(`${req.method.toUpperCase()} ${req.path}`);
22
+ console.log("-- query:");
23
+ console.log(JSON.stringify(req.query, undefined, 2));
24
+ console.log("-- headers:");
25
+ console.log(JSON.stringify(req.headers, undefined, 2));
26
+ console.log("-- body:");
27
+ if (req.body instanceof Buffer) {
28
+ console.log(`Buffer of ${req.body.length}`);
29
+ }
30
+ else if (req.body) {
31
+ console.log(req.body);
32
+ }
33
+ else {
34
+ console.log("Empty body (could be stream)");
35
+ }
36
+ const resJson = res.json.bind(res);
37
+ res.json = (...args) => {
38
+ console.log("-- response:");
39
+ args.forEach((data) => console.log(JSON.stringify(data, undefined, 2)));
40
+ return resJson.call(res, ...args);
41
+ };
42
+ const resSendStatus = res.sendStatus.bind(res);
43
+ res.sendStatus = (status) => {
44
+ console.log("-- response status:");
45
+ console.log(status);
46
+ return resSendStatus.call(res, status);
47
+ };
48
+ const resStatus = res.status.bind(res);
49
+ res.status = (status) => {
50
+ console.log("-- response status:");
51
+ console.log(status);
52
+ return resStatus.call(res, status);
53
+ };
54
+ next();
55
+ });
56
+ }
18
57
  gcloudStorageAPI.use(/.*\/b\/(.+?)\/.*/, (req, res, next) => {
19
58
  adminStorageLayer.createBucket(req.params[0]);
20
59
  next();
@@ -25,7 +64,11 @@ function createCloudEndpoints(emulator) {
25
64
  items: await adminStorageLayer.listBuckets(),
26
65
  });
27
66
  });
28
- gcloudStorageAPI.get(["/b/:bucketId/o/:objectId", "/download/storage/v1/b/:bucketId/o/:objectId"], async (req, res) => {
67
+ gcloudStorageAPI.get([
68
+ "/b/:bucketId/o/:objectId",
69
+ "/download/storage/v1/b/:bucketId/o/:objectId",
70
+ "/storage/v1/b/:bucketId/o/:objectId",
71
+ ], async (req, res) => {
29
72
  let getObjectResponse;
30
73
  try {
31
74
  getObjectResponse = await adminStorageLayer.getObject({
@@ -87,7 +130,7 @@ function createCloudEndpoints(emulator) {
87
130
  throw err;
88
131
  }
89
132
  return res.status(200).json({
90
- kind: "#storage/objects",
133
+ kind: "storage#objects",
91
134
  nextPageToken: listResponse.nextPageToken,
92
135
  prefixes: listResponse.prefixes,
93
136
  items: (_a = listResponse.items) === null || _a === void 0 ? void 0 : _a.map((item) => new metadata_1.CloudStorageObjectMetadata(item)),
@@ -177,11 +220,8 @@ function createCloudEndpoints(emulator) {
177
220
  });
178
221
  });
179
222
  gcloudStorageAPI.post("/upload/storage/v1/b/:bucketId/o", async (req, res) => {
180
- const contentTypeHeader = req.header("content-type") || req.header("x-upload-content-type");
181
- if (!contentTypeHeader) {
182
- return res.sendStatus(400);
183
- }
184
- if (req.query.uploadType === "resumable") {
223
+ const uploadType = req.query.uploadType || req.header("X-Goog-Upload-Protocol");
224
+ if (uploadType === "resumable") {
185
225
  const emulatorInfo = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE);
186
226
  if (emulatorInfo === undefined) {
187
227
  return res.sendStatus(500);
@@ -204,45 +244,65 @@ function createCloudEndpoints(emulator) {
204
244
  uploadUrl.searchParams.set("upload_id", upload.id);
205
245
  return res.header("location", uploadUrl.toString()).sendStatus(200);
206
246
  }
207
- let metadataRaw;
208
- let dataRaw;
209
- try {
210
- ({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await (0, request_1.reqBodyToBuffer)(req)));
247
+ async function finalizeOneShotUpload(upload) {
248
+ let metadata;
249
+ try {
250
+ metadata = await adminStorageLayer.uploadObject(upload);
251
+ }
252
+ catch (err) {
253
+ if (err instanceof errors_1.ForbiddenError) {
254
+ return res.sendStatus(403);
255
+ }
256
+ throw err;
257
+ }
258
+ return res.status(200).json(new metadata_1.CloudStorageObjectMetadata(metadata));
211
259
  }
212
- catch (err) {
213
- if (err instanceof Error) {
214
- return res.status(400).json({
215
- error: {
216
- code: 400,
217
- message: err.message,
218
- },
219
- });
260
+ if (uploadType === "multipart") {
261
+ const contentTypeHeader = req.header("content-type") || req.header("x-upload-content-type");
262
+ if (!contentTypeHeader) {
263
+ return res.sendStatus(400);
220
264
  }
221
- throw err;
265
+ let metadataRaw;
266
+ let dataRaw;
267
+ try {
268
+ ({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await (0, request_1.reqBodyToBuffer)(req)));
269
+ }
270
+ catch (err) {
271
+ if (err instanceof Error) {
272
+ return res.status(400).json({
273
+ error: {
274
+ code: 400,
275
+ message: err.message,
276
+ },
277
+ });
278
+ }
279
+ throw err;
280
+ }
281
+ const name = getIncomingFileNameFromRequest(req.query, JSON.parse(metadataRaw));
282
+ if (name === undefined) {
283
+ res.sendStatus(400);
284
+ return;
285
+ }
286
+ const upload = uploadService.multipartUpload({
287
+ bucketId: req.params.bucketId,
288
+ objectId: name,
289
+ metadataRaw: metadataRaw,
290
+ dataRaw: dataRaw,
291
+ authorization: req.header("authorization"),
292
+ });
293
+ return await finalizeOneShotUpload(upload);
222
294
  }
223
- const name = getIncomingFileNameFromRequest(req.query, JSON.parse(metadataRaw));
224
- if (name === undefined) {
295
+ const name = req.query.name;
296
+ if (!name) {
225
297
  res.sendStatus(400);
226
- return;
227
298
  }
228
- const upload = uploadService.multipartUpload({
299
+ const upload = uploadService.mediaUpload({
229
300
  bucketId: req.params.bucketId,
230
- objectId: name,
231
- metadataRaw: metadataRaw,
232
- dataRaw: dataRaw,
301
+ objectId: name.toString(),
302
+ dataRaw: await (0, request_1.reqBodyToBuffer)(req),
233
303
  authorization: req.header("authorization"),
234
304
  });
235
- let metadata;
236
- try {
237
- metadata = await adminStorageLayer.uploadObject(upload);
238
- }
239
- catch (err) {
240
- if (err instanceof errors_1.ForbiddenError) {
241
- return res.sendStatus(403);
242
- }
243
- throw err;
244
- }
245
- return res.status(200).json(new metadata_1.CloudStorageObjectMetadata(metadata));
305
+ return await finalizeOneShotUpload(upload);
246
306
  });
247
307
  gcloudStorageAPI.get("/:bucketId/:objectId(**)", async (req, res) => {
248
308
  let getObjectResponse;
@@ -324,11 +384,11 @@ function sendFileBytes(md, data, req, res) {
324
384
  data = (0, zlib_1.gunzipSync)(data);
325
385
  }
326
386
  res.setHeader("Accept-Ranges", "bytes");
327
- res.setHeader("Content-Type", md.contentType);
328
- res.setHeader("Content-Disposition", md.contentDisposition);
329
- res.setHeader("Content-Encoding", isGZipped ? "identity" : md.contentEncoding);
387
+ res.setHeader("Content-Type", md.contentType || "application/octet-stream");
388
+ res.setHeader("Content-Disposition", md.contentDisposition || "attachment");
389
+ res.setHeader("Content-Encoding", isGZipped ? "identity" : md.contentEncoding || "");
330
390
  res.setHeader("ETag", md.etag);
331
- res.setHeader("Cache-Control", md.cacheControl);
391
+ res.setHeader("Cache-Control", md.cacheControl || "");
332
392
  res.setHeader("x-goog-generation", `${md.generation}`);
333
393
  res.setHeader("x-goog-metadatageneration", `${md.metageneration}`);
334
394
  res.setHeader("x-goog-storage-class", md.storageClass);
@@ -142,20 +142,26 @@ class StorageLayer {
142
142
  }
143
143
  const storedMetadata = this.getMetadata(upload.bucketId, upload.objectId);
144
144
  const filePath = this.path(upload.bucketId, upload.objectId);
145
+ function getIncomingMetadata(field) {
146
+ if (!upload.metadata) {
147
+ return undefined;
148
+ }
149
+ const value = upload.metadata[field];
150
+ return value === null ? undefined : value;
151
+ }
145
152
  const metadata = new metadata_1.StoredFileMetadata({
146
153
  name: upload.objectId,
147
154
  bucket: upload.bucketId,
148
- contentType: upload.metadata.contentType || "application/octet-stream",
149
- contentDisposition: upload.metadata.contentDisposition,
150
- contentEncoding: upload.metadata.contentEncoding,
151
- contentLanguage: upload.metadata.contentLanguage,
152
- cacheControl: upload.metadata.cacheControl,
153
- customMetadata: upload.metadata.metadata,
155
+ contentType: getIncomingMetadata("contentType"),
156
+ contentDisposition: getIncomingMetadata("contentDisposition"),
157
+ contentEncoding: getIncomingMetadata("contentEncoding"),
158
+ contentLanguage: getIncomingMetadata("contentLanguage"),
159
+ cacheControl: getIncomingMetadata("cacheControl"),
160
+ customMetadata: getIncomingMetadata("metadata"),
154
161
  }, this._cloudFunctions, this._persistence.readBytes(upload.path, upload.size));
155
- metadata.update(upload.metadata, false);
156
162
  const authorized = await this._rulesValidator.validate(["b", upload.bucketId, "o", upload.objectId].join("/"), upload.bucketId, types_1.RulesetOperationMethod.CREATE, {
157
163
  before: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource(),
158
- after: metadata === null || metadata === void 0 ? void 0 : metadata.asRulesResource(),
164
+ after: metadata.asRulesResource(),
159
165
  }, upload.authorization);
160
166
  if (!authorized) {
161
167
  this._persistence.deleteFile(upload.path);
@@ -192,16 +198,20 @@ class StorageLayer {
192
198
  newMetadata.metadata[k] = "";
193
199
  }
194
200
  }
201
+ function getMetadata(field) {
202
+ const value = newMetadata[field];
203
+ return value === null ? undefined : value;
204
+ }
195
205
  const copiedFileMetadata = new metadata_1.StoredFileMetadata({
196
206
  name: destinationObject,
197
207
  bucket: destinationBucket,
198
- contentType: newMetadata.contentType || "application/octet-stream",
199
- contentDisposition: newMetadata.contentDisposition,
200
- contentEncoding: newMetadata.contentEncoding,
201
- contentLanguage: newMetadata.contentLanguage,
202
- cacheControl: newMetadata.cacheControl,
203
- customMetadata: newMetadata.metadata,
204
- }, this._cloudFunctions, sourceBytes, incomingMetadata);
208
+ contentType: getMetadata("contentType"),
209
+ contentDisposition: getMetadata("contentDisposition"),
210
+ contentEncoding: getMetadata("contentEncoding"),
211
+ contentLanguage: getMetadata("contentLanguage"),
212
+ cacheControl: getMetadata("cacheControl"),
213
+ customMetadata: getMetadata("metadata"),
214
+ }, this._cloudFunctions, sourceBytes);
205
215
  const file = new StoredFile(copiedFileMetadata);
206
216
  this._files.set(destinationFilePath, file);
207
217
  this._cloudFunctions.dispatch("finalize", new metadata_1.CloudStorageObjectMetadata(file.metadata));