| @@ -12,6 +12,31 @@ export interface SpecFetchCredentials { · 11 unmodified lines |
| 12 | 12 | readonly queryParams?: Record<string, string>; |
| 13 | 13 | } |
| 14 | 14 | |
| 15 | +export const MAX_GENERIC_OPENAPI_SPEC_TEXT_LENGTH = 25 * 1024 * 1024; |
| 16 | + |
| 17 | +const formatMiB = (bytes: number): string => `${(bytes / 1024 / 1024).toFixed(1)} MiB`; |
| 18 | + |
| 19 | +const oversizedOpenApiSpecError = (size: number): OpenApiParseError => |
| 20 | + new OpenApiParseError({ |
| 21 | + message: `OpenAPI document is too large for the generic OpenAPI importer (${formatMiB(size)}; limit ${formatMiB(MAX_GENERIC_OPENAPI_SPEC_TEXT_LENGTH)}). Use a provider-specific integration when available, or import a smaller filtered OpenAPI document.`, |
| 22 | + }); |
| 23 | + |
| 24 | +const parseContentLength = ( |
| 25 | + headers: Readonly<Record<string, string | undefined>>, |
| 26 | +): number | undefined => { |
| 27 | + const raw = headers["content-length"] ?? headers["Content-Length"]; |
| 28 | + if (!raw) return undefined; |
| 29 | + const parsed = Number(raw); |
| 30 | + return Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined; |
| 31 | +}; |
| 32 | + |
| 33 | +export const ensureGenericOpenApiSpecTextWithinLimit = ( |
| 34 | + specText: string, |
| 35 | +): Effect.Effect<string, OpenApiParseError> => |
| 36 | + specText.length > MAX_GENERIC_OPENAPI_SPEC_TEXT_LENGTH |
| 37 | + ? Effect.fail(oversizedOpenApiSpecError(specText.length)) |
| 38 | + : Effect.succeed(specText); |
| 39 | + |
| 15 | 40 | // ExtractionError subclass raised from parse() for non-3.x specs |
| 16 | 41 | class OpenApiExtractionErrorFromParse extends OpenApiExtractionError {} |
| 17 | 42 | |
| @@ -50,7 +75,11 @@ export const fetchSpecText = Effect.fn("OpenApi.fetchSpecText")(function* ( · 32 unmodified lines |
| 50 | 75 | message: `Failed to fetch OpenAPI document: HTTP ${response.status}`, |
| 51 | 76 | }); |
| 52 | 77 | } |
| 53 | | − return yield* response.text.pipe( |
| 78 | + const contentLength = parseContentLength(response.headers); |
| 79 | + if (contentLength !== undefined && contentLength > MAX_GENERIC_OPENAPI_SPEC_TEXT_LENGTH) { |
| 80 | + return yield* oversizedOpenApiSpecError(contentLength); |
| 81 | + } |
| 82 | + const specText = yield* response.text.pipe( |
| 54 | 83 | Effect.mapError( |
| 55 | 84 | (_cause) => |
| 56 | 85 | new OpenApiParseError({ |
| @@ -58,6 +87,7 @@ export const fetchSpecText = Effect.fn("OpenApi.fetchSpecText")(function* ( · 1 unmodified line |
| 58 | 87 | }), |
| 59 | 88 | ), |
| 60 | 89 | ); |
| 90 | + return yield* ensureGenericOpenApiSpecTextWithinLimit(specText); |
| 61 | 91 | }); |
| 62 | 92 | |
| 63 | 93 | /** |
| @@ -67,7 +97,7 @@ export const fetchSpecText = Effect.fn("OpenApi.fetchSpecText")(function* ( · 3 unmodified lines |
| 67 | 97 | export const resolveSpecText = (input: string, credentials?: SpecFetchCredentials) => |
| 68 | 98 | input.startsWith("http://") || input.startsWith("https://") |
| 69 | 99 | ? fetchSpecText(input, credentials) |
| 70 | | − : Effect.succeed(input); |
| 100 | + : ensureGenericOpenApiSpecTextWithinLimit(input); |
| 71 | 101 | |
| 72 | 102 | /** |
| 73 | 103 | * Parse an OpenAPI document from spec text and validate it's OpenAPI 3.x. |