mirror of
https://github.com/n8n-io/n8n.git
synced 2024-09-20 14:57:31 -07:00
fix(core): Restore body parsing for all content-types for non webhook routes (no-changelog)(#6911)
Changes in https://github.com/n8n-io/n8n/pull/6394 removed xml body parsing for all non-webhook routes. This broken SAML endpoints as they need the XML body parser to function correctly.
This commit is contained in:
parent
78c83168ac
commit
10c15874b2
|
@ -11,7 +11,7 @@ import { N8nInstanceType } from '@/Interfaces';
|
||||||
import type { IExternalHooksClass } from '@/Interfaces';
|
import type { IExternalHooksClass } from '@/Interfaces';
|
||||||
import { ExternalHooks } from '@/ExternalHooks';
|
import { ExternalHooks } from '@/ExternalHooks';
|
||||||
import { send, sendErrorResponse, ServiceUnavailableError } from '@/ResponseHelper';
|
import { send, sendErrorResponse, ServiceUnavailableError } from '@/ResponseHelper';
|
||||||
import { rawBody, jsonParser, corsMiddleware } from '@/middlewares';
|
import { rawBodyReader, bodyParser, corsMiddleware } from '@/middlewares';
|
||||||
import { TestWebhooks } from '@/TestWebhooks';
|
import { TestWebhooks } from '@/TestWebhooks';
|
||||||
import { WaitingWebhooks } from '@/WaitingWebhooks';
|
import { WaitingWebhooks } from '@/WaitingWebhooks';
|
||||||
import { webhookRequestHandler } from '@/WebhookHelpers';
|
import { webhookRequestHandler } from '@/WebhookHelpers';
|
||||||
|
@ -98,7 +98,7 @@ export abstract class AbstractServer {
|
||||||
this.app.use(compression());
|
this.app.use(compression());
|
||||||
|
|
||||||
// Read incoming data into `rawBody`
|
// Read incoming data into `rawBody`
|
||||||
this.app.use(rawBody);
|
this.app.use(rawBodyReader);
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupDevMiddlewares() {
|
private setupDevMiddlewares() {
|
||||||
|
@ -274,8 +274,8 @@ export abstract class AbstractServer {
|
||||||
this.setupDevMiddlewares();
|
this.setupDevMiddlewares();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup JSON parsing middleware after the webhook handlers are setup
|
// Setup body parsing middleware after the webhook handlers are setup
|
||||||
this.app.use(jsonParser);
|
this.app.use(bodyParser);
|
||||||
|
|
||||||
await this.configure();
|
await this.configure();
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,6 @@ import { Container } from 'typedi';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import stream from 'stream';
|
import stream from 'stream';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
import { parse as parseQueryString } from 'querystring';
|
|
||||||
import { Parser as XmlParser } from 'xml2js';
|
|
||||||
import formidable from 'formidable';
|
import formidable from 'formidable';
|
||||||
|
|
||||||
import { BinaryDataManager, NodeExecuteFunctions } from 'n8n-core';
|
import { BinaryDataManager, NodeExecuteFunctions } from 'n8n-core';
|
||||||
|
@ -40,7 +38,6 @@ import {
|
||||||
BINARY_ENCODING,
|
BINARY_ENCODING,
|
||||||
createDeferredPromise,
|
createDeferredPromise,
|
||||||
ErrorReporterProxy as ErrorReporter,
|
ErrorReporterProxy as ErrorReporter,
|
||||||
jsonParse,
|
|
||||||
LoggerProxy as Logger,
|
LoggerProxy as Logger,
|
||||||
NodeHelpers,
|
NodeHelpers,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
@ -64,6 +61,7 @@ import type { User } from '@db/entities/User';
|
||||||
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||||
import { EventsService } from '@/services/events.service';
|
import { EventsService } from '@/services/events.service';
|
||||||
import { OwnershipService } from './services/ownership.service';
|
import { OwnershipService } from './services/ownership.service';
|
||||||
|
import { parseBody } from './middlewares';
|
||||||
|
|
||||||
const pipeline = promisify(stream.pipeline);
|
const pipeline = promisify(stream.pipeline);
|
||||||
|
|
||||||
|
@ -76,13 +74,6 @@ export const WEBHOOK_METHODS: IHttpRequestMethods[] = [
|
||||||
'PUT',
|
'PUT',
|
||||||
];
|
];
|
||||||
|
|
||||||
const xmlParser = new XmlParser({
|
|
||||||
async: true,
|
|
||||||
normalize: true, // Trim whitespace inside text nodes
|
|
||||||
normalizeTags: true, // Transform tags to lowercase
|
|
||||||
explicitArray: false, // Only put properties in array if length > 1
|
|
||||||
});
|
|
||||||
|
|
||||||
export const webhookRequestHandler =
|
export const webhookRequestHandler =
|
||||||
(webhookManager: IWebhookManager) =>
|
(webhookManager: IWebhookManager) =>
|
||||||
async (req: WebhookRequest | WebhookCORSRequest, res: express.Response) => {
|
async (req: WebhookRequest | WebhookCORSRequest, res: express.Response) => {
|
||||||
|
@ -316,28 +307,7 @@ export async function executeWebhook(
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await req.readRawBody();
|
await parseBody(req);
|
||||||
const { rawBody } = req;
|
|
||||||
if (rawBody?.length) {
|
|
||||||
try {
|
|
||||||
if (contentType === 'application/json') {
|
|
||||||
req.body = jsonParse(rawBody.toString(encoding));
|
|
||||||
} else if (contentType?.endsWith('/xml') || contentType?.endsWith('+xml')) {
|
|
||||||
req.body = await xmlParser.parseStringPromise(rawBody.toString(encoding));
|
|
||||||
} else if (contentType === 'application/x-www-form-urlencoded') {
|
|
||||||
req.body = parseQueryString(rawBody.toString(encoding), undefined, undefined, {
|
|
||||||
maxKeys: 1000,
|
|
||||||
});
|
|
||||||
} else if (contentType === 'text/plain') {
|
|
||||||
req.body = rawBody.toString(encoding);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw new ResponseHelper.UnprocessableRequestError(
|
|
||||||
'Failed to parse request body',
|
|
||||||
error.message,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,22 @@
|
||||||
import { parse as parseContentDisposition } from 'content-disposition';
|
import { parse as parseContentDisposition } from 'content-disposition';
|
||||||
import { parse as parseContentType } from 'content-type';
|
import { parse as parseContentType } from 'content-type';
|
||||||
import getRawBody from 'raw-body';
|
import getRawBody from 'raw-body';
|
||||||
import { type RequestHandler } from 'express';
|
import type { Request, RequestHandler } from 'express';
|
||||||
|
import { parse as parseQueryString } from 'querystring';
|
||||||
|
import { Parser as XmlParser } from 'xml2js';
|
||||||
import { jsonParse } from 'n8n-workflow';
|
import { jsonParse } from 'n8n-workflow';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
|
import { UnprocessableRequestError } from '@/ResponseHelper';
|
||||||
|
|
||||||
|
const xmlParser = new XmlParser({
|
||||||
|
async: true,
|
||||||
|
normalize: true, // Trim whitespace inside text nodes
|
||||||
|
normalizeTags: true, // Transform tags to lowercase
|
||||||
|
explicitArray: false, // Only put properties in array if length > 1
|
||||||
|
});
|
||||||
|
|
||||||
const payloadSizeMax = config.getEnv('endpoints.payloadSizeMax');
|
const payloadSizeMax = config.getEnv('endpoints.payloadSizeMax');
|
||||||
export const rawBody: RequestHandler = async (req, res, next) => {
|
export const rawBodyReader: RequestHandler = async (req, res, next) => {
|
||||||
if ('content-type' in req.headers) {
|
if ('content-type' in req.headers) {
|
||||||
const { type: contentType, parameters } = (() => {
|
const { type: contentType, parameters } = (() => {
|
||||||
try {
|
try {
|
||||||
|
@ -41,21 +51,32 @@ export const rawBody: RequestHandler = async (req, res, next) => {
|
||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const jsonParser: RequestHandler = async (req, res, next) => {
|
export const parseBody = async (req: Request) => {
|
||||||
await req.readRawBody();
|
await req.readRawBody();
|
||||||
|
const { rawBody, contentType, encoding } = req;
|
||||||
if (Buffer.isBuffer(req.rawBody)) {
|
if (rawBody?.length) {
|
||||||
if (req.contentType === 'application/json') {
|
try {
|
||||||
try {
|
if (contentType === 'application/json') {
|
||||||
req.body = jsonParse<unknown>(req.rawBody.toString(req.encoding));
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
} catch (error) {
|
req.body = jsonParse(rawBody.toString(encoding));
|
||||||
res.status(400).send({ error: 'Failed to parse request body' });
|
} else if (contentType?.endsWith('/xml') || contentType?.endsWith('+xml')) {
|
||||||
return;
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
req.body = await xmlParser.parseStringPromise(rawBody.toString(encoding));
|
||||||
|
} else if (contentType === 'application/x-www-form-urlencoded') {
|
||||||
|
req.body = parseQueryString(rawBody.toString(encoding), undefined, undefined, {
|
||||||
|
maxKeys: 1000,
|
||||||
|
});
|
||||||
|
} else if (contentType === 'text/plain') {
|
||||||
|
req.body = rawBody.toString(encoding);
|
||||||
}
|
}
|
||||||
} else {
|
} catch (error) {
|
||||||
req.body = {};
|
throw new UnprocessableRequestError('Failed to parse request body', (error as Error).message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const bodyParser: RequestHandler = async (req, res, next) => {
|
||||||
|
await parseBody(req);
|
||||||
|
if (!req.body) req.body = {};
|
||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
|
|
|
@ -30,7 +30,7 @@ import {
|
||||||
TagsController,
|
TagsController,
|
||||||
UsersController,
|
UsersController,
|
||||||
} from '@/controllers';
|
} from '@/controllers';
|
||||||
import { rawBody, jsonParser, setupAuthMiddlewares } from '@/middlewares';
|
import { rawBodyReader, bodyParser, setupAuthMiddlewares } from '@/middlewares';
|
||||||
|
|
||||||
import { InternalHooks } from '@/InternalHooks';
|
import { InternalHooks } from '@/InternalHooks';
|
||||||
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
|
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
|
||||||
|
@ -118,7 +118,7 @@ export const setupTestServer = ({
|
||||||
enabledFeatures,
|
enabledFeatures,
|
||||||
}: SetupProps): TestServer => {
|
}: SetupProps): TestServer => {
|
||||||
const app = express();
|
const app = express();
|
||||||
app.use(rawBody);
|
app.use(rawBodyReader);
|
||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
|
|
||||||
const testServer: TestServer = {
|
const testServer: TestServer = {
|
||||||
|
@ -153,7 +153,7 @@ export const setupTestServer = ({
|
||||||
|
|
||||||
if (!endpointGroups) return;
|
if (!endpointGroups) return;
|
||||||
|
|
||||||
app.use(jsonParser);
|
app.use(bodyParser);
|
||||||
|
|
||||||
const [routerEndpoints, functionEndpoints] = classifyEndpointGroups(endpointGroups);
|
const [routerEndpoints, functionEndpoints] = classifyEndpointGroups(endpointGroups);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue