Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Read name from metadata as backup for gcs upload #4832

Merged
merged 7 commits into from
Aug 4, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- Make storage emulator content type case insensitive. (#3953)
- Add storage emulator support to init.js useEmulator flag. (#4805)
- Populate resource correctly in storage rules evaluation. (#4329)
- Read name from metadata as backup for gcs upload into storage emulator. (#3953)
- Fix bug where invalid CPU was set for 16GiB functions. (#4823)
- Fix bug where failed function discovery crashed the entire emulator. (#4826)
- Fix LIST security rule evaluation in storage emulator. (#4827)
38 changes: 38 additions & 0 deletions scripts/storage-emulator-integration/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,44 @@ describe("Storage emulator", () => {
expect(fileMetadata).to.deep.include(metadata);
});

it("should handle resumable upload with name only in metadata", async () => {
const fileName = "test_upload.jpg";
const uploadURL = await supertest(STORAGE_EMULATOR_HOST)
.post(`/upload/storage/v1/b/${storageBucket}/o?uploadType=resumable`)
.send({ name: fileName })
.set({
Authorization: "Bearer owner",
})
.expect(200)
.then((res) => new URL(res.header["location"]));
expect(uploadURL.searchParams?.get("name")).to.equal(fileName);
});

it("should handle multipart upload with name only in metadata", async () => {
const body = Buffer.from(`--b1d5b2e3-1845-4338-9400-6ac07ce53c1e\r
content-type: application/json\r
\r
{"name":"test_upload.jpg"}\r
--b1d5b2e3-1845-4338-9400-6ac07ce53c1e\r
content-type: text/plain\r
\r
hello there!
\r
--b1d5b2e3-1845-4338-9400-6ac07ce53c1e--\r
`);
const fileName = "test_upload.jpg";
const responseName = await supertest(STORAGE_EMULATOR_HOST)
.post(`/upload/storage/v1/b/${storageBucket}/o?uploadType=multipart`)
.send(body)
.set({
Authorization: "Bearer owner",
"content-type": "multipart/related; boundary=b1d5b2e3-1845-4338-9400-6ac07ce53c1e",
})
.expect(200)
.then((res) => res.body.name);
expect(responseName).to.equal(fileName);
});

it("should return an error message when uploading a file with invalid metadata", async () => {
const fileName = "test_upload.jpg";
const errorMessage = await supertest(STORAGE_EMULATOR_HOST)
Expand Down
28 changes: 17 additions & 11 deletions src/emulator/storage/apis/gcloud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { parseObjectUploadMultipartRequest } from "../multipart";
import { Upload, UploadNotActiveError } from "../upload";
import { ForbiddenError, NotFoundError } from "../errors";
import { reqBodyToBuffer } from "../../shared/request";
import { Query } from "express-serve-static-core";

export function createCloudEndpoints(emulator: StorageEmulator): Router {
// eslint-disable-next-line new-cap
Expand Down Expand Up @@ -199,18 +200,7 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router {
});

gcloudStorageAPI.post("/upload/storage/v1/b/:bucketId/o", async (req, res) => {
if (!req.query.name) {
res.sendStatus(400);
return;
}
let name = req.query.name.toString();

if (name.startsWith("/")) {
name = name.slice(1);
}

const contentTypeHeader = req.header("content-type") || req.header("x-upload-content-type");

if (!contentTypeHeader) {
return res.sendStatus(400);
}
Expand All @@ -219,6 +209,11 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router {
if (emulatorInfo === undefined) {
return res.sendStatus(500);
}
const name = getValidName(req.query, req.body);
if (name == null) {
Yuangwang marked this conversation as resolved.
Show resolved Hide resolved
res.sendStatus(400);
return;
}
const upload = uploadService.startResumableUpload({
bucketId: req.params.bucketId,
objectId: name,
Expand All @@ -237,11 +232,17 @@ export function createCloudEndpoints(emulator: StorageEmulator): Router {
// Multipart upload
let metadataRaw: string;
let dataRaw: Buffer;
let name: string | undefined;
try {
({ metadataRaw, dataRaw } = parseObjectUploadMultipartRequest(
contentTypeHeader!,
await reqBodyToBuffer(req)
));
name = getValidName(req.query, JSON.parse(metadataRaw));
Yuangwang marked this conversation as resolved.
Show resolved Hide resolved
if (name == null) {
Yuangwang marked this conversation as resolved.
Show resolved Hide resolved
res.sendStatus(400);
return;
}
} catch (err) {
if (err instanceof Error) {
return res.status(400).json({
Expand Down Expand Up @@ -411,3 +412,8 @@ function sendObjectNotFound(req: Request, res: Response): void {
});
}
}

function getValidName(query: Query, metadata: IncomingMetadata): string | undefined {
Yuangwang marked this conversation as resolved.
Show resolved Hide resolved
const name = query?.name?.toString() || metadata?.name;
return name?.startsWith("/") ? name.slice(1) : name;
}
1 change: 1 addition & 0 deletions src/emulator/storage/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ export interface RulesResourceMetadata {
}

export interface IncomingMetadata {
name?: string;
contentType?: string;
contentLanguage?: string;
contentEncoding?: string;
Expand Down