diff --git a/packages/frontend/editor-ui/src/composables/useImportCurlCommand.test.ts b/packages/frontend/editor-ui/src/composables/useImportCurlCommand.test.ts index db4282d598a..adc502b338e 100644 --- a/packages/frontend/editor-ui/src/composables/useImportCurlCommand.test.ts +++ b/packages/frontend/editor-ui/src/composables/useImportCurlCommand.test.ts @@ -1,4 +1,8 @@ -import { toHttpNodeParameters, useImportCurlCommand } from '@/composables/useImportCurlCommand'; +import { + sanitizeCurlUrlPlaceholders, + toHttpNodeParameters, + useImportCurlCommand, +} from '@/composables/useImportCurlCommand'; const showToast = vi.fn(); @@ -570,3 +574,77 @@ describe('useImportCurlCommand', () => { }); }); }); + +describe('sanitizeCurlUrlPlaceholders', () => { + test('should replace angle-bracket placeholders in URL', () => { + const curl = 'curl https://api.openai.com/v1/agents//runs'; + const result = sanitizeCurlUrlPlaceholders(curl); + expect(result).toBe('curl https://api.openai.com/v1/agents/{AGENT_ID}/runs'); + }); + + test('should handle multiple placeholders in URL', () => { + const curl = 'curl https://example.com//files/'; + const result = sanitizeCurlUrlPlaceholders(curl); + expect(result).toBe('curl https://example.com/{USER_ID}/files/{FILE_ID}'); + }); + + test('should not modify body containing tags', () => { + const curl = "curl https://example.com -d '
Hello World
'"; + const result = sanitizeCurlUrlPlaceholders(curl); + expect(result).toBe(curl); + }); + + test('should handle quoted URLs correctly', () => { + const curl = 'curl "https://api.test.com/"'; + const result = sanitizeCurlUrlPlaceholders(curl); + expect(result).toBe('curl "https://api.test.com/{PLACEHOLDER}"'); + }); + + test('should leave URLs without placeholders untouched', () => { + const curl = 'curl https://example.com/v1/data'; + const result = sanitizeCurlUrlPlaceholders(curl); + expect(result).toBe(curl); + }); + + test('should not crash on malformed curl commands without URLs', () => { + const curl = 'curl -X POST -d "key=value"'; + const result = sanitizeCurlUrlPlaceholders(curl); + expect(result).toBe(curl); + }); + + test('should handle URLs with query parameters and placeholders', () => { + const curl = 'curl https://example.com/?a=1&b='; + const result = sanitizeCurlUrlPlaceholders(curl); + expect(result).toBe('curl https://example.com/{ID}?a=1&b={B}'); + }); + + test('should not alter JSON body containing HTML tags', () => { + const curl = + 'curl https://example.com -H "Content-Type: application/json" -d \'{"message": "Hello"}\''; + const result = sanitizeCurlUrlPlaceholders(curl); + expect(result).toBe(curl); + }); + + test('should not alter JSON body with HTML in nested properties', () => { + const curl = + 'curl https://example.com/api -H "Content-Type: application/json" -d \'{"user": {"bio": "
Hi there
"}}\''; + const result = sanitizeCurlUrlPlaceholders(curl); + expect(result).toBe(curl); + }); + + test('should not alter JSON body where key names contain HTML', () => { + const curl = + 'curl https://example.com -H "Content-Type: application/json" -d \'{"key": "value"}\''; + const result = sanitizeCurlUrlPlaceholders(curl); + expect(result).toBe(curl); + }); + + test('should sanitize URL but keep HTML in JSON body intact', () => { + const curl = + 'curl https://api.example.com/ -H "Content-Type: application/json" -d \'{"content": "
Text
"}\''; + const result = sanitizeCurlUrlPlaceholders(curl); + expect(result).toBe( + 'curl https://api.example.com/{USER_ID} -H "Content-Type: application/json" -d \'{"content": "
Text
"}\'', + ); + }); +}); diff --git a/packages/frontend/editor-ui/src/composables/useImportCurlCommand.ts b/packages/frontend/editor-ui/src/composables/useImportCurlCommand.ts index 636267b9972..2a353d76c17 100644 --- a/packages/frontend/editor-ui/src/composables/useImportCurlCommand.ts +++ b/packages/frontend/editor-ui/src/composables/useImportCurlCommand.ts @@ -231,8 +231,26 @@ export const flattenObject = >(obj: T, prefix {} as Record, ); +/** + * Extracts and sanitizes the URL in a cURL command. + * Converts invalid placeholder syntax like \ → {PLACEHOLDER} + */ +export function sanitizeCurlUrlPlaceholders(curlCommand: string): string { + const CURL_URL_REGEX = /curl\s+(['"]?)(https?:\/\/[^\s'"]+)\1/; + const PLACEHOLDER_REGEX = /<([A-Za-z0-9_-]+)>/g; + + const urlMatch = curlCommand.match(CURL_URL_REGEX); + if (!urlMatch || !urlMatch[2]) return curlCommand; + + const originalUrl = urlMatch[2]; + const sanitizedUrl = originalUrl.replaceAll(PLACEHOLDER_REGEX, '{$1}'); + + return curlCommand.replace(originalUrl, sanitizedUrl); +} + export const toHttpNodeParameters = (curlCommand: string): HttpNodeParameters => { - const curlJson = curlToJson(curlCommand); + const curlJson = curlToJson(sanitizeCurlUrlPlaceholders(curlCommand)); + const headers = curlJson.headers ?? {}; lowerCaseContentTypeKey(headers);