Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flatten WSDL file when importing [INS-4291] #8314

Draft
wants to merge 1 commit into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion packages/insomnia/src/common/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { isWorkspace, type Workspace } from '../models/workspace';
import type { CurrentPlan } from '../ui/routes/organization';
import { convert, type InsomniaImporter } from '../utils/importers/convert';
import { id as postmanEnvImporterId } from '../utils/importers/importers/postman-env';
import { flattenWsdl } from '../utils/importers/importers/wsdl';
import { invariant } from '../utils/invariant';
import { database as db } from './database';
import { generateId } from './misc';
Expand Down Expand Up @@ -71,6 +72,7 @@ export async function fetchImportContentFromURI({ uri }: { uri: string }) {
export interface ImportFileDetail {
contentStr: string;
oriFileName: string;
oriFilePath?: string;
}

export interface PostmanDataDumpRawData {
Expand Down Expand Up @@ -119,12 +121,24 @@ let resourceCacheList: ResourceCacheType[] = [];
export async function scanResources(contentList: string[] | ImportFileDetail[]): Promise<ScanResult[]> {
resourceCacheList = [];
const results = await Promise.allSettled(contentList.map(async content => {
const contentStr = typeof content === 'string' ? content : content.contentStr;
let contentStr = typeof content === 'string' ? content : content.contentStr;
const oriFileName = typeof content === 'string' ? '' : content.oriFileName;

let result: ConvertResult | null = null;

try {
if (oriFileName.toLowerCase().endsWith('.wsdl')) {
let oriFilePath = '';
if (typeof content === 'object' && content.oriFilePath) {
oriFilePath = content.oriFilePath;
}
if (oriFilePath) {
// Try to find referenced files in the WSDL file and merge them into the main file
try {
contentStr = await flattenWsdl(contentStr, oriFilePath);
} catch (err) { }
}
}
result = (await convert(contentStr)) as unknown as ConvertResult;
} catch (err: unknown) {
if (err instanceof Error) {
Expand Down
1 change: 1 addition & 0 deletions packages/insomnia/src/ui/routes/import.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export const scanForResourcesAction: ActionFunction = async ({ request }): Promi
contentList.push({
contentStr: await fetchImportContentFromURI({ uri }),
oriFileName: path.basename(filePath),
oriFilePath: filePath,
});
}
} else {
Expand Down
113 changes: 106 additions & 7 deletions packages/insomnia/src/utils/importers/importers/wsdl.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { readFile } from 'node:fs/promises';
import path from 'node:path';

import { DOMParser, XMLSerializer } from '@xmldom/xmldom';
import {
findWSDLForServiceName,
getJsonForWSDL,
Expand Down Expand Up @@ -95,18 +99,113 @@ const convertWsdlToPostman = async (input: string) => {

export const convert: Converter = async rawData => {
try {
if (rawData.indexOf('wsdl:definition') !== -1) {
const postmanData = await convertWsdlToPostman(
`<?xml version="1.0" encoding="UTF-8" ?>${rawData}`,
);
postmanData.info.schema += 'collection.json';
const postmanJson = JSON.stringify(postmanData);
return postman.convert(postmanJson);
if (!verifyWsdl(rawData)) {
return null;
}
const postmanData = await convertWsdlToPostman(
`<?xml version="1.0" encoding="UTF-8" ?>${rawData}`,
);
postmanData.info.schema += 'collection.json';
const postmanJson = JSON.stringify(postmanData);
return postman.convert(postmanJson);
} catch (error) {
console.error(error);
// Nothing
}

return null;
};

const xmlSchemaNamespaceUri = 'http://www.w3.org/2001/XMLSchema';
const wsdlNamespaceUri = 'http://schemas.xmlsoap.org/wsdl/';

function verifyWsdl(fileContent: string) {
try {
const mainWsdlDocument = new DOMParser().parseFromString(fileContent, 'text/xml');
return mainWsdlDocument.documentElement.namespaceURI === wsdlNamespaceUri &&
mainWsdlDocument.documentElement.localName === 'definitions';
} catch (error) {
return false;
}
}

function isXmlSchemaElement(element: Element) {
return element.namespaceURI === xmlSchemaNamespaceUri && element.localName === 'schema';
}

async function recurseXmlSchema(xsdFilePath: string, onTrackSet: Set<string>, needToVerifyXmlSchema = true) {
if (onTrackSet.has(xsdFilePath)) {
return null;
}
const fileContent = await readFile(xsdFilePath, 'utf-8');
const xsdDocument = new DOMParser().parseFromString(fileContent, 'text/xml');
if (needToVerifyXmlSchema) {
if (
!isXmlSchemaElement(xsdDocument.documentElement)
) {
return null;
}
}

onTrackSet.add(xsdFilePath);

try {
// find all import and include tags
const referenceElements = [
...Array.from(xsdDocument.getElementsByTagNameNS(xmlSchemaNamespaceUri, 'import')),
...Array.from(xsdDocument.getElementsByTagNameNS(xmlSchemaNamespaceUri, 'include')),
];
if (referenceElements.length === 0) {
onTrackSet.delete(xsdFilePath);
return xsdDocument.documentElement;
} else {
for (const referenceElement of referenceElements) {
const schemaLocation = referenceElement.getAttribute('schemaLocation');
if (!schemaLocation) {
continue;
}
// only handle relative paths that exist
const absolutePath = path.resolve(path.dirname(xsdFilePath), schemaLocation);
try {
// assure that the file exists
await readFile(absolutePath, 'utf-8');
const childElement = await recurseXmlSchema(absolutePath, onTrackSet);
if (!childElement) {
continue;
}
const parentElementOfReferenceElement = referenceElement.parentNode;
parentElementOfReferenceElement?.replaceChild(childElement, referenceElement);
// remove nested schema element
if (parentElementOfReferenceElement && isXmlSchemaElement(parentElementOfReferenceElement as Element)) {
parentElementOfReferenceElement.parentNode?.replaceChild(childElement, parentElementOfReferenceElement);
}
} catch (error) {
continue;
}
}
onTrackSet.delete(xsdFilePath);
return xsdDocument.documentElement;
}
} catch (error) {
onTrackSet.delete(xsdFilePath);
return null;
}
}

// Merge all referenced xml schema files into the main wsdl file
export async function flattenWsdl(mainFileContent: string, mainFilePath: string) {
if (!verifyWsdl(mainFileContent)) {
throw new Error('Invalid WSDL file');
}

// keep track of all on track filepaths to avoid circular references
const onTrackSet = new Set<string>();

const documentElement = await recurseXmlSchema(mainFilePath, onTrackSet, false);

if (documentElement && documentElement.ownerDocument) {
return new XMLSerializer().serializeToString(documentElement.ownerDocument);
} else {
throw new Error('Cannot flatten WSDL');
}
}
Loading