diff --git a/package-lock.json b/package-lock.json index 99198ff3e8..b07a640db8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2485,6 +2485,57 @@ "node": ">=0.1.90" } }, + "node_modules/@curlconverter/yargs": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@curlconverter/yargs/-/yargs-0.0.2.tgz", + "integrity": "sha512-Q1YEebpCY61kxme4wvU0/IN/uMBfG5pZOKCo9FU+w20ElPvN+eH2qEVbK1C12t3Tee3qeYLLEU6HkiUeO1gc4A==", + "dependencies": { + "@curlconverter/yargs-parser": "^0.0.1", + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@curlconverter/yargs-parser": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@curlconverter/yargs-parser/-/yargs-parser-0.0.1.tgz", + "integrity": "sha512-DbEVRYqrorzwqc63MQ3RODflut1tNla8ZCKo1h83lF7+fbntgubZsDfRDBv5Lxj3vkKuvAolysNM2ekwJev8wA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@curlconverter/yargs/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/@curlconverter/yargs/node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/@curlconverter/yargs/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, "node_modules/@dabh/diagnostics": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", @@ -9038,6 +9089,11 @@ "vite": "^2.1.5 || ^3.0.5" } }, + "node_modules/a-sync-waterfall": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", + "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==" + }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -9808,8 +9864,7 @@ "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" }, "node_modules/asn1": { "version": "0.2.6", @@ -13788,6 +13843,59 @@ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", "dev": true }, + "node_modules/curlconverter": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/curlconverter/-/curlconverter-3.21.0.tgz", + "integrity": "sha512-DXCnp1A/Xa69FujksUfdvWQFAnIn/C+4Wuv8t+UVdZkF/lY5bzj98GGKOGme7V/ckSHDLxE29Xp76sJ5Cpsp5A==", + "dependencies": { + "@curlconverter/yargs": "^0.0.2", + "cookie": "^0.4.1", + "jsesc": "^3.0.2", + "nunjucks": "^3.2.3", + "query-string": "^7.0.1", + "string.prototype.startswith": "^1.0.0", + "yamljs": "^0.3.0" + }, + "bin": { + "curlconverter": "bin/cli.js" + } + }, + "node_modules/curlconverter/node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/curlconverter/node_modules/query-string": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.1.tgz", + "integrity": "sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w==", + "dependencies": { + "decode-uri-component": "^0.2.0", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/curlconverter/node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "engines": { + "node": ">=4" + } + }, "node_modules/currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -13958,7 +14066,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==", - "dev": true, "engines": { "node": ">=0.10" } @@ -17037,6 +17144,14 @@ "node": ">=8" } }, + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/finalhandler": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", @@ -28003,6 +28118,38 @@ "node": ">=0.10.0" } }, + "node_modules/nunjucks": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.3.tgz", + "integrity": "sha512-psb6xjLj47+fE76JdZwskvwG4MYsQKXUtMsPh6U0YMvmyjRtKRFcxnlXGWglNybtNTNVmGdp94K62/+NjF5FDQ==", + "dependencies": { + "a-sync-waterfall": "^1.0.0", + "asap": "^2.0.3", + "commander": "^5.1.0" + }, + "bin": { + "nunjucks-precompile": "bin/precompile" + }, + "engines": { + "node": ">= 6.9.0" + }, + "peerDependencies": { + "chokidar": "^3.3.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/nunjucks/node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "engines": { + "node": ">= 6" + } + }, "node_modules/nwsapi": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", @@ -33584,6 +33731,14 @@ "node": ">=4.5" } }, + "node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "engines": { + "node": ">=6" + } + }, "node_modules/split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -34265,6 +34420,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/string.prototype.startswith": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.startswith/-/string.prototype.startswith-1.0.0.tgz", + "integrity": "sha512-VHhsDkuf8gsw4JNRK9cIZjYe6r7PsVUutVohaBhqYAoPaRADoQH+mMgUg7Cs/TgQeDGEvI+PzPEMOdvdsCMvpg==", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, "node_modules/string.prototype.trimend": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", @@ -41556,6 +41720,7 @@ "cookie-parser": "^1.4.6", "crypto-js": "~4.1.1", "csrf": "^3.1.0", + "curlconverter": "^3.0.0", "dotenv": "^8.0.0", "express": "^4.16.4", "express-openapi-validator": "^4.13.6", @@ -44294,6 +44459,47 @@ "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==" }, + "@curlconverter/yargs": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@curlconverter/yargs/-/yargs-0.0.2.tgz", + "integrity": "sha512-Q1YEebpCY61kxme4wvU0/IN/uMBfG5pZOKCo9FU+w20ElPvN+eH2qEVbK1C12t3Tee3qeYLLEU6HkiUeO1gc4A==", + "requires": { + "@curlconverter/yargs-parser": "^0.0.1", + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5" + }, + "dependencies": { + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + } + } + }, + "@curlconverter/yargs-parser": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@curlconverter/yargs-parser/-/yargs-parser-0.0.1.tgz", + "integrity": "sha512-DbEVRYqrorzwqc63MQ3RODflut1tNla8ZCKo1h83lF7+fbntgubZsDfRDBv5Lxj3vkKuvAolysNM2ekwJev8wA==" + }, "@dabh/diagnostics": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", @@ -50563,6 +50769,11 @@ "integrity": "sha512-HlhVMhrOpBHlkkdSm5wQ6YN65ZqdF9YqP7HS1at6NjQgEOIhBiP7tRZ+dYM3ymztKAX0ovhyKCY9ZnQ/GMK0Qg==", "dev": true }, + "a-sync-waterfall": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", + "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==" + }, "abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -51132,8 +51343,7 @@ "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" }, "asn1": { "version": "0.2.6", @@ -54331,6 +54541,43 @@ } } }, + "curlconverter": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/curlconverter/-/curlconverter-3.21.0.tgz", + "integrity": "sha512-DXCnp1A/Xa69FujksUfdvWQFAnIn/C+4Wuv8t+UVdZkF/lY5bzj98GGKOGme7V/ckSHDLxE29Xp76sJ5Cpsp5A==", + "requires": { + "@curlconverter/yargs": "^0.0.2", + "cookie": "^0.4.1", + "jsesc": "^3.0.2", + "nunjucks": "^3.2.3", + "query-string": "^7.0.1", + "string.prototype.startswith": "^1.0.0", + "yamljs": "^0.3.0" + }, + "dependencies": { + "jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==" + }, + "query-string": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.1.tgz", + "integrity": "sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w==", + "requires": { + "decode-uri-component": "^0.2.0", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + } + }, + "strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==" + } + } + }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -54463,8 +54710,7 @@ "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==", - "dev": true + "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==" }, "dedent": { "version": "0.7.0", @@ -56777,6 +57023,11 @@ "to-regex-range": "^5.0.1" } }, + "filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==" + }, "finalhandler": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", @@ -64791,6 +65042,7 @@ "cookie-parser": "^1.4.6", "crypto-js": "~4.1.1", "csrf": "^3.1.0", + "curlconverter": "^3.0.0", "dotenv": "^8.0.0", "express": "^4.16.4", "express-openapi-validator": "^4.13.6", @@ -66266,6 +66518,23 @@ "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", "dev": true }, + "nunjucks": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.3.tgz", + "integrity": "sha512-psb6xjLj47+fE76JdZwskvwG4MYsQKXUtMsPh6U0YMvmyjRtKRFcxnlXGWglNybtNTNVmGdp94K62/+NjF5FDQ==", + "requires": { + "a-sync-waterfall": "^1.0.0", + "asap": "^2.0.3", + "commander": "^5.1.0" + }, + "dependencies": { + "commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==" + } + } + }, "nwsapi": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", @@ -70672,6 +70941,11 @@ "resolved": "https://registry.npmjs.org/spex/-/spex-3.2.0.tgz", "integrity": "sha512-9srjJM7NaymrpwMHvSmpDeIK5GoRMX/Tq0E8aOlDPS54dDnDUIp30DrP9SphMPEETDLzEM9+4qo+KipmbtPecg==" }, + "split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==" + }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -71205,6 +71479,15 @@ "es-abstract": "^1.19.1" } }, + "string.prototype.startswith": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.startswith/-/string.prototype.startswith-1.0.0.tgz", + "integrity": "sha512-VHhsDkuf8gsw4JNRK9cIZjYe6r7PsVUutVohaBhqYAoPaRADoQH+mMgUg7Cs/TgQeDGEvI+PzPEMOdvdsCMvpg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, "string.prototype.trimend": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", diff --git a/packages/cli/package.json b/packages/cli/package.json index e56a95e6c8..2c5c4d38eb 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -124,6 +124,7 @@ "cookie-parser": "^1.4.6", "crypto-js": "~4.1.1", "csrf": "^3.1.0", + "curlconverter": "^3.0.0", "dotenv": "^8.0.0", "express": "^4.16.4", "express-openapi-validator": "^4.13.6", diff --git a/packages/cli/src/CurlConverter.d.ts b/packages/cli/src/CurlConverter.d.ts new file mode 100644 index 0000000000..f55cf6c968 --- /dev/null +++ b/packages/cli/src/CurlConverter.d.ts @@ -0,0 +1,5 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable import/no-default-export */ +declare module 'curlconverter' { + export function toJsonString(data: string): string; +} diff --git a/packages/cli/src/CurlConverterHelper.ts b/packages/cli/src/CurlConverterHelper.ts new file mode 100644 index 0000000000..a58294630d --- /dev/null +++ b/packages/cli/src/CurlConverterHelper.ts @@ -0,0 +1,466 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import curlconverter from 'curlconverter'; +import get from 'lodash.get'; + +interface CurlJson { + url: string; + raw_url?: string; + method: string; + contentType?: string; + cookies?: { + [key: string]: string; + }; + auth?: { + user: string; + password: string; + }; + headers?: { + [key: string]: string; + }; + files?: { + [key: string]: string; + }; + queries: { + [key: string]: string; + }; + data?: { + [key: string]: string; + }; +} + +interface Parameter { + parameterType?: string; + name: string; + value: string; +} + +export interface HttpNodeParameters { + url?: string; + method: string; + sendBody?: boolean; + authentication: string; + contentType?: 'form-urlencoded' | 'multipart-form-data' | 'json' | 'raw' | 'binaryData'; + rawContentType?: string; + specifyBody?: 'json' | 'keypair'; + bodyParameters?: { + parameters: Parameter[]; + }; + jsonBody?: object; + options: { + allowUnauthorizedCerts?: boolean; + proxy?: string; + timeout?: number; + redirect: { + redirect: { + followRedirects?: boolean; + maxRedirects?: number; + }; + }; + response: { + response: { + fullResponse?: boolean; + responseFormat?: string; + outputPropertyName?: string; + }; + }; + }; + sendHeaders?: boolean; + headerParameters?: { + parameters: Parameter[]; + }; + sendQuery?: boolean; + queryParameters?: { + parameters: Parameter[]; + }; +} + +type HttpNodeHeaders = Pick; + +type HttpNodeQueries = Pick; + +enum ContentTypes { + applicationJson = 'application/json', + applicationFormUrlEncoded = 'application/x-www-form-urlencoded', + applicationMultipart = 'multipart/form-data', +} + +const SUPPORTED_CONTENT_TYPES = [ + ContentTypes.applicationJson, + ContentTypes.applicationFormUrlEncoded, + ContentTypes.applicationMultipart, +]; + +const CONTENT_TYPE_KEY = 'content-type'; + +const FOLLOW_REDIRECT_FLAGS = ['--location', '-L']; + +const MAX_REDIRECT_FLAG = '--max-redirs'; + +const PROXY_FLAGS = ['-x', '--proxy']; + +const INCLUDE_HEADERS_IN_OUTPUT_FLAGS = ['-i', '--include']; + +const REQUEST_FLAGS = ['-X', '--request']; + +const TIMEOUT_FLAGS = ['--connect-timeout']; + +const DOWNLOAD_FILE_FLAGS = ['-O', '-o']; + +const IGNORE_SSL_ISSUES_FLAGS = ['-k', '--insecure']; + +const curlToJson = (curlCommand: string): CurlJson => { + // eslint-disable-next-line n8n-local-rules/no-uncaught-json-parse + return JSON.parse(curlconverter.toJsonString(curlCommand)) as CurlJson; +}; + +const isContentType = (headers: CurlJson['headers'], contentType: ContentTypes): boolean => { + return get(headers, CONTENT_TYPE_KEY) === contentType; +}; + +const isJsonRequest = (curlJson: CurlJson): boolean => { + if (isContentType(curlJson.headers, ContentTypes.applicationJson)) return true; + + if (curlJson.data) { + const bodyKey = Object.keys(curlJson.data)[0]; + try { + JSON.parse(bodyKey); + return true; + } catch (_) { + return false; + } + } + return false; +}; + +const isFormUrlEncodedRequest = (curlJson: CurlJson): boolean => { + if (isContentType(curlJson.headers, ContentTypes.applicationFormUrlEncoded)) return true; + if (curlJson.data && !curlJson.files) return true; + return false; +}; + +const isMultipartRequest = (curlJson: CurlJson): boolean => { + if (isContentType(curlJson.headers, ContentTypes.applicationMultipart)) return true; + + // only multipart/form-data request include files + if (curlJson.files) return true; + return false; +}; + +const isBinaryRequest = (curlJson: CurlJson): boolean => { + if (curlJson?.headers?.[CONTENT_TYPE_KEY]) { + const contentType = curlJson?.headers?.[CONTENT_TYPE_KEY]; + return ['image', 'video', 'audio'].some((d) => contentType.includes(d)); + } + return false; +}; + +const sanatizeCurlCommand = (curlCommand: string) => + curlCommand + .replace(/\r\n/g, ' ') + .replace(/\n/g, ' ') + .replace(/\\/g, ' ') + .replace(/[ ]{2,}/g, ' '); + +const toKeyValueArray = ([key, value]: string[]) => ({ name: key, value }); + +const extractHeaders = (headers: CurlJson['headers'] = {}): HttpNodeHeaders => { + const emptyHeaders = !Object.keys(headers).length; + + const onlyContentTypeHeaderDefined = + Object.keys(headers).length === 1 && headers[CONTENT_TYPE_KEY] !== undefined; + + if (emptyHeaders || onlyContentTypeHeaderDefined) return { sendHeaders: false }; + + return { + sendHeaders: true, + headerParameters: { + parameters: Object.entries(headers) + .map(toKeyValueArray) + .filter((parameter) => parameter.name !== CONTENT_TYPE_KEY), + }, + }; +}; + +const extractQueries = (queries: CurlJson['queries'] = {}): HttpNodeQueries => { + const emptyQueries = !Object.keys(queries).length; + + if (emptyQueries) return { sendQuery: false }; + + return { + sendQuery: true, + queryParameters: { + parameters: Object.entries(queries).map(toKeyValueArray), + }, + }; +}; + +const extractJson = (body: CurlJson['data']) => + //@ts-ignore + // eslint-disable-next-line n8n-local-rules/no-uncaught-json-parse + JSON.parse(Object.keys(body)[0]) as { [key: string]: string }; + +const jsonBodyToNodeParameters = (body: CurlJson['data'] = {}): Parameter[] | [] => { + const data = extractJson(body); + return Object.entries(data).map(toKeyValueArray); +}; + +const multipartToNodeParameters = ( + body: CurlJson['data'] = {}, + files: CurlJson['files'] = {}, +): Parameter[] | [] => { + return [ + ...Object.entries(body) + .map(toKeyValueArray) + .map((e) => ({ parameterType: 'formData', ...e })), + ...Object.entries(files) + .map(toKeyValueArray) + .map((e) => ({ parameterType: 'formBinaryData', ...e })), + ]; +}; + +const keyValueBodyToNodeParameters = (body: CurlJson['data'] = {}): Parameter[] | [] => { + return Object.entries(body).map(toKeyValueArray); +}; + +const lowerCaseContentTypeKey = (obj: { [x: string]: string }): void => { + const regex = new RegExp(CONTENT_TYPE_KEY, 'gi'); + + const contentTypeKey = Object.keys(obj).find((key) => { + const group = Array.from(key.matchAll(regex)); + if (group.length) return true; + return false; + }); + + if (!contentTypeKey) return; + + const value = obj[contentTypeKey]; + delete obj[contentTypeKey]; + obj[CONTENT_TYPE_KEY] = value; +}; + +const encodeBasicAuthentication = (username: string, password: string) => + Buffer.from(`${username}:${password}`).toString('base64'); + +const jsonHasNestedObjects = (json: { [key: string]: string | number | object }) => + Object.values(json).some((e) => typeof e === 'object'); + +const extractGroup = (curlCommand: string, regex: RegExp) => curlCommand.matchAll(regex); + +const mapCookies = (cookies: CurlJson['cookies']): { cookie: string } | {} => { + if (!cookies) return {}; + + const cookiesValues = Object.entries(cookies).reduce( + (accumulator: string, entry: [string, string]) => { + accumulator += `${entry[0]}=${entry[1]};`; + return accumulator; + }, + '', + ); + + if (!cookiesValues) return {}; + + return { + cookie: cookiesValues, + }; +}; + +export const toHttpNodeParameters = (curlCommand: string): HttpNodeParameters => { + const curlJson = curlToJson(curlCommand); + + if (!curlJson.headers) curlJson.headers = {}; + + lowerCaseContentTypeKey(curlJson.headers); + + // set basic authentication + if (curlJson.auth) { + const { user, password: pass } = curlJson.auth; + Object.assign(curlJson.headers, { + authorization: `Basic ${encodeBasicAuthentication(user, pass)}`, + }); + } + + const httpNodeParameters: HttpNodeParameters = { + url: curlJson.url, + authentication: 'none', + method: curlJson.method.toUpperCase(), + ...extractHeaders({ ...curlJson.headers, ...mapCookies(curlJson.cookies) }), + ...extractQueries(curlJson.queries), + options: { + redirect: { + redirect: {}, + }, + response: { + response: {}, + }, + }, + }; + + //attempt to get the curl flags not supported by the library + const curl = sanatizeCurlCommand(curlCommand); + + //check for follow redirect flags + if (FOLLOW_REDIRECT_FLAGS.some((flag) => curl.includes(` ${flag}`))) { + Object.assign(httpNodeParameters.options.redirect?.redirect, { followRedirects: true }); + + if (curl.includes(` ${MAX_REDIRECT_FLAG}`)) { + const extractedValue = Array.from( + extractGroup(curl, new RegExp(` ${MAX_REDIRECT_FLAG} (\\d+)`, 'g')), + ); + if (extractedValue.length) { + const [_, maxRedirects] = extractedValue[0]; + if (maxRedirects) { + Object.assign(httpNodeParameters.options.redirect?.redirect, { maxRedirects }); + } + } + } + } + + //check for proxy flags + if (PROXY_FLAGS.some((flag) => curl.includes(` ${flag}`))) { + const foundFlag = PROXY_FLAGS.find((flag) => curl.includes(` ${flag}`)); + if (foundFlag) { + const extractedValue = Array.from( + extractGroup(curl, new RegExp(` ${foundFlag} (\\S*)`, 'g')), + ); + if (extractedValue.length) { + const [_, proxy] = extractedValue[0]; + Object.assign(httpNodeParameters.options, { proxy }); + } + } + } + + // check for "include header in output" flag + if (INCLUDE_HEADERS_IN_OUTPUT_FLAGS.some((flag) => curl.includes(` ${flag}`))) { + Object.assign(httpNodeParameters.options?.response?.response, { + fullResponse: true, + responseFormat: 'autodetect', + }); + } + + // check for request flag + if (REQUEST_FLAGS.some((flag) => curl.includes(` ${flag}`))) { + const foundFlag = REQUEST_FLAGS.find((flag) => curl.includes(` ${flag}`)); + if (foundFlag) { + const extractedValue = Array.from( + extractGroup(curl, new RegExp(` ${foundFlag} (\\w+)`, 'g')), + ); + if (extractedValue.length) { + const [_, request] = extractedValue[0]; + httpNodeParameters.method = request.toUpperCase(); + } + } + } + + // check for timeout flag + if (TIMEOUT_FLAGS.some((flag) => curl.includes(` ${flag}`))) { + const foundFlag = TIMEOUT_FLAGS.find((flag) => curl.includes(` ${flag}`)); + if (foundFlag) { + const extractedValue = Array.from( + extractGroup(curl, new RegExp(` ${foundFlag} (\\d+)`, 'g')), + ); + if (extractedValue.length) { + const [_, timeout] = extractedValue[0]; + Object.assign(httpNodeParameters.options, { + timeout: parseInt(timeout, 10) * 1000, + }); + } + } + } + + // check for download flag + if (DOWNLOAD_FILE_FLAGS.some((flag) => curl.includes(` ${flag}`))) { + const foundFlag = DOWNLOAD_FILE_FLAGS.find((flag) => curl.includes(` ${flag}`)); + if (foundFlag) { + Object.assign(httpNodeParameters.options.response.response, { + responseFormat: 'file', + outputPropertyName: 'data', + }); + } + } + + if (IGNORE_SSL_ISSUES_FLAGS.some((flag) => curl.includes(` ${flag}`))) { + const foundFlag = IGNORE_SSL_ISSUES_FLAGS.find((flag) => curl.includes(` ${flag}`)); + if (foundFlag) { + Object.assign(httpNodeParameters.options, { + allowUnauthorizedCerts: true, + }); + } + } + + const contentType = curlJson?.headers?.[CONTENT_TYPE_KEY] as ContentTypes; + + if (isBinaryRequest(curlJson)) { + return Object.assign(httpNodeParameters, { + contentType: 'binaryData', + sendBody: true, + }); + } + + if (contentType && !SUPPORTED_CONTENT_TYPES.includes(contentType)) { + return Object.assign(httpNodeParameters, { + sendBody: true, + contentType: 'raw', + rawContentType: contentType, + body: Object.keys(curlJson?.data ?? {})[0], + }); + } + + if (isJsonRequest(curlJson)) { + Object.assign(httpNodeParameters, { + contentType: 'json', + sendBody: true, + }); + + const json = extractJson(curlJson.data); + + if (jsonHasNestedObjects(json)) { + // json body + Object.assign(httpNodeParameters, { + specifyBody: 'json', + jsonBody: JSON.stringify(json), + }); + } else { + // key-value body + Object.assign(httpNodeParameters, { + specifyBody: 'keypair', + bodyParameters: { + parameters: jsonBodyToNodeParameters(curlJson.data), + }, + }); + } + } else if (isFormUrlEncodedRequest(curlJson)) { + Object.assign(httpNodeParameters, { + contentType: 'form-urlencoded', + sendBody: true, + specifyBody: 'keypair', + bodyParameters: { + parameters: keyValueBodyToNodeParameters(curlJson.data), + }, + }); + } else if (isMultipartRequest(curlJson)) { + Object.assign(httpNodeParameters, { + contentType: 'multipart-form-data', + sendBody: true, + bodyParameters: { + parameters: multipartToNodeParameters(curlJson.data, curlJson.files), + }, + }); + } else { + // could not figure the content type so do not set the body + Object.assign(httpNodeParameters, { + sendBody: false, + }); + } + + if (!Object.keys(httpNodeParameters.options?.redirect.redirect).length) { + // @ts-ignore + delete httpNodeParameters.options.redirect; + } + + if (!Object.keys(httpNodeParameters.options.response.response).length) { + // @ts-ignore + delete httpNodeParameters.options.response; + } + + return httpNodeParameters; +}; diff --git a/packages/cli/src/ResponseHelper.ts b/packages/cli/src/ResponseHelper.ts index 9d237c25f7..2518169136 100644 --- a/packages/cli/src/ResponseHelper.ts +++ b/packages/cli/src/ResponseHelper.ts @@ -221,3 +221,13 @@ export function unflattenExecutionData(fullExecutionData: IExecutionFlattedDb): return returnData; } + +export const flattenObject = (obj: { [x: string]: any }, prefix = '') => + Object.keys(obj).reduce((acc, k) => { + const pre = prefix.length ? prefix + '.' : ''; + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + if (typeof obj[k] === 'object') Object.assign(acc, flattenObject(obj[k], pre + k)); + //@ts-ignore + else acc[pre + k] = obj[k]; + return acc; + }, {}); diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index ed8d7fdd6e..67db996f18 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -43,6 +43,7 @@ import { FindManyOptions, getConnectionManager, In } from 'typeorm'; // eslint-disable-next-line import/no-extraneous-dependencies import axios, { AxiosRequestConfig } from 'axios'; import clientOAuth1, { RequestOptions } from 'oauth-1.0a'; +import curlconverter from 'curlconverter'; // IMPORTANT! Do not switch to anther bcrypt library unless really necessary and // tested with all possible systems like Windows, Alpine on ARM, FreeBSD, ... import { compare } from 'bcryptjs'; @@ -96,6 +97,7 @@ import { AUTH_COOKIE_NAME, RESPONSE_ERROR_MESSAGES } from './constants'; import { credentialsController } from './credentials/credentials.controller'; import { oauth2CredentialController } from './credentials/oauth2Credential.api'; import type { + CurlHelper, ExecutionRequest, NodeListSearchRequest, NodeParameterOptionsRequest, @@ -151,6 +153,8 @@ import { import glob from 'fast-glob'; import { ResponseError } from './ResponseHelper'; +import { toHttpNodeParameters } from './CurlConverterHelper'; + require('body-parser-xml')(bodyParser); const exec = promisify(callbackExec); @@ -1061,6 +1065,27 @@ class App { }), ); + // ---------------------------------------- + // curl-converter + // ---------------------------------------- + this.app.post( + `/${this.restEndpoint}/curl-to-json`, + ResponseHelper.send( + async ( + req: CurlHelper.ToJson, + res: express.Response, + ): Promise<{ [key: string]: string }> => { + const curlCommand = req.body.curlCommand ?? ''; + + try { + const parameters = toHttpNodeParameters(curlCommand); + return ResponseHelper.flattenObject(parameters, 'parameters'); + } catch (e) { + throw new ResponseHelper.ResponseError(`Invalid cURL command`, undefined, 400); + } + }, + ), + ); // ---------------------------------------- // Credential-Types // ---------------------------------------- diff --git a/packages/cli/src/requests.d.ts b/packages/cli/src/requests.d.ts index fed9b6088e..03cd9ec6f8 100644 --- a/packages/cli/src/requests.d.ts +++ b/packages/cli/src/requests.d.ts @@ -327,3 +327,11 @@ export declare namespace NodeRequest { type Update = Post; } + +// ---------------------------------- +// /curl-to-json +// ---------------------------------- + +export declare namespace CurlHelper { + type ToJson = AuthenticatedRequest<{}, {}, { curlCommand?: string }>; +} diff --git a/packages/cli/test/unit/CurlConverterHelper.test.ts b/packages/cli/test/unit/CurlConverterHelper.test.ts new file mode 100644 index 0000000000..9725795a9d --- /dev/null +++ b/packages/cli/test/unit/CurlConverterHelper.test.ts @@ -0,0 +1,275 @@ +import { toHttpNodeParameters } from "../../src/CurlConverterHelper"; + +describe('CurlConverterHelper', () => { + + test('Should parse form-urlencoded content type correctly', () => { + const curl = 'curl -X POST https://reqbin.com/echo/post/form -H "Content-Type: application/x-www-form-urlencoded" -d "param1=value1¶m2=value2"'; + const parameters = toHttpNodeParameters(curl); + expect(parameters.url).toBe('https://reqbin.com/echo/post/form'); + expect(parameters.sendBody).toBe(true); + expect(parameters.bodyParameters?.parameters[0].name).toBe('param1'); + expect(parameters.bodyParameters?.parameters[0].value).toBe('value1'); + expect(parameters.bodyParameters?.parameters[1].name).toBe('param2'); + expect(parameters.bodyParameters?.parameters[1].value).toBe('value2'); + expect(parameters.contentType).toBe('form-urlencoded'); + expect(parameters.sendHeaders).toBe(false); + expect(parameters.sendQuery).toBe(false); + }); + + test('Should parse JSON content type correctly', () => { + const curl = `curl -X POST https://reqbin.com/echo/post/json -H 'Content-Type: application/json' -d '{"login":"my_login","password":"my_password"}'`; + const parameters = toHttpNodeParameters(curl); + expect(parameters.url).toBe('https://reqbin.com/echo/post/json'); + expect(parameters.sendBody).toBe(true); + expect(parameters.bodyParameters?.parameters[0].name).toBe('login'); + expect(parameters.bodyParameters?.parameters[0].value).toBe('my_login'); + expect(parameters.bodyParameters?.parameters[1].name).toBe('password'); + expect(parameters.bodyParameters?.parameters[1].value).toBe('my_password'); + expect(parameters.contentType).toBe('json'); + expect(parameters.sendHeaders).toBe(false); + expect(parameters.sendQuery).toBe(false); + }); + + test('Should parse multipart-form-data content type correctly', () => { + const curl = `curl -X POST https://reqbin.com/echo/post/json -v -F key1=value1 -F upload=@localfilename`; + const parameters = toHttpNodeParameters(curl); + expect(parameters.url).toBe('https://reqbin.com/echo/post/json'); + expect(parameters.sendBody).toBe(true); + expect(parameters.bodyParameters?.parameters[0].parameterType).toBe('formData'); + expect(parameters.bodyParameters?.parameters[0].name).toBe('key1'); + expect(parameters.bodyParameters?.parameters[0].value).toBe('value1'); + expect(parameters.bodyParameters?.parameters[1].parameterType).toBe('formBinaryData'); + expect(parameters.bodyParameters?.parameters[1].name).toBe('upload'); + expect(parameters.contentType).toBe('multipart-form-data'); + expect(parameters.sendHeaders).toBe(false); + expect(parameters.sendQuery).toBe(false); + }); + + test('Should parse binary request correctly', () => { + const curl = `curl --location --request POST 'https://www.website.com' --header 'Content-Type: image/png' --data-binary '@/Users/image.png`; + const parameters = toHttpNodeParameters(curl); + console.log(JSON.stringify(parameters, undefined, 2)); + expect(parameters.url).toBe('https://www.website.com'); + expect(parameters.method).toBe('POST'); + expect(parameters.sendBody).toBe(true); + expect(parameters.contentType).toBe('binaryData'); + expect(parameters.sendHeaders).toBe(false); + expect(parameters.sendQuery).toBe(false); + }); + + test('Should parse unknown content type correctly', () => { + const curl = `curl -X POST https://reqbin.com/echo/post/xml + -H "Content-Type: application/xml" + -H "Accept: application/xml" + -d "my_loginmy_password"`; + const parameters = toHttpNodeParameters(curl); + expect(parameters.url).toBe('https://reqbin.com/echo/post/xml'); + expect(parameters.method).toBe('POST'); + expect(parameters.sendBody).toBe(true); + expect(parameters.contentType).toBe('raw'); + expect(parameters.rawContentType).toBe('application/xml'); + expect(parameters.sendHeaders).toBe(true); + expect(parameters.headerParameters?.parameters[0].name).toBe('Accept'); + expect(parameters.headerParameters?.parameters[0].value).toBe('application/xml'); + expect(parameters.sendQuery).toBe(false); + }); + + test('Should parse header properties and keep the original case', () => { + const curl = `curl -X POST https://reqbin.com/echo/post/json -v -F key1=value1 -F upload=@localfilename -H "ACCEPT: text/javascript" -H "content-type: multipart/form-data"`; + const parameters = toHttpNodeParameters(curl); + expect(parameters.url).toBe('https://reqbin.com/echo/post/json'); + expect(parameters.sendBody).toBe(true); + expect(parameters.bodyParameters?.parameters[0].parameterType).toBe('formData'); + expect(parameters.bodyParameters?.parameters[0].name).toBe('key1'); + expect(parameters.bodyParameters?.parameters[0].value).toBe('value1'); + expect(parameters.bodyParameters?.parameters[1].parameterType).toBe('formBinaryData'); + expect(parameters.bodyParameters?.parameters[1].name).toBe('upload'); + expect(parameters.contentType).toBe('multipart-form-data'); + expect(parameters.sendHeaders).toBe(true); + expect(parameters.headerParameters?.parameters[0].name).toBe('ACCEPT'); + expect(parameters.headerParameters?.parameters[0].value).toBe('text/javascript'); + expect(parameters.sendQuery).toBe(false); + }); + + test('Should parse querystring properties', () => { + const curl = `curl -G -d 'q=kitties' -d 'count=20' https://google.com/search`; + const parameters = toHttpNodeParameters(curl); + expect(parameters.url).toBe('https://google.com/search'); + expect(parameters.sendBody).toBe(false); + expect(parameters.contentType).toBeUndefined(); + expect(parameters.sendHeaders).toBe(false); + expect(parameters.sendQuery).toBe(true); + expect(parameters.queryParameters?.parameters[0].name).toBe('q'); + expect(parameters.queryParameters?.parameters[0].value).toBe('kitties'); + expect(parameters.queryParameters?.parameters[1].name).toBe('count'); + expect(parameters.queryParameters?.parameters[1].value).toBe('20'); + }); + + test('Should parse basic authentication property and keep the original case', () => { + const curl = `curl https://reqbin.com/echo -u "login:password"`; + const parameters = toHttpNodeParameters(curl); + expect(parameters.url).toBe('https://reqbin.com/echo'); + expect(parameters.sendBody).toBe(false); + expect(parameters.contentType).toBeUndefined(); + expect(parameters.sendQuery).toBe(false); + expect(parameters.sendHeaders).toBe(true); + expect(parameters.headerParameters?.parameters[0].name).toBe('authorization'); + expect(parameters.headerParameters?.parameters[0].value).toBe(`Basic ${Buffer.from('login:password').toString('base64')}`); + }); + + test('Should parse location flag with --location', () => { + const curl = `curl https://reqbin.com/echo -u "login:password" --location`; + const parameters = toHttpNodeParameters(curl); + expect(parameters.url).toBe('https://reqbin.com/echo'); + expect(parameters.sendBody).toBe(false); + expect(parameters.contentType).toBeUndefined(); + expect(parameters.sendQuery).toBe(false); + expect(parameters.sendHeaders).toBe(true); + expect(parameters.headerParameters?.parameters[0].name).toBe('authorization'); + expect(parameters.headerParameters?.parameters[0].value).toBe(`Basic ${Buffer.from('login:password').toString('base64')}`); + expect(parameters.options.redirect.redirect.followRedirects).toBe(true); + }); + + test('Should parse location flag with --L', () => { + const curl = `curl https://reqbin.com/echo -u "login:password" -L`; + const parameters = toHttpNodeParameters(curl); + expect(parameters.url).toBe('https://reqbin.com/echo'); + expect(parameters.sendBody).toBe(false); + expect(parameters.contentType).toBeUndefined(); + expect(parameters.sendQuery).toBe(false); + expect(parameters.sendHeaders).toBe(true); + expect(parameters.headerParameters?.parameters[0].name).toBe('authorization'); + expect(parameters.headerParameters?.parameters[0].value).toBe(`Basic ${Buffer.from('login:password').toString('base64')}`); + expect(parameters.options.redirect.redirect.followRedirects).toBe(true); + }); + + test('Should parse location and max redirects flags with --location and --max-redirs 10', () => { + const curl = `curl https://reqbin.com/echo -u "login:password" --location --max-redirs 10`; + const parameters = toHttpNodeParameters(curl); + expect(parameters.url).toBe('https://reqbin.com/echo'); + expect(parameters.sendBody).toBe(false); + expect(parameters.contentType).toBeUndefined(); + expect(parameters.sendQuery).toBe(false); + expect(parameters.sendHeaders).toBe(true); + expect(parameters.headerParameters?.parameters[0].name).toBe('authorization'); + expect(parameters.headerParameters?.parameters[0].value).toBe(`Basic ${Buffer.from('login:password').toString('base64')}`); + expect(parameters.options.redirect.redirect.followRedirects).toBe(true); + expect(parameters.options.redirect.redirect.maxRedirects).toBe("10"); + }); + + test('Should parse proxy flag -x', () => { + const curl = `curl https://reqbin.com/echo -u "login:password" -x https://google.com`; + const parameters = toHttpNodeParameters(curl); + expect(parameters.url).toBe('https://reqbin.com/echo'); + expect(parameters.sendBody).toBe(false); + expect(parameters.contentType).toBeUndefined(); + expect(parameters.sendQuery).toBe(false); + expect(parameters.sendHeaders).toBe(true); + expect(parameters.headerParameters?.parameters[0].name).toBe('authorization'); + expect(parameters.headerParameters?.parameters[0].value).toBe(`Basic ${Buffer.from('login:password').toString('base64')}`); + expect(parameters.options.proxy).toBe('https://google.com'); + }); + + test('Should parse proxy flag --proxy', () => { + const curl = `curl https://reqbin.com/echo -u "login:password" -x https://google.com`; + const parameters = toHttpNodeParameters(curl); + expect(parameters.url).toBe('https://reqbin.com/echo'); + expect(parameters.sendBody).toBe(false); + expect(parameters.contentType).toBeUndefined(); + expect(parameters.sendQuery).toBe(false); + expect(parameters.sendHeaders).toBe(true); + expect(parameters.headerParameters?.parameters[0].name).toBe('authorization'); + expect(parameters.headerParameters?.parameters[0].value).toBe(`Basic ${Buffer.from('login:password').toString('base64')}`); + expect(parameters.options.proxy).toBe('https://google.com'); + }); + + test('Should parse include headers on output flag --include', () => { + const curl = `curl https://reqbin.com/echo -u "login:password" --include -x https://google.com`; + const parameters = toHttpNodeParameters(curl); + expect(parameters.url).toBe('https://reqbin.com/echo'); + expect(parameters.sendBody).toBe(false); + expect(parameters.contentType).toBeUndefined(); + expect(parameters.sendQuery).toBe(false); + expect(parameters.sendHeaders).toBe(true); + expect(parameters.headerParameters?.parameters[0].name).toBe('authorization'); + expect(parameters.headerParameters?.parameters[0].value).toBe(`Basic ${Buffer.from('login:password').toString('base64')}`); + expect(parameters.options.response.response.fullResponse).toBe(true); + }); + + test('Should parse include headers on output flag -i', () => { + const curl = `curl https://reqbin.com/echo -u "login:password" -x https://google.com -i`; + const parameters = toHttpNodeParameters(curl); + expect(parameters.url).toBe('https://reqbin.com/echo'); + expect(parameters.sendBody).toBe(false); + expect(parameters.contentType).toBeUndefined(); + expect(parameters.sendQuery).toBe(false); + expect(parameters.sendHeaders).toBe(true); + expect(parameters.headerParameters?.parameters[0].name).toBe('authorization'); + expect(parameters.headerParameters?.parameters[0].value).toBe(`Basic ${Buffer.from('login:password').toString('base64')}`); + expect(parameters.options.response.response.fullResponse).toBe(true); + }); + + test('Should parse include request flag -X', () => { + const curl = `curl -X POST https://reqbin.com/echo -u "login:password" -x https://google.com`; + const parameters = toHttpNodeParameters(curl); + expect(parameters.url).toBe('https://reqbin.com/echo'); + expect(parameters.method).toBe('POST'); + expect(parameters.sendBody).toBe(false); + }); + + test('Should parse include request flag --request', () => { + const curl = `curl --request POST https://reqbin.com/echo -u "login:password" -x https://google.com`; + const parameters = toHttpNodeParameters(curl); + expect(parameters.url).toBe('https://reqbin.com/echo'); + expect(parameters.method).toBe('POST'); + expect(parameters.sendBody).toBe(false); + }); + + test('Should parse include timeout flag --connect-timeout', () => { + const curl = `curl --request POST https://reqbin.com/echo -u "login:password" --connect-timeout 20`; + const parameters = toHttpNodeParameters(curl); + expect(parameters.url).toBe('https://reqbin.com/echo'); + expect(parameters.method).toBe('POST'); + expect(parameters.sendBody).toBe(false); + expect(parameters.options.timeout).toBe(20000); + }); + + test('Should parse download file flag -O', () => { + const curl = `curl --request POST https://reqbin.com/echo -u "login:password" -O`; +; const parameters = toHttpNodeParameters(curl); + expect(parameters.url).toBe('https://reqbin.com/echo'); + expect(parameters.method).toBe('POST'); + expect(parameters.sendBody).toBe(false); + expect(parameters.options.response.response.responseFormat).toBe('file'); + expect(parameters.options.response.response.outputPropertyName).toBe('data'); + }) + + test('Should parse download file flag -o', () => { + const curl = `curl --request POST https://reqbin.com/echo -u "login:password" -o`; + const parameters = toHttpNodeParameters(curl); + expect(parameters.url).toBe('https://reqbin.com/echo'); + expect(parameters.method).toBe('POST'); + expect(parameters.sendBody).toBe(false); + expect(parameters.options.response.response.responseFormat).toBe('file'); + expect(parameters.options.response.response.outputPropertyName).toBe('data'); + }) + + test('Should parse ignore SSL flag -k', () => { + const curl = `curl --request POST https://reqbin.com/echo -u "login:password" -k`; + const parameters = toHttpNodeParameters(curl); + expect(parameters.url).toBe('https://reqbin.com/echo'); + expect(parameters.method).toBe('POST'); + expect(parameters.sendBody).toBe(false); + expect(parameters.options.allowUnauthorizedCerts).toBe(true); + }) + + test('Should parse ignore SSL flag --insecure', () => { + const curl = `curl --request POST https://reqbin.com/echo -u "login:password" --insecure`; + const parameters = toHttpNodeParameters(curl); + expect(parameters.url).toBe('https://reqbin.com/echo'); + expect(parameters.method).toBe('POST'); + expect(parameters.sendBody).toBe(false); + expect(parameters.options.allowUnauthorizedCerts).toBe(true); + }) +}); + diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index be590635c5..2521f1cabc 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -127,7 +127,7 @@ export interface IEndpointOptions { export interface IUpdateInformation { name: string; key: string; - value: string | number; // with null makes problems in NodeSettings.vue + value: string | number | { [key: string]: string | number | boolean }; // with null makes problems in NodeSettings.vue node?: string; oldValue?: string | number; } @@ -924,6 +924,8 @@ export interface IModalState { open: boolean; mode?: string | null; activeId?: string | null; + curlCommand?: string; + httpNodeParameters?: string; } export type IRunDataDisplayMode = 'table' | 'json' | 'binary'; diff --git a/packages/editor-ui/src/api/curlHelper.ts b/packages/editor-ui/src/api/curlHelper.ts new file mode 100644 index 0000000000..a0f0d98a5e --- /dev/null +++ b/packages/editor-ui/src/api/curlHelper.ts @@ -0,0 +1,6 @@ +import {IRestApiContext} from "@/Interface"; +import {makeRestApiRequest} from "@/api/helpers"; + +export function getCurlToJson(context: IRestApiContext, curlCommand: string): Promise<{ curlCommand: string | null }> { + return makeRestApiRequest(context, 'POST', '/curl-to-json', { curlCommand }); +} diff --git a/packages/editor-ui/src/components/ImportCurlModal.vue b/packages/editor-ui/src/components/ImportCurlModal.vue new file mode 100644 index 0000000000..4ffe36c2c1 --- /dev/null +++ b/packages/editor-ui/src/components/ImportCurlModal.vue @@ -0,0 +1,196 @@ + + + + + diff --git a/packages/editor-ui/src/components/ImportParameter.vue b/packages/editor-ui/src/components/ImportParameter.vue new file mode 100644 index 0000000000..e279fde24f --- /dev/null +++ b/packages/editor-ui/src/components/ImportParameter.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/packages/editor-ui/src/components/Modals.vue b/packages/editor-ui/src/components/Modals.vue index feefb8f80f..b6b0aa7204 100644 --- a/packages/editor-ui/src/components/Modals.vue +++ b/packages/editor-ui/src/components/Modals.vue @@ -93,6 +93,10 @@ + + + +