mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
feat(HTTP Request Node): Determine binary file name from content-disposition headers (#7032)
Fixes: https://community.n8n.io/t/http-request-node-read-filename-from-content-disposition-header-when-downloading-files/13453 https://community.n8n.io/t/read-filename-from-content-disposition-header-when-downloading-files/22192
This commit is contained in:
committed by
GitHub
parent
25dc4d7825
commit
273d0913fe
@@ -1,3 +1,5 @@
|
||||
import type { Readable } from 'stream';
|
||||
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
IDataObject,
|
||||
@@ -632,7 +634,7 @@ export class HttpRequestV1 implements INodeType {
|
||||
oAuth2Api = await this.getCredentials('oAuth2Api');
|
||||
} catch {}
|
||||
|
||||
let requestOptions: OptionsWithUri;
|
||||
let requestOptions: OptionsWithUri & { useStream?: boolean };
|
||||
let setUiParameter: IDataObject;
|
||||
|
||||
const uiParameters: IDataObject = {
|
||||
@@ -873,6 +875,7 @@ export class HttpRequestV1 implements INodeType {
|
||||
|
||||
if (responseFormat === 'file') {
|
||||
requestOptions.encoding = null;
|
||||
requestOptions.useStream = true;
|
||||
|
||||
if (options.bodyContentType !== 'raw') {
|
||||
requestOptions.body = JSON.stringify(requestOptions.body);
|
||||
@@ -885,6 +888,7 @@ export class HttpRequestV1 implements INodeType {
|
||||
}
|
||||
} else if (options.bodyContentType === 'raw') {
|
||||
requestOptions.json = false;
|
||||
requestOptions.useStream = true;
|
||||
} else {
|
||||
requestOptions.json = true;
|
||||
}
|
||||
@@ -991,7 +995,6 @@ export class HttpRequestV1 implements INodeType {
|
||||
response = response.value;
|
||||
|
||||
const options = this.getNodeParameter('options', itemIndex, {});
|
||||
const url = this.getNodeParameter('url', itemIndex) as string;
|
||||
|
||||
const fullResponse = !!options.fullResponse;
|
||||
|
||||
@@ -1014,8 +1017,7 @@ export class HttpRequestV1 implements INodeType {
|
||||
Object.assign(newItem.binary, items[itemIndex].binary);
|
||||
}
|
||||
|
||||
const fileName = url.split('/').pop();
|
||||
|
||||
let binaryData: Buffer | Readable;
|
||||
if (fullResponse) {
|
||||
const returnItem: IDataObject = {};
|
||||
for (const property of fullResponseProperties) {
|
||||
@@ -1026,20 +1028,13 @@ export class HttpRequestV1 implements INodeType {
|
||||
}
|
||||
|
||||
newItem.json = returnItem;
|
||||
|
||||
newItem.binary![dataPropertyName] = await this.helpers.prepareBinaryData(
|
||||
response!.body as Buffer,
|
||||
fileName,
|
||||
);
|
||||
binaryData = response!.body;
|
||||
} else {
|
||||
newItem.json = items[itemIndex].json;
|
||||
|
||||
newItem.binary![dataPropertyName] = await this.helpers.prepareBinaryData(
|
||||
response! as Buffer,
|
||||
fileName,
|
||||
);
|
||||
binaryData = response;
|
||||
}
|
||||
|
||||
newItem.binary![dataPropertyName] = await this.helpers.prepareBinaryData(binaryData);
|
||||
returnItems.push(newItem);
|
||||
} else if (responseFormat === 'string') {
|
||||
const dataPropertyName = this.getNodeParameter('dataPropertyName', 0);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { Readable } from 'stream';
|
||||
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
@@ -672,7 +674,7 @@ export class HttpRequestV2 implements INodeType {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
let requestOptions: OptionsWithUri;
|
||||
let requestOptions: OptionsWithUri & { useStream?: boolean };
|
||||
let setUiParameter: IDataObject;
|
||||
|
||||
const uiParameters: IDataObject = {
|
||||
@@ -913,6 +915,7 @@ export class HttpRequestV2 implements INodeType {
|
||||
|
||||
if (responseFormat === 'file') {
|
||||
requestOptions.encoding = null;
|
||||
requestOptions.useStream = true;
|
||||
|
||||
if (options.bodyContentType !== 'raw') {
|
||||
requestOptions.body = JSON.stringify(requestOptions.body);
|
||||
@@ -925,6 +928,7 @@ export class HttpRequestV2 implements INodeType {
|
||||
}
|
||||
} else if (options.bodyContentType === 'raw') {
|
||||
requestOptions.json = false;
|
||||
requestOptions.useStream = true;
|
||||
} else {
|
||||
requestOptions.json = true;
|
||||
}
|
||||
@@ -1044,7 +1048,6 @@ export class HttpRequestV2 implements INodeType {
|
||||
response = response.value;
|
||||
|
||||
const options = this.getNodeParameter('options', itemIndex, {});
|
||||
const url = this.getNodeParameter('url', itemIndex) as string;
|
||||
|
||||
const fullResponse = !!options.fullResponse;
|
||||
|
||||
@@ -1067,8 +1070,7 @@ export class HttpRequestV2 implements INodeType {
|
||||
Object.assign(newItem.binary, items[itemIndex].binary);
|
||||
}
|
||||
|
||||
const fileName = url.split('/').pop();
|
||||
|
||||
let binaryData: Buffer | Readable;
|
||||
if (fullResponse) {
|
||||
const returnItem: IDataObject = {};
|
||||
for (const property of fullResponseProperties) {
|
||||
@@ -1079,20 +1081,13 @@ export class HttpRequestV2 implements INodeType {
|
||||
}
|
||||
|
||||
newItem.json = returnItem;
|
||||
|
||||
newItem.binary![dataPropertyName] = await this.helpers.prepareBinaryData(
|
||||
response!.body as Buffer,
|
||||
fileName,
|
||||
);
|
||||
binaryData = response!.body;
|
||||
} else {
|
||||
newItem.json = items[itemIndex].json;
|
||||
|
||||
newItem.binary![dataPropertyName] = await this.helpers.prepareBinaryData(
|
||||
response! as Buffer,
|
||||
fileName,
|
||||
);
|
||||
binaryData = response;
|
||||
}
|
||||
|
||||
newItem.binary![dataPropertyName] = await this.helpers.prepareBinaryData(binaryData);
|
||||
returnItems.push(newItem);
|
||||
} else if (responseFormat === 'string') {
|
||||
const dataPropertyName = this.getNodeParameter('dataPropertyName', 0);
|
||||
|
||||
@@ -1452,8 +1452,6 @@ export class HttpRequestV3 implements INodeType {
|
||||
|
||||
response = response.value;
|
||||
|
||||
const url = this.getNodeParameter('url', itemIndex) as string;
|
||||
|
||||
let responseFormat = this.getNodeParameter(
|
||||
'options.response.response.responseFormat',
|
||||
0,
|
||||
@@ -1525,8 +1523,7 @@ export class HttpRequestV3 implements INodeType {
|
||||
Object.assign(newItem.binary as IBinaryKeyData, items[itemIndex].binary);
|
||||
}
|
||||
|
||||
const fileName = url.split('/').pop();
|
||||
|
||||
let binaryData: Buffer | Readable;
|
||||
if (fullResponse) {
|
||||
const returnItem: IDataObject = {};
|
||||
for (const property of fullResponseProperties) {
|
||||
@@ -1537,19 +1534,12 @@ export class HttpRequestV3 implements INodeType {
|
||||
}
|
||||
|
||||
newItem.json = returnItem;
|
||||
|
||||
newItem.binary![outputPropertyName] = await this.helpers.prepareBinaryData(
|
||||
response!.body as Buffer | Readable,
|
||||
fileName,
|
||||
);
|
||||
binaryData = response!.body;
|
||||
} else {
|
||||
newItem.json = items[itemIndex].json;
|
||||
|
||||
newItem.binary![outputPropertyName] = await this.helpers.prepareBinaryData(
|
||||
response! as Buffer | Readable,
|
||||
fileName,
|
||||
);
|
||||
binaryData = response;
|
||||
}
|
||||
newItem.binary![outputPropertyName] = await this.helpers.prepareBinaryData(binaryData);
|
||||
|
||||
returnItems.push(newItem);
|
||||
} else if (responseFormat === 'text') {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import nock from 'nock';
|
||||
import {
|
||||
setup,
|
||||
equalityTest,
|
||||
workflowToTests,
|
||||
getWorkflowFilenames,
|
||||
initBinaryDataManager,
|
||||
} from '@test/nodes/Helpers';
|
||||
|
||||
describe('Test Binary Data Download', () => {
|
||||
const workflows = getWorkflowFilenames(__dirname);
|
||||
const tests = workflowToTests(workflows);
|
||||
|
||||
const baseUrl = 'https://dummy.domain';
|
||||
|
||||
beforeAll(async () => {
|
||||
await initBinaryDataManager();
|
||||
|
||||
nock.disableNetConnect();
|
||||
|
||||
nock(baseUrl)
|
||||
.persist()
|
||||
.get('/path/to/image.png')
|
||||
.reply(200, Buffer.from('test'), { 'content-type': 'image/png' });
|
||||
|
||||
nock(baseUrl)
|
||||
.persist()
|
||||
.get('/redirect-to-image')
|
||||
.reply(302, {}, { location: baseUrl + '/path/to/image.png' });
|
||||
|
||||
nock(baseUrl).persist().get('/custom-content-disposition').reply(200, Buffer.from('testing'), {
|
||||
'content-disposition': 'attachment; filename="testing.jpg"',
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
nock.restore();
|
||||
});
|
||||
|
||||
const nodeTypes = setup(tests);
|
||||
|
||||
for (const testData of tests) {
|
||||
test(testData.description, async () => equalityTest(testData, nodeTypes));
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,242 @@
|
||||
{
|
||||
"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": {
|
||||
"mimeType": "image/png",
|
||||
"fileType": "image",
|
||||
"fileExtension": "png",
|
||||
"fileName": "image.png",
|
||||
"fileSize": "4 B"
|
||||
}
|
||||
},
|
||||
"json": {}
|
||||
}
|
||||
],
|
||||
"HTTP Request (v2)": [
|
||||
{
|
||||
"binary": {
|
||||
"data": {
|
||||
"mimeType": "image/png",
|
||||
"fileType": "image",
|
||||
"fileExtension": "png",
|
||||
"fileName": "image.png",
|
||||
"fileSize": "4 B"
|
||||
}
|
||||
},
|
||||
"json": {}
|
||||
}
|
||||
],
|
||||
"HTTP Request (v3)": [
|
||||
{
|
||||
"binary": {
|
||||
"data": {
|
||||
"mimeType": "image/png",
|
||||
"fileType": "image",
|
||||
"fileExtension": "png",
|
||||
"fileName": "image.png",
|
||||
"fileSize": "4 B"
|
||||
}
|
||||
},
|
||||
"json": {}
|
||||
}
|
||||
],
|
||||
"HTTP Request (v4)": [
|
||||
{
|
||||
"binary": {
|
||||
"data": {
|
||||
"mimeType": "image/png",
|
||||
"fileType": "image",
|
||||
"fileExtension": "png",
|
||||
"fileName": "image.png",
|
||||
"fileSize": "4 B"
|
||||
}
|
||||
},
|
||||
"json": {}
|
||||
}
|
||||
],
|
||||
"Follow Redirect": [
|
||||
{
|
||||
"binary": {
|
||||
"data": {
|
||||
"mimeType": "image/png",
|
||||
"fileType": "image",
|
||||
"fileExtension": "png",
|
||||
"fileName": "image.png",
|
||||
"fileSize": "4 B"
|
||||
}
|
||||
},
|
||||
"json": {}
|
||||
}
|
||||
],
|
||||
"Content Disposition": [
|
||||
{
|
||||
"binary": {
|
||||
"data": {
|
||||
"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
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user