diff --git a/packages/bruno-app/src/utils/curl/parse-curl.js b/packages/bruno-app/src/utils/curl/parse-curl.js index d44aa547a..558611810 100644 --- a/packages/bruno-app/src/utils/curl/parse-curl.js +++ b/packages/bruno-app/src/utils/curl/parse-curl.js @@ -312,7 +312,22 @@ const isURL = (arg) => { if (typeof arg !== 'string') { return false; } - return !!URL.parse(arg || '').host; + + // First try to parse as a regular URL (with protocol) + if (URL.parse(arg || '').host) { + return true; + } + + // Check if it looks like a domain without protocol + // This regex matches domain patterns like: + // - example.com + // - sub.example.com + // - example.com/path + // - example.com/path?query=value + // Must contain at least one dot to be considered a domain + const DOMAIN_PATTERN = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(\/[^\s]*)?(\?[^\s]*)?$/; + + return DOMAIN_PATTERN.test(arg); }; /** @@ -320,8 +335,9 @@ const isURL = (arg) => { * Handles shell-quote operator objects and query parameter patterns */ const isURLFragment = (arg) => { + // If it's a glob pattern that looks like a URL, treat it as a complete URL if (arg && typeof arg === 'object' && arg.op === 'glob') { - return !!URL.parse(arg.pattern || '').host; + return isURL(arg.pattern); } if (arg && typeof arg === 'object' && arg.op === '&') { return true; @@ -341,7 +357,13 @@ const setURL = (request, url) => { const urlString = getUrlString(url); if (!urlString) return; - const newUrl = request.url ? request.url + urlString : urlString; + // Add default protocol if none is present + let processedUrl = urlString; + if (!request.url && !urlString.match(/^[a-zA-Z]+:\/\//)) { + processedUrl = 'https://' + urlString; + } + + const newUrl = request.url ? request.url + processedUrl : processedUrl; const { url: formattedUrl, queries, urlWithoutQuery } = parseUrl(newUrl); diff --git a/packages/bruno-app/src/utils/curl/parse-curl.spec.js b/packages/bruno-app/src/utils/curl/parse-curl.spec.js index 3ab767f62..7e8cff58a 100644 --- a/packages/bruno-app/src/utils/curl/parse-curl.spec.js +++ b/packages/bruno-app/src/utils/curl/parse-curl.spec.js @@ -438,6 +438,73 @@ describe('parseCurlCommand', () => { }); }); + describe('handling URLs without protocols', () => { + it('should parse URL without protocol and default to https', () => { + const result = parseCurlCommand(` + curl echo.usebruno.com + `); + + expect(result).toEqual({ + method: 'get', + url: 'https://echo.usebruno.com', + urlWithoutQuery: 'https://echo.usebruno.com' + }); + }); + + it('should parse URL without protocol with path and query parameters', () => { + const result = parseCurlCommand(` + curl api.example.com/users?page=1&limit=10 + `); + + expect(result).toEqual({ + method: 'get', + url: 'https://api.example.com/users?page=1&limit=10', + urlWithoutQuery: 'https://api.example.com/users', + queries: [ + { name: 'page', value: '1' }, + { name: 'limit', value: '10' } + ] + }); + }); + + it('should parse a complex curl command with multiple features and no protocol', () => { + const result = parseCurlCommand(` + curl -X POST \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer token123" \ + -H "X-Custom-Header: custom header" \ + -d '{"name": "John\\'s data", "email": "john@example.com", "message": "Don\\'t stop believing!", "path": "/home/user/file.txt", "json": {"nested": "value", "array": [1, 2, 3]}}' \ + -u "api_user:api_pass" \ + --compressed \ + api.example.com/v1/users?param1=value1¶m2=custom+param + `); + + expect(result).toEqual({ + method: 'post', + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer token123', + 'X-Custom-Header': 'custom header', + 'Accept-Encoding': 'deflate, gzip' + }, + data: '{"name": "John\'s data", "email": "john@example.com", "message": "Don\'t stop believing!", "path": "/home/user/file.txt", "json": {"nested": "value", "array": [1, 2, 3]}}', + auth: { + mode: 'basic', + basic: { + username: 'api_user', + password: 'api_pass' + } + }, + queries: [ + { name: 'param1', value: 'value1' }, + { name: 'param2', value: 'custom+param' } + ], + url: 'https://api.example.com/v1/users?param1=value1¶m2=custom+param', + urlWithoutQuery: 'https://api.example.com/v1/users' + }); + }); + }); + describe('Edge Cases', () => { it('should handle compressed flag', () => { const result = parseCurlCommand(`