mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 01:26:44 +00:00
fix(HTTP Request Node): Process text files (#16226)
This commit is contained in:
@@ -27,6 +27,8 @@ import type { Readable } from 'stream';
|
||||
import { keysToLowercase } from '@utils/utilities';
|
||||
|
||||
import { mainProperties } from './Description';
|
||||
import { setFilename } from './utils/binaryData';
|
||||
import { mimeTypeFromResponse } from './utils/parse';
|
||||
import type { BodyParameter, IAuthDataSanitizeKeys } from '../GenericFunctions';
|
||||
import {
|
||||
binaryContentTypes,
|
||||
@@ -123,6 +125,8 @@ export class HttpRequestV3 implements INodeType {
|
||||
|
||||
let autoDetectResponseFormat = false;
|
||||
|
||||
let responseFileName: string | undefined;
|
||||
|
||||
// Can not be defined on a per item level
|
||||
const pagination = this.getNodeParameter('options.pagination.pagination', 0, null, {
|
||||
rawExpressions: true,
|
||||
@@ -240,12 +244,19 @@ export class HttpRequestV3 implements INodeType {
|
||||
allowUnauthorizedCerts: boolean;
|
||||
queryParameterArrays: 'indices' | 'brackets' | 'repeat';
|
||||
response: {
|
||||
response: { neverError: boolean; responseFormat: string; fullResponse: boolean };
|
||||
response: {
|
||||
neverError: boolean;
|
||||
responseFormat: string;
|
||||
fullResponse: boolean;
|
||||
outputPropertyName: string;
|
||||
};
|
||||
};
|
||||
redirect: { redirect: { maxRedirects: number; followRedirects: boolean } };
|
||||
lowercaseHeaders: boolean;
|
||||
};
|
||||
|
||||
responseFileName = response?.response?.outputPropertyName;
|
||||
|
||||
const url = this.getNodeParameter('url', itemIndex) as string;
|
||||
|
||||
const responseFormat = response?.response?.responseFormat || 'autodetect';
|
||||
@@ -904,17 +915,14 @@ export class HttpRequestV3 implements INodeType {
|
||||
const preparedBinaryData = await this.helpers.prepareBinaryData(
|
||||
binaryData,
|
||||
undefined,
|
||||
responseContentType || undefined,
|
||||
mimeTypeFromResponse(responseContentType),
|
||||
);
|
||||
|
||||
if (
|
||||
!preparedBinaryData.fileName &&
|
||||
preparedBinaryData.fileExtension &&
|
||||
typeof requestOptions.uri === 'string' &&
|
||||
requestOptions.uri.endsWith(preparedBinaryData.fileExtension)
|
||||
) {
|
||||
preparedBinaryData.fileName = requestOptions.uri.split('/').pop();
|
||||
}
|
||||
preparedBinaryData.fileName = setFilename(
|
||||
preparedBinaryData,
|
||||
requestOptions,
|
||||
responseFileName,
|
||||
);
|
||||
|
||||
newItem.binary![outputPropertyName] = preparedBinaryData;
|
||||
|
||||
|
||||
22
packages/nodes-base/nodes/HttpRequest/V3/utils/binaryData.ts
Normal file
22
packages/nodes-base/nodes/HttpRequest/V3/utils/binaryData.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { IBinaryData, IRequestOptions } from 'n8n-workflow';
|
||||
|
||||
export const setFilename = (
|
||||
preparedBinaryData: IBinaryData,
|
||||
requestOptions: IRequestOptions,
|
||||
responseFileName: string | undefined,
|
||||
) => {
|
||||
if (
|
||||
!preparedBinaryData.fileName &&
|
||||
preparedBinaryData.fileExtension &&
|
||||
typeof requestOptions.uri === 'string' &&
|
||||
requestOptions.uri.endsWith(preparedBinaryData.fileExtension)
|
||||
) {
|
||||
return requestOptions.uri.split('/').pop();
|
||||
}
|
||||
|
||||
if (!preparedBinaryData.fileName && preparedBinaryData.fileExtension) {
|
||||
return `${responseFileName ?? 'data'}.${preparedBinaryData.fileExtension}`;
|
||||
}
|
||||
|
||||
return preparedBinaryData.fileName;
|
||||
};
|
||||
9
packages/nodes-base/nodes/HttpRequest/V3/utils/parse.ts
Normal file
9
packages/nodes-base/nodes/HttpRequest/V3/utils/parse.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export const mimeTypeFromResponse = (
|
||||
responseContentType: string | undefined,
|
||||
): string | undefined => {
|
||||
if (!responseContentType) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return responseContentType.split(' ')[0].split(';')[0];
|
||||
};
|
||||
@@ -10,6 +10,11 @@ describe('Test Binary Data Download', () => {
|
||||
.get('/path/to/image.png')
|
||||
.reply(200, Buffer.from('test'), { 'content-type': 'image/png' });
|
||||
|
||||
nock(baseUrl)
|
||||
.persist()
|
||||
.get('/path/to/text.txt')
|
||||
.reply(200, Buffer.from('test'), { 'content-type': 'text/plain; charset=utf-8' });
|
||||
|
||||
nock(baseUrl)
|
||||
.persist()
|
||||
.get('/redirect-to-image')
|
||||
|
||||
@@ -1,227 +1,264 @@
|
||||
{
|
||||
"name": "Download as Binary Data",
|
||||
"nodes": [
|
||||
{
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"parameters": {},
|
||||
"position": [580, 300]
|
||||
},
|
||||
{
|
||||
"name": "HTTP Request (v1)",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 1,
|
||||
"parameters": {
|
||||
"url": "https://dummy.domain/path/to/image.png",
|
||||
"responseFormat": "file"
|
||||
},
|
||||
"position": [1020, -100]
|
||||
},
|
||||
{
|
||||
"name": "HTTP Request (v2)",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"url": "https://dummy.domain/path/to/image.png",
|
||||
"responseFormat": "file",
|
||||
"options": {}
|
||||
},
|
||||
"position": [1020, 80]
|
||||
},
|
||||
{
|
||||
"name": "HTTP Request (v3)",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 3,
|
||||
"parameters": {
|
||||
"url": "https://dummy.domain/path/to/image.png",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"responseFormat": "file"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": [1020, 240]
|
||||
},
|
||||
{
|
||||
"name": "HTTP Request (v4)",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4,
|
||||
"parameters": {
|
||||
"url": "https://dummy.domain/path/to/image.png",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"responseFormat": "file"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": [1020, 400]
|
||||
},
|
||||
{
|
||||
"name": "Follow Redirect",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.1,
|
||||
"parameters": {
|
||||
"url": "https://dummy.domain/redirect-to-image",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"responseFormat": "file"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": [1020, 560]
|
||||
},
|
||||
{
|
||||
"name": "Content Disposition",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.1,
|
||||
"parameters": {
|
||||
"url": "https://dummy.domain/custom-content-disposition",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"responseFormat": "file"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": [1020, 720]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"HTTP Request (v1)": [
|
||||
{
|
||||
"binary": {
|
||||
"data": {
|
||||
"data": "dGVzdA==",
|
||||
"mimeType": "image/png",
|
||||
"fileType": "image",
|
||||
"fileExtension": "png",
|
||||
"fileName": "image.png",
|
||||
"fileSize": "4 B"
|
||||
}
|
||||
},
|
||||
"json": {}
|
||||
}
|
||||
],
|
||||
"HTTP Request (v2)": [
|
||||
{
|
||||
"binary": {
|
||||
"data": {
|
||||
"data": "dGVzdA==",
|
||||
"mimeType": "image/png",
|
||||
"fileType": "image",
|
||||
"fileExtension": "png",
|
||||
"fileName": "image.png",
|
||||
"fileSize": "4 B"
|
||||
}
|
||||
},
|
||||
"json": {}
|
||||
}
|
||||
],
|
||||
"HTTP Request (v3)": [
|
||||
{
|
||||
"binary": {
|
||||
"data": {
|
||||
"data": "dGVzdA==",
|
||||
"mimeType": "image/png",
|
||||
"fileType": "image",
|
||||
"fileExtension": "png",
|
||||
"fileName": "image.png",
|
||||
"fileSize": "4 B"
|
||||
}
|
||||
},
|
||||
"json": {}
|
||||
}
|
||||
],
|
||||
"HTTP Request (v4)": [
|
||||
{
|
||||
"binary": {
|
||||
"data": {
|
||||
"data": "dGVzdA==",
|
||||
"mimeType": "image/png",
|
||||
"fileType": "image",
|
||||
"fileExtension": "png",
|
||||
"fileName": "image.png",
|
||||
"fileSize": "4 B"
|
||||
}
|
||||
},
|
||||
"json": {}
|
||||
}
|
||||
],
|
||||
"Follow Redirect": [
|
||||
{
|
||||
"binary": {
|
||||
"data": {
|
||||
"data": "dGVzdA==",
|
||||
"mimeType": "image/png",
|
||||
"fileType": "image",
|
||||
"fileExtension": "png",
|
||||
"fileName": "image.png",
|
||||
"fileSize": "4 B"
|
||||
}
|
||||
},
|
||||
"json": {}
|
||||
}
|
||||
],
|
||||
"Content Disposition": [
|
||||
{
|
||||
"binary": {
|
||||
"data": {
|
||||
"data": "dGVzdGluZw==",
|
||||
"mimeType": "image/jpeg",
|
||||
"fileType": "image",
|
||||
"fileExtension": "jpg",
|
||||
"fileName": "testing.jpg",
|
||||
"fileSize": "7 B"
|
||||
}
|
||||
},
|
||||
"json": {}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "HTTP Request (v1)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "HTTP Request (v2)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "HTTP Request (v3)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "HTTP Request (v4)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Follow Redirect",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Content Disposition",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
"name": "Download as Binary Data",
|
||||
"nodes": [
|
||||
{
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"parameters": {},
|
||||
"position": [580, 300]
|
||||
},
|
||||
{
|
||||
"name": "HTTP Request (v1)",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 1,
|
||||
"parameters": {
|
||||
"url": "https://dummy.domain/path/to/image.png",
|
||||
"responseFormat": "file"
|
||||
},
|
||||
"position": [1020, -100]
|
||||
},
|
||||
{
|
||||
"name": "HTTP Request (v2)",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 2,
|
||||
"parameters": {
|
||||
"url": "https://dummy.domain/path/to/image.png",
|
||||
"responseFormat": "file",
|
||||
"options": {}
|
||||
},
|
||||
"position": [1020, 80]
|
||||
},
|
||||
{
|
||||
"name": "HTTP Request (v3)",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 3,
|
||||
"parameters": {
|
||||
"url": "https://dummy.domain/path/to/image.png",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"responseFormat": "file"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": [1020, 240]
|
||||
},
|
||||
{
|
||||
"name": "HTTP Request (v4)",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4,
|
||||
"parameters": {
|
||||
"url": "https://dummy.domain/path/to/image.png",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"responseFormat": "file"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": [1020, 400]
|
||||
},
|
||||
{
|
||||
"name": "Follow Redirect",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.1,
|
||||
"parameters": {
|
||||
"url": "https://dummy.domain/redirect-to-image",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"responseFormat": "file"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": [1020, 560]
|
||||
},
|
||||
{
|
||||
"name": "Content Disposition",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.1,
|
||||
"parameters": {
|
||||
"url": "https://dummy.domain/custom-content-disposition",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"responseFormat": "file"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"position": [1020, 720]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://dummy.domain/path/to/text.txt",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"responseFormat": "file"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "HTTP Request (v4)2",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4,
|
||||
"position": [1680, 600],
|
||||
"id": "665fef5c-6380-4bc0-be2a-430446b140ca"
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"HTTP Request (v1)": [
|
||||
{
|
||||
"binary": {
|
||||
"data": {
|
||||
"data": "dGVzdA==",
|
||||
"mimeType": "image/png",
|
||||
"fileType": "image",
|
||||
"fileExtension": "png",
|
||||
"fileName": "image.png",
|
||||
"fileSize": "4 B"
|
||||
}
|
||||
},
|
||||
"json": {}
|
||||
}
|
||||
],
|
||||
"HTTP Request (v2)": [
|
||||
{
|
||||
"binary": {
|
||||
"data": {
|
||||
"data": "dGVzdA==",
|
||||
"mimeType": "image/png",
|
||||
"fileType": "image",
|
||||
"fileExtension": "png",
|
||||
"fileName": "image.png",
|
||||
"fileSize": "4 B"
|
||||
}
|
||||
},
|
||||
"json": {}
|
||||
}
|
||||
],
|
||||
"HTTP Request (v3)": [
|
||||
{
|
||||
"binary": {
|
||||
"data": {
|
||||
"data": "dGVzdA==",
|
||||
"mimeType": "image/png",
|
||||
"fileType": "image",
|
||||
"fileExtension": "png",
|
||||
"fileName": "image.png",
|
||||
"fileSize": "4 B"
|
||||
}
|
||||
},
|
||||
"json": {}
|
||||
}
|
||||
],
|
||||
"HTTP Request (v4)": [
|
||||
{
|
||||
"binary": {
|
||||
"data": {
|
||||
"data": "dGVzdA==",
|
||||
"mimeType": "image/png",
|
||||
"fileType": "image",
|
||||
"fileExtension": "png",
|
||||
"fileName": "image.png",
|
||||
"fileSize": "4 B"
|
||||
}
|
||||
},
|
||||
"json": {}
|
||||
}
|
||||
],
|
||||
"Follow Redirect": [
|
||||
{
|
||||
"binary": {
|
||||
"data": {
|
||||
"data": "dGVzdA==",
|
||||
"mimeType": "image/png",
|
||||
"fileType": "image",
|
||||
"fileExtension": "png",
|
||||
"fileName": "image.png",
|
||||
"fileSize": "4 B"
|
||||
}
|
||||
},
|
||||
"json": {}
|
||||
}
|
||||
],
|
||||
"Content Disposition": [
|
||||
{
|
||||
"binary": {
|
||||
"data": {
|
||||
"data": "dGVzdGluZw==",
|
||||
"mimeType": "image/jpeg",
|
||||
"fileType": "image",
|
||||
"fileExtension": "jpg",
|
||||
"fileName": "testing.jpg",
|
||||
"fileSize": "7 B"
|
||||
}
|
||||
},
|
||||
"json": {}
|
||||
}
|
||||
],
|
||||
"HTTP Request (v4)2": [
|
||||
{
|
||||
"binary": {
|
||||
"data": {
|
||||
"data": "dGVzdA==",
|
||||
"mimeType": "text/plain",
|
||||
"fileType": "text",
|
||||
"fileExtension": "txt",
|
||||
"fileName": "text.txt",
|
||||
"fileSize": "4 B"
|
||||
}
|
||||
},
|
||||
"json": {}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "HTTP Request (v1)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "HTTP Request (v2)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "HTTP Request (v3)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "HTTP Request (v4)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Follow Redirect",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Content Disposition",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "HTTP Request (v4)2",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { IBinaryData, IRequestOptions } from 'n8n-workflow';
|
||||
|
||||
import { setFilename } from '../../V3/utils/binaryData';
|
||||
|
||||
describe('setFilename', () => {
|
||||
it('returns filename from URI if fileName is missing and URI ends with fileExtension', () => {
|
||||
const preparedBinaryData = { fileExtension: 'png' } as IBinaryData;
|
||||
const requestOptions = { uri: 'https://example.com/image.png' } as IRequestOptions;
|
||||
expect(setFilename(preparedBinaryData, requestOptions, undefined)).toBe('image.png');
|
||||
});
|
||||
|
||||
it('returns constructed filename if fileName is missing and URI does not end with fileExtension', () => {
|
||||
const preparedBinaryData = { fileExtension: 'jpg' } as IBinaryData;
|
||||
const requestOptions = { uri: 'https://example.com/image.png' } as IRequestOptions;
|
||||
expect(setFilename(preparedBinaryData, requestOptions, 'response')).toBe('response.jpg');
|
||||
});
|
||||
|
||||
it('returns constructed filename with default "data" if responseFileName is undefined', () => {
|
||||
const preparedBinaryData = { fileExtension: 'txt' } as IBinaryData;
|
||||
const requestOptions = { uri: 'https://example.com/file' } as IRequestOptions;
|
||||
expect(setFilename(preparedBinaryData, requestOptions, undefined)).toBe('data.txt');
|
||||
});
|
||||
|
||||
it('returns fileName if it exists', () => {
|
||||
const preparedBinaryData = { fileName: 'myfile.pdf', fileExtension: 'pdf' } as IBinaryData;
|
||||
const requestOptions = { uri: 'https://example.com/file.pdf' } as IRequestOptions;
|
||||
expect(setFilename(preparedBinaryData, requestOptions, 'response')).toBe('myfile.pdf');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import { mimeTypeFromResponse } from '../../V3/utils/parse';
|
||||
|
||||
describe('mimeTypeFromResponse', () => {
|
||||
it('should return undefined if input is undefined', () => {
|
||||
expect(mimeTypeFromResponse(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return the mime type for a simple type', () => {
|
||||
expect(mimeTypeFromResponse('image/png')).toBe('image/png');
|
||||
});
|
||||
|
||||
it('should strip charset from content type', () => {
|
||||
expect(mimeTypeFromResponse('text/html; charset=utf-8')).toBe('text/html');
|
||||
});
|
||||
|
||||
it('should strip charset from content type', () => {
|
||||
expect(mimeTypeFromResponse('text/plain; charset=utf-8')).toBe('text/plain');
|
||||
});
|
||||
|
||||
it('should strip boundary from multipart content type', () => {
|
||||
expect(mimeTypeFromResponse('multipart/form-data; boundary=ExampleBoundaryString')).toBe(
|
||||
'multipart/form-data',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle content type with extra spaces', () => {
|
||||
expect(mimeTypeFromResponse('application/json ; charset=utf-8')).toBe('application/json');
|
||||
});
|
||||
|
||||
it('should handle content type with space before semicolon', () => {
|
||||
expect(mimeTypeFromResponse('application/xml ;charset=utf-8')).toBe('application/xml');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user