From ec2ec4014cc86304c087f6029bafee4644b5a4bd Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 17 May 2024 14:01:43 +0100 Subject: [PATCH 1/3] Fixing an issue with images and REST queries, these traditionally have only come back as binary data to Budibase, but this isn't very useful, its very difficult to convert these into something that can be used. Instead we will now download images into temporary attachments as we do for other types with a real content-disposition. --- packages/server/src/integrations/utils/restUtils.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/server/src/integrations/utils/restUtils.ts b/packages/server/src/integrations/utils/restUtils.ts index 2da9307071..8042cdba80 100644 --- a/packages/server/src/integrations/utils/restUtils.ts +++ b/packages/server/src/integrations/utils/restUtils.ts @@ -23,6 +23,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 (contentType.startsWith("image/")) { + const format = contentType.split("/")[1] + return { + contentDisposition: `attachment; filename="image.${format}"`, + contentType, + } + } return { contentDisposition, contentType } } From 16c69dcc33c4355b5e649b552ba9154b60e8cda2 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 17 May 2024 14:16:08 +0100 Subject: [PATCH 2/3] Backwards compat. --- .../ConfigEditor/stores/validatedConfig.js | 3 ++- packages/server/src/integrations/rest.ts | 23 ++++++++++++------- .../src/integrations/utils/restUtils.ts | 7 ++++-- .../types/src/documents/app/datasource.ts | 1 + 4 files changed, 23 insertions(+), 11 deletions(-) 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/utils/restUtils.ts b/packages/server/src/integrations/utils/restUtils.ts index 8042cdba80..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") || "" @@ -25,7 +28,7 @@ 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 (contentType.startsWith("image/")) { + else if (opts?.downloadImages && contentType.startsWith("image/")) { const format = contentType.split("/")[1] return { contentDisposition: `attachment; filename="image.${format}"`, 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 } From e320524c6394272f41980f276222f803b6137b09 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 17 May 2024 14:37:01 +0100 Subject: [PATCH 3/3] Test case. --- .../src/integrations/tests/restUtils.spec.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) 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"`) + }) })