diff --git a/packages/builder/src/components/backend/Datasources/ConfigEditor/stores/validatedConfig.js b/packages/builder/src/components/backend/Datasources/ConfigEditor/stores/validatedConfig.js index 7b8b2c0975..edd39cc49f 100644 --- a/packages/builder/src/components/backend/Datasources/ConfigEditor/stores/validatedConfig.js +++ b/packages/builder/src/components/backend/Datasources/ConfigEditor/stores/validatedConfig.js @@ -86,8 +86,9 @@ export const createValidatedConfigStore = (integration, config) => { ([$configStore, $errorsStore, $selectedValidatorsStore]) => { const validatedConfig = [] + const allowedRestKeys = ["rejectUnauthorized", "downloadImages"] Object.entries(integration.datasource).forEach(([key, properties]) => { - if (integration.name === "REST" && key !== "rejectUnauthorized") { + if (integration.name === "REST" && !allowedRestKeys.includes(key)) { return } diff --git a/packages/server/src/integrations/rest.ts b/packages/server/src/integrations/rest.ts index 6ed8e4e4ec..7104c9daca 100644 --- a/packages/server/src/integrations/rest.ts +++ b/packages/server/src/integrations/rest.ts @@ -1,22 +1,22 @@ import { - Integration, DatasourceFieldType, - QueryType, - PaginationConfig, + HttpMethod, + Integration, IntegrationBase, + PaginationConfig, PaginationValues, - RestQueryFields as RestQuery, - RestConfig, + QueryType, RestAuthType, RestBasicAuthConfig, RestBearerAuthConfig, - HttpMethod, + RestConfig, + RestQueryFields as RestQuery, } from "@budibase/types" import get from "lodash/get" import * as https from "https" import qs from "querystring" -import fetch from "node-fetch" import type { Response } from "node-fetch" +import fetch from "node-fetch" import { formatBytes } from "../utilities" import { performance } from "perf_hooks" import FormData from "form-data" @@ -87,6 +87,12 @@ const SCHEMA: Integration = { default: true, required: false, }, + downloadImages: { + display: "Download images", + type: DatasourceFieldType.BOOLEAN, + default: true, + required: false, + }, }, query: { create: { @@ -139,7 +145,8 @@ class RestIntegration implements IntegrationBase { filename: string | undefined const { contentType, contentDisposition } = getAttachmentHeaders( - response.headers + response.headers, + { downloadImages: this.config.downloadImages } ) if ( contentDisposition.includes("filename") || diff --git a/packages/server/src/integrations/tests/restUtils.spec.ts b/packages/server/src/integrations/tests/restUtils.spec.ts index cdcaaec489..b26bd14c44 100644 --- a/packages/server/src/integrations/tests/restUtils.spec.ts +++ b/packages/server/src/integrations/tests/restUtils.spec.ts @@ -1,13 +1,13 @@ import { getAttachmentHeaders } from "../utils/restUtils" import type { Headers } from "node-fetch" -function headers(dispositionValue: string) { +function headers(dispositionValue: string, contentType?: string) { return { get: (name: string) => { if (name.toLowerCase() === "content-disposition") { return dispositionValue } else { - return "application/pdf" + return contentType || "application/pdf" } }, set: () => {}, @@ -35,4 +35,14 @@ describe("getAttachmentHeaders", () => { ) expect(contentDisposition).toBe(`inline; filename="report.pdf"`) }) + + it("should handle an image", () => { + const { contentDisposition } = getAttachmentHeaders( + headers("", "image/png"), + { + downloadImages: true, + } + ) + expect(contentDisposition).toBe(`attachment; filename="image.png"`) + }) }) diff --git a/packages/server/src/integrations/utils/restUtils.ts b/packages/server/src/integrations/utils/restUtils.ts index 2da9307071..848cb7e33f 100644 --- a/packages/server/src/integrations/utils/restUtils.ts +++ b/packages/server/src/integrations/utils/restUtils.ts @@ -1,6 +1,9 @@ import type { Headers } from "node-fetch" -export function getAttachmentHeaders(headers: Headers) { +export function getAttachmentHeaders( + headers: Headers, + opts?: { downloadImages?: boolean } +) { const contentType = headers.get("content-type") || "" let contentDisposition = headers.get("content-disposition") || "" @@ -23,6 +26,15 @@ export function getAttachmentHeaders(headers: Headers) { } } } + // for images which don't supply a content disposition, make one up, as binary + // data for images in REST responses isn't really useful, we should always download them + else if (opts?.downloadImages && contentType.startsWith("image/")) { + const format = contentType.split("/")[1] + return { + contentDisposition: `attachment; filename="image.${format}"`, + contentType, + } + } return { contentDisposition, contentType } } diff --git a/packages/types/src/documents/app/datasource.ts b/packages/types/src/documents/app/datasource.ts index 32f5bbb132..e52019fc18 100644 --- a/packages/types/src/documents/app/datasource.ts +++ b/packages/types/src/documents/app/datasource.ts @@ -46,6 +46,7 @@ export interface DynamicVariable { export interface RestConfig { url: string rejectUnauthorized: boolean + downloadImages?: boolean defaultHeaders: { [key: string]: any }