From a3a113a7683dd6603d683fbaef1beb3bacf5f0d6 Mon Sep 17 00:00:00 2001 From: chaosrealm Date: Wed, 3 Jul 2024 15:07:22 -0700 Subject: [PATCH 1/3] check body size for POST and POSTForm requests --- api-client.ts | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/api-client.ts b/api-client.ts index a4d54e1..2fb4723 100644 --- a/api-client.ts +++ b/api-client.ts @@ -4,6 +4,8 @@ type Method = "POST" | "GET" | "PUT" | "DELETE"; /* eslint-disable @typescript-eslint/no-explicit-any */ export class CortexApiClient { + private readonly maxRequestSize = 32 * 1000 * 1000; // API is configured with max request body size of 32mb + constructor( private org: string, private apiUrl: string, @@ -27,6 +29,11 @@ export class CortexApiClient { } async POSTForm(path: string, form: FormData) { + const requestSize = CortexApiClient.getFormDataSize(form); + if (requestSize > this.maxRequestSize) { + throw new Error("Request body too large"); + } + return fetch(`${this.apiUrl}/org/${this.org}${path}`, { method: "POST", headers: { @@ -34,16 +41,32 @@ export class CortexApiClient { }, body: form, }); + form.values.length; } private async makeRequest(method: Method, path: string, body?: any) { + const requestBody = body ? JSON.stringify(body) : undefined; + // Note that we use character size instead of byte size. This is still a useful heuristic as we don't want to incur the overhead + // of using TextEncoder to calculate the precise byte count + if (requestBody && requestBody.length >= this.maxRequestSize) { + throw new Error("Request body too large"); + } + return fetch(`${this.apiUrl}/org/${this.org}${path}`, { method, headers: { Authorization: `Bearer ${this.accessToken}`, "Content-Type": "application/json", }, - body: body ? JSON.stringify(body) : undefined, + body: requestBody, }); } + + private static getFormDataSize(formData: FormData) { + return [...formData].reduce( + (size, [name, value]) => + size + (typeof value === "string" ? value.length : value.size), + 0, + ); + } } From 92bdfd50f1026b8d5f094c9b36d8e363711efcbe Mon Sep 17 00:00:00 2001 From: chaosrealm Date: Wed, 3 Jul 2024 15:09:10 -0700 Subject: [PATCH 2/3] Linting --- api-client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api-client.ts b/api-client.ts index 2fb4723..8aa007a 100644 --- a/api-client.ts +++ b/api-client.ts @@ -64,7 +64,7 @@ export class CortexApiClient { private static getFormDataSize(formData: FormData) { return [...formData].reduce( - (size, [name, value]) => + (size, [_, value]) => size + (typeof value === "string" ? value.length : value.size), 0, ); From 8affacc1e8bfc88c279092dfa381f05f72222fd4 Mon Sep 17 00:00:00 2001 From: chaosrealm Date: Wed, 3 Jul 2024 16:50:24 -0700 Subject: [PATCH 3/3] Add test --- api-client.ts | 4 ++-- content.test.ts | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/api-client.ts b/api-client.ts index 8aa007a..7288a35 100644 --- a/api-client.ts +++ b/api-client.ts @@ -41,14 +41,13 @@ export class CortexApiClient { }, body: form, }); - form.values.length; } private async makeRequest(method: Method, path: string, body?: any) { const requestBody = body ? JSON.stringify(body) : undefined; // Note that we use character size instead of byte size. This is still a useful heuristic as we don't want to incur the overhead // of using TextEncoder to calculate the precise byte count - if (requestBody && requestBody.length >= this.maxRequestSize) { + if (requestBody && requestBody.length > this.maxRequestSize) { throw new Error("Request body too large"); } @@ -65,6 +64,7 @@ export class CortexApiClient { private static getFormDataSize(formData: FormData) { return [...formData].reduce( (size, [_, value]) => + // Use heuristic of string length instead of byte size, to avoid incurring the cost of using TextEncoder size + (typeof value === "string" ? value.length : value.size), 0, ); diff --git a/content.test.ts b/content.test.ts index 6f7597c..715afba 100644 --- a/content.test.ts +++ b/content.test.ts @@ -82,6 +82,12 @@ test( expect(content.version).toBe(0); expect(content.commands.length).toBe(1); + // check that prompt that is too large will fail gracefully without hitting the service + const hugePrompt = "p".repeat(32 * 1000 * 1000); + await expect(() => + cortex.generateContent({ title, prompt: hugePrompt }), + ).rejects.toThrow("Request body too large"); + // get content const getContent = await testClient.getContent(content.id); expect(getContent.content.length).toBe(content.content.length);