@budibase/server 3.23.10 → 3.23.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@budibase/server",
3
3
  "email": "hi@budibase.com",
4
- "version": "3.23.10",
4
+ "version": "3.23.11",
5
5
  "description": "Budibase Web Server",
6
6
  "main": "src/index.ts",
7
7
  "repository": {
@@ -219,5 +219,5 @@
219
219
  }
220
220
  }
221
221
  },
222
- "gitHead": "ee30281f12deb91eb732b3a423ffcaacec8acc4a"
222
+ "gitHead": "c085b108795137b085f13ae0b0387bcf8454ee52"
223
223
  }
@@ -481,59 +481,41 @@ export class RestIntegration implements IntegrationBase {
481
481
  }
482
482
  }
483
483
  )
484
- const nodeForm = form as FormData & {
485
- getHeaders?: () => Record<string, string | string[]>
486
- }
487
- const formHeaders = nodeForm.getHeaders?.()
488
- if (formHeaders) {
489
- const ensureHeaderObject = (): Record<
490
- string,
491
- string | readonly string[]
492
- > => {
493
- if (!input.headers) {
494
- const headerObject: Record<string, string> = {}
495
- input.headers = headerObject
496
- return headerObject
497
- }
498
- if (Array.isArray(input.headers)) {
499
- const headerObject = input.headers.reduce<Record<string, string>>(
500
- (acc, [name, value]) => {
501
- acc[name] = value
502
- return acc
503
- },
504
- {}
505
- )
506
- input.headers = headerObject
507
- return headerObject
508
- }
509
- if (input.headers instanceof Headers) {
510
- const headerObject: Record<string, string> = {}
511
- input.headers.forEach((value, key) => {
512
- headerObject[key] = value
513
- })
514
- input.headers = headerObject
515
- return headerObject
516
- }
517
- return input.headers
484
+ const ensureHeaderObject = (): Record<
485
+ string,
486
+ string | readonly string[]
487
+ > => {
488
+ if (!input.headers) {
489
+ const headerObject: Record<string, string> = {}
490
+ return headerObject
518
491
  }
519
- const headers = ensureHeaderObject()
520
- for (let [headerName, headerValue] of Object.entries(formHeaders)) {
521
- const normalizedName = headerName.toLowerCase()
522
- const existingKey = Object.keys(headers).find(
523
- key => key.toLowerCase() === normalizedName
492
+ if (Array.isArray(input.headers)) {
493
+ const headerObject = input.headers.reduce<Record<string, string>>(
494
+ (acc, [name, value]) => {
495
+ acc[name] = value
496
+ return acc
497
+ },
498
+ {}
524
499
  )
525
- if (existingKey) {
526
- continue
527
- }
528
- if (typeof headerValue === "undefined" || headerValue === null) {
529
- continue
530
- }
531
- const headerString = Array.isArray(headerValue)
532
- ? headerValue.join(", ")
533
- : String(headerValue)
534
- headers[headerName] = headerString
500
+ return headerObject
535
501
  }
502
+ if (input.headers instanceof Headers) {
503
+ return Object.fromEntries(input.headers)
504
+ }
505
+ return input.headers
506
+ }
507
+
508
+ const headers = ensureHeaderObject()
509
+
510
+ // Delete Content-Type to allow fetch to auto-generate the correct header/boundary.
511
+ const existingContentTypeKey = Object.keys(headers).find(
512
+ key => key.toLowerCase() === "content-type"
513
+ )
514
+ if (existingContentTypeKey) {
515
+ delete headers[existingContentTypeKey]
536
516
  }
517
+
518
+ input.headers = headers
537
519
  input.body = form
538
520
  break
539
521
  }
@@ -28,9 +28,10 @@ import {
28
28
  import { createServer } from "http"
29
29
  import { AddressInfo } from "net"
30
30
  import * as undici from "undici"
31
- import type {
31
+ import {
32
32
  RequestInit as UndiciRequestInit,
33
33
  Response as UndiciResponse,
34
+ FormData as UndiciFormData,
34
35
  } from "undici"
35
36
  import TestConfiguration from "../../../src/tests/utilities/TestConfiguration"
36
37
  import sdk from "../../sdk"
@@ -299,6 +300,48 @@ describe("REST Integration", () => {
299
300
  expectFormDataToMatch(body, { a: "1", b: "2" })
300
301
  })
301
302
 
303
+ it("should correctly clean conflicting Content-Type header for form data", async () => {
304
+ const input = { payload: "data", count: 42 }
305
+
306
+ queueJsonResponse(
307
+ (url, options) => {
308
+ expect(url).toEqual("https://example.com/api/submit")
309
+ expect(options?.method).toEqual("POST")
310
+ expect(options?.body).toBeInstanceOf(UndiciFormData)
311
+
312
+ const headers = options?.headers as Record<string, any>
313
+ const contentTypeHeader =
314
+ headers["Content-Type"] || headers["content-type"]
315
+ // The original Content-Type inserted in the test data below should be stripped so that
316
+ // undici can automatically insert the correct multipart/form-data header with boundary
317
+ expect(contentTypeHeader).toBeUndefined()
318
+ },
319
+ { success: true }
320
+ )
321
+
322
+ const { data } = await integration.create({
323
+ path: "api/submit",
324
+ bodyType: BodyType.FORM_DATA,
325
+ requestBody: input,
326
+ // Injects a conflicting header that the production code MUST delete
327
+ headers: {
328
+ "Content-Type": "application/json",
329
+ "X-Custom-Header": "KeepMe",
330
+ },
331
+ })
332
+
333
+ // Assert that the non-Content-Type header was kept
334
+ const lastFetchOptions = fetchMock.mock.calls[0][1]
335
+ expect((lastFetchOptions!.headers! as any)["X-Custom-Header"]).toEqual(
336
+ "KeepMe"
337
+ )
338
+ expect(
339
+ (lastFetchOptions!.headers! as any)["Content-Type"]
340
+ ).toBeUndefined()
341
+
342
+ expect(data).toEqual({ success: true })
343
+ })
344
+
302
345
  it("should allow encoded form data", () => {
303
346
  const { URLSearchParams } = require("url")
304
347
  const output = integration.addBody("encoded", input, {})