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.
- package/lib/deploy/functions/build.js +2 -2
- package/lib/deploy/functions/params.js +7 -1
- package/lib/deploy/functions/prepare.js +1 -1
- package/lib/emulator/storage/apis/firebase.js +139 -125
- package/lib/emulator/storage/apis/gcloud.js +102 -42
- package/lib/emulator/storage/files.js +25 -15
- package/lib/emulator/storage/metadata.js +76 -55
- package/lib/emulator/storage/rules/runtime.js +10 -2
- package/lib/emulator/storage/upload.js +45 -9
- package/lib/functions/constants.js +14 -0
- package/lib/gcp/cloudfunctions.js +15 -18
- package/lib/gcp/cloudfunctionsv2.js +15 -18
- package/lib/gcp/cloudscheduler.js +2 -1
- package/lib/track.js +1 -1
- package/npm-shrinkwrap.json +33 -9
- package/package.json +4 -4
|
@@ -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 (
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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.
|
|
199
|
+
if (!metadata.contentDisposition) {
|
|
200
|
+
metadata.contentDisposition = "inline";
|
|
201
|
+
}
|
|
217
202
|
return res.status(200).json(new metadata_1.OutgoingFirebaseMetadata(metadata));
|
|
218
203
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
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
|
-
|
|
263
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
274
|
-
|
|
242
|
+
catch (err) {
|
|
243
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
244
|
+
return res.sendStatus(404);
|
|
245
|
+
}
|
|
246
|
+
throw err;
|
|
275
247
|
}
|
|
276
|
-
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
251
|
+
if (uploadCommand === "cancel") {
|
|
252
|
+
try {
|
|
253
|
+
uploadService.cancelResumableUpload(uploadId);
|
|
288
254
|
}
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
286
|
+
if (uploadCommand.includes("finalize")) {
|
|
287
|
+
let upload;
|
|
288
|
+
try {
|
|
289
|
+
upload = uploadService.finalizeResumableUpload(uploadId);
|
|
308
290
|
}
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
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
|
|
319
|
+
let metadataRaw;
|
|
320
|
+
let dataRaw;
|
|
315
321
|
try {
|
|
316
|
-
|
|
322
|
+
({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await (0, request_1.reqBodyToBuffer)(req)));
|
|
317
323
|
}
|
|
318
324
|
catch (err) {
|
|
319
|
-
if (err instanceof
|
|
320
|
-
return res.status(
|
|
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
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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([
|
|
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: "#
|
|
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
|
|
181
|
-
if (
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
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 =
|
|
224
|
-
if (name
|
|
295
|
+
const name = req.query.name;
|
|
296
|
+
if (!name) {
|
|
225
297
|
res.sendStatus(400);
|
|
226
|
-
return;
|
|
227
298
|
}
|
|
228
|
-
const upload = uploadService.
|
|
299
|
+
const upload = uploadService.mediaUpload({
|
|
229
300
|
bucketId: req.params.bucketId,
|
|
230
|
-
objectId: name,
|
|
231
|
-
|
|
232
|
-
dataRaw: dataRaw,
|
|
301
|
+
objectId: name.toString(),
|
|
302
|
+
dataRaw: await (0, request_1.reqBodyToBuffer)(req),
|
|
233
303
|
authorization: req.header("authorization"),
|
|
234
304
|
});
|
|
235
|
-
|
|
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:
|
|
149
|
-
contentDisposition:
|
|
150
|
-
contentEncoding:
|
|
151
|
-
contentLanguage:
|
|
152
|
-
cacheControl:
|
|
153
|
-
customMetadata:
|
|
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
|
|
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:
|
|
199
|
-
contentDisposition:
|
|
200
|
-
contentEncoding:
|
|
201
|
-
contentLanguage:
|
|
202
|
-
cacheControl:
|
|
203
|
-
customMetadata:
|
|
204
|
-
}, this._cloudFunctions, sourceBytes
|
|
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));
|