Skip to content

Commit d30afb0

Browse files
Merge pull request #417 from shapehq/fix/make-sure-proxy-only-serves-json-or-yaml
Make sure file proxy endpoint only serves JSON or YAML
2 parents 1627a57 + 30d3a0b commit d30afb0

File tree

1 file changed

+23
-5
lines changed

1 file changed

+23
-5
lines changed

src/app/api/proxy/route.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { NextRequest, NextResponse } from "next/server"
22
import { env, makeAPIErrorResponse, makeUnauthenticatedAPIErrorResponse } from "@/common"
33
import { session } from "@/composition"
4+
import { parse as parseYaml } from "yaml"
45

56
const ErrorName = {
67
MAX_FILE_SIZE_EXCEEDED: "MaxFileSizeExceededError",
7-
TIMEOUT: "TimeoutError"
8+
TIMEOUT: "TimeoutError",
9+
NOT_JSON_OR_YAML: "NotJsonOrYamlError",
810
}
911

1012
export async function GET(req: NextRequest) {
@@ -26,8 +28,9 @@ export async function GET(req: NextRequest) {
2628
const maxMegabytes = Number(env.getOrThrow("PROXY_API_MAXIMUM_FILE_SIZE_IN_MEGABYTES"))
2729
const timeoutInSeconds = Number(env.getOrThrow("PROXY_API_TIMEOUT_IN_SECONDS"))
2830
const maxBytes = maxMegabytes * 1024 * 1024
29-
const file = await downloadFile({ url, maxBytes, timeoutInSeconds })
30-
return new NextResponse(file, { status: 200 })
31+
const fileText = await downloadFile({ url, maxBytes, timeoutInSeconds })
32+
checkIfJsonOrYaml(fileText)
33+
return new NextResponse(fileText, { status: 200 })
3134
} catch (error) {
3235
if (error instanceof Error == false) {
3336
return makeAPIErrorResponse(500, "An unknown error occurred.")
@@ -36,6 +39,8 @@ export async function GET(req: NextRequest) {
3639
return makeAPIErrorResponse(413, "The operation was aborted.")
3740
} else if (error.name === ErrorName.TIMEOUT) {
3841
return makeAPIErrorResponse(408, "The operation timed out.")
42+
} else if (error.name === ErrorName.NOT_JSON_OR_YAML) {
43+
return makeAPIErrorResponse(400, "Url does not point to a JSON or YAML file.")
3944
} else {
4045
return makeAPIErrorResponse(500, error.message)
4146
}
@@ -46,7 +51,7 @@ async function downloadFile(params: {
4651
url: URL,
4752
maxBytes: number,
4853
timeoutInSeconds: number
49-
}): Promise<Blob> {
54+
}): Promise<string> {
5055
const { url, maxBytes, timeoutInSeconds } = params
5156
const abortController = new AbortController()
5257
const timeoutSignal = AbortSignal.timeout(timeoutInSeconds * 1000)
@@ -80,5 +85,18 @@ async function downloadFile(params: {
8085
error.name = ErrorName.MAX_FILE_SIZE_EXCEEDED
8186
throw error
8287
}
83-
return new Blob(chunks)
88+
const blob = new Blob(chunks)
89+
const arrayBuffer = await blob.arrayBuffer()
90+
const decoder = new TextDecoder()
91+
return decoder.decode(arrayBuffer)
92+
}
93+
94+
function checkIfJsonOrYaml(fileText: string) {
95+
try {
96+
parseYaml(fileText) // will also parse JSON as it is a subset of YAML
97+
} catch {
98+
const error = new Error("File is not JSON or YAML")
99+
error.name = ErrorName.NOT_JSON_OR_YAML
100+
throw error
101+
}
84102
}

0 commit comments

Comments
 (0)