firebase-tools 10.2.0 → 10.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/apiv2.js +3 -0
- package/lib/appdistribution/options-parser-util.js +1 -1
- package/lib/auth.js +3 -3
- package/lib/command.js +1 -1
- package/lib/commands/apps-android-sha-create.js +2 -2
- package/lib/commands/apps-sdkconfig.js +1 -1
- package/lib/commands/auth-import.js +1 -1
- package/lib/commands/database-rules-list.js +2 -2
- package/lib/commands/emulators-start.js +1 -1
- package/lib/commands/ext-configure.js +58 -4
- package/lib/commands/ext-dev-init.js +49 -49
- package/lib/commands/ext-export.js +7 -2
- package/lib/commands/ext-install.js +163 -104
- package/lib/commands/ext-uninstall.js +17 -8
- package/lib/commands/ext-update.js +64 -11
- package/lib/commands/functions-config-clone.js +1 -1
- package/lib/commands/functions-config-export.js +1 -1
- package/lib/commands/hosting-clone.js +3 -3
- package/lib/commands/remoteconfig-get.js +1 -1
- package/lib/config.js +6 -3
- package/lib/deploy/extensions/deploymentSummary.js +3 -3
- package/lib/deploy/extensions/planner.js +7 -6
- package/lib/deploy/extensions/tasks.js +1 -1
- package/lib/deploy/functions/backend.js +21 -5
- package/lib/deploy/functions/checkIam.js +5 -5
- package/lib/deploy/functions/containerCleaner.js +3 -3
- package/lib/deploy/functions/ensure.js +3 -3
- package/lib/deploy/functions/functionsDeployHelper.js +2 -2
- package/lib/deploy/functions/prepare.js +5 -3
- package/lib/deploy/functions/pricing.js +1 -1
- package/lib/deploy/functions/prompts.js +2 -2
- package/lib/deploy/functions/release/fabricator.js +7 -7
- package/lib/deploy/functions/release/index.js +1 -1
- package/lib/deploy/functions/release/planner.js +43 -26
- package/lib/deploy/functions/release/reporter.js +3 -0
- package/lib/deploy/functions/runtimes/discovery/index.js +6 -6
- package/lib/deploy/functions/runtimes/discovery/parsing.js +1 -1
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +22 -12
- package/lib/deploy/functions/runtimes/golang/index.js +2 -2
- package/lib/deploy/functions/runtimes/node/index.js +53 -0
- package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +2 -2
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +52 -15
- package/lib/deploy/functions/runtimes/node/versioning.js +2 -2
- package/lib/deploy/functions/services/firebaseAlerts.js +30 -0
- package/lib/deploy/functions/services/index.js +9 -1
- package/lib/deploy/functions/services/storage.js +10 -4
- package/lib/deploy/functions/triggerRegionHelper.js +1 -1
- package/lib/deploy/functions/validate.js +3 -3
- package/lib/deploy/hosting/client.js +9 -0
- package/lib/deploy/hosting/convertConfig.js +6 -0
- package/lib/deploy/hosting/deploy.js +2 -2
- package/lib/deploy/hosting/hashcache.js +21 -19
- package/lib/deploy/hosting/index.js +5 -5
- package/lib/deploy/hosting/prepare.js +25 -25
- package/lib/deploy/hosting/release.js +21 -24
- package/lib/deploy/hosting/uploader.js +5 -5
- package/lib/deploy/remoteconfig/functions.js +2 -2
- package/lib/emulator/auth/cloudFunctions.js +1 -1
- package/lib/emulator/auth/operations.js +1 -1
- package/lib/emulator/commandUtils.js +5 -1
- package/lib/emulator/constants.js +4 -0
- package/lib/emulator/controller.js +54 -24
- package/lib/emulator/download.js +18 -1
- package/lib/emulator/downloadableEmulators.js +30 -13
- package/lib/emulator/emulatorLogger.js +12 -1
- package/lib/emulator/extensions/validation.js +70 -0
- package/lib/emulator/extensionsEmulator.js +175 -0
- package/lib/emulator/functionsEmulator.js +106 -44
- package/lib/emulator/functionsEmulatorRuntime.js +44 -36
- package/lib/emulator/functionsEmulatorShared.js +17 -10
- package/lib/emulator/functionsEmulatorShell.js +1 -1
- package/lib/emulator/functionsEmulatorUtils.js +4 -4
- package/lib/emulator/functionsRuntimeWorker.js +2 -2
- package/lib/emulator/hub.js +4 -3
- package/lib/emulator/loggingEmulator.js +1 -1
- package/lib/emulator/pubsubEmulator.js +1 -1
- package/lib/emulator/registry.js +10 -2
- package/lib/emulator/storage/apis/firebase.js +314 -332
- package/lib/emulator/storage/apis/gcloud.js +241 -121
- package/lib/emulator/storage/crc.js +5 -1
- package/lib/emulator/storage/errors.js +9 -0
- package/lib/emulator/storage/files.js +159 -300
- package/lib/emulator/storage/index.js +27 -73
- package/lib/emulator/storage/metadata.js +65 -51
- package/lib/emulator/storage/multipart.js +62 -0
- package/lib/emulator/storage/persistence.js +78 -0
- package/lib/emulator/storage/rules/config.js +33 -0
- package/lib/emulator/storage/rules/manager.js +81 -0
- package/lib/emulator/storage/rules/runtime.js +8 -7
- package/lib/emulator/storage/rules/utils.js +48 -0
- package/lib/emulator/storage/server.js +2 -2
- package/lib/emulator/storage/upload.js +106 -0
- package/lib/emulator/types.js +3 -0
- package/lib/ensureApiEnabled.js +5 -1
- package/lib/error.js +1 -1
- package/lib/extensions/askUserForParam.js +1 -1
- package/lib/extensions/changelog.js +3 -1
- package/lib/extensions/checkProjectBilling.js +1 -1
- package/lib/extensions/displayExtensionInfo.js +1 -1
- package/lib/extensions/emulator/optionsHelper.js +56 -8
- package/lib/extensions/emulator/specHelper.js +10 -23
- package/lib/extensions/export.js +1 -51
- package/lib/extensions/extensionsApi.js +1 -1
- package/lib/extensions/extensionsHelper.js +32 -19
- package/lib/extensions/listExtensions.js +2 -0
- package/lib/extensions/manifest.js +144 -0
- package/lib/extensions/metricsUtils.js +4 -4
- package/lib/extensions/paramHelper.js +9 -8
- package/lib/extensions/refs.js +1 -1
- package/lib/extensions/secretsUtils.js +3 -3
- package/lib/functional.js +1 -1
- package/lib/functions/env.js +6 -7
- package/lib/functions/events/v2.js +11 -0
- package/lib/gcp/cloudfunctions.js +42 -11
- package/lib/gcp/cloudfunctionsv2.js +48 -17
- package/lib/gcp/cloudtasks.js +1 -1
- package/lib/gcp/docker.js +2 -2
- package/lib/gcp/resourceManager.js +4 -4
- package/lib/gcp/run.js +2 -2
- package/lib/gcp/storage.js +1 -0
- package/lib/hosting/api.js +1 -1
- package/lib/hosting/functionsProxy.js +15 -5
- package/lib/hosting/proxy.js +2 -2
- package/lib/init/features/account.js +1 -1
- package/lib/management/database.js +1 -1
- package/lib/previews.js +1 -1
- package/lib/responseToError.js +16 -7
- package/lib/serve/functions.js +2 -1
- package/lib/serve/hosting.js +1 -1
- package/lib/utils.js +15 -2
- package/npm-shrinkwrap.json +904 -412
- package/package.json +3 -3
- package/schema/firebase-config.json +32 -0
- package/templates/init/functions/javascript/package.lint.json +3 -3
- package/templates/init/functions/javascript/package.nolint.json +2 -2
- package/templates/init/functions/typescript/package.lint.json +7 -7
- package/templates/init/functions/typescript/package.nolint.json +3 -3
- package/lib/deploy/extensions/params.js +0 -39
- package/lib/deploy/functions/eventTypes.js +0 -10
|
@@ -5,34 +5,14 @@ const emulatorLogger_1 = require("../../emulatorLogger");
|
|
|
5
5
|
const types_1 = require("../../types");
|
|
6
6
|
const zlib_1 = require("zlib");
|
|
7
7
|
const metadata_1 = require("../metadata");
|
|
8
|
-
const mime = require("mime");
|
|
9
8
|
const express_1 = require("express");
|
|
10
9
|
const registry_1 = require("../../registry");
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE).log("WARN", `Can not process SDK request with no loaded ruleset`);
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
if (["Bearer owner", "Firebase owner"].includes(opts.authorization || "")) {
|
|
18
|
-
return true;
|
|
19
|
-
}
|
|
20
|
-
const { permitted, issues } = await opts.ruleset.verify({
|
|
21
|
-
method: opts.method,
|
|
22
|
-
path: opts.path,
|
|
23
|
-
file: opts.file,
|
|
24
|
-
token: opts.authorization ? opts.authorization.split(" ")[1] : undefined,
|
|
25
|
-
});
|
|
26
|
-
if (issues.exist()) {
|
|
27
|
-
issues.all.forEach((warningOrError) => {
|
|
28
|
-
emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE).log("WARN", warningOrError);
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
return !!permitted;
|
|
32
|
-
}
|
|
10
|
+
const multipart_1 = require("../multipart");
|
|
11
|
+
const errors_1 = require("../errors");
|
|
12
|
+
const upload_1 = require("../upload");
|
|
33
13
|
function createFirebaseEndpoints(emulator) {
|
|
34
14
|
const firebaseStorageAPI = (0, express_1.Router)();
|
|
35
|
-
const { storageLayer } = emulator;
|
|
15
|
+
const { storageLayer, uploadService } = emulator;
|
|
36
16
|
if (process.env.STORAGE_EMULATOR_DEBUG) {
|
|
37
17
|
firebaseStorageAPI.use((req, res, next) => {
|
|
38
18
|
console.log("--------------INCOMING REQUEST--------------");
|
|
@@ -89,328 +69,244 @@ function createFirebaseEndpoints(emulator) {
|
|
|
89
69
|
next();
|
|
90
70
|
});
|
|
91
71
|
firebaseStorageAPI.get("/b/:bucketId/o/:objectId", async (req, res) => {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
path: operationPath,
|
|
103
|
-
file: rulesFiles,
|
|
104
|
-
authorization: req.header("authorization"),
|
|
105
|
-
});
|
|
106
|
-
const isPermittedViaToken = req.query.token && md && md.downloadTokens.includes(req.query.token.toString());
|
|
107
|
-
const isRequestPermitted = isPermittedViaHeader || !!isPermittedViaToken;
|
|
108
|
-
if (!isRequestPermitted) {
|
|
109
|
-
res.sendStatus(403);
|
|
110
|
-
return;
|
|
72
|
+
var _a;
|
|
73
|
+
let metadata;
|
|
74
|
+
let data;
|
|
75
|
+
try {
|
|
76
|
+
({ metadata, data } = await storageLayer.handleGetObject({
|
|
77
|
+
bucketId: req.params.bucketId,
|
|
78
|
+
decodedObjectId: decodeURIComponent(req.params.objectId),
|
|
79
|
+
authorization: req.header("authorization"),
|
|
80
|
+
downloadToken: (_a = req.query.token) === null || _a === void 0 ? void 0 : _a.toString(),
|
|
81
|
+
}));
|
|
111
82
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
83
|
+
catch (err) {
|
|
84
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
85
|
+
return res.sendStatus(404);
|
|
86
|
+
}
|
|
87
|
+
else if (err instanceof errors_1.ForbiddenError) {
|
|
88
|
+
return res.status(403).json({
|
|
89
|
+
error: {
|
|
90
|
+
code: 403,
|
|
91
|
+
message: `Permission denied. No READ permission.`,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
throw err;
|
|
115
96
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
isGZipped = true;
|
|
97
|
+
if (!metadata.downloadTokens.length) {
|
|
98
|
+
metadata.addDownloadToken();
|
|
119
99
|
}
|
|
120
|
-
if (req.query.alt
|
|
121
|
-
|
|
122
|
-
if (!data) {
|
|
123
|
-
res.sendStatus(404);
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
100
|
+
if (req.query.alt === "media") {
|
|
101
|
+
const isGZipped = metadata.contentEncoding === "gzip";
|
|
126
102
|
if (isGZipped) {
|
|
127
103
|
data = (0, zlib_1.gunzipSync)(data);
|
|
128
104
|
}
|
|
129
105
|
res.setHeader("Accept-Ranges", "bytes");
|
|
130
|
-
res.setHeader("Content-Type",
|
|
131
|
-
setObjectHeaders(res,
|
|
132
|
-
const byteRange =
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
end: rangeEnd ? parseInt(rangeEnd) : data.byteLength,
|
|
138
|
-
};
|
|
139
|
-
res.setHeader("Content-Range", `bytes ${range.start}-${range.end - 1}/${data.byteLength}`);
|
|
140
|
-
res.status(206).end(data.slice(range.start, range.end));
|
|
106
|
+
res.setHeader("Content-Type", metadata.contentType);
|
|
107
|
+
setObjectHeaders(res, metadata, { "Content-Encoding": isGZipped ? "identity" : undefined });
|
|
108
|
+
const byteRange = req.range(data.byteLength, { combine: true });
|
|
109
|
+
if (Array.isArray(byteRange) && byteRange.type === "bytes" && byteRange.length > 0) {
|
|
110
|
+
const range = byteRange[0];
|
|
111
|
+
res.setHeader("Content-Range", `${byteRange.type} ${range.start}-${range.end}/${data.byteLength}`);
|
|
112
|
+
res.status(206).end(data.slice(range.start, range.end + 1));
|
|
141
113
|
}
|
|
142
114
|
else {
|
|
143
115
|
res.end(data);
|
|
144
116
|
}
|
|
145
117
|
return;
|
|
146
118
|
}
|
|
147
|
-
|
|
148
|
-
md.addDownloadToken();
|
|
149
|
-
}
|
|
150
|
-
res.json(new metadata_1.OutgoingFirebaseMetadata(md));
|
|
119
|
+
return res.json(new metadata_1.OutgoingFirebaseMetadata(metadata));
|
|
151
120
|
});
|
|
152
|
-
const handleMetadataUpdate = async (req, res) => {
|
|
153
|
-
const md = storageLayer.getMetadata(req.params.bucketId, req.params.objectId);
|
|
154
|
-
if (!md) {
|
|
155
|
-
res.sendStatus(404);
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
const decodedObjectId = decodeURIComponent(req.params.objectId);
|
|
159
|
-
const operationPath = ["b", req.params.bucketId, "o", decodedObjectId].join("/");
|
|
160
|
-
if (!(await isPermitted({
|
|
161
|
-
ruleset: emulator.rules,
|
|
162
|
-
method: types_2.RulesetOperationMethod.UPDATE,
|
|
163
|
-
path: operationPath,
|
|
164
|
-
authorization: req.header("authorization"),
|
|
165
|
-
file: {
|
|
166
|
-
before: md.asRulesResource(),
|
|
167
|
-
after: md.asRulesResource(req.body),
|
|
168
|
-
},
|
|
169
|
-
}))) {
|
|
170
|
-
return res.status(403).json({
|
|
171
|
-
error: {
|
|
172
|
-
code: 403,
|
|
173
|
-
message: `Permission denied. No WRITE permission.`,
|
|
174
|
-
},
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
md.update(req.body);
|
|
178
|
-
setObjectHeaders(res, md);
|
|
179
|
-
const outgoingMetadata = new metadata_1.OutgoingFirebaseMetadata(md);
|
|
180
|
-
res.json(outgoingMetadata);
|
|
181
|
-
return;
|
|
182
|
-
};
|
|
183
121
|
firebaseStorageAPI.get("/b/:bucketId/o", async (req, res) => {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
path: operationPath,
|
|
196
|
-
file: {},
|
|
197
|
-
authorization: req.header("authorization"),
|
|
198
|
-
}))) {
|
|
199
|
-
return res.status(403).json({
|
|
200
|
-
error: {
|
|
201
|
-
code: 403,
|
|
202
|
-
message: `Permission denied. No LIST permission.`,
|
|
203
|
-
},
|
|
122
|
+
var _a, _b;
|
|
123
|
+
const maxResults = (_a = req.query.maxResults) === null || _a === void 0 ? void 0 : _a.toString();
|
|
124
|
+
let response;
|
|
125
|
+
try {
|
|
126
|
+
response = await storageLayer.handleListObjects({
|
|
127
|
+
bucketId: req.params.bucketId,
|
|
128
|
+
prefix: req.query.prefix ? req.query.prefix.toString() : "",
|
|
129
|
+
delimiter: req.query.delimiter ? req.query.delimiter.toString() : "",
|
|
130
|
+
pageToken: (_b = req.query.pageToken) === null || _b === void 0 ? void 0 : _b.toString(),
|
|
131
|
+
maxResults: maxResults ? +maxResults : undefined,
|
|
132
|
+
authorization: req.header("authorization"),
|
|
204
133
|
});
|
|
205
134
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const handleUpload = async (req, res) => {
|
|
209
|
-
var _a;
|
|
210
|
-
if (req.query.create_token || req.query.delete_token) {
|
|
211
|
-
const decodedObjectId = decodeURIComponent(req.params.objectId);
|
|
212
|
-
const operationPath = ["b", req.params.bucketId, "o", decodedObjectId].join("/");
|
|
213
|
-
const mdBefore = storageLayer.getMetadata(req.params.bucketId, req.params.objectId);
|
|
214
|
-
if (!(await isPermitted({
|
|
215
|
-
ruleset: emulator.rules,
|
|
216
|
-
method: types_2.RulesetOperationMethod.UPDATE,
|
|
217
|
-
path: operationPath,
|
|
218
|
-
authorization: req.header("authorization"),
|
|
219
|
-
file: {
|
|
220
|
-
before: mdBefore === null || mdBefore === void 0 ? void 0 : mdBefore.asRulesResource(),
|
|
221
|
-
},
|
|
222
|
-
}))) {
|
|
135
|
+
catch (err) {
|
|
136
|
+
if (err instanceof errors_1.ForbiddenError) {
|
|
223
137
|
return res.status(403).json({
|
|
224
138
|
error: {
|
|
225
139
|
code: 403,
|
|
226
|
-
message: `Permission denied. No
|
|
140
|
+
message: `Permission denied. No LIST permission.`,
|
|
227
141
|
},
|
|
228
142
|
});
|
|
229
143
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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);
|
|
175
|
+
}
|
|
176
|
+
let metadataRaw;
|
|
177
|
+
let dataRaw;
|
|
178
|
+
try {
|
|
179
|
+
({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await reqBodyToBuffer(req)));
|
|
237
180
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
+
});
|
|
245
189
|
}
|
|
246
|
-
|
|
190
|
+
throw err;
|
|
247
191
|
}
|
|
248
|
-
|
|
249
|
-
|
|
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);
|
|
250
202
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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;
|
|
254
213
|
}
|
|
255
|
-
|
|
256
|
-
return res.json(new metadata_1.OutgoingFirebaseMetadata(
|
|
214
|
+
metadata.addDownloadToken();
|
|
215
|
+
return res.status(200).json(new metadata_1.OutgoingFirebaseMetadata(metadata));
|
|
257
216
|
}
|
|
258
|
-
|
|
217
|
+
const uploadCommand = req.header("x-goog-upload-command");
|
|
218
|
+
if (!uploadCommand) {
|
|
259
219
|
res.sendStatus(400);
|
|
260
220
|
return;
|
|
261
221
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
res.sendStatus(400);
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
const boundary = `--${contentType.split("boundary=")[1]}`;
|
|
271
|
-
const bodyString = req.body.toString();
|
|
272
|
-
const bodyStringParts = bodyString.split(boundary).filter((v) => v);
|
|
273
|
-
const metadataString = bodyStringParts[0].split("\r\n")[3];
|
|
274
|
-
const blobParts = bodyStringParts[1].split("\r\n");
|
|
275
|
-
const blobContentTypeString = blobParts[1];
|
|
276
|
-
if (!blobContentTypeString || !blobContentTypeString.startsWith("Content-Type: ")) {
|
|
277
|
-
res.sendStatus(400);
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
const blobContentType = blobContentTypeString.slice("Content-Type: ".length);
|
|
281
|
-
const bodyBuffer = req.body;
|
|
282
|
-
const metadataSegment = `${boundary}${bodyString.split(boundary)[1]}`;
|
|
283
|
-
const dataSegment = `${boundary}${bodyString.split(boundary).slice(2)[0]}`;
|
|
284
|
-
const dataSegmentHeader = (dataSegment.match(/.+Content-Type:.+?\r\n\r\n/s) || [])[0];
|
|
285
|
-
if (!dataSegmentHeader) {
|
|
286
|
-
res.sendStatus(400);
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
const bufferOffset = metadataSegment.length + dataSegmentHeader.length;
|
|
290
|
-
const blobBytes = Buffer.from(bodyBuffer.slice(bufferOffset, -`\r\n${boundary}--`.length));
|
|
291
|
-
const md = storageLayer.oneShotUpload(req.params.bucketId, name, blobContentType, JSON.parse(metadataString), Buffer.from(blobBytes));
|
|
292
|
-
if (!md) {
|
|
293
|
-
res.sendStatus(400);
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
const operationPath = ["b", req.params.bucketId, "o", name].join("/");
|
|
297
|
-
if (!(await isPermitted({
|
|
298
|
-
ruleset: emulator.rules,
|
|
299
|
-
method: types_2.RulesetOperationMethod.CREATE,
|
|
300
|
-
path: operationPath,
|
|
222
|
+
if (uploadCommand === "start") {
|
|
223
|
+
const upload = uploadService.startResumableUpload({
|
|
224
|
+
bucketId,
|
|
225
|
+
objectId,
|
|
226
|
+
metadataRaw: JSON.stringify(req.body),
|
|
301
227
|
authorization: req.header("authorization"),
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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);
|
|
313
245
|
}
|
|
314
|
-
|
|
315
|
-
|
|
246
|
+
catch (err) {
|
|
247
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
248
|
+
return res.sendStatus(404);
|
|
249
|
+
}
|
|
250
|
+
throw err;
|
|
316
251
|
}
|
|
317
|
-
res.
|
|
318
|
-
return;
|
|
252
|
+
res.header("X-Goog-Upload-Size-Received", upload.size.toString());
|
|
253
|
+
return res.sendStatus(200);
|
|
319
254
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
if (uploadCommand == "start") {
|
|
328
|
-
let objectContentType = req.header("x-goog-upload-header-content-type") ||
|
|
329
|
-
req.header("x-goog-upload-content-type");
|
|
330
|
-
if (!objectContentType) {
|
|
331
|
-
const mimeTypeFromName = mime.getType(name);
|
|
332
|
-
if (!mimeTypeFromName) {
|
|
333
|
-
objectContentType = "application/octet-stream";
|
|
334
|
-
}
|
|
335
|
-
else {
|
|
336
|
-
objectContentType = mimeTypeFromName;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
const upload = storageLayer.startUpload(req.params.bucketId, name, objectContentType, req.body);
|
|
340
|
-
storageLayer.uploadBytes(upload.uploadId, Buffer.alloc(0));
|
|
341
|
-
const emulatorInfo = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE);
|
|
342
|
-
res.header("x-goog-upload-chunk-granularity", "10000");
|
|
343
|
-
res.header("x-goog-upload-control-url", "");
|
|
344
|
-
res.header("x-goog-upload-status", "active");
|
|
345
|
-
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`);
|
|
346
|
-
res.header("x-gupload-uploadid", upload.uploadId);
|
|
347
|
-
res.status(200).send();
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
if (!req.query.upload_id) {
|
|
351
|
-
res.sendStatus(400);
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
const uploadId = req.query.upload_id.toString();
|
|
355
|
-
if (uploadCommand == "query") {
|
|
356
|
-
const upload = storageLayer.queryUpload(uploadId);
|
|
357
|
-
if (!upload) {
|
|
358
|
-
res.sendStatus(400);
|
|
359
|
-
return;
|
|
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);
|
|
360
262
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
if (uploadCommand == "cancel") {
|
|
366
|
-
const upload = storageLayer.cancelUpload(uploadId);
|
|
367
|
-
if (!upload) {
|
|
368
|
-
res.sendStatus(400);
|
|
369
|
-
return;
|
|
263
|
+
else if (err instanceof upload_1.NotCancellableError) {
|
|
264
|
+
return res.sendStatus(400);
|
|
370
265
|
}
|
|
371
|
-
|
|
372
|
-
return;
|
|
266
|
+
throw err;
|
|
373
267
|
}
|
|
268
|
+
return res.sendStatus(200);
|
|
269
|
+
}
|
|
270
|
+
if (uploadCommand.includes("upload")) {
|
|
374
271
|
let upload;
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
await new Promise((resolve) => {
|
|
382
|
-
req.on("end", () => {
|
|
383
|
-
req.body = Buffer.concat(bufs);
|
|
384
|
-
resolve();
|
|
385
|
-
});
|
|
386
|
-
});
|
|
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);
|
|
387
278
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
res.sendStatus(400);
|
|
391
|
-
return;
|
|
279
|
+
else if (err instanceof upload_1.UploadNotActiveError) {
|
|
280
|
+
return res.sendStatus(400);
|
|
392
281
|
}
|
|
282
|
+
throw err;
|
|
283
|
+
}
|
|
284
|
+
if (!uploadCommand.includes("finalize")) {
|
|
393
285
|
res.header("x-goog-upload-status", "active");
|
|
394
|
-
res.header("x-gupload-uploadid", upload.
|
|
286
|
+
res.header("x-gupload-uploadid", upload.id);
|
|
287
|
+
return res.sendStatus(200);
|
|
395
288
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
289
|
+
}
|
|
290
|
+
if (uploadCommand.includes("finalize")) {
|
|
291
|
+
let upload;
|
|
292
|
+
try {
|
|
293
|
+
upload = uploadService.finalizeResumableUpload(uploadId);
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
297
|
+
return res.sendStatus(404);
|
|
401
298
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
storageLayer.deleteFile(upload.bucketId, name);
|
|
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) {
|
|
414
310
|
return res.status(403).json({
|
|
415
311
|
error: {
|
|
416
312
|
code: 403,
|
|
@@ -418,20 +314,107 @@ function createFirebaseEndpoints(emulator) {
|
|
|
418
314
|
},
|
|
419
315
|
});
|
|
420
316
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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
|
+
},
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
354
|
+
return res.sendStatus(404);
|
|
424
355
|
}
|
|
425
|
-
|
|
356
|
+
throw err;
|
|
426
357
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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
|
+
});
|
|
430
367
|
}
|
|
431
|
-
|
|
432
|
-
|
|
368
|
+
catch (err) {
|
|
369
|
+
if (err instanceof errors_1.ForbiddenError) {
|
|
370
|
+
return res.status(403).json({
|
|
371
|
+
error: {
|
|
372
|
+
code: 403,
|
|
373
|
+
message: `Missing admin credentials.`,
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
378
|
+
return res.sendStatus(404);
|
|
379
|
+
}
|
|
380
|
+
throw err;
|
|
381
|
+
}
|
|
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
|
+
});
|
|
410
|
+
}
|
|
411
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
412
|
+
return res.sendStatus(404);
|
|
433
413
|
}
|
|
414
|
+
throw err;
|
|
434
415
|
}
|
|
416
|
+
setObjectHeaders(res, metadata);
|
|
417
|
+
return res.json(new metadata_1.OutgoingFirebaseMetadata(metadata));
|
|
435
418
|
};
|
|
436
419
|
firebaseStorageAPI.patch("/b/:bucketId/o/:objectId", handleMetadataUpdate);
|
|
437
420
|
firebaseStorageAPI.put("/b/:bucketId/o/:objectId?", async (req, res) => {
|
|
@@ -440,34 +423,33 @@ function createFirebaseEndpoints(emulator) {
|
|
|
440
423
|
case "patch":
|
|
441
424
|
return handleMetadataUpdate(req, res);
|
|
442
425
|
default:
|
|
443
|
-
return
|
|
426
|
+
return handleObjectPostRequest(req, res);
|
|
444
427
|
}
|
|
445
428
|
});
|
|
446
|
-
firebaseStorageAPI.post("/b/:bucketId/o/:objectId?",
|
|
429
|
+
firebaseStorageAPI.post("/b/:bucketId/o/:objectId?", handleObjectPostRequest);
|
|
447
430
|
firebaseStorageAPI.delete("/b/:bucketId/o/:objectId", async (req, res) => {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
path: operationPath,
|
|
454
|
-
authorization: req.header("authorization"),
|
|
455
|
-
file: {},
|
|
456
|
-
}))) {
|
|
457
|
-
return res.status(403).json({
|
|
458
|
-
error: {
|
|
459
|
-
code: 403,
|
|
460
|
-
message: `Permission denied. No WRITE permission.`,
|
|
461
|
-
},
|
|
431
|
+
try {
|
|
432
|
+
await storageLayer.handleDeleteObject({
|
|
433
|
+
bucketId: req.params.bucketId,
|
|
434
|
+
decodedObjectId: decodeURIComponent(req.params.objectId),
|
|
435
|
+
authorization: req.header("authorization"),
|
|
462
436
|
});
|
|
463
437
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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;
|
|
468
451
|
}
|
|
469
|
-
|
|
470
|
-
res.sendStatus(200);
|
|
452
|
+
res.sendStatus(204);
|
|
471
453
|
});
|
|
472
454
|
firebaseStorageAPI.get("/", (req, res) => {
|
|
473
455
|
res.json({ emulator: "storage" });
|