├── .nvmrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── index.js ├── .editorconfig ├── src ├── UserError.js ├── validate.js ├── convert.js ├── constants.js ├── getMetaData.js └── lib.js ├── scripts ├── test.sh ├── test-lint.sh ├── test-unit.sh └── release.sh ├── .github └── workflows │ ├── test.yaml │ ├── publish-new-release.yml │ └── draft-new-release.yml ├── test ├── importer.plugin.test.js ├── unit.test.js ├── large-request.js └── conversion.test.js ├── package.json ├── README.md ├── assets ├── unnecessaryOptions.js ├── supportedOptions.js └── shell-quote.js ├── CHANGELOG.md ├── .eslintrc └── LICENSE /.nvmrc: -------------------------------------------------------------------------------- 1 | 18.13.0 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | coverage/* 3 | .idea 4 | .vscode 5 | .nyc_output 6 | .coverage 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | sample_files 2 | node_modules 3 | test 4 | coverage 5 | .idea 6 | .vscode 7 | .nyc_output -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "7" 4 | - "8" 5 | - "9" 6 | - "17" 7 | - "lts/*" -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | validate: require('./src/validate'), 3 | convert: require('./src/convert'), 4 | getMetaData: require('./src/getMetaData') 5 | }; 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | max_length = 120 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.{json, yml, html, hbs}] 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /src/UserError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * constructor userError 3 | * @constructor 4 | * @param {*} message errorMessage 5 | * @param {*} data additional data to be reported 6 | */ 7 | class UserError extends Error { 8 | constructor(message, data) { 9 | super(message); 10 | this.name = 'UserError'; 11 | this.data = data || {}; 12 | } 13 | } 14 | 15 | module.exports = UserError; 16 | -------------------------------------------------------------------------------- /src/validate.js: -------------------------------------------------------------------------------- 1 | var lib = require('./lib.js'); 2 | module.exports = function (curlCommand) { 3 | // must be a string that starts with "curl " 4 | if ( 5 | typeof curlCommand === 'string' && 6 | curlCommand.startsWith('curl ') 7 | ) { 8 | return lib.validate(curlCommand); 9 | } 10 | 11 | return { 12 | result: false, 13 | reason: 'Curl commands must begin with `curl `' 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Stop on first error 4 | set -e; 5 | 6 | # information block 7 | 8 | echo -e "\033[0m\033[2m"; 9 | date; 10 | echo "node `node -v`"; 11 | echo "npm v`npm -v`"; 12 | which git &>/dev/null && git --version; 13 | echo -e "\033[0m"; 14 | 15 | # git version 16 | which git &>/dev/null && \ 17 | echo -e "Running on branch: \033[4m`git rev-parse --abbrev-ref HEAD`\033[0m (${NODE_ENV:=development} environment)"; 18 | 19 | # run lint 20 | npm run test-lint; 21 | 22 | # run unit tests 23 | npm run test-unit; 24 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [develop, master] 6 | pull_request: 7 | 8 | jobs: 9 | Unit-Tests: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | node-version: [12.x, 16.x, 18.x] 14 | steps: 15 | - name: Get Code 16 | uses: actions/checkout@v3 17 | - name: Use Node.js ${{ matrix.node-version }} 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | - run: npm ci 22 | - run: npm test -------------------------------------------------------------------------------- /scripts/test-lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ---------------------------------------------------------------------------------------------------------------------- 3 | # This script is intended to contain all actions pertaining to code style checking, linting and normalisation. 4 | # 5 | # 1. The script executes linting routines on specific folders. 6 | # ---------------------------------------------------------------------------------------------------------------------- 7 | 8 | # Stop on first error 9 | set -e; 10 | 11 | # banner 12 | echo -e "\033[93mLinting and style-checking...\033[0m"; 13 | echo -en "\033[0m\033[2m"; 14 | echo -e "eslint `eslint -v`\033[0m\n"; 15 | 16 | # run style checker 17 | eslint index.js ./src/** ./test/** ; 18 | echo -en "\033[92mNo lint errors found.\n\033[0m"; 19 | -------------------------------------------------------------------------------- /src/convert.js: -------------------------------------------------------------------------------- 1 | var lib = require('./lib.js'); 2 | 3 | module.exports = function (input, cb) { 4 | var result; 5 | 6 | if (!input) { 7 | cb(null, { 8 | result: false, 9 | reason: 'Invalid input object provided' 10 | }); 11 | } 12 | 13 | if (input.type === 'string') { 14 | result = lib.convertCurlToRequest(input.data); 15 | if (result.error) { 16 | return cb(null, { 17 | result: false, 18 | error: result.error, 19 | reason: result.error.message 20 | }); 21 | } 22 | 23 | return cb(null, { 24 | result: true, 25 | output: [{ 26 | type: 'request', 27 | data: result 28 | }] 29 | }); 30 | 31 | } 32 | 33 | return cb(null, { 34 | result: false, 35 | reason: 'Only input.type=string supported for cURL' 36 | }); 37 | 38 | }; 39 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | 3 | module.exports = { 4 | USER_ERRORS: { 5 | INVALID_FORMAT: 'Invalid format for cURL.', 6 | METHOD_NOT_SUPPORTED: (_, method) => { return `The method ${method} is not supported.`; }, 7 | UNABLE_TO_PARSE_HEAD_AND_DATA: 'Unable to parse: Both (--head/-I) and (-d/--data/--data-raw/--data-binary/--data-ascii/--data-urlencode) are not supported.', 8 | UNABLE_TO_PARSE_NO_URL: 'Unable to parse: Could not identify the URL. Please use the --url option.', 9 | CANNOT_DETECT_URL: 'Could not detect the URL from cURL. Please make sure it\'s a valid cURL.', 10 | INPUT_WITHOUT_OPTIONS: 'Only the URL can be provided without an option preceding it. All other inputs must be specified via options.', 11 | MALFORMED_URL: 'Please check your cURL string for malformed URL.' 12 | } 13 | }; 14 | 15 | -------------------------------------------------------------------------------- /src/getMetaData.js: -------------------------------------------------------------------------------- 1 | var lib = require('./lib.js'); 2 | 3 | module.exports = function (input, cb) { 4 | var result; 5 | 6 | if (!input) { 7 | cb(null, { 8 | result: false, 9 | reason: 'Invalid input object provided' 10 | }); 11 | } 12 | 13 | if (input.type === 'string') { 14 | result = lib.getMetaData(input.data); 15 | if (result.error) { 16 | return cb(null, { 17 | result: false, 18 | error: result.error, 19 | reason: result.error.message 20 | }); 21 | } 22 | 23 | return cb(null, { 24 | result: true, 25 | name: result.url, 26 | output: [{ 27 | type: 'request', 28 | data: result.url 29 | }] 30 | }); 31 | 32 | } 33 | 34 | return cb(null, { 35 | result: false, 36 | reason: 'Only input.type=string supported for cURL' 37 | }); 38 | }; 39 | -------------------------------------------------------------------------------- /test/importer.plugin.test.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect.js'), 2 | path = require('path'), 3 | packageJson = require(path.resolve('package.json')), 4 | package = require('../'); 5 | 6 | describe('packageJson should', function() { 7 | it('have the right metadata in com_postman_plugin', function() { 8 | expect(packageJson).to.have.property('com_postman_plugin'); 9 | expect(packageJson.com_postman_plugin).to.have.property('name'); 10 | expect(packageJson.com_postman_plugin).to.have.property('source_format'); 11 | expect(packageJson.com_postman_plugin.plugin_type).to.be('importer'); 12 | }); 13 | }); 14 | 15 | describe('the package should', function() { 16 | it('expose validate', function() { 17 | expect(typeof package.validate).to.be('function'); 18 | }); 19 | 20 | it('expose convert', function() { 21 | expect(typeof package.convert).to.be('function'); 22 | }); 23 | }); 24 | 25 | 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "curl-to-postmanv2", 3 | "version": "1.8.3", 4 | "description": "Convert a given CURL command to a Postman request", 5 | "main": "index.js", 6 | "com_postman_plugin": { 7 | "plugin_type": "importer", 8 | "name": "cURL", 9 | "source_format": "cURL" 10 | }, 11 | "engines": { 12 | "node": ">=12" 13 | }, 14 | "dependencies": { 15 | "commander": "^2.20.3", 16 | "lodash": "^4.17.21", 17 | "shell-quote": "^1.8.2", 18 | "valid-url": "^1.0.9" 19 | }, 20 | "devDependencies": { 21 | "editorconfig": "^0.15.3", 22 | "eslint": "5.16.0", 23 | "eslint-plugin-jsdoc": "3.8.0", 24 | "eslint-plugin-mocha": "5.2.0", 25 | "eslint-plugin-security": "1.4.0", 26 | "expect.js": "0.3.1", 27 | "mocha": "6.1.4", 28 | "nyc": "14.1.1" 29 | }, 30 | "author": "Postman", 31 | "license": "Apache", 32 | "scripts": { 33 | "test": "./scripts/test.sh", 34 | "test-unit": "./scripts/test-unit.sh", 35 | "test-lint": "./scripts/test-lint.sh", 36 | "coverage": "nyc report --reporter=html --reporter=text mocha", 37 | "release": "./scripts/release.sh" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /scripts/test-unit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ---------------------------------------------------------------------------------------------------------------------- 3 | # This script is intended to execute repository unit and integration tests. 4 | # ---------------------------------------------------------------------------------------------------------------------- 5 | 6 | # stop on first error 7 | set -e; 8 | 9 | # function to be called on exit 10 | # and ensure cleanup is called before the script exits 11 | function cleanup { 12 | unset XUNIT_FILE; 13 | 14 | if [ "$?" != "0" ]; then 15 | exit 1; 16 | fi 17 | } 18 | trap cleanup EXIT; 19 | 20 | # set file for xunit report 21 | export XUNIT_FILE=".tmp/report.xml"; 22 | 23 | # run mocha tests 24 | echo -e "\033[93mRunning mocha unit tests...\033[0m"; 25 | echo -en "\033[0m\033[2mmocha `mocha --version`\033[0m"; 26 | 27 | # set mocha reporter 28 | if [ "$CI" = "true" ]; then 29 | MOCHA_REPORTER="xunit"; 30 | else 31 | MOCHA_REPORTER="spec"; 32 | fi 33 | 34 | # delete old repor directory 35 | [ -d .coverage ] && rm -rf .coverage && mkdir .coverage; 36 | 37 | # run test 38 | node --max-old-space-size=2048 node_modules/.bin/nyc --reporter=text --reporter=text-summary \ 39 | node_modules/.bin/_mocha --print _mocha -- \ 40 | --reporter ${MOCHA_REPORTER} --reporter-options output=${XUNIT_FILE} \ 41 | test/*.test.js --recursive --prof --grep "$1"; 42 | -------------------------------------------------------------------------------- /.github/workflows/publish-new-release.yml: -------------------------------------------------------------------------------- 1 | name: "Publish new release" 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | types: 8 | - closed 9 | 10 | jobs: 11 | release: 12 | name: Publish new release 13 | runs-on: ubuntu-latest 14 | # only merged pull requests that begin with 'release/' or 'hotfix/' must trigger this job 15 | if: github.event.pull_request.merged == true && 16 | (contains(github.event.pull_request.head.ref, 'release/') || contains(github.event.pull_request.head.ref, 'hotfix/')) 17 | permissions: 18 | contents: write 19 | 20 | steps: 21 | - name: Extract version from branch name (for release branches) 22 | if: contains(github.event.pull_request.head.ref, 'release/') 23 | run: | 24 | BRANCH_NAME="${{ github.event.pull_request.head.ref }}" 25 | VERSION=${BRANCH_NAME#release/} 26 | 27 | echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV 28 | 29 | - name: Extract version from branch name (for hotfix branches) 30 | if: contains(github.event.pull_request.head.ref, 'hotfix/') 31 | run: | 32 | BRANCH_NAME="${{ github.event.pull_request.head.ref }}" 33 | VERSION=${BRANCH_NAME#hotfix/} 34 | 35 | echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV 36 | 37 | - name: Create Release 38 | uses: thomaseizinger/create-release@1.0.0 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | with: 42 | target_commitish: ${{ github.event.pull_request.merge_commit_sha }} 43 | tag_name: ${{ env.RELEASE_VERSION }} 44 | name: ${{ env.RELEASE_VERSION }} 45 | draft: false 46 | prerelease: false 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A cURL to POSTMan converter. 2 | 3 | Current CURL options that are supported are: 4 | 5 | -A, --user-agent 6 | -d, --data 7 | --data-binary 8 | -F, --form 9 | -G, --get 10 | -H, --header 11 | -X, --request 12 | 13 | Installation 14 | 15 | ```cli 16 | npm install curl-to-postmanv2 17 | ``` 18 | 19 | **Usage Examples of the Lib:** 20 | 21 | **1. Validate function**: Helps you to validate the curl command. 22 | 23 | ```js 24 | const { validate } = require("curl-to-postmanv2"); 25 | 26 | let v = validate("curl -X https://google.co.in"); 27 | console.log(v); // { result: true } 28 | ``` 29 | 30 | **2. Convert Function**: Helps to convert curl to postman 31 | 32 | ```js 33 | const { convert } = require("curl-to-postmanv2"); 34 | 35 | let con = convert( 36 | { type: "string", data: "curl https://google.co.in" }, 37 | (err, result) => { 38 | if (err) { 39 | console.log(err); 40 | 41 | process.exit(1); 42 | } 43 | console.log(result); 44 | console.log("data: ", result.output[0].data); 45 | } 46 | ); 47 | ``` 48 | 49 | **3. getMetaData Function**: To get meta data for the curl request 50 | 51 | ```js 52 | const { getMetaData } = require("curl-to-postmanv2"); 53 | 54 | let meta = getMetaData( 55 | { type: "string", data: "curl https://google.co.in" }, 56 | (err, result) => { 57 | if (err) { 58 | console.log(err); 59 | 60 | process.exit(1); 61 | } 62 | console.log(result); 63 | console.log("data: ", result.output[0].data); 64 | } 65 | ); 66 | ``` 67 | 68 | Usage examples: 69 | 70 | Read spec.json and store the output in output.json after grouping the requests into folders 71 | ./curl2postman -s spec.json -o output.json -g 72 | 73 | Read spec.json and print the output to the console 74 | ./curl2postman -s spec.json 75 | 76 | Read spec.json and print the prettified output to the console 77 | ./curl2postman -s spec.json -p 78 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ---------------------------------------------------------------------------------------------------------------------- 3 | # This script is intended to automate release process. 4 | # Explanation: This script will pull all latest changes from master and develop and checkout release branch from develop 5 | # with specified version and then bump up package version and add CHANGELOGS (input required) and commit them. After 6 | # that merge release branch into master and develop with appropriate tags. 7 | # 8 | # Example: "npm run release 1.2.3" 9 | # ---------------------------------------------------------------------------------------------------------------------- 10 | 11 | # Stop on first error 12 | set -e; 13 | 14 | # Ensure that the provided version is in valid semver format 15 | if [[ ! $1 =~ ^v?[0-9]+(\.[0-9]+){2}(-[a-z]+\.\d+)?$ ]]; then 16 | echo "A valid version must be provided as the first argument."; 17 | exit 1; 18 | fi 19 | 20 | ver=${1/v/}; # Strip the leading v from the version (if it exists) 21 | msg=$2; 22 | 23 | [[ -z $msg ]] && msg="Released v${ver}"; 24 | 25 | # Update the master branch to the latest 26 | git checkout master; 27 | git pull origin master; 28 | 29 | # Update develop to the latest, and create a release brach off of it. 30 | git checkout develop; 31 | git pull origin develop; 32 | git checkout -b release/$ver; 33 | 34 | # Bump version in package.json, but do not create a git tag 35 | npm version $ver --no-git-tag-version; 36 | 37 | # Inject the current release version and date into the CHANGELOG file 38 | sed -i "" "3i\\ 39 | #### v${ver} (`date '+%B %d, %Y'`)\\ 40 | \\ 41 | " CHANGELOG.md; 42 | 43 | # Find all commits between the HEAD on develop and the latest tag on master, and pipe their messages into the clipboard 44 | git log $(git describe --tags master --abbrev=0)..HEAD --merges --pretty=format:'* %s' | pbcopy; 45 | 46 | # Provision manual intervention for CHANGELOG.md 47 | vi CHANGELOG.md 48 | 49 | # Create the release 50 | git add CHANGELOG.md package.json; 51 | [[ -f package-lock.json ]] && git add package-lock.json; 52 | git commit -am "$msg"; 53 | 54 | # Merge the release branch into develop and push 55 | git checkout develop; 56 | git merge --no-ff release/$ver; 57 | git push origin develop; 58 | 59 | # Merge the release branch into master, create a tag and push 60 | git checkout master; 61 | git merge --no-ff release/$ver; 62 | git tag -a "$ver" -m "$msg"; 63 | git push origin master --follow-tags; 64 | 65 | # Move back to develop 66 | git checkout develop; 67 | git branch -d release/$ver; 68 | 69 | unset msg ver; 70 | -------------------------------------------------------------------------------- /assets/unnecessaryOptions.js: -------------------------------------------------------------------------------- 1 | // list of all curl non-argument options which are not necessary 2 | 3 | let unnecessaryOptions = [ 4 | '--anyauth', 5 | '-a', 6 | '--append', 7 | '--basic', 8 | '--cert-status', 9 | '--compressed-ssh', 10 | '--compressed', 11 | '--create-dirs', 12 | '--crlf', 13 | '--disable-eprt', 14 | '--disable-epsv', 15 | '-q', 16 | '--disable', 17 | '--disallow-username-in-url', 18 | '--fail-early', 19 | '-f', 20 | '--fail', 21 | '--false-start', 22 | '--ftp-create-dirs', 23 | '--ftp-pasv', 24 | '--ftp-pret', 25 | '--ftp-skip-pasv-ip', 26 | '--ftp-ssl-ccc', 27 | '--ftp-ssl-control', 28 | '-g', 29 | '--globoff', 30 | '--haproxy-protocol', 31 | '-h', 32 | '--help', 33 | '--http0.9', 34 | '-0', 35 | '--http1.0', 36 | '--http1.1', 37 | '--http2-prior-knowledge', 38 | '--http2', 39 | '--http3', 40 | '--ignore-content-length', 41 | '-i', 42 | '--include', 43 | '-k', 44 | '--insecure', 45 | '-4', 46 | '--ipv4', 47 | '-6', 48 | '--ipv6', 49 | '-j', 50 | '--junk-session-cookies', 51 | '-l', 52 | '--list-only', 53 | '--location-trusted', 54 | '-L', 55 | '--location', 56 | '-M', 57 | '--manual', 58 | '--metalink', 59 | '--negotiate', 60 | '--netrc-optional', 61 | '-n', 62 | '--netrc', 63 | '-:', 64 | '--next', 65 | '--no-alpn', 66 | '-N', 67 | '--no-buffer', 68 | '--no-keepalive', 69 | '--no-npn', 70 | '--no-sessionid', 71 | '--ntlm-wb', 72 | '--parallel-max', 73 | '-Z', 74 | '--parallel', 75 | '--path-as-is', 76 | '--post301', 77 | '--post302', 78 | '--post303', 79 | '--preproxy [protocol://]host[:port]', 80 | '-#', 81 | '--progress-bar', 82 | '--proxy-anyauth', 83 | '--proxy-basic', 84 | '--proxy-digest', 85 | '--proxy-insecure', 86 | '--proxy-negotiate', 87 | '--proxy-ntlm', 88 | '--proxy-ssl-allow-beast', 89 | '--proxy-tlsv1', 90 | '-x', 91 | '--proxy [protocol://]host[:port]', 92 | '-p', 93 | '--proxytunnel', 94 | '-Q', 95 | '--quote', 96 | '--raw', 97 | '-J', 98 | '--remote-header-name', 99 | '--remote-name-all', 100 | '-O', 101 | '--remote-name', 102 | '-R', 103 | '--remote-time', 104 | '--request-target', 105 | '--retry-connrefused', 106 | '--sasl-authzid', 107 | '--sasl-ir', 108 | '-S', 109 | '--show-error', 110 | '-s', 111 | '--silent', 112 | '--socks5-basic', 113 | '--socks5-gssapi-nec', 114 | '--socks5-gssapi', 115 | '--ssl-allow-beast', 116 | '--ssl-no-revoke', 117 | '--ssl-reqd', 118 | '--ssl', 119 | '-2', 120 | '--sslv2', 121 | '-3', 122 | '--sslv3', 123 | '--stderr', 124 | '--styled-output', 125 | '--suppress-connect-headers', 126 | '--tcp-fastopen', 127 | '--tcp-nodelay', 128 | '--tftp-no-options', 129 | '--tlspassword', 130 | '--tlsv1.0', 131 | '--tlsv1.1', 132 | '--tlsv1.2', 133 | '--tlsv1.3', 134 | '-1', 135 | '--tlsv1', 136 | '--tr-encoding', 137 | '--trace-time', 138 | '-B', 139 | '--use-ascii', 140 | '-v', 141 | '--verbose', 142 | '-V', 143 | '--version', 144 | '--xattr' 145 | ]; 146 | 147 | module.exports = unnecessaryOptions; 148 | -------------------------------------------------------------------------------- /.github/workflows/draft-new-release.yml: -------------------------------------------------------------------------------- 1 | name: Draft new release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: The version you want to release. Must be a valid semver version. 8 | required: true 9 | type: string 10 | 11 | jobs: 12 | draft-new-release: 13 | if: startsWith(github.event.inputs.version, 'v') 14 | name: Draft a new release 15 | runs-on: ubuntu-latest 16 | permissions: 17 | contents: write 18 | pull-requests: write 19 | 20 | steps: 21 | - name: Checkout code 22 | uses: actions/checkout@v3 23 | 24 | - name: Create release branch 25 | run: git checkout -b release/${{ github.event.inputs.version }} 26 | 27 | - name: Update changelog 28 | uses: thomaseizinger/keep-a-changelog-new-release@1.1.0 29 | with: 30 | version: ${{ github.event.inputs.version }} 31 | 32 | - name: Initialize mandatory git config 33 | run: | 34 | git config user.name "GitHub Actions" 35 | git config user.email noreply@github.com 36 | 37 | - name: Bump version 38 | run: npm version ${{ github.event.inputs.version }} --git-tag-version false 39 | 40 | - name: Commit changelog and manifest files 41 | id: make-commit 42 | run: | 43 | git add CHANGELOG.md package.json package-lock.json 44 | git commit --message "Prepare release ${{ github.event.inputs.version }}" 45 | echo "::set-output name=commit::$(git rev-parse HEAD)" 46 | 47 | - name: Push new branch 48 | run: git push origin release/${{ github.event.inputs.version }} 49 | 50 | - name: Create pull request for master 51 | uses: thomaseizinger/create-pull-request@1.0.0 52 | env: 53 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 54 | with: 55 | head: release/${{ github.event.inputs.version }} 56 | base: master 57 | title: "Release version ${{ github.event.inputs.version }}" 58 | reviewers: ${{ github.actor }} 59 | body: | 60 | Hi @${{ github.actor }}! 61 | 62 | This PR was created in response to a manual trigger of the release workflow here: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}. 63 | I've updated the changelog and bumped the versions in the manifest files in this commit: ${{ steps.make-commit.outputs.commit }}. 64 | 65 | - name: Create pull request for develop 66 | uses: thomaseizinger/create-pull-request@1.0.0 67 | env: 68 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 69 | with: 70 | head: release/${{ github.event.inputs.version }} 71 | base: develop 72 | title: "Release version ${{ github.event.inputs.version }}" 73 | reviewers: ${{ github.actor }} 74 | body: | 75 | Hi @${{ github.actor }}! 76 | 77 | This PR was created in response to a manual trigger of the release workflow here: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}. 78 | I've updated the changelog and bumped the versions in the manifest files in this commit: ${{ steps.make-commit.outputs.commit }}. 79 | -------------------------------------------------------------------------------- /assets/supportedOptions.js: -------------------------------------------------------------------------------- 1 | // list of all options that are supported by this module 2 | /* eslint-disable max-len */ 3 | let supportedOptions = [ 4 | { 5 | short: '-A', 6 | long: '--user-agent', 7 | description: 'An optional user-agent string', 8 | format: '', 9 | collectValues: false 10 | }, 11 | { 12 | short: '-d', 13 | long: '--data', 14 | description: 'Sends the specified data to the server with type application/x-www-form-urlencoded. application/x-www-form-urlencoded', 15 | format: '[string]', 16 | collectValues: true 17 | }, 18 | { 19 | long: '--data-raw', 20 | description: 'Sends the specified data to the server with type application/x-www-form-urlencoded. application/x-www-form-urlencoded', 21 | format: '[string]', 22 | collectValues: true 23 | }, 24 | { 25 | long: '--data-ascii', 26 | description: 'Sends the specified data to the server with type application/x-www-form-urlencoded. application/x-www-form-urlencoded', 27 | format: '[string]', 28 | collectValues: true 29 | }, 30 | { 31 | long: '--data-urlencode', 32 | description: 'Sends the specified data to the server with type application/x-www-form-urlencoded. application/x-www-form-urlencoded', 33 | format: '[string]', 34 | collectValues: true 35 | }, 36 | { 37 | long: '--data-binary', 38 | description: 'Data sent as-is', 39 | format: '[string]', 40 | collectValues: false 41 | }, 42 | { 43 | short: '-F', 44 | long: '--form', 45 | description: 'A single form-data field', 46 | format: '', 47 | collectValues: true 48 | }, 49 | { 50 | short: '-G', 51 | long: '--get', 52 | description: 'Forces the request to be sent as GET, with the --data parameters appended to the query string', 53 | collectValues: false 54 | }, 55 | { 56 | short: '-H', 57 | long: '--header', 58 | description: 'Add a header (can be used multiple times)', 59 | format: '[string]', 60 | collectValues: true 61 | }, 62 | { 63 | short: '-X', 64 | long: '--request', 65 | description: 'Specify a custom request method to be used', 66 | format: '[string]', 67 | collectValues: false 68 | }, 69 | { 70 | short: '-I', 71 | long: '--head', 72 | description: 'Forces the request to be sent as HEAD, with the --data parameters appended to the query string', 73 | collectValues: false 74 | }, 75 | { 76 | short: '-T', 77 | long: '--upload-file', 78 | description: 'Forces the request to be sent as PUT with the specified local file to the server', 79 | format: '[string]', 80 | collectValues: true 81 | }, 82 | { 83 | long: '--url', 84 | description: 'An alternate way to specify the URL', 85 | format: '[string]', 86 | collectValues: false 87 | }, 88 | { 89 | long: '--basic', 90 | description: 'Use HTTP Basic authentication', 91 | collectValues: false 92 | }, 93 | { 94 | long: '--digest', 95 | description: 'Use HTTP Digest authentication', 96 | collectValues: false 97 | }, 98 | { 99 | long: '--ntlm', 100 | description: 'Use NTLM Digest authentication', 101 | collectValues: false 102 | }, 103 | { 104 | short: '-u', 105 | long: '--user', 106 | description: 'Username and password for server authentication', 107 | format: '[string]', 108 | collectValues: false 109 | }, 110 | { 111 | short: '-b', 112 | long: '--cookie', 113 | description: 'Specifies cookies to be used in the format "NAME=VALUE" or a file to read cookies from', 114 | format: '[string]', 115 | collectValues: true 116 | } 117 | ]; 118 | module.exports = supportedOptions; 119 | -------------------------------------------------------------------------------- /test/unit.test.js: -------------------------------------------------------------------------------- 1 | var Converter = require('../src/lib.js'), 2 | validate = require('../src/validate.js'), 3 | shellQuote = require('../assets/shell-quote'), 4 | expect = require('expect.js'); 5 | 6 | describe('trimQuotesFromString should', function() { 7 | it('work properly', function(done) { 8 | expect(Converter.trimQuotesFromString('"glasnost"')).to.equal('glasnost'); 9 | expect(Converter.trimQuotesFromString('\'glasnost\'')).to.equal('glasnost'); 10 | expect(Converter.trimQuotesFromString()).to.be.equal(''); 11 | done(); 12 | }); 13 | }); 14 | 15 | describe('convertArrayToAmpersandString should', function() { 16 | it('work properly', function(done) { 17 | expect(Converter.convertArrayToAmpersandString([1, 2])).to.equal('1&2'); 18 | done(); 19 | }); 20 | }); 21 | 22 | describe('validator should', function () { 23 | it('indicate valid curl', function () { 24 | expect(validate('curl helloworld').result).to.be(true); 25 | 26 | var result = validate('invalid curl'); 27 | expect(result.result).to.be(false); 28 | }); 29 | }); 30 | 31 | describe('getRequestMethod should', function() { 32 | it('return POST if -d options is given in the curl command', function(done) { 33 | let object = { 34 | data: ['a=b'], 35 | dataAscii: [], 36 | dataUrlencode: [], 37 | dataBinary: null, 38 | uploadFile: [] 39 | }, 40 | result = Converter.getRequestMethod(object); 41 | expect(result).to.equal('POST'); 42 | 43 | done(); 44 | }); 45 | 46 | it('return GET if --get and -d options are given in the curl command', function(done) { 47 | let object = { 48 | data: ['a=b'], 49 | dataAscii: [], 50 | dataUrlencode: [], 51 | dataBinary: null, 52 | uploadFile: [], 53 | get: true 54 | }, 55 | result = Converter.getRequestMethod(object); 56 | expect(result).to.equal('GET'); 57 | 58 | done(); 59 | }); 60 | 61 | it('return HEAD if --get, -d, -I options are given in the curl command', function(done) { 62 | let object = { 63 | data: ['a=b'], 64 | dataAscii: [], 65 | dataUrlencode: [], 66 | dataBinary: null, 67 | uploadFile: [], 68 | get: true, 69 | head: true 70 | }, 71 | result = Converter.getRequestMethod(object); 72 | expect(result).to.equal('HEAD'); 73 | 74 | done(); 75 | }); 76 | 77 | it('return PUT if --get, -d, -I and -T options are given in the curl command', function(done) { 78 | let object = { 79 | data: ['a=b'], 80 | dataAscii: [], 81 | dataUrlencode: [], 82 | dataBinary: null, 83 | uploadFile: ['./text.txt'], 84 | get: true, 85 | head: true 86 | }, 87 | result = Converter.getRequestMethod(object); 88 | expect(result).to.equal('PUT'); 89 | 90 | done(); 91 | }); 92 | }); 93 | 94 | describe('sanitizeArgs function should', function () { 95 | it('remove all unnecessary $ from string', function (done) { 96 | let string = 'curl -X $\'POST\' ' + 97 | '-H $\'Host: example.com.br\' -H $\'User-Agent: Mozilla/5.0 ' + 98 | '(Macintosh; Intel Mac OS X 10.13;rv:68.0) Gecko/20100101 Firefox/68.0\' ' + 99 | '$\'https://example.com.br/login.html\'', 100 | sanitizedArgs = Converter.sanitizeArgs(string); 101 | expect(sanitizedArgs[3]).to.equal('POST'); 102 | expect(sanitizedArgs[5]).to.equal('Host: example.com.br'); 103 | expect(sanitizedArgs[7]).to.equal('User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13;' + 104 | 'rv:68.0) Gecko/20100101 Firefox/68.0'); 105 | expect(sanitizedArgs[8]).to.equal('https://example.com.br/login.html'); 106 | 107 | done(); 108 | }); 109 | 110 | it('remove all unnecessary options which are in the unnecessary options list', function(done) { 111 | // In this case -i, -v and -a will be removed 112 | let string = 'curl -i -v http://example.com -a', 113 | sanitizeArgs = Converter.sanitizeArgs(string); 114 | expect(sanitizeArgs[1]).to.equal('curl'); 115 | expect(sanitizeArgs[2]).to.equal('http://example.com'); 116 | done(); 117 | }); 118 | }); 119 | 120 | describe('shell-quote should', function() { 121 | it('escape characters correctly', function(done) { 122 | let string = '\\\'"\\\""', 123 | result = shellQuote.parse(string); 124 | expect(result[0]).to.equal('\'"'); 125 | done(); 126 | }); 127 | }); 128 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # cURL to Postman Importer Changelog 2 | 3 | ## [Unreleased] 4 | 5 | ## [v1.8.3] - 2025-09-30 6 | 7 | ### Changed 8 | 9 | - Upgrade dep: shell-quote 10 | 11 | ## [v1.8.2] - 2025-02-20 12 | 13 | ### Added 14 | 15 | - [#13455](https://github.com/postmanlabs/postman-app-support/issues/13455) Added support for -b and --cookie options and create relavant cookie header. 16 | 17 | ## [v1.8.1] - 2024-04-17 18 | 19 | ### Added 20 | 21 | - Added support for determining raw body language based on content-type header. 22 | 23 | ## [v1.8.0] - 2024-01-17 24 | 25 | ### Changed 26 | 27 | - Fix for - [#12349](https://github.com/postmanlabs/postman-app-support/issues/12349) Fixed issue where GraphQL requests were failing to send correct data. 28 | - Fixed various TypeErrors that were occurring frequently for users. 29 | 30 | ## [v1.7.1] - 2023-07-17 31 | 32 | ## [v1.7.0] - 2023-06-27 33 | 34 | ### Added 35 | 36 | - Fix for - [#9941](https://github.com/postmanlabs/postman-app-support/issues/9941) Add method to identify GraphQL requests from body data 37 | 38 | ### Changed 39 | 40 | - Assigned user errors for various handled errors 41 | 42 | ## [v1.6.0] - 2023-04-17 43 | 44 | ### Added 45 | 46 | - Add url validation in validate and convert functions 47 | - GitHub Actions for Release management. 48 | 49 | ### Changed 50 | 51 | - Bumped up minimum Node version to 12. 52 | - Unit tests now run on Node versions 12, 16 and 18. 53 | 54 | ## [v1.5.0] - 2023-03-31 55 | 56 | - Fixed an issue where request generation failed for certain bash operators. 57 | - Fixed an issue where cURL with comments described was converted incorrectly. 58 | 59 | ## Previous Releases 60 | 61 | Newer releases follow the [Keep a Changelog](https://keepachangelog.com) format. 62 | 63 | #### v1.4.0 (March 17, 2023) 64 | 65 | - Fixed issue [#7895](https://github.com/postmanlabs/postman-app-support/issues/7895) where cURL with no specific method defined for formdata type of body were not converted correctly. 66 | 67 | #### v1.3.0 (March 02, 2023) 68 | 69 | - Fix for [#8087](https://github.com/postmanlabs/postman-app-support/issues/8087) - Add support to convert digest and NTLM auth types 70 | 71 | #### v1.2.0 (February 07, 2023) 72 | 73 | - Fix an issue where a correct error is thrown if curl string has invalid args 74 | 75 | #### v1.1.3 (February 03, 2023) 76 | 77 | - Fixed issue [#5182](https://github.com/postmanlabs/postman-app-support/issues/5182) where cURL in Windows cmd formats were not imported correctly. 78 | 79 | #### v1.1.2 (January 10, 2023) 80 | 81 | - Changed regex to check for prefix space in url with query parameters for given curl string 82 | 83 | #### v1.1.1 (June 2, 2022) 84 | 85 | - Updated how error was handled in case of malformed URL. 86 | 87 | #### v1.1.0 (May 16, 2022) 88 | 89 | - Fixes #8433 - non-apostrophed ('...') url with multiple params support in cURL import. 90 | 91 | #### v1.0.0 (October 18, 2021) 92 | 93 | - Fixed issue where file references were not present in imported cURL.◊ 94 | - Fixed issue where formdata value were not un-escaped correctly. 95 | - Fixed issue where raw formdata string with boundary were not converted as formdata body. 96 | - Fixed issue where escaped single character sequence were not correctly represented in request body. 97 | - Fixed issue where some characters were not escaped correctly. 98 | - Updated README with installation and use cases and added LICENSE. 99 | - Added script for automating release process. 100 | 101 | #### 0.5.1: Apr 29, 2020 102 | 103 | - Added getMetaData function in root exports 104 | 105 | #### 0.5.0: Apr 29, 2020 106 | 107 | - Added a function to get meta data from a curl command. 108 | 109 | #### 0.4.0: Apr 21, 2020 110 | 111 | - Fix for - --data-urlencode now successfully imports body 112 | 113 | #### 0.3.0: Mar 27, 2020 114 | 115 | - Fix for - -X argument parses method correcrtly, not interfere with any other args 116 | 117 | #### 0.2.0: Mar 11, 2020 118 | 119 | - Fix for - --data-raw now successfully imports body 120 | 121 | #### 0.1.0: Nov 22, 2019 122 | 123 | - Fix for - not escaping single quotes correctly in the cURL commands 124 | - Fix for - removing unnecessary options from the cURL commands 125 | 126 | #### 0.0.5: Sep 3, 2019 127 | 128 | - Fix for - cURL commands with `$` prepended to arguments not importing correctly 129 | - Fix for - the importer was using -X to determine method, not -d or --head 130 | - Fix for - Data parameters are added to the URL if the method is determined to be GET, PUT, or HEAD 131 | 132 | #### 0.0.4: June 5, 2019 133 | 134 | - Updated dependency versions 135 | - Updated lockfile for npm@6.4.1 136 | 137 | #### 0.0.3: May 29, 2019 138 | 139 | - First public (beta) release 140 | - Conforming to the internal Postman plugin interface 141 | - Fixes for Github issues - 4770,3623,3135,4018,5737,5286, among others 142 | 143 | [Unreleased]: https://github.com/postmanlabs/curl-to-postman/compare/v1.8.3...HEAD 144 | 145 | [v1.8.3]: https://github.com/postmanlabs/curl-to-postman/compare/v1.8.2...v1.8.3 146 | 147 | [v1.8.2]: https://github.com/postmanlabs/curl-to-postman/compare/v1.8.1...v1.8.2 148 | 149 | [v1.8.1]: https://github.com/postmanlabs/curl-to-postman/compare/v1.8.0...v1.8.1 150 | 151 | [v1.8.0]: https://github.com/postmanlabs/curl-to-postman/compare/v1.7.1...v1.8.0 152 | 153 | [v1.7.1]: https://github.com/postmanlabs/curl-to-postman/compare/v1.7.0...v1.7.1 154 | 155 | [v1.7.0]: https://github.com/postmanlabs/curl-to-postman/compare/v1.6.0...v1.7.0 156 | 157 | [v1.6.0]: https://github.com/postmanlabs/curl-to-postman/compare/v1.5.0...v1.6.0 158 | 159 | [v1.5.0]: https://github.com/postmanlabs/curl-to-postman/compare/1.4.0...1.5.0 160 | -------------------------------------------------------------------------------- /assets/shell-quote.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | exports.quote = function (xs) { 3 | return xs.map(function (s) { 4 | if (s && typeof s === 'object') { 5 | return s.op.replace(/(.)/g, '\\$1'); 6 | } 7 | else if (/["\s]/.test(s) && !/'/.test(s)) { 8 | return "'" + s.replace(/(['\\])/g, '\\$1') + "'"; 9 | } 10 | else if (/["'\s]/.test(s)) { 11 | return '"' + s.replace(/(["\\$`!])/g, '\\$1') + '"'; 12 | } 13 | else { 14 | return String(s).replace(/([A-z]:)?([#!"$&'()*,:;<=>?@\[\\\]^`{|}])/g, '$1\\$2'); 15 | } 16 | }).join(' '); 17 | }; 18 | 19 | // '<(' is process substitution operator and 20 | // can be parsed the same as control operator 21 | var CONTROL = '(?:' + [ 22 | '\\|\\|', '\\&\\&', ';;', '\\|\\&', '\\<\\(', '>>', '>\\&', '[&;()|<>]' 23 | ].join('|') + ')'; 24 | var META = '|&;()<> \\t'; 25 | var BAREWORD = '(\\\\[\'"' + META + ']|[^\\s\'"' + META + '])+'; 26 | var SINGLE_QUOTE = '"((\\\\"|[^"])*?)"'; 27 | var DOUBLE_QUOTE = '\'((\\\\\'|[^\'])*?)\''; 28 | 29 | var TOKEN = ''; 30 | for (var i = 0; i < 4; i++) { 31 | TOKEN += (Math.pow(16, 8) * Math.random()).toString(16); 32 | } 33 | 34 | /** 35 | Regex expression to identify standard unicode escape sequences, matched group corrsponds to hexadecimal code. 36 | 37 | First matching group correspond to Hexadecimal code, between U+0000 and U+00FF (ISO-8859-1) 38 | Second matching group correspond to Unicode, between U+0000 and U+FFFF (the Unicode Basic Multilingual Plane) 39 | Third matching group correspond to Unicode with surrounding curly braces, 40 | between U+0000 and U+10FFFF (the entirety of Unicode) 41 | Forth matching group correspond to single character codes 42 | 43 | Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#escape_sequences 44 | */ 45 | const unicodeRegExp = /\\x([0-9A-Fa-f]{2})|\\u([0-9A-Fa-f]{4})|\\u\{([0-9A-Fa-f]{1,6})\}|\\([\s\S])/gm; 46 | 47 | // Single character escape sequence mapping to corresponding character 48 | const escapeCharMap = { 49 | 'b': '\b', 50 | 'f': '\f', 51 | 'n': '\n', 52 | 'r': '\r', 53 | 't': '\t', 54 | 'v': '\v', 55 | '0': '\0', 56 | }; 57 | 58 | exports.parse = function (s, env, opts) { 59 | var mapped = parse(s, env, opts); 60 | if (typeof env !== 'function') return mapped; 61 | return mapped.reduce(function (acc, s) { 62 | if (typeof s === 'object') return acc.concat(s); 63 | var xs = s.split(RegExp('(' + TOKEN + '.*?' + TOKEN + ')', 'g')); 64 | if (xs.length === 1) return acc.concat(xs[0]); 65 | return acc.concat(xs.filter(Boolean).map(function (x) { 66 | if (RegExp('^' + TOKEN).test(x)) { 67 | return JSON.parse(x.split(TOKEN)[1]); 68 | } 69 | else return x; 70 | })); 71 | }, []); 72 | }; 73 | 74 | function parse(s, env, opts) { 75 | var chunker = new RegExp([ 76 | '(' + CONTROL + ')', // control chars 77 | '(' + BAREWORD + '|' + SINGLE_QUOTE + '|' + DOUBLE_QUOTE + ')*' 78 | ].join('|'), 'g'); 79 | var match = s.match(chunker).filter(Boolean); 80 | var commented = false; 81 | 82 | if (!match) return []; 83 | if (!env) env = {}; 84 | if (!opts) opts = {}; 85 | return match.map(function (s, j) { 86 | if (commented) { 87 | return; 88 | } 89 | if (RegExp('^' + CONTROL + '$').test(s)) { 90 | return { op: s }; 91 | } 92 | 93 | var replacer = function (match, p1, p2, p3, p4) { 94 | // escape single character sequence by replacing it with actual char. i.e. "\\n" to "\n" 95 | if (p4) { 96 | return escapeCharMap[p4] || `\\${p4}`; 97 | } 98 | // only one of three will be defined at a time 99 | var matchedCodeUnit = p1 || p2 || p3; 100 | // parse matched hexadecimal code to integer 101 | EqCodeUnit = parseInt(matchedCodeUnit, 16); 102 | 103 | // don't convert unicode chars outside range 104 | if (EqCodeUnit > parseInt('10FFFF', 16)) { 105 | return match; 106 | } 107 | // convert to equivalent unicode character 108 | return String.fromCharCode(EqCodeUnit); 109 | }; 110 | 111 | // resolve ansi_c_like_strings (https://wiki.bash-hackers.org/syntax/quoting#ansi_c_like_strings) 112 | if (typeof s === 'string' && s.startsWith('$\'') && s.endsWith('\'') && s.length > 3) { 113 | // replace escaped unicode sequence with coresponding unicode character 114 | s = s.replace(unicodeRegExp, replacer); 115 | } 116 | 117 | // Hand-written scanner/parser for Bash quoting rules: 118 | // 119 | // 1. inside single quotes, all characters are printed literally. 120 | // 2. inside double quotes, all characters are printed literally 121 | // except variables prefixed by '$' and backslashes followed by 122 | // either a double quote or another backslash. 123 | // 3. outside of any quotes, backslashes are treated as escape 124 | // characters and not printed (unless they are themselves escaped) 125 | // 4. quote context can switch mid-token if there is no whitespace 126 | // between the two quote contexts (e.g. all'one'"token" parses as 127 | // "allonetoken") 128 | var SQ = "'"; 129 | var DQ = '"'; 130 | var DS = '$'; 131 | var BS = opts.escape || '\\'; 132 | var quote = false; 133 | var esc = false; 134 | var out = ''; 135 | var isGlob = false; 136 | 137 | for (var i = 0, len = s.length; i < len; i++) { 138 | var c = s.charAt(i); 139 | isGlob = isGlob || (!quote && (c === '*' || c === '?')); 140 | if (esc) { 141 | out += c; 142 | esc = false; 143 | } 144 | else if (quote) { 145 | if (c === quote) { 146 | quote = false; 147 | } 148 | else if (quote == SQ && s.charAt(0) !== DS) { 149 | out += c; 150 | } 151 | else { // Double quote 152 | if (c === BS) { 153 | i += 1; 154 | c = s.charAt(i); 155 | if (c === DQ || c === BS || c === DS || (c === SQ && s.charAt(0) === DS)) { 156 | out += c; 157 | } else { 158 | out += BS + c; 159 | } 160 | } 161 | else if (c === DS) { 162 | out += parseEnvVar(); 163 | } 164 | else { 165 | out += c; 166 | } 167 | } 168 | } 169 | else if (c === DQ || c === SQ) { 170 | quote = c; 171 | } 172 | else if (RegExp('^' + CONTROL + '$').test(c)) { 173 | return { op: s }; 174 | } 175 | else if (RegExp('^#$').test(c)) { 176 | commented = true; 177 | if (out.length) { 178 | return [out, { comment: s.slice(i + 1) + match.slice(j + 1).join(' ') }]; 179 | } 180 | return [{ comment: s.slice(i + 1) + match.slice(j + 1).join(' ') }]; 181 | } 182 | else if (c === BS) { 183 | esc = true; 184 | } 185 | else if (c === DS) { 186 | out += parseEnvVar(); 187 | } 188 | else out += c; 189 | } 190 | 191 | if (isGlob) return { op: 'glob', pattern: out }; 192 | 193 | return out; 194 | 195 | function parseEnvVar() { 196 | i += 1; 197 | var varend, varname; 198 | //debugger 199 | if (s.charAt(i) === '{') { 200 | i += 1; 201 | if (s.charAt(i) === '}') { 202 | throw new Error("Bad substitution: " + s.substr(i - 2, 3)); 203 | } 204 | varend = s.indexOf('}', i); 205 | if (varend < 0) { 206 | throw new Error("Bad substitution: " + s.substr(i)); 207 | } 208 | varname = s.substr(i, varend - i); 209 | i = varend; 210 | } 211 | else if (/[*@#?$!_\-]/.test(s.charAt(i))) { 212 | varname = s.charAt(i); 213 | i += 1; 214 | } 215 | else { 216 | varend = s.substr(i).match(/[^\w\d_]/); 217 | if (!varend) { 218 | varname = s.substr(i); 219 | i = s.length; 220 | } else { 221 | varname = s.substr(i, varend.index); 222 | i += varend.index - 1; 223 | } 224 | } 225 | return getVar(null, '', varname); 226 | } 227 | }) 228 | // finalize parsed aruments 229 | .reduce(function (prev, arg) { 230 | if (arg === undefined) { 231 | return prev; 232 | } 233 | return prev.concat(arg); 234 | }, []); 235 | 236 | function getVar(_, pre, key) { 237 | var r = typeof env === 'function' ? env(key) : env[key]; 238 | if (r === undefined && key != '') 239 | r = ''; 240 | else if (r === undefined) 241 | r = '$'; 242 | 243 | if (typeof r === 'object') { 244 | return pre + TOKEN + JSON.stringify(r) + TOKEN; 245 | } 246 | else return pre + r; 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 2017 4 | }, 5 | 6 | "plugins": [ 7 | "security", 8 | "jsdoc", 9 | "mocha" 10 | ], 11 | "env": { 12 | "browser": false, 13 | "node": true, 14 | "es6": true 15 | }, 16 | "rules": { 17 | // Possible Errors 18 | "comma-dangle": ["error", "never"], 19 | "no-cond-assign": "error", 20 | "no-console": ["error", { "allow": ["info", "warn", "error"] }], 21 | "no-constant-condition": "error", 22 | "no-control-regex": "error", 23 | "no-debugger": "error", 24 | "no-dupe-args": "error", 25 | "no-dupe-keys": "error", 26 | "no-duplicate-case": "error", 27 | "no-empty": "error", 28 | "no-empty-character-class": "error", 29 | "no-ex-assign": "error", 30 | "no-extra-boolean-cast": "error", 31 | "no-extra-parens": "off", 32 | "no-extra-semi": "error", 33 | "no-func-assign": "error", 34 | "no-inner-declarations": "off", 35 | "no-invalid-regexp": "error", 36 | "no-irregular-whitespace": "error", 37 | "no-negated-in-lhs": "error", 38 | "no-obj-calls": "error", 39 | "no-regex-spaces": "error", 40 | "no-sparse-arrays": "error", 41 | "no-unexpected-multiline": "error", 42 | "no-unreachable": "error", 43 | "no-unsafe-finally": "error", 44 | "use-isnan": "error", 45 | "valid-jsdoc": "warn", 46 | "valid-typeof": "error", 47 | 48 | // Best Practices 49 | "accessor-pairs": "error", 50 | //"array-callback-return": "error", 51 | "block-scoped-var": "error", 52 | "complexity": "off", 53 | "consistent-return": "warn", 54 | "curly": "error", 55 | "default-case": "error", 56 | "dot-location": ["error", "property"], 57 | "dot-notation": "error", 58 | "eqeqeq": "error", 59 | "guard-for-in": "warn", 60 | "no-alert": "error", 61 | "no-caller": "error", 62 | "no-case-declarations": "error", 63 | "no-div-regex": "error", 64 | "no-else-return": "error", 65 | "no-empty-function": "error", 66 | "no-empty-pattern": "error", 67 | "no-eq-null": "error", 68 | "no-eval": "error", 69 | "no-extend-native": "error", 70 | "no-extra-bind": "error", 71 | "no-extra-label": "error", 72 | "no-fallthrough": "error", 73 | "no-floating-decimal": "error", 74 | "no-implicit-coercion": "error", 75 | "no-implicit-globals": "error", 76 | "no-implied-eval": "error", 77 | "no-invalid-this": "error", 78 | "no-iterator": "error", 79 | "no-labels": "error", 80 | "no-lone-blocks": "error", 81 | "no-loop-func": "error", 82 | "no-magic-numbers": "off", 83 | "no-multi-spaces": "error", 84 | "no-multi-str": "error", 85 | "no-native-reassign": "error", 86 | "no-new": "error", 87 | "no-new-func": "error", 88 | "no-new-wrappers": "error", 89 | "no-octal": "error", 90 | "no-octal-escape": "error", 91 | // "_no-param-reassign": "error", 92 | "no-proto": "error", 93 | "no-redeclare": "error", 94 | "no-return-assign": "error", 95 | "no-script-url": "error", 96 | "no-self-assign": "error", 97 | "no-self-compare": "error", 98 | "no-sequences": "error", 99 | "no-throw-literal": "error", 100 | "no-unmodified-loop-condition": "error", 101 | "no-unused-expressions": "off", 102 | "no-unused-labels": "error", 103 | "no-useless-call": "error", 104 | "no-useless-concat": "error", 105 | "no-useless-escape": "off", 106 | "no-void": "error", 107 | "no-warning-comments": "off", 108 | "no-with": "error", 109 | "radix": "off", 110 | // "vars-on-top": "error", 111 | "wrap-iife": "error", 112 | "yoda": "error", 113 | 114 | // Strict Mode 115 | "strict": "off", 116 | 117 | // Variables 118 | "init-declarations": "off", 119 | "no-catch-shadow": "error", 120 | "no-delete-var": "error", 121 | "no-label-var": "error", 122 | "no-restricted-globals": "error", 123 | //"no-shadow": "error", 124 | "no-shadow-restricted-names": "error", 125 | "no-undef": "off", 126 | "no-undef-init": "error", 127 | "no-undefined": "off", 128 | "no-unused-vars": "error", 129 | "no-use-before-define": "error", 130 | 131 | // Stylistic Issues 132 | "array-bracket-spacing": "error", 133 | "block-spacing": "error", 134 | "brace-style": [2, "stroustrup", { "allowSingleLine": true }], 135 | "camelcase": "off", 136 | "comma-spacing": [2, { "before": false, "after": true }], 137 | "comma-style": ["error", "last"], 138 | "computed-property-spacing": "error", 139 | // "consistent-this": "warn", 140 | "eol-last": "error", 141 | "func-names": "off", 142 | "func-style": "off", 143 | "id-blacklist": "error", 144 | "id-length": "off", 145 | "id-match": "error", 146 | "indent": ["error", 2, { 147 | "VariableDeclarator": { "var": 1, "let": 1, "const": 1 }, 148 | "SwitchCase": 1 149 | }], 150 | "jsx-quotes": ["error", "prefer-single"], 151 | "key-spacing": "error", 152 | "keyword-spacing": "error", 153 | "linebreak-style": ["error", "unix"], 154 | "lines-around-comment": ["error", { 155 | "beforeBlockComment": true, 156 | "afterBlockComment": false, 157 | "beforeLineComment": false, 158 | "afterLineComment": false, 159 | "allowBlockStart": true, 160 | "allowBlockEnd": false, 161 | "allowObjectStart": true, 162 | "allowObjectEnd": false, 163 | "allowArrayStart": true, 164 | "allowArrayEnd": false 165 | }], 166 | "max-depth": "error", 167 | "max-len": ["error", { 168 | "code": 120 169 | }], 170 | "max-nested-callbacks": "error", 171 | "max-params": "off", 172 | "max-statements": "off", 173 | "max-statements-per-line": "off", 174 | "new-cap": "off", 175 | "new-parens": "error", 176 | "newline-after-var": ["off", "always"], 177 | "newline-before-return": "off", 178 | "newline-per-chained-call": "off", 179 | "no-array-constructor": "error", 180 | // "no-bitwise": "error", 181 | // "no-continue": "error", 182 | "no-inline-comments": "off", 183 | "no-lonely-if": "error", 184 | "no-mixed-spaces-and-tabs": "error", 185 | "no-multiple-empty-lines": "error", 186 | "no-negated-condition": "off", 187 | "no-nested-ternary": "off", 188 | "no-new-object": "error", 189 | "no-plusplus": "off", 190 | "no-restricted-syntax": "error", 191 | "no-spaced-func": "error", 192 | "no-ternary": "off", 193 | "no-trailing-spaces": "error", 194 | "no-underscore-dangle": "off", 195 | "no-unneeded-ternary": "error", 196 | "no-whitespace-before-property": "error", 197 | "object-curly-spacing": ["error", "always"], 198 | "one-var": ["error", "always"], 199 | "one-var-declaration-per-line": "error", 200 | "operator-assignment": "error", 201 | "operator-linebreak": ["error", "after"], 202 | "padded-blocks": "off", 203 | "quote-props": "off", 204 | "quotes": ["error", "single"], 205 | "require-jsdoc": "warn", 206 | "semi": "error", 207 | "semi-spacing": "error", 208 | "sort-vars": "off", 209 | "space-before-blocks": "error", 210 | "space-in-parens": "error", 211 | "space-infix-ops": "error", 212 | "space-unary-ops": "error", 213 | "spaced-comment": ["error", "always", { 214 | "block": { 215 | "exceptions": ["!"] 216 | } 217 | }], 218 | "wrap-regex": "error", 219 | 220 | // ECMAScript 6 221 | "arrow-body-style": ["error", "always"], 222 | "arrow-parens": ["error", "always"], 223 | "arrow-spacing": "error", 224 | "constructor-super": "error", 225 | "generator-star-spacing": "error", 226 | "no-class-assign": "error", 227 | "no-confusing-arrow": "error", 228 | "no-const-assign": "error", 229 | "no-dupe-class-members": "error", 230 | "no-duplicate-imports": "error", 231 | "no-new-symbol": "error", 232 | "no-restricted-imports": "error", 233 | "no-this-before-super": "error", 234 | "no-useless-computed-key": "error", 235 | "no-useless-constructor": "off", 236 | "no-var": "off", 237 | "object-shorthand": "off", 238 | // "_prefer-arrow-callback": "error", 239 | "prefer-const": "off", 240 | "prefer-reflect": "off", 241 | // "prefer-rest-params": "error", 242 | "prefer-spread": "error", 243 | "prefer-template": "off", 244 | "require-yield": "error", 245 | "sort-imports": "off", 246 | "template-curly-spacing": "error", 247 | "yield-star-spacing": "error", 248 | 249 | // Mocha 250 | "mocha/no-exclusive-tests": 2, 251 | "mocha/no-skipped-tests": 1, 252 | "mocha/no-pending-tests": 2, 253 | "mocha/handle-done-callback": 2, 254 | "mocha/no-synchronous-tests": 0, 255 | "mocha/no-global-tests": 2, 256 | "mocha/no-return-and-callback": 2, 257 | "mocha/valid-test-description": 0, 258 | "mocha/valid-suite-description": 0, 259 | "mocha/no-sibling-hooks": 2, 260 | "mocha/no-mocha-arrows": 2, 261 | "mocha/no-hooks": 0, 262 | "mocha/no-hooks-for-single-case": 1, 263 | "mocha/no-top-level-hooks": 1, 264 | "mocha/no-identical-title": 2 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /test/large-request.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable quotes */ 2 | /* eslint-disable no-multi-str */ 3 | /* eslint-disable max-len */ 4 | let largeRequest = `curl 'https://www.example.com/views/ajax?etc_category_tid=5&d=1' \ 5 | -H 'Accept: application/json, text/javascript, */*; q=0.01' \ 6 | -H 'Accept-Language: en-US,en;q=0.7' \ 7 | -H 'Connection: keep-alive' \ 8 | -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \ 9 | -H 'Cookie: has_js=1' \ 10 | -H 'Origin: https://www.example.com' \ 11 | -H 'Referer: https://www.example.com/board-example-videos?etc_category_tid=5' \ 12 | -H 'Sec-Fetch-Dest: empty' \ 13 | -H 'Sec-Fetch-Mode: cors' \ 14 | -H 'Sec-Fetch-Site: same-origin' \ 15 | -H 'Sec-GPC: 1' \ 16 | -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36' \ 17 | -H 'X-Requested-With: XMLHttpRequest' \ 18 | --data-raw 'title=&etc_contributors_tid=All&etc_category_tid=5&etc_series_tid=All&etc_etc_approved_value=All&sort_by=etc_rating_value&sort_order=DESC&view_name=video_episodes&view_display_id=block_3&view_args=&view_path=node%2F799&view_base_path=feeds%2Fvideos.rss&view_dom_id=41f87a88590adfd7b37b305b314f511f&pager_element=0&ajax_html_ids%5B%5D=fit-vids-style&ajax_html_ids%5B%5D=top-line&ajax_html_ids%5B%5D=home-link&ajax_html_ids%5B%5D=block-block-5&ajax_html_ids%5B%5D=block-superfish-1&ajax_html_ids%5B%5D=superfish-1&ajax_html_ids%5B%5D=menu-1196-1&ajax_html_ids%5B%5D=menu-1321-1&ajax_html_ids%5B%5D=menu-14229-1&ajax_html_ids%5B%5D=menu-1322-1&ajax_html_ids%5B%5D=menu-38009-1&ajax_html_ids%5B%5D=menu-1323-1&ajax_html_ids%5B%5D=menu-38006-1&ajax_html_ids%5B%5D=menu-1202-1&ajax_html_ids%5B%5D=menu-37854-1&ajax_html_ids%5B%5D=menu-1201-1&ajax_html_ids%5B%5D=menu-38003-1&ajax_html_ids%5B%5D=menu-38004-1&ajax_html_ids%5B%5D=menu-37951-1&ajax_html_ids%5B%5D=menu-38005-1&ajax_html_ids%5B%5D=menu-4851-1&ajax_html_ids%5B%5D=menu-5087-1&ajax_html_ids%5B%5D=menu-5166-1&ajax_html_ids%5B%5D=menu-1197-1&ajax_html_ids%5B%5D=menu-1325-1&ajax_html_ids%5B%5D=menu-1206-1&ajax_html_ids%5B%5D=menu-1341-1&ajax_html_ids%5B%5D=menu-1198-1&ajax_html_ids%5B%5D=menu-1327-1&ajax_html_ids%5B%5D=menu-1328-1&ajax_html_ids%5B%5D=menu-6576-1&ajax_html_ids%5B%5D=menu-2405-1&ajax_html_ids%5B%5D=menu-2648-1&ajax_html_ids%5B%5D=menu-2649-1&ajax_html_ids%5B%5D=menu-2412-1&ajax_html_ids%5B%5D=menu-11242-1&ajax_html_ids%5B%5D=menu-2413-1&ajax_html_ids%5B%5D=menu-1326-1&ajax_html_ids%5B%5D=menu-2946-1&ajax_html_ids%5B%5D=menu-1199-1&ajax_html_ids%5B%5D=menu-1524-1&ajax_html_ids%5B%5D=menu-1635-1&ajax_html_ids%5B%5D=menu-18120-1&ajax_html_ids%5B%5D=menu-13604-1&ajax_html_ids%5B%5D=menu-11255-1&ajax_html_ids%5B%5D=menu-6448-1&ajax_html_ids%5B%5D=menu-30187-1&ajax_html_ids%5B%5D=menu-1200-1&ajax_html_ids%5B%5D=menu-38013-1&ajax_html_ids%5B%5D=menu-38012-1&ajax_html_ids%5B%5D=menu-38007-1&ajax_html_ids%5B%5D=menu-38002-1&ajax_html_ids%5B%5D=menu-36932-1&ajax_html_ids%5B%5D=menu-26592-1&ajax_html_ids%5B%5D=menu-11328-1&ajax_html_ids%5B%5D=menu-6479-1&ajax_html_ids%5B%5D=menu-1332-1&ajax_html_ids%5B%5D=menu-3403-1&ajax_html_ids%5B%5D=menu-3428-1&ajax_html_ids%5B%5D=menu-3597-1&ajax_html_ids%5B%5D=menu-3642-1&ajax_html_ids%5B%5D=menu-3604-1&ajax_html_ids%5B%5D=menu-3635-1&ajax_html_ids%5B%5D=menu-38010-1&ajax_html_ids%5B%5D=block-system-user-menu&ajax_html_ids%5B%5D=block-block-4&ajax_html_ids%5B%5D=block-example-site-search-example-site-search-form&ajax_html_ids%5B%5D=search-form&ajax_html_ids%5B%5D=example-site-search-form&ajax_html_ids%5B%5D=edit-example-site-search&ajax_html_ids%5B%5D=edit-term&ajax_html_ids%5B%5D=edit-submit&ajax_html_ids%5B%5D=edit-full-search&ajax_html_ids%5B%5D=example-search-results&ajax_html_ids%5B%5D=example-search-preview&ajax_html_ids%5B%5D=main-content&ajax_html_ids%5B%5D=page-page&ajax_html_ids%5B%5D=views_slideshow_cycle_main_ad_cycle-block_1&ajax_html_ids%5B%5D=views_slideshow_cycle_teaser_section_ad_cycle-block_1&ajax_html_ids%5B%5D=views_slideshow_cycle_div_ad_cycle-block_1_0&ajax_html_ids%5B%5D=views-exposed-form-video-episodes-block-3&ajax_html_ids%5B%5D=edit-title-wrapper&ajax_html_ids%5B%5D=edit-title&ajax_html_ids%5B%5D=edit-etc-contributors-tid-wrapper&ajax_html_ids%5B%5D=edit-etc-contributors-tid&ajax_html_ids%5B%5D=edit-etc-category-tid-wrapper&ajax_html_ids%5B%5D=edit-etc-category-tid&ajax_html_ids%5B%5D=edit-etc-series-tid-wrapper&ajax_html_ids%5B%5D=edit-etc-series-tid&ajax_html_ids%5B%5D=edit-etc-etc-approved-value-wrapper&ajax_html_ids%5B%5D=edit-etc-etc-approved-value&ajax_html_ids%5B%5D=edit-sort-by&ajax_html_ids%5B%5D=edit-sort-order&ajax_html_ids%5B%5D=edit-submit-video-episodes&ajax_html_ids%5B%5D=block-block-1&ajax_html_ids%5B%5D=block-views-ad-cycle-block-1&ajax_html_ids%5B%5D=views_slideshow_cycle_main_ad_cycle-block_1_1&ajax_html_ids%5B%5D=views_slideshow_cycle_teaser_section_ad_cycle-block_1_1&ajax_html_ids%5B%5D=views_slideshow_cycle_div_ad_cycle-block_1_1_0&ajax_html_ids%5B%5D=block-views-recently-featured-examples-block-1&ajax_html_ids%5B%5D=block-block-2&ajax_html_ids%5B%5D=block-block-7&ajax_html_ids%5B%5D=cboxOverlay&ajax_html_ids%5B%5D=colorbox&ajax_html_ids%5B%5D=cboxWrapper&ajax_html_ids%5B%5D=cboxTopLeft&ajax_html_ids%5B%5D=cboxTopCenter&ajax_html_ids%5B%5D=cboxTopRight&ajax_html_ids%5B%5D=cboxMiddleLeft&ajax_html_ids%5B%5D=cboxContent&ajax_html_ids%5B%5D=cboxTitle&ajax_html_ids%5B%5D=cboxCurrent&ajax_html_ids%5B%5D=cboxPrevious&ajax_html_ids%5B%5D=cboxNext&ajax_html_ids%5B%5D=cboxSlideshow&ajax_html_ids%5B%5D=cboxLoadingOverlay&ajax_html_ids%5B%5D=cboxLoadingGraphic&ajax_html_ids%5B%5D=cboxMiddleRight&ajax_html_ids%5B%5D=cboxBottomLeft&ajax_html_ids%5B%5D=cboxBottomCenter&ajax_html_ids%5B%5D=cboxBottomRight&ajax_page_state%5Btheme%5D=example&ajax_page_state%5Btheme_token%5D=PguGAz_7iVf7Atl154MXHwryMWyc9JkY1xOy4bijRfk&ajax_page_state%5Bcss%5D%5Bsites%2Fall%2Fthemes%2Fdelta%2Fdelta%2Fcss%2Fpens%2Fsystem%2Fsystem.base.css%5D=1&ajax_page_state%5Bcss%5D%5Bsites%2Fall%2Fthemes%2Fdelta%2Fdelta%2Fcss%2Fpens%2Fsystem%2Fsystem.menus.theme.css%5D=1&ajax_page_state%5Bcss%5D%5Bsites%2Fall%2Fthemes%2Fdelta%2Fdelta%2Fcss%2Fpens%2Fsystem%2Fsystem.messages.theme.css%5D=1&ajax_page_state%5Bcss%5D%5Bsites%2Fall%2Fthemes%2Fdelta%2Fdelta%2Fcss%2Fpens%2Fsystem%2Fsystem.theme.css%5D=1&ajax_page_state%5Bcss%5D%5Bsites%2Fall%2Fpens%2Ffitvids%2Ffitvids.css%5D=1&ajax_page_state%5Bcss%5D%5Bpens%2Fnode%2Fnode.css%5D=1&ajax_page_state%5Bcss%5D%5Bproexamples%2Fexample%2Fpens%2Fexample%2Fexample_admin%2Fexample-admin.css%5D=1&ajax_page_state%5Bcss%5D%5Bsites%2Fall%2Fthemes%2Fdelta%2Fdelta%2Fcss%2Fpens%2Fetc%2Fetc.theme.css%5D=1&ajax_page_state%5Bcss%5D%5Bproexamples%2Fexample%2Fpens%2Fexample%2Fexample_admin%2Fexample-admin-navbar.css%5D=1&ajax_page_state%5Bcss%5D%5Bproexamples%2Fexample%2Fpens%2Fexample%2Fexample_core%2Fcss%2Fexample-dropbutton.css%5D=1&ajax_page_state%5Bcss%5D%5Bproexamples%2Fexample%2Fpens%2Fexample%2Fexample_magic%2Fcss%2Fexample-magic.css%5D=1&ajax_page_state%5Bcss%5D%5Bproexamples%2Fexample%2Fpens%2Fexample%2Fexample_magic%2Fcss%2Fexample-modal.css%5D=1&ajax_page_state%5Bcss%5D%5Bproexamples%2Fexample%2Fpens%2Fexample%2Fexample_theme%2Fcss%2Fexample-featured.css%5D=1&ajax_page_state%5Bcss%5D%5Bproexamples%2Fexample%2Fpens%2Fexample%2Fexample_theme%2Fcss%2Fexample-accordian.css%5D=1&ajax_page_state%5Bcss%5D%5Bproexamples%2Fexample%2Fpens%2Fexample%2Fexample_theme%2Fcss%2Fexample-layouts.css%5D=1&ajax_page_state%5Bcss%5D%5Bproexamples%2Fexample%2Fpens%2Fexample%2Fexample_widgets%2Fexample-widgets.css%5D=1&ajax_page_state%5Bcss%5D%5Bproexamples%2Fexample%2Fpens%2Fexample%2Fexample_widgets%2Fexample-widgets-spotlight.css%5D=1&ajax_page_state%5Bcss%5D%5Bproexamples%2Fexample%2Fpens%2Fexample%2Fexample_wysiwyg%2Fexample-wysiwyg.css%5D=1&ajax_page_state%5Bcss%5D%5Bproexamples%2Fexample%2Fpens%2Fcontrib%2Fradix_layouts%2Fradix_layouts.css%5D=1&ajax_page_state%5Bcss%5D%5Bproexamples%2Fexample%2Fpens%2Fcontrib%2Fviews%2Fcss%2Fviews.css%5D=1&ajax_page_state%5Bcss%5D%5Bsites%2Fall%2Fthemes%2Fdelta%2Fdelta%2Fcss%2Fpens%2Fsearch%2Fsearch.theme.css%5D=1&ajax_page_state%5Bcss%5D%5Bsites%2Fall%2Fthemes%2Fdelta%2Fdelta%2Fcss%2Fpens%2Fuser%2Fuser.base.css%5D=1&ajax_page_state%5Bcss%5D%5Bsites%2Fall%2Fthemes%2Fdelta%2Fdelta%2Fcss%2Fpens%2Fuser%2Fuser.theme.css%5D=1&ajax_page_state%5Bcss%5D%5Bproexamples%2Fexample%2Fpens%2Fcontrib%2Fcaption_filter%2Fcaption-filter.css%5D=1&ajax_page_state%5Bcss%5D%5Bmisc%2Fui%2Fjquery.ui.theme.css%5D=1&ajax_page_state%5Bcss%5D%5Bmisc%2Fui%2Fjquery.ui.accordion.css%5D=1&ajax_page_state%5Bcss%5D%5Bsites%2Fall%2Fpens%2Fviews_slideshow%2Fviews_slideshow.css%5D=1&ajax_page_state%5Bcss%5D%5Bsites%2Fall%2Fpens%2Fcolorbox%2Fstyles%2Fdefault%2Fcolorbox_style.css%5D=1&ajax_page_state%5Bcss%5D%5Bproexamples%2Fexample%2Fpens%2Fcontrib%2Fctools%2Fcss%2Fctools.css%5D=1&ajax_page_state%5Bcss%5D%5Bproexamples%2Fexample%2Fpens%2Fcontrib%2Fpanels%2Fcss%2Fpanels.css%5D=1&ajax_page_state%5Bcss%5D%5Bsites%2Fall%2Fpens%2Fexample_site_search%2Fcss%2Fexample-site-search.css%5D=1&ajax_page_state%5Bcss%5D%5Bsites%2Fall%2Fpens%2Fviews_slideshow%2Fcontrib%2Fviews_slideshow_cycle%2Fviews_slideshow_cycle.css%5D=1&ajax_page_state%5Bcss%5D%5Bsites%2Fall%2Fpens%2Fhide_submit%2Fhide_submit.css%5D=1&ajax_page_state%5Bcss%5D%5Bsites%2Fall%2Flibraries%2Fsuperfish%2Fcss%2Fsuperfish.css%5D=1&ajax_page_state%5Bcss%5D%5Bsites%2Fall%2Fthemes%2Fexample%2Fcss%2Fexample.styles.css%5D=1&ajax_page_state%5Bcss%5D%5Bproexamples%2Fexample%2Fpens%2Fexample%2Fexample_images%2Fexample-images.css%5D=1&ajax_page_state%5Bjs%5D%5B0%5D=1&ajax_page_state%5Bjs%5D%5Bproexamples%2Fexample%2Fpens%2Fexample%2Fexample_widgets%2Fexample-widgets.js%5D=1&ajax_page_state%5Bjs%5D%5Bproexamples%2Fexample%2Fpens%2Fexample%2Fexample_widgets%2Fexample-widgets-spotlight.js%5D=1&ajax_page_state%5Bjs%5D%5Bproexamples%2Fexample%2Fpens%2Fcontrib%2Fjquery_update%2Freplace%2Fjquery%2F1.7%2Fjquery.min.js%5D=1&ajax_page_state%5Bjs%5D%5Bmisc%2Fjquery.once.js%5D=1&ajax_page_state%5Bjs%5D%5Bmisc%2Fdrupal.js%5D=1&ajax_page_state%5Bjs%5D%5Bsites%2Fall%2Fthemes%2Fdelta%2Fdelta%2Fjs%2Fno-js.js%5D=1&ajax_page_state%5Bjs%5D%5Bsites%2Fall%2Flibraries%2Ffitvids%2Fjquery.fitvids.js%5D=1&ajax_page_state%5Bjs%5D%5Bproexamples%2Fexample%2Fpens%2Fcontrib%2Fjquery_update%2Freplace%2Fui%2Fui%2Fminified%2Fjquery.ui.core.min.js%5D=1&ajax_page_state%5Bjs%5D%5Bproexamples%2Fexample%2Fpens%2Fcontrib%2Fjquery_update%2Freplace%2Fui%2Fui%2Fminified%2Fjquery.ui.widget.min.js%5D=1&ajax_page_state%5Bjs%5D%5Bproexamples%2Fexample%2Fpens%2Fcontrib%2Fjquery_update%2Freplace%2Fui%2Fexternal%2Fjquery.cookie.js%5D=1&ajax_page_state%5Bjs%5D%5Bproexamples%2Fexample%2Fpens%2Fcontrib%2Fjquery_update%2Freplace%2Fui%2Fui%2Fminified%2Fjquery.ui.tabs.min.js%5D=1&ajax_page_state%5Bjs%5D%5Bproexamples%2Fexample%2Fpens%2Fcontrib%2Fjquery_update%2Freplace%2Fui%2Fui%2Fminified%2Fjquery.ui.accordion.min.js%5D=1&ajax_page_state%5Bjs%5D%5Bsites%2Fall%2Fpens%2Fviews_slideshow%2Fjs%2Fviews_slideshow.js%5D=1&ajax_page_state%5Bjs%5D%5Bproexamples%2Fexample%2Fpens%2Fcontrib%2Fjquery_update%2Freplace%2Fmisc%2Fjquery.form.min.js%5D=1&ajax_page_state%5Bjs%5D%5Bmisc%2Fform.js%5D=1&ajax_page_state%5Bjs%5D%5Bmisc%2Fajax.js%5D=1&ajax_page_state%5Bjs%5D%5Bproexamples%2Fexample%2Fpens%2Fcontrib%2Fjquery_update%2Fjs%2Fjquery_update.js%5D=1&ajax_page_state%5Bjs%5D%5Bproexamples%2Fexample%2Fpens%2Fexample%2Fexample_images%2Fexample-images.js%5D=1&ajax_page_state%5Bjs%5D%5Bsites%2Fall%2Fpens%2Ffitvids%2Ffitvids.js%5D=1&ajax_page_state%5Bjs%5D%5Bsites%2Fall%2Fpens%2Fmove_user%2Fmove-user.js%5D=1&ajax_page_state%5Bjs%5D%5Bproexamples%2Fexample%2Fpens%2Fexample%2Fexample_admin%2Fexample-admin.js%5D=1&ajax_page_state%5Bjs%5D%5Bproexamples%2Fexample%2Fpens%2Fexample%2Fexample_magic%2Fexample-magic.js%5D=1&ajax_page_state%5Bjs%5D%5Bproexamples%2Fexample%2Fpens%2Fexample%2Fexample_theme%2Fjs%2Fexample-accordion.js%5D=1&ajax_page_state%5Bjs%5D%5Bproexamples%2Fexample%2Fpens%2Fcontrib%2Fcaption_filter%2Fjs%2Fcaption-filter.js%5D=1&ajax_page_state%5Bjs%5D%5Bsites%2Fall%2Fpens%2Fauthcache%2Fauthcache.js%5D=1&ajax_page_state%5Bjs%5D%5Bsites%2Fall%2Flibraries%2Fcolorbox%2Fjquery.colorbox-min.js%5D=1&ajax_page_state%5Bjs%5D%5Bsites%2Fall%2Fpens%2Fcolorbox%2Fjs%2Fcolorbox.js%5D=1&ajax_page_state%5Bjs%5D%5Bsites%2Fall%2Fpens%2Fcolorbox%2Fstyles%2Fdefault%2Fcolorbox_style.js%5D=1&ajax_page_state%5Bjs%5D%5Bsites%2Fall%2Fpens%2Fexample_site_search%2Fjs%2FsiteSearch.js%5D=1&ajax_page_state%5Bjs%5D%5Bproexamples%2Fexample%2Flibraries%2Fjquery.imagesloaded%2Fjquery.imagesloaded.min.js%5D=1&ajax_page_state%5Bjs%5D%5Bsites%2Fall%2Flibraries%2Fjstorage%2Fjstorage.min.js%5D=1&ajax_page_state%5Bjs%5D%5Bproexamples%2Fexample%2Fpens%2Fcontrib%2Fviews%2Fjs%2Fbase.js%5D=1&ajax_page_state%5Bjs%5D%5Bmisc%2Fprogress.js%5D=1&ajax_page_state%5Bjs%5D%5Bproexamples%2Fexample%2Fpens%2Fcontrib%2Fviews%2Fjs%2Fajax_view.js%5D=1&ajax_page_state%5Bjs%5D%5Bsites%2Fall%2Flibraries%2Fjquery.cycle%2Fjquery.cycle.all.min.js%5D=1&ajax_page_state%5Bjs%5D%5Bsites%2Fall%2Fpens%2Fviews_load_more%2Fviews_load_more.js%5D=1&ajax_page_state%5Bjs%5D%5Bsites%2Fall%2Fpens%2Fviews_slideshow%2Fcontrib%2Fviews_slideshow_cycle%2Fjs%2Fviews_slideshow_cycle.js%5D=1&ajax_page_state%5Bjs%5D%5Blibraries%2Fjquery.hoverIntent%2Fjquery.hoverIntent.js%5D=1&ajax_page_state%5Bjs%5D%5Blibraries%2Fjson2%2Fjson2.js%5D=1&ajax_page_state%5Bjs%5D%5Bsites%2Fall%2Fpens%2Fgoogle_analytics%2Fgoogleanalytics.js%5D=1&ajax_page_state%5Bjs%5D%5Bsites%2Fall%2Fpens%2Fhide_submit%2Fhide_submit.js%5D=1&ajax_page_state%5Bjs%5D%5Bsites%2Fall%2Flibraries%2Fsuperfish%2Fjquery.hoverIntent.minified.js%5D=1&ajax_page_state%5Bjs%5D%5Bsites%2Fall%2Flibraries%2Fsuperfish%2Fsftouchscreen.js%5D=1&ajax_page_state%5Bjs%5D%5Bsites%2Fall%2Flibraries%2Fsuperfish%2Fsupposition.js%5D=1&ajax_page_state%5Bjs%5D%5Bsites%2Fall%2Flibraries%2Fsuperfish%2Fsuperfish.js%5D=1&ajax_page_state%5Bjs%5D%5Bsites%2Fall%2Fpens%2Fsuperfish%2Fsuperfish.js%5D=1&ajax_page_state%5Bjs%5D%5Bsites%2Fall%2Fthemes%2Fexample%2Fjs%2Fexample.behaviors.js%5D=1&ajax_page_state%5Bjs%5D%5Bsites%2Fall%2Fthemes%2Fexample%2Flibraries%2Fheadroom%2Fheadroom.min.js%5D=1&ajax_page_state%5Bjs%5D%5Bsites%2Fall%2Fthemes%2Fexample%2Flibraries%2Fheadroom%2FjQuery.headroom.min.js%5D=1&ajax_page_state%5Bjs%5D%5Bsites%2Fall%2Fthemes%2Fexample%2Fjs%2Fcommon.js%5D=1&ajax_page_state%5Bjquery_version%5D=1.7' \ 19 | `; 20 | 21 | module.exports = largeRequest; 22 | -------------------------------------------------------------------------------- /src/lib.js: -------------------------------------------------------------------------------- 1 | const commander = require('commander'), 2 | validUrl = require('valid-url'), 3 | _ = require('lodash').noConflict(), 4 | shellQuote = require('../assets/shell-quote'), 5 | unnecessaryOptions = require('../assets/unnecessaryOptions'), 6 | supportedOptions = require('../assets/supportedOptions'), 7 | UserError = require('./UserError'), 8 | { USER_ERRORS } = require('./constants'), 9 | formDataOptions = ['-d', '--data', '--data-raw', '--data-binary', '--data-ascii'], 10 | allowedOperators = ['<', '>', '(', ')'], 11 | REQUEST_BODY_LANGUAGE_TEXT = 'text', 12 | REQUEST_BODY_LANGUAGE_JSON = 'json', 13 | REQUEST_BODY_LANGUAGE_JAVASCRIPT = 'javascript', 14 | REQUEST_BODY_LANGUAGE_HTML = 'html', 15 | REQUEST_BODY_LANGUAGE_XML = 'xml', 16 | LANGUAGE_REGEX_MATCH = { 17 | [REQUEST_BODY_LANGUAGE_JSON]: /^application\/(\S+\+)?json/, 18 | [REQUEST_BODY_LANGUAGE_JAVASCRIPT]: /^(text|application)\/(\S+\+)?javascript/, 19 | [REQUEST_BODY_LANGUAGE_XML]: /^(text|application)\/(\S+\+)?xml/, 20 | [REQUEST_BODY_LANGUAGE_HTML]: /^text\/html/ 21 | }, 22 | ALLOWED_DUPLICATE_HEADERS = ['cookie']; 23 | 24 | var program, 25 | 26 | curlConverter = { 27 | requestUrl: '', 28 | initialize: function() { 29 | /** 30 | * Collects values from the command line arguments and adds them to the memo array. 31 | * 32 | * @param {string} str - The argument value to collect. 33 | * @param {Array} memo - The array to add the collected values to. 34 | * @returns {Array} - The updated memo array. 35 | */ 36 | function collectValues(str, memo) { 37 | memo.push(str); 38 | return memo; 39 | } 40 | 41 | program = new commander.Command(); 42 | 43 | program.version('0.0.1') 44 | .allowUnknownOption() 45 | .usage('[options] '); 46 | supportedOptions.forEach((option) => { 47 | var optionStr = ''; 48 | optionStr += option.short ? `${option.short}, ${option.long}` : option.long; 49 | if (option.format) { 50 | optionStr += ` ${option.format}`; 51 | } 52 | if (option.collectValues) { 53 | program.option(optionStr, option.description, collectValues, []); 54 | } 55 | else { 56 | program.option(optionStr, option.description, null); 57 | } 58 | }); 59 | }, 60 | 61 | trimQuotesFromString: function(str) { 62 | if (!str) { return ''; } 63 | var strlen = str.length; 64 | if ((str[0] === '"' && str[strlen - 1] === '"') || (str[0] === '\'' && str[strlen - 1] === '\'')) { 65 | return str.substring(1, strlen - 1); 66 | } 67 | return str; 68 | }, 69 | 70 | // What this will do: 71 | // If URL is http://example.com?a=b and -d 'c=d' => http://example.com?a=b&c=d 72 | // If URL is http://example.com#fragment and -d 'c=d' => http://example.com#fragment 73 | addQueryParamsFromDataOption: function(curlObj, urlData, request) { 74 | // If --get/-G option is given with --data/-d/--data-raw/--data-binary/--data-urlencode/--data-ascii. 75 | // Then the value of data body will append to the URL query params regardless what method is mentioned. 76 | // Related Doc - https://curl.haxx.se/docs/manpage.html#-G 77 | if (curlObj.get && (curlObj.data.length > 0 || curlObj.dataAscii.length > 0 || 78 | curlObj.dataUrlencode.length > 0 || curlObj.dataRaw.length > 0 || curlObj.dataBinary)) { 79 | if (urlData) { 80 | if (request.url.includes('?')) { 81 | request.url += '&' + urlData; 82 | } 83 | else { 84 | request.url += '?' + urlData; 85 | } 86 | } 87 | } 88 | }, 89 | 90 | getRequestMethod: function(curlObj) { 91 | // RFC- https://curl.haxx.se/docs/manpage.html 92 | // checking if the user has mentioned -T or --upload-file in curl command 93 | if (curlObj.uploadFile.length > 0) { 94 | return 'PUT'; 95 | } 96 | // checking if the user has mentioned -I or --head in curl command 97 | else if (curlObj.head) { 98 | return 'HEAD'; 99 | } 100 | // checking if the user has mentioned -G or --get in curl command 101 | else if (curlObj.get) { 102 | return 'GET'; 103 | } 104 | // checking if the user has mentioned any of these (-d, --data, --data-raw 105 | // --data-binary, --data-ascii) in curl command 106 | else if (curlObj.data.length > 0 || curlObj.dataAscii.length > 0 || 107 | curlObj.dataUrlencode.length > 0 || curlObj.dataRaw.length > 0 || 108 | curlObj.dataBinary || curlObj.form.length > 0) { 109 | return 'POST'; 110 | } 111 | // set method to GET if no param is present 112 | return 'GET'; 113 | }, 114 | 115 | validateCurlRequest: function(curlObj) { 116 | // must be a valid method 117 | var validMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'COPY', 'HEAD', 118 | 'OPTIONS', 'LINK', 'UNLINK', 'PURGE', 'LOCK', 'UNLOCK', 'PROPFIND'], 119 | singleWordXMethod, 120 | singleWordMethodPrefix = '-X', 121 | reqMethod = _.toUpper(curlObj.request); 122 | 123 | if (validMethods.indexOf(reqMethod) === -1) { 124 | 125 | // no valid method 126 | // -XPOST might have been used 127 | // try the POST part again 128 | singleWordXMethod = _.find(curlObj.rawArgs, function (arg) { 129 | return typeof arg === 'string' && arg.startsWith(singleWordMethodPrefix); 130 | }); 131 | 132 | if (singleWordXMethod) { 133 | // try to re-set curlObj.request to the newly extracted method 134 | curlObj.request = singleWordXMethod.substring(singleWordMethodPrefix.length); 135 | } 136 | 137 | reqMethod = _.toUpper(curlObj.request); 138 | 139 | if (validMethods.indexOf(reqMethod) === -1) { 140 | // the method is still not valid 141 | throw new UserError(USER_ERRORS.METHOD_NOT_SUPPORTED`${curlObj.request}`); 142 | } 143 | } 144 | 145 | // cannot send HEAD request in the curl command with POST data option and without --get/-G 146 | // Ex- 'curl -I http://example.com -d "a=b"' will throw an error. 147 | if ((curlObj.data.length > 0 || curlObj.dataAscii.length > 0 || 148 | curlObj.dataBinary || curlObj.dataUrlencode.length > 0) && 149 | curlObj.head && !curlObj.get) { 150 | throw new UserError(USER_ERRORS.UNABLE_TO_PARSE_HEAD_AND_DATA); 151 | } 152 | 153 | /** 154 | * For cURL with ^ as line termination character, each such line termination char will be an separate arg. 155 | * throw an error as we have separate handling for parsing such cURLs 156 | * once it fails here using convertForCMDFormat() 157 | */ 158 | if (curlObj.args.length > 1 && _.includes(curlObj.args, '^')) { 159 | throw new UserError(USER_ERRORS.INPUT_WITHOUT_OPTIONS); 160 | } 161 | }, 162 | 163 | getHeaders: function(curlObj) { 164 | var headerArray = curlObj.header, 165 | numHeaders, 166 | retVal = [], 167 | uaString; 168 | 169 | headerArray = headerArray || []; 170 | 171 | 172 | if (curlObj.userAgent) { 173 | uaString = this.trimQuotesFromString(curlObj.userAgent); 174 | this.headerPairs['User-Agent'] = uaString; 175 | retVal.push({ 176 | key: 'User-Agent', 177 | value: uaString 178 | }); 179 | } 180 | 181 | // If any cookies are added under -b or --cookie arg, add them as Cookie header 182 | if (curlObj.cookie && Array.isArray(curlObj.cookie)) { 183 | curlObj.cookie.forEach((cookieVal) => { 184 | headerArray.push('Cookie: ' + this.trimQuotesFromString(cookieVal)); 185 | }); 186 | } 187 | 188 | if (headerArray === null || headerArray.length === 0) { 189 | return retVal; 190 | } 191 | 192 | numHeaders = headerArray.length; 193 | 194 | for (let i = 0; i < numHeaders; i++) { 195 | let thisHeader = headerArray[i], 196 | keyIndex; 197 | 198 | if (!(typeof thisHeader === 'string')) { 199 | console.warn('Unparseable header in curl conversion: ', thisHeader); 200 | continue; 201 | } 202 | // remove leading and trailing quotes 203 | thisHeader = this.trimQuotesFromString(thisHeader); 204 | keyIndex = thisHeader.indexOf(':'); 205 | if (keyIndex === -1) { 206 | if (thisHeader.endsWith(';')) { 207 | // If you send the custom header with no-value then its header must be \ 208 | // terminated with a semicolon, such as -H "X-Custom-Header;" to send "X-Custom-Header:". 209 | thisHeader = thisHeader.slice(0, -1) + ':'; 210 | keyIndex = thisHeader.indexOf(':'); 211 | } 212 | else { 213 | continue; 214 | } 215 | } 216 | /* eslint-disable no-implicit-globals */ 217 | key = thisHeader.substring(0, keyIndex).trim(); 218 | value = thisHeader.substring(keyIndex + 1, thisHeader.length).trim(); 219 | /* eslint-enable */ 220 | 221 | if (this.headerPairs.hasOwnProperty(key) && !ALLOWED_DUPLICATE_HEADERS.includes(key.toLowerCase())) { 222 | // don't add the same header twice 223 | continue; 224 | } 225 | 226 | this.headerPairs[key] = value; 227 | 228 | retVal.push({ 229 | key: key, 230 | value: value 231 | }); 232 | } 233 | 234 | return retVal; 235 | }, 236 | 237 | /** 238 | * Generates the auth object for the request 239 | * 240 | * @param {Object} curlObj The curl object 241 | * @returns {Object} The auth object 242 | */ 243 | getAuth: function(curlObj) { 244 | let authObject; 245 | 246 | // It is a valid cURL to have only username, in that case keep password empty 247 | const userParts = (typeof curlObj.user === 'string' && curlObj.user.split(':')) || []; 248 | if (userParts.length === 1) { 249 | userParts[1] = ''; 250 | } 251 | 252 | if (curlObj.digest === true) { 253 | authObject = { 254 | type: 'digest', 255 | digest: [ 256 | { key: 'username', value: userParts[0], type: 'string' }, 257 | { key: 'password', value: userParts[1], type: 'string' } 258 | ] 259 | }; 260 | } 261 | else if (curlObj.ntlm === true) { 262 | authObject = { 263 | type: 'ntlm', 264 | ntlm: [ 265 | { key: 'username', value: userParts[0], type: 'string' }, 266 | { key: 'password', value: userParts[1], type: 'string' } 267 | ] 268 | }; 269 | } 270 | else { 271 | // Fallback to basic auth 272 | authObject = { 273 | type: 'basic', 274 | basic: [ 275 | { key: 'username', value: userParts[0], type: 'string' }, 276 | { key: 'password', value: userParts[1], type: 'string' } 277 | ] 278 | }; 279 | } 280 | 281 | return authObject; 282 | }, 283 | 284 | resetProgram: function() { 285 | this.requestUrl = ''; 286 | }, 287 | 288 | getDataForForm: function(dataArray, toDecodeUri, mode) { 289 | var numElems = dataArray.length, 290 | retVal = [], 291 | equalIndex, 292 | key = '', 293 | val = '', 294 | headerMatch; 295 | 296 | for (let i = 0; i < numElems; i++) { 297 | let thisElem = dataArray[i], 298 | paramObj = { type: 'text' }; 299 | 300 | if (dataArray[i] === '') { continue; } 301 | 302 | thisElem = this.trimQuotesFromString(thisElem); 303 | 304 | equalIndex = thisElem.indexOf('='); 305 | if (equalIndex === -1) { 306 | key = thisElem; 307 | val = ''; 308 | } 309 | else { 310 | key = thisElem.substring(0, equalIndex); 311 | val = thisElem.substring(equalIndex + 1, thisElem.length); 312 | 313 | if (mode === 'formdata') { 314 | /** 315 | * Following regexp tries to find sytax like "";type=application/json" from value. 316 | * Here first matching group is type of content-type (i.e. "application") and 317 | * second matching group is subtype of content type (i.e. "json") 318 | * Similar to usecase: https://github.com/postmanlabs/openapi-to-postman/blob/develop/lib/schemaUtils.js 319 | */ 320 | headerMatch = val.match(/;\s*type=([^\s\/;]+)\/([^;\s]+)\s*(?:;(.*))?$/); 321 | 322 | // remove content type header from value 323 | if (headerMatch) { 324 | paramObj.contentType = headerMatch[1] + '/' + headerMatch[2]; 325 | val = val.slice(0, headerMatch.index); 326 | } 327 | 328 | // set type of param as file for value starting with @ 329 | if (val.startsWith('@')) { 330 | paramObj.type = 'file'; 331 | val = val.slice(1); 332 | } 333 | 334 | // remove starting and ending double quotes if present 335 | if (val.length > 1 && val.startsWith('"') && val.endsWith('"')) { 336 | val = val.slice(1, -1); 337 | // unescape all double quotes as we have removed starting and ending double quotes 338 | val = val.replace(/\\\"/gm, '"'); 339 | } 340 | } 341 | } 342 | 343 | if (toDecodeUri) { 344 | key = decodeURIComponent(key.replace(/\+/g, '%20')); 345 | val = decodeURIComponent(val.replace(/\+/g, '%20')); 346 | } 347 | 348 | _.assign(paramObj, { key, value: val }); 349 | retVal.push(paramObj); 350 | } 351 | 352 | return retVal; 353 | }, 354 | 355 | getDataForUrlEncoded: function(dataArray, enableDecoding) { 356 | var concatString = dataArray.join('&').trim(); 357 | 358 | dataArray = this.trimQuotesFromString(concatString).split('&'); 359 | return this.getDataForForm(dataArray, enableDecoding); 360 | }, 361 | 362 | getLowerCaseHeader: function(hk, rHeaders) { 363 | for (var hKey in rHeaders) { 364 | if (rHeaders.hasOwnProperty(hKey)) { 365 | if (hKey.toLowerCase() === hk.toLowerCase()) { 366 | return rHeaders[hKey]; 367 | } 368 | } 369 | } 370 | return ''; 371 | }, 372 | 373 | convertArrayToAmpersandString: function(arr) { 374 | return arr.join('&'); 375 | }, 376 | 377 | sanitizeArgs: function(string) { 378 | var argv = shellQuote.parse('node ' + string, function(key) { 379 | // this is done to prevent converting vars like $id in the curl input to '' 380 | return '$' + key; 381 | }), 382 | sanitizedArgs = _.map(_.filter(argv, function(arg) { 383 | // remove arg if it is present in unnecessary options list 384 | if (unnecessaryOptions.includes(arg)) { 385 | return false; 386 | } 387 | return !_.isEmpty(arg); 388 | }), function (arg) { 389 | if (_.isObject(arg) && arg.op === 'glob') { 390 | return arg.pattern; 391 | } 392 | else if (arg.op && arg.op.startsWith('$') && arg.op.length > 3) { 393 | // in the app, certain headers like -H $'cookie: abc' are treated as operators 394 | // converting the arg to cookie: abc instead of op: $'cookie: abc' 395 | return arg.op.substring(2, arg.op.length - 1); 396 | } 397 | else if (typeof (arg) !== 'string' && arg.op === '&') { 398 | // non-string data should not be here, might be a case of malformed URL or unsanitized characters 399 | const inCorrectlyFormedcURLRegex1 = /[^ "]+\?[^ "]+&[^ "]+($|(?=\s))/g, // checks - foo/bar?foo=1&bar=2 400 | inCorrectlyFormedcURLRegex2 = /(\w+=\w+&?)/g; // checks - foo?bar=1&baz=2 401 | 402 | if (string.match(inCorrectlyFormedcURLRegex1) || string.match(inCorrectlyFormedcURLRegex2)) { 403 | throw new UserError(USER_ERRORS.MALFORMED_URL); 404 | } 405 | } 406 | else if (_.isFunction(arg.startsWith) && arg.startsWith('$') && arg.length > 1) { 407 | // removing $ before every arg like $'POST' and 408 | // converting the arg to 'POST' 409 | // link of RFC- http://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html 410 | return arg.substring(1); 411 | } 412 | 413 | return arg; 414 | 415 | }), 416 | validArgs = [], 417 | i; 418 | supportedOptions.forEach((option) => { 419 | validArgs.push(option.long); 420 | option.short && validArgs.push(option.short); 421 | }); 422 | for (i = 0; i < sanitizedArgs.length; i++) { 423 | let arg = sanitizedArgs[i]; 424 | // check for not exact equal to -X also, as it can be of the form -X POST 425 | if (_.isFunction(arg.startsWith) && arg.startsWith('-X') && arg !== '-X') { 426 | // suppose arg = -XPOST 427 | // the arg preceding isn't a commander option(e.g. -H) 428 | if (!validArgs.includes(sanitizedArgs[i - 1])) { 429 | // gets POST from -XPOST 430 | let method = arg.slice(2); 431 | // replaces value at index i to -X from -XPOST 432 | sanitizedArgs[i] = '-X'; 433 | // inserts value 'POST' at index i+1 434 | sanitizedArgs.splice(i + 1, 0, method); 435 | } 436 | } 437 | } 438 | return sanitizedArgs; 439 | }, 440 | 441 | getMetaData: function(curlString) { 442 | try { 443 | this.initialize(); 444 | var sanitizeArgs = this.sanitizeArgs(curlString), 445 | curlObj = program.parse(sanitizeArgs); 446 | 447 | this.getRequestUrl(curlObj); 448 | 449 | return { 450 | url: this.requestUrl 451 | }; 452 | } 453 | catch (e) { 454 | if (e.message === 'process.exit is not a function') { 455 | // happened because of 456 | return { error: new UserError(USER_ERRORS.INVALID_FORMAT) }; 457 | } 458 | return { error: e }; 459 | } 460 | }, 461 | 462 | getRequestUrl: function(curlObj) { 463 | this.requestUrl = ''; 464 | if (curlObj.args.length === 0) { 465 | if (curlObj.url) { 466 | // url is populated if there's no unknown option 467 | this.requestUrl = curlObj.url; 468 | } 469 | else { 470 | // if there is an unknown option, we have to take it from the rawArgs 471 | try { 472 | this.requestUrl = curlObj.rawArgs.slice(-1)[0]; 473 | /* eslint-disable max-depth */ 474 | if (!validUrl.isUri(this.requestUrl)) { 475 | // eslint-disable-next-line no-throw-literal 476 | throw 'No valid URL found'; 477 | } 478 | } 479 | catch (e) { 480 | throw new UserError(USER_ERRORS.UNABLE_TO_PARSE_NO_URL); 481 | } 482 | } 483 | /* eslint-enable */ 484 | } 485 | else if (curlObj.args.length > 0) { 486 | let argStr = typeof curlObj.url === 'string' ? curlObj.url : ''; 487 | 488 | // eslint-disable-next-line consistent-return 489 | _.forEach(curlObj.args, (arg, index) => { 490 | const previousArgOp = _.get(curlObj.args, `${index - 1}.op`, ''), 491 | shouldAddCurrentArg = index === 0 || allowedOperators.includes(previousArgOp); 492 | 493 | if (typeof arg === 'string' && shouldAddCurrentArg) { 494 | /** 495 | * Add current string arg only if previous arg is an allowed op. 496 | * For URL like "hello.com/", as "<" and ">" are treated as bash operators, 497 | * we'll add such operator and next arg that was split up by it in URL 498 | */ 499 | argStr += arg; 500 | } 501 | else if (typeof arg === 'object' && allowedOperators.includes(arg.op)) { 502 | argStr += arg.op; 503 | } 504 | else { 505 | /** 506 | * Stop adding more args as soon as we know that args are not split by allowed operators. 507 | */ 508 | return false; 509 | } 510 | }); 511 | this.requestUrl = argStr; 512 | } 513 | else { 514 | throw new UserError(USER_ERRORS.CANNOT_DETECT_URL); 515 | } 516 | }, 517 | 518 | /** 519 | * Transforms corresponding cURL in Windows CMD format to Bash compatible version, 520 | * via unescaping escaped characters for Windows CMD. 521 | * 522 | * @param {String} curlString - curL string to be transformed into respectivve bash version 523 | * @returns {String} - Transformed cURL in Bash format 524 | */ 525 | transformCmdToBash: function (curlString) { 526 | /** 527 | * three kinds of matching groups can be captured with following regexp. 528 | * 1. Certain characters that can be escaped by ^ (caret). Namely ^,{,},[,],<,>,\,",|,&,\n(new line) 529 | * These will be replaced with character itself resulting in removal of escape char (^) 530 | * 2. ^%^ specifically will be replaced with % due to special escape rule 531 | * 3. "" will be replaced with " 532 | * (single quotations are escaped with double quotes when inside string that's wrapped in double quotes) 533 | * 534 | * Ref: https://ss64.com/nt/syntax-esc.html 535 | * See detail regexp composition over here: https://regex101.com/r/Xbiqbq/1 536 | */ 537 | return curlString.replace(new RegExp(/\^(\^|{|}|\[|\]|<|>|\\|"|\||&|\n)|\^(%)\^|"(")/, 'g'), '$1$2$3'); 538 | }, 539 | 540 | /** 541 | * Parses raw data and generates object from it by understanding content present in it 542 | * 543 | * @param {String} data - Raw data string 544 | * @param {String} contentType - Content type header value 545 | * @returns {Object} Parsed data in key-value pairs as object 546 | */ 547 | parseFormBoundryData: function (data, contentType) { 548 | var m, 549 | boundary, 550 | parts, 551 | parsedFormData = []; 552 | 553 | // Examples for content types: 554 | // multipart/form-data; boundary="----7dd322351017c"; ... 555 | // multipart/form-data; boundary=----7dd322351017c; ... 556 | m = contentType.match(/boundary=(?:"([^"]+)"|([^;]+))/i); 557 | 558 | // if correct boundary match is not found, keep formdata fields empty 559 | if (!m || typeof data !== 'string') { 560 | return parsedFormData; 561 | } 562 | 563 | // \r\n is part of the boundary. 564 | boundary = '\r\n--' + (m[1] || m[2]); 565 | 566 | // split data based on boundary string 567 | parts = data.split(new RegExp(boundary)); 568 | 569 | _.forEach(parts, (part) => { 570 | var subparts = part.split('\r\n\r\n'), 571 | headers, 572 | formDataRow = {}; 573 | 574 | if (subparts.length < 2) { 575 | return; 576 | } 577 | 578 | // identify key/name from subpart 1 579 | headers = subparts[0].split('\r\n'); 580 | _.forEach(headers, (header) => { 581 | // first try to identify if header contains both name and filename 582 | var matchResult = header.match(/^.*name="([^"]*)"\s*;\s*filename="([^"]*)"$/); 583 | 584 | if (matchResult) { 585 | // keep formdata row type as file if filename is present 586 | formDataRow = { 587 | key: matchResult[1], 588 | value: matchResult[2], 589 | type: 'file' 590 | }; 591 | } 592 | else { 593 | // if both name and filename is not present, use "name" as key and data present in subpart[1] as value 594 | matchResult = header.match(/^.*name="([^"]*)"$/); 595 | if (matchResult) { 596 | formDataRow = { 597 | key: matchResult[1], 598 | value: subparts[1], 599 | type: 'text' 600 | }; 601 | } 602 | } 603 | }); 604 | 605 | // assign key values to parsed data 606 | if (!_.isEmpty(formDataRow)) { 607 | parsedFormData.push(formDataRow); 608 | } 609 | }); 610 | 611 | return parsedFormData; 612 | }, 613 | 614 | /** 615 | * Converts cURL string that's in windows cmd compatible format into collection request 616 | * 617 | * @param {String} curlString - Windows cmd compatible cURL string 618 | * @returns {Object} Collection request JSON 619 | */ 620 | convertForCMDFormat: function (curlString) { 621 | try { 622 | const bashCurlString = this.transformCmdToBash(curlString), 623 | request = this.convertCurlToRequest(bashCurlString, false); 624 | 625 | request.description = 'Generated from a curl request: \n' + curlString.split('"').join('\\\"'); 626 | return request; 627 | } 628 | catch (error) { 629 | throw e; 630 | } 631 | }, 632 | 633 | /** 634 | * Sanitise and parse the input cURl string 635 | * 636 | * @param {string} curlString - Input cURL string 637 | * @returns {object} - Parsed cURL Object 638 | */ 639 | getCurlObject: function (curlString) { 640 | let cleanedCurlString = curlString, 641 | sanitizedArgs, 642 | isMethodGuessed = false, 643 | curlObj; 644 | try { 645 | sanitizedArgs = this.sanitizeArgs(cleanedCurlString); 646 | curlObj = program.parse(sanitizedArgs); 647 | } 648 | catch (e) { 649 | // [Github #8843] - RegEx to fix malformed cURLs with unquoted multi-param URLs 650 | const multiParamUrlRegEx = /\s([^'` "\n]+)\.([^ \n]+)&((?!["'])[^ "`'\n])+($|(?=\s))/gm; 651 | let matchedStrings = curlString.match(multiParamUrlRegEx), 652 | matchedString = '', 653 | prefixString = ''; 654 | 655 | if (matchedStrings && matchedStrings.length > 0) { 656 | prefixString = matchedStrings[0].slice(0, 1); 657 | matchedString = matchedStrings[0].slice(1); 658 | } 659 | cleanedCurlString = curlString.replace(multiParamUrlRegEx, `${prefixString}'${matchedString}'`); 660 | sanitizedArgs = this.sanitizeArgs(cleanedCurlString); 661 | curlObj = program.parse(sanitizedArgs); 662 | } 663 | 664 | // Filter out comments from Args 665 | curlObj.args = _.filter(curlObj.args, (arg) => { 666 | // Each arg should be string itself, for comment we receive an object from parser 667 | return !(typeof arg === 'object' && typeof arg.comment === 'string'); 668 | }); 669 | 670 | this.headerPairs = {}; 671 | 672 | // if method is not given in the curl command 673 | if (typeof curlObj.request !== 'string' || !curlObj.request) { 674 | curlObj.request = this.getRequestMethod(curlObj); 675 | isMethodGuessed = true; 676 | } 677 | 678 | curlObj.request = this.trimQuotesFromString(curlObj.request); 679 | 680 | this.validateCurlRequest(curlObj); 681 | 682 | this.getRequestUrl(curlObj); 683 | 684 | return { isMethodGuessed, curlObj }; 685 | }, 686 | 687 | /** 688 | * Valid if the input cURL string is valid or not 689 | * 690 | * @param {string} curlString - Input cURL string 691 | * @param {boolean} shouldRetry - Whether we should retry parsing for Windows CMD style cURL input 692 | * @returns {Object} - { result: true } if cURL is valid otherwise { result: false } with reason 693 | */ 694 | validate: function (curlString, shouldRetry = true) { 695 | try { 696 | this.initialize(); 697 | this.getCurlObject(curlString); 698 | return { result: true }; 699 | } 700 | catch (e) { 701 | if (shouldRetry) { 702 | curlString = this.transformCmdToBash(curlString); 703 | return this.validate(curlString, false); 704 | } 705 | 706 | return { result: false, reason: e.message, error: e }; 707 | } 708 | }, 709 | 710 | /** 711 | * Escape JSON strings before JSON.parse 712 | * 713 | * @param {string} jsonString - Input JSON string 714 | * @returns {string} - JSON string with escaped characters 715 | */ 716 | escapeJson: function (jsonString) { 717 | // eslint-disable-next-line no-implicit-globals 718 | meta = { // table of character substitutions 719 | '\t': '\\t', 720 | '\n': '\\n', 721 | '\f': '\\f', 722 | '\r': '\\r' 723 | }; 724 | return jsonString.replace(/[\t\n\f\r]/g, (char) => { 725 | return meta[char]; 726 | }); 727 | }, 728 | 729 | /** 730 | * Identifies whether the input data string is a graphql query or not 731 | * 732 | * @param {string} dataString - Input data string to check if it is a graphql query 733 | * @param {string} contentType - Content type header value 734 | * @returns {Object} - { result: true, graphql: {Object} } if dataString is a graphql query else { result: false } 735 | */ 736 | identifyGraphqlRequest: function (dataString, contentType) { 737 | try { 738 | const rawDataObj = _.attempt(JSON.parse, this.escapeJson(dataString)); 739 | 740 | if (contentType === 'application/json' && rawDataObj && !_.isError(rawDataObj)) { 741 | if (!_.has(rawDataObj, 'query') || !_.isString(rawDataObj.query)) { 742 | return { result: false }; 743 | } 744 | if (_.has(rawDataObj, 'variables')) { 745 | if (!_.isObject(rawDataObj.variables)) { 746 | return { result: false }; 747 | } 748 | } 749 | else { 750 | rawDataObj.variables = {}; 751 | } 752 | if (_.has(rawDataObj, 'operationName')) { 753 | if (!_.isString(rawDataObj.operationName)) { 754 | return { result: false }; 755 | } 756 | delete rawDataObj.operationName; 757 | } 758 | if (_.keys(rawDataObj).length === 2) { 759 | const graphqlVariables = JSON.stringify(rawDataObj.variables, null, 2); 760 | return { 761 | result: true, 762 | graphql: { 763 | query: rawDataObj.query, 764 | variables: graphqlVariables === '{}' ? '' : graphqlVariables 765 | } 766 | }; 767 | } 768 | } 769 | return { result: false }; 770 | } 771 | catch (e) { 772 | return { result: false }; 773 | } 774 | }, 775 | 776 | 777 | convertCurlToRequest: function(curlString, shouldRetry = true) { 778 | try { 779 | this.initialize(); 780 | this.requestUrl = ''; 781 | 782 | let { isMethodGuessed, curlObj } = this.getCurlObject(curlString), 783 | request = {}, 784 | content_type, 785 | urlData = '', 786 | bodyArr = [], 787 | dataString, 788 | dataRawString, 789 | dataAsciiString, 790 | dataUrlencode, 791 | formData; 792 | 793 | request.method = 'GET'; 794 | if (curlObj.request && curlObj.request.length !== 0) { 795 | request.method = curlObj.request; 796 | } 797 | 798 | request.url = request.name = this.trimQuotesFromString(this.requestUrl); 799 | 800 | request.header = this.getHeaders(curlObj); 801 | 802 | request.body = {}; 803 | 804 | if (curlObj.user) { 805 | request.auth = this.getAuth(curlObj); 806 | } 807 | 808 | content_type = this.getLowerCaseHeader('content-type', this.headerPairs); 809 | 810 | if (curlObj.dataBinary !== null) { 811 | request.body.mode = 'raw'; 812 | request.body.raw = curlObj.dataBinary; 813 | } 814 | else if (curlObj.form && curlObj.form.length !== 0) { 815 | request.body.mode = 'formdata'; 816 | request.body.formdata = this.getDataForForm(curlObj.form, false, request.body.mode); 817 | } 818 | else if ((curlObj.data && curlObj.data.length !== 0) || (curlObj.dataAscii && curlObj.dataAscii.length !== 0) || 819 | (curlObj.dataRaw && curlObj.dataRaw.length !== 0) || 820 | (curlObj.dataUrlencode && curlObj.dataUrlencode.length !== 0)) { 821 | if (content_type === '' || content_type === 'application/x-www-form-urlencoded') { 822 | // No content-type set 823 | // set to urlencoded 824 | request.body.mode = 'urlencoded'; 825 | request.body.urlencoded = this.getDataForUrlEncoded(curlObj.data, true) 826 | .concat(this.getDataForUrlEncoded(curlObj.dataRaw, true)) 827 | .concat(this.getDataForUrlEncoded(curlObj.dataUrlencode, true)) 828 | .concat(this.getDataForUrlEncoded(curlObj.dataAscii, false)); 829 | 830 | bodyArr.push(this.convertArrayToAmpersandString(curlObj.data)); 831 | bodyArr.push(this.convertArrayToAmpersandString(curlObj.dataRaw)); 832 | bodyArr.push(this.convertArrayToAmpersandString(curlObj.dataUrlencode)); 833 | bodyArr.push(this.convertArrayToAmpersandString(curlObj.dataAscii)); 834 | urlData = _.join(_.reject(bodyArr, (ele) => { 835 | return !ele; 836 | }), '&'); 837 | } 838 | else { 839 | dataString = this.convertArrayToAmpersandString(curlObj.data); 840 | dataRawString = this.convertArrayToAmpersandString(curlObj.dataRaw); 841 | dataUrlencode = this.convertArrayToAmpersandString(curlObj.dataUrlencode); 842 | dataAsciiString = this.convertArrayToAmpersandString(curlObj.dataAscii); 843 | bodyArr.push(this.trimQuotesFromString(dataString)); 844 | bodyArr.push(this.trimQuotesFromString(dataRawString)); 845 | bodyArr.push(this.trimQuotesFromString(dataUrlencode)); 846 | bodyArr.push(this.trimQuotesFromString(dataAsciiString)); 847 | 848 | const rawDataString = _.join(_.reject(bodyArr, (ele) => { 849 | return !ele; 850 | }), '&'), 851 | graphqlRequestData = this.identifyGraphqlRequest(rawDataString, content_type); 852 | 853 | if (graphqlRequestData.result) { 854 | request.body.mode = 'graphql'; 855 | request.body.graphql = graphqlRequestData.graphql; 856 | } 857 | else { 858 | request.body.mode = 'raw'; 859 | request.body.raw = rawDataString; 860 | 861 | request.body.options = {}; 862 | request.body.options.raw = {}; 863 | 864 | /* eslint-disable max-depth */ 865 | try { 866 | request.body.options.raw.language = Object.keys(LANGUAGE_REGEX_MATCH) 867 | .find((key) => { 868 | return LANGUAGE_REGEX_MATCH[key].test(content_type); 869 | }) || REQUEST_BODY_LANGUAGE_TEXT; 870 | } 871 | catch (err) { 872 | // In case of error, set language to text as default 873 | request.body.options.raw.language = REQUEST_BODY_LANGUAGE_TEXT; 874 | } 875 | /* eslint-enable max-depth */ 876 | } 877 | 878 | urlData = request.data; 879 | } 880 | } 881 | else if (_.toLower(content_type).startsWith('multipart/form-data')) { 882 | /** 883 | * get data arg value from raw args as formdata boundary separated string 884 | * is not parsed as any of data options by commander 885 | */ 886 | _.forEach(curlObj.rawArgs, (arg, index) => { 887 | if (_.includes(formDataOptions, arg)) { 888 | formData = _.get(curlObj.rawArgs, index + 1); 889 | return false; 890 | } 891 | }); 892 | request.body.mode = 'formdata'; 893 | request.body.formdata = this.parseFormBoundryData(formData, content_type); 894 | 895 | /** 896 | * As we are parsing raw args here to detect form-data body, make sure we are also 897 | * defining method if not already defined in cURL 898 | */ 899 | (!_.isEmpty(request.body.formdata) && isMethodGuessed) && (request.method = 'POST'); 900 | } 901 | 902 | if (request.body.mode === 'formdata') { 903 | /** 904 | * remove content-type header for form-data body type as it overrides the header added by postman 905 | * resulting in incorrect boundary details in header value 906 | */ 907 | _.remove(request.header, (h) => { 908 | return _.toLower(h.key) === 'content-type'; 909 | }); 910 | } 911 | 912 | // add data to query parameteres in the URL from --data or -d option 913 | this.addQueryParamsFromDataOption(curlObj, urlData, request); 914 | request.description = 'Generated from a curl request: \n' + curlString.split('"').join('\\\"'); 915 | return request; 916 | } 917 | catch (e) { 918 | if (shouldRetry) { 919 | try { 920 | // Retry conversion again by considering cURL to be in windows cmd compatible format 921 | return this.convertForCMDFormat(curlString); 922 | } 923 | catch (error) { 924 | // Retry error is not reported 925 | } 926 | } 927 | if (e.message === 'process.exit is not a function') { 928 | return { error: new UserError(USER_ERRORS.INVALID_FORMAT) }; 929 | } 930 | return { error: e }; 931 | } 932 | } 933 | }; 934 | 935 | module.exports = curlConverter; 936 | -------------------------------------------------------------------------------- /test/conversion.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | const largeRequest = require('./large-request'); 3 | 4 | var Converter = require('../src/lib'), 5 | convert = require('../src/convert'), 6 | getMetaData = require('../index').getMetaData, 7 | validate = require('../index').validate, 8 | expect = require('expect.js'), 9 | _ = require('lodash'); 10 | 11 | describe('validate', function () { 12 | it('return false result for malformed curl snippet', function (done) { 13 | const result = validate('curl --request'); 14 | expect(result.result).to.equal(false); 15 | expect(result.reason).to.equal('Unable to parse: Could not identify the URL.' + 16 | ' Please use the --url option.'); 17 | expect(result.error).to.have.property('message', result.reason); 18 | done(); 19 | }); 20 | }); 21 | 22 | describe('getMetaData', function () { 23 | it('get meta data for a correct simple request', function (done) { 24 | getMetaData({ 25 | type: 'string', 26 | data: 'curl --request GET --url http://www.google.com' 27 | }, function (err, result) { 28 | expect(result.result).to.equal(true); 29 | 30 | expect(result.output.length).to.equal(1); 31 | expect(result.output[0].type).to.equal('request'); 32 | expect(result.name).to.equal('http://www.google.com'); 33 | done(); 34 | }); 35 | }); 36 | 37 | it('return false result for malformed curl snippet', function (done) { 38 | getMetaData({ 39 | type: 'string', 40 | data: 'curl --request' 41 | }, function (err, result) { 42 | expect(result.result).to.equal(false); 43 | expect(result.reason).to.equal('Unable to parse: Could not identify the URL.' + 44 | ' Please use the --url option.'); 45 | expect(result.error).to.have.property('message', result.reason); 46 | done(); 47 | }); 48 | }); 49 | 50 | it('return false result requests that do not contain a URL', function (done) { 51 | getMetaData({ 52 | type: 'string', 53 | data: 'curl --location --request POST --header "Content-Type: application/json"' 54 | }, function (err, result) { 55 | expect(result.result).to.equal(false); 56 | expect(result.reason).to.equal('Unable to parse: Could not identify the URL.' + 57 | ' Please use the --url option.'); 58 | expect(result.error).to.have.property('message', result.reason); 59 | done(); 60 | }); 61 | }); 62 | }); 63 | 64 | describe('Curl converter should', function() { 65 | 66 | it('throw an error for a malformed request', function (done) { 67 | convert({ 68 | type: 'string', 69 | data: 'curl --request' 70 | }, function (err, result) { 71 | expect(result.result).to.equal(false); 72 | expect(result.reason).to.equal('Unable to parse: Could not identify the URL.' + 73 | ' Please use the --url option.'); 74 | expect(result.error).to.have.property('message', result.reason); 75 | done(); 76 | }); 77 | }); 78 | 79 | it('return false result requests that do not contain a URL', function (done) { 80 | convert({ 81 | type: 'string', 82 | data: 'curl --location --request POST --header "Content-Type: application/json"' 83 | }, function (err, result) { 84 | expect(result.result).to.equal(false); 85 | expect(result.reason).to.equal('Unable to parse: Could not identify the URL.' + 86 | ' Please use the --url option.'); 87 | expect(result.error).to.have.property('message', result.reason); 88 | done(); 89 | }); 90 | }); 91 | 92 | it('throw an error when an invalid method is specificied', function (done) { 93 | convert({ 94 | type: 'string', 95 | data: 'curl --request INVALIDMETHOD --url http://www.google.com' 96 | }, function (err, result) { 97 | expect(result.result).to.equal(false); 98 | expect(result.reason).to.equal('The method INVALIDMETHOD is not supported.'); 99 | expect(result.error).to.have.property('message', result.reason); 100 | done(); 101 | }); 102 | }); 103 | 104 | it('throw an error for a cURL without URL defined correctly', function (done) { 105 | convert({ 106 | type: 'string', 107 | data: 'curl -X POST -H \'Content-type: application/json\' #{reply_url} --data \'#{response.to_json}\'' 108 | }, function (err, result) { 109 | expect(result.result).to.equal(false); 110 | expect(result.reason).to.equal('Unable to parse: Could not identify the URL.' + 111 | ' Please use the --url option.'); 112 | expect(result.error).to.have.property('message', result.reason); 113 | done(); 114 | }); 115 | }); 116 | 117 | it('[Github #7390]: set request URL correctly irrespective of where it is mentioned', function (done) { 118 | convert({ 119 | type: 'string', 120 | data: 'curl -i http://example.com -d "{\\"a\\": 1}"' 121 | }, function (err, result) { 122 | expect(result.result).to.equal(true); 123 | expect(result.output[0].data.url).to.equal('http://example.com'); 124 | done(); 125 | }); 126 | }); 127 | 128 | describe('[Github #2791]: escaping single and double quotes correctly', function() { 129 | it('in case where there is nothing between single and double quote', function(done) { 130 | convert({ 131 | type: 'string', 132 | // eslint-disable-next-line quotes 133 | data: `curl http://example.com -d $'{"a":"\\'\""}' -H 'Content-type: application/json'` 134 | }, function (err, result) { 135 | expect(result.result).to.equal(true); 136 | expect(result.output[0].data.body.raw).to.equal('{"a":"\'\""}'); 137 | done(); 138 | }); 139 | }); 140 | it('in case where there is something between single and double quote', function(done) { 141 | convert({ 142 | type: 'string', 143 | // eslint-disable-next-line quotes 144 | data: `curl http://example.com -d $'{"a":"\"abcd\\'"}' -H 'Content-type: application/json'` 145 | }, function (err, result) { 146 | expect(result.result).to.equal(true); 147 | expect(result.output[0].data.body.raw).to.equal('{"a":"\"abcd\'"}'); 148 | done(); 149 | }); 150 | }); 151 | }); 152 | 153 | it('not throw an error for sending GET with a request body', function (done) { 154 | convert({ 155 | type: 'string', 156 | data: 'curl -X GET -d "a=b&c=d" http://post.com' 157 | }, function (err, result) { 158 | expect(result.result).to.equal(true); 159 | done(); 160 | }); 161 | }); 162 | 163 | describe('[Github #2] - set the method to ', function() { 164 | it('GET if --get option is given in the curl command', function(done) { 165 | convert({ 166 | type: 'string', 167 | data: 'curl --get https://example.com' 168 | }, function (err, result) { 169 | expect(result.result).to.equal(true); 170 | expect(result.output[0].data.method).to.equal('GET'); 171 | done(); 172 | }); 173 | }); 174 | 175 | it('POST if only -d option is given in the curl command', function(done) { 176 | convert({ 177 | type: 'string', 178 | data: 'curl -d "key=example" https://example.com' 179 | }, function (err, result) { 180 | expect(result.result).to.equal(true); 181 | expect(result.output[0].data.method).to.equal('POST'); 182 | done(); 183 | }); 184 | }); 185 | 186 | it('HEAD if --head or -I is given in the curl command', function(done) { 187 | convert({ 188 | type: 'string', 189 | data: 'curl --head https://example.com' 190 | }, function (err, result) { 191 | expect(result.result).to.equal(true); 192 | expect(result.output[0].data.method).to.equal('HEAD'); 193 | done(); 194 | }); 195 | }); 196 | 197 | it('PUT if -T or --upload-file is given in the curl command', function(done) { 198 | convert({ 199 | type: 'string', 200 | data: 'curl --upload-file "./example.txt" https://example.com' 201 | }, function (err, result) { 202 | expect(result.result).to.equal(true); 203 | expect(result.output[0].data.method).to.equal('PUT'); 204 | done(); 205 | }); 206 | }); 207 | }); 208 | it('[Github: #1]: not throw an error for having $ before method', function (done) { 209 | convert({ 210 | type: 'string', 211 | data: 'curl -X $\'POST\' \'https://example.com.br/login.html\'' 212 | }, function (err, result) { 213 | expect(result.result).to.equal(true); 214 | expect(result.output[0].data.method).to.equal('POST'); 215 | done(); 216 | }); 217 | }); 218 | 219 | it('convert a correct simple request', function (done) { 220 | convert({ 221 | type: 'string', 222 | data: 'curl --request GET --url http://www.google.com' 223 | }, function (err, result) { 224 | expect(result.result).to.equal(true); 225 | 226 | expect(result.output.length).to.equal(1); 227 | expect(result.output[0].type).to.equal('request'); 228 | 229 | var request = result.output[0].data; 230 | expect(request.method).to.equal('GET'); 231 | expect(request.url).to.equal('http://www.google.com'); 232 | done(); 233 | }); 234 | }); 235 | 236 | it('convert a simple request with comment at the end correctly', function (done) { 237 | convert({ 238 | type: 'string', 239 | data: 'curl --request GET --url http://www.google.com #comment1' 240 | }, function (err, result) { 241 | expect(result.result).to.equal(true); 242 | 243 | expect(result.output.length).to.equal(1); 244 | expect(result.output[0].type).to.equal('request'); 245 | 246 | var request = result.output[0].data; 247 | expect(request.method).to.equal('GET'); 248 | expect(request.url).to.equal('http://www.google.com'); 249 | done(); 250 | }); 251 | }); 252 | 253 | it('convert a simple GET request', function (done) { 254 | var result = Converter.convertCurlToRequest('curl --request GET --url http://www.google.com'); 255 | expect(result.method).to.equal('GET'); 256 | expect(result.url).to.equal('http://www.google.com'); 257 | done(); 258 | }); 259 | 260 | it('throw an error if POST body option is given with HEAD without --get/-G', function(done) { 261 | convert({ 262 | type: 'string', 263 | data: 'curl -I http://example.com -d "a=b"' 264 | }, function (err, result) { 265 | expect(result.result).to.equal(false); 266 | expect(result.reason).to.equal('Unable to parse: Both (--head/-I) and' + 267 | ' (-d/--data/--data-raw/--data-binary/--data-ascii/--data-urlencode) are not supported.'); 268 | expect(result.error).to.have.property('message', result.reason); 269 | done(); 270 | }); 271 | }); 272 | 273 | it('convert a simple GET request w/o the --url param', function (done) { 274 | var result = Converter.convertCurlToRequest('curl --request GET http://www.google.com'); 275 | expect(result.method).to.equal('GET'); 276 | expect(result.url).to.equal('http://www.google.com'); 277 | done(); 278 | }); 279 | 280 | it('append the data params with & if query params are already present in the url', function (done) { 281 | convert({ 282 | type: 'string', 283 | data: 'curl -d "a=b" --get http://post.com?name=example' 284 | }, function (err, result) { 285 | expect(result.result).to.equal(true); 286 | expect(result.output[0].data.url).to.equal('http://post.com?name=example&a=b'); 287 | done(); 288 | }); 289 | }); 290 | 291 | it('convert a simple GET request w/ headers', function (done) { 292 | var result = Converter.convertCurlToRequest('curl --request GET http://www.google.com -H ' + 293 | '"h1:v1" -H "h2:v2" -H "h3;" -H "h4" -H "h1:v11"'), 294 | h1Header = _.find(result.header, function (header) { return header.key === 'h1'; }), 295 | h2Header = _.find(result.header, function (header) { return header.key === 'h2'; }), 296 | h3Header = _.find(result.header, function (header) { return header.key === 'h3'; }), 297 | h4Header = _.find(result.header, function (header) { return header.key === 'h4'; }); 298 | 299 | expect(h1Header.value).to.equal('v1'); 300 | expect(h2Header.value).to.equal('v2'); 301 | expect(h3Header.value).to.equal(''); 302 | expect(h4Header).to.be(undefined); 303 | 304 | done(); 305 | }); 306 | 307 | it('convert a simple GET request w/ user-agent', function (done) { 308 | var result = Converter.convertCurlToRequest('curl --request GET http://www.google.com --user-agent mosaic'), 309 | uaHeader = _.find(result.header, function (header) { return header.key === 'User-Agent'; }); 310 | expect(uaHeader.value).to.equal('mosaic'); 311 | 312 | 313 | // should clear user-agent this time 314 | result = Converter.convertCurlToRequest('curl --request GET http://www.google.com'); 315 | 316 | uaHeader = _.find(result.header, function (header) { return header.key === 'User-Agent'; }); 317 | expect(uaHeader).to.be(undefined); 318 | 319 | done(); 320 | }); 321 | 322 | describe('auth', function () { 323 | it('convert a simple GET request with Basic auth', function () { 324 | const result = Converter.convertCurlToRequest('curl -u testUser:testPass --url "http://postman-echo.com/get"'); 325 | 326 | expect(result.auth.type).to.equal('basic'); 327 | expect(result.auth.basic[0].key).to.equal('username'); 328 | expect(result.auth.basic[0].value).to.equal('testUser'); 329 | expect(result.auth.basic[1].key).to.equal('password'); 330 | expect(result.auth.basic[1].value).to.equal('testPass'); 331 | }); 332 | 333 | it('convert a simple GET request with Digest auth', function () { 334 | const result = Converter.convertCurlToRequest('curl -u testUser:testPass --digest "http://postman-echo.com/get"'); 335 | 336 | expect(result.auth.type).to.equal('digest'); 337 | expect(result.auth.digest[0].key).to.equal('username'); 338 | expect(result.auth.digest[0].value).to.equal('testUser'); 339 | expect(result.auth.digest[1].key).to.equal('password'); 340 | expect(result.auth.digest[1].value).to.equal('testPass'); 341 | }); 342 | 343 | it('convert a simple GET request with NTLM auth', function () { 344 | const result = Converter.convertCurlToRequest('curl -u testUser:testPass --ntlm "http://postman-echo.com/get"'); 345 | 346 | expect(result.auth.type).to.equal('ntlm'); 347 | expect(result.auth.ntlm[0].key).to.equal('username'); 348 | expect(result.auth.ntlm[0].value).to.equal('testUser'); 349 | expect(result.auth.ntlm[1].key).to.equal('password'); 350 | expect(result.auth.ntlm[1].value).to.equal('testPass'); 351 | }); 352 | 353 | it('convert a simple GET request with Basic auth with only username', function () { 354 | const result = Converter.convertCurlToRequest('curl -u testUser --url "http://postman-echo.com/get"'); 355 | 356 | expect(result.auth.type).to.equal('basic'); 357 | expect(result.auth.basic[0].key).to.equal('username'); 358 | expect(result.auth.basic[0].value).to.equal('testUser'); 359 | expect(result.auth.basic[1].key).to.equal('password'); 360 | expect(result.auth.basic[1].value).to.equal(''); 361 | }); 362 | }); 363 | 364 | it('convert a request with a forced POST', function (done) { 365 | var result = Converter.convertCurlToRequest('curl -X POST --get --url http://www.google.com -d ' + 366 | '"username=postman&password=newman&randomKey"'); 367 | 368 | // even with --get 369 | expect(result.method).to.equal('POST'); 370 | expect(result.url).to.equal('http://www.google.com?username=postman&password=newman&randomKey'); 371 | 372 | done(); 373 | }); 374 | 375 | it('convert a simple POST request', function (done) { 376 | var result = Converter.convertCurlToRequest('curl --request POST --url http://www.google.com'); 377 | expect(result.method).to.equal('POST'); 378 | expect(result.url).to.equal('http://www.google.com'); 379 | 380 | done(); 381 | }); 382 | 383 | it('convert a simple POST request with -X', function (done) { 384 | var result = Converter.convertCurlToRequest('curl -X POST --url http://www.google.com'); 385 | expect(result.method).to.equal('POST'); 386 | expect(result.url).to.equal('http://www.google.com'); 387 | 388 | done(); 389 | }); 390 | 391 | it('convert a simple POST request with formdata', function (done) { 392 | var result = Converter.convertCurlToRequest('curl --request POST --url http://google.com -F ' + 393 | '"username=postman" -F "password=newman"'), 394 | usernameRow, passwordRow; 395 | 396 | expect(result.body.mode).to.equal('formdata'); 397 | 398 | usernameRow = _.find(result.body.formdata, function (row) { return row.key === 'username'; }); 399 | passwordRow = _.find(result.body.formdata, function (row) { return row.key === 'password'; }); 400 | 401 | expect(usernameRow.value).to.equal('postman'); 402 | expect(usernameRow.type).to.equal('text'); 403 | 404 | expect(passwordRow.value).to.equal('newman'); 405 | expect(passwordRow.type).to.equal('text'); 406 | 407 | done(); 408 | }); 409 | 410 | it('convert a simple POST request with x-www-form-urlencoded data', function (done) { 411 | var result = Converter.convertCurlToRequest('curl --request POST --url http://google.com ' + 412 | '-d "username=postman&password=newman&randomKey"'), 413 | usernameRow, passwordRow, randomKeyRow; 414 | expect(result.body.mode).to.equal('urlencoded'); 415 | 416 | usernameRow = _.find(result.body.urlencoded, function (row) { return row.key === 'username'; }); 417 | passwordRow = _.find(result.body.urlencoded, function (row) { return row.key === 'password'; }); 418 | randomKeyRow = _.find(result.body.urlencoded, function (row) { return row.key === 'randomKey'; }); 419 | 420 | expect(usernameRow.value).to.equal('postman'); 421 | expect(usernameRow.type).to.equal('text'); 422 | 423 | expect(passwordRow.value).to.equal('newman'); 424 | expect(passwordRow.type).to.equal('text'); 425 | 426 | expect(randomKeyRow.value).to.equal(''); 427 | expect(randomKeyRow.type).to.equal('text'); 428 | 429 | done(); 430 | }); 431 | 432 | it('convert a simple request with a arg-less option before the URL', function (done) { 433 | var result = Converter.convertCurlToRequest('curl --compressed \'http://www.google.com\''); 434 | expect(result.method).to.equal('GET'); 435 | expect(result.url).to.equal('http://www.google.com'); 436 | 437 | done(); 438 | }); 439 | 440 | it('convert a simple request with a arg-less option after the URL (Github #4770)', function (done) { 441 | var result = Converter.convertCurlToRequest('curl -X POST http://www.google.com --compressed'); 442 | expect(result.method).to.equal('POST'); 443 | expect(result.url).to.equal('http://www.google.com'); 444 | 445 | done(); 446 | }); 447 | 448 | it('convert a simple request with a arg-less option after the URL', function (done) { 449 | var result = Converter.convertCurlToRequest('curl -XPOST http://www.google.com --compressed'); 450 | expect(result.method).to.equal('POST'); 451 | expect(result.url).to.equal('http://www.google.com'); 452 | 453 | done(); 454 | }); 455 | 456 | it('urldecode urlencoded data params while importing (Github #3623)', function (done) { 457 | var result = Converter.convertCurlToRequest('curl --url testUrl -X POST -H \'content-type:' + 458 | 'application/x-www-form-urlencoded\' -d \'config_type=v1%3Av2%3Av3\''), 459 | row; 460 | 461 | expect(result.body.mode).to.equal('urlencoded'); 462 | 463 | row = _.find(result.body.urlencoded, function (row) { return row.key === 'config_type'; }); 464 | expect(row.value).to.equal('v1:v2:v3'); // and not v1%3Av2%3Av3 465 | 466 | done(); 467 | }); 468 | 469 | it('support -XPOST type method specifiers (Github #3135)', function (done) { 470 | var result = Converter.convertCurlToRequest('curl --url testUrl -XPOST'); 471 | expect(result.method).to.equal('POST'); 472 | expect(result.url).to.equal('testUrl'); 473 | 474 | result = Converter.convertCurlToRequest('curl testUrl -XPOST'); 475 | expect(result.method).to.equal('POST'); 476 | expect(result.url).to.equal('testUrl'); 477 | 478 | done(); 479 | }); 480 | 481 | it('Github #2791', function (done) { 482 | // no idea how to solve this yet 483 | // eslint-disable-next-line no-implicit-globals 484 | str = 'curl -XPOST \'http://httpbin.org/post\' --data-binary $\'{"SearchTerms":' + 485 | '[{"termValue":"`~!@#$%^&*()-+=<.,./?;:\'\\"[{]}\\\\|","termOption"\:2}]}\''; 486 | Converter.convertCurlToRequest(str); 487 | done(); 488 | }); 489 | 490 | it('Empty data strings should work (Github #4018)', function (done) { 491 | let result1 = Converter.convertCurlToRequest('curl http://getpostman.com/ --data \"\"'), 492 | result2 = Converter.convertCurlToRequest('curl http://getpostman.com/ --data \'\''); 493 | expect(result1.body).to.be.empty(); 494 | expect(result2.body).to.be.empty(); 495 | 496 | done(); 497 | }); 498 | 499 | it('Should not try to resolve env vars in the curl input', function (done) { 500 | var result = Converter.convertCurlToRequest('curl --location --request POST ' + 501 | '"https://sample.com" --header "h1: $v1"'), 502 | header = _.find(result.header, function (header) { return header.key === 'h1'; }); 503 | expect(header.value).to.equal('$v1'); 504 | done(); 505 | }); 506 | 507 | it('[GitHub #8126] [GitHub #7983] [GitHub #7895]: should import body data with --data-raw argument', function (done) { 508 | 509 | // content-type application/json, so mode = raw 510 | var result = Converter.convertCurlToRequest(`curl --location --request POST "https://sample.com" 511 | --header "Content-Type: application/json" 512 | --data-raw '{ "sampleKey": "sampleValue" }'`), 513 | rawBody = { 514 | sampleKey: 'sampleValue' 515 | }; 516 | expect(result.body).to.have.property('mode', 'raw'); 517 | expect(JSON.parse(result.body.raw)).to.eql(rawBody); 518 | 519 | // no content-type, so mode = appliation/x-www-form-urlencoded 520 | result = Converter.convertCurlToRequest(`curl --location --request POST 'https://postman-echo.com/post' 521 | --data-raw 'raw body'`); 522 | expect(result.body).to.have.property('mode', 'urlencoded'); 523 | expect(result.body.urlencoded[0]).to.eql({ 524 | key: 'raw body', 525 | value: '', 526 | type: 'text' 527 | }); 528 | done(); 529 | }); 530 | 531 | it('should import body data with --data-raw argument containing "+"', function (done) { 532 | var result = Converter.convertCurlToRequest(`curl --location --request POST "https://sample.com" 533 | --header "Content-Type: application/x-www-form-urlencoded" 534 | --data-raw 'a=hello+world&key+with+space=hello%20world'`); 535 | 536 | expect(result.body).to.have.property('mode', 'urlencoded'); 537 | expect(result.body.urlencoded[0]).to.eql({ 538 | key: 'a', 539 | value: 'hello world', 540 | type: 'text' 541 | }); 542 | expect(result.body.urlencoded[1]).to.eql({ 543 | key: 'key with space', 544 | value: 'hello world', 545 | type: 'text' 546 | }); 547 | 548 | done(); 549 | }); 550 | 551 | it('[GitHub #7806]: should parse -X method correctly', function (done) { 552 | var result = Converter.convertCurlToRequest('curl -H "X-XSRF-Token: token_value" https://domain.com'); 553 | expect(result.method).to.eql('GET'); 554 | expect(result.header).to.eql([ 555 | { 556 | key: 'X-XSRF-Token', 557 | value: 'token_value' 558 | } 559 | ]); 560 | 561 | // should not tamper -X present elsewhere than method name 562 | result = Converter.convertCurlToRequest(`curl -XPUT -H "accept:application/-Xjson" 563 | https://domain-Xvalue.com --data-raw "a=-Xb"`); 564 | expect(result.method).to.equal('PUT'); 565 | expect(result.url).to.equal('https://domain-Xvalue.com'); 566 | expect(result.header).to.eql([ 567 | { 568 | key: 'accept', 569 | value: 'application/-Xjson' 570 | } 571 | ]); 572 | expect(result.body).to.eql({ 573 | mode: 'urlencoded', 574 | urlencoded: [ 575 | { 576 | key: 'a', 577 | value: '-Xb', 578 | type: 'text' 579 | } 580 | ] 581 | }); 582 | 583 | // check for various positions of -XMETHOD 584 | result = Converter.convertCurlToRequest('curl -XPUT https://domain.com'); 585 | expect(result.method).to.equal('PUT'); 586 | result = Converter.convertCurlToRequest('curl https://domain.com -XPUT --data “d“'); 587 | expect(result.method).to.equal('PUT'); 588 | result = Converter.convertCurlToRequest('curl https://domain.com --data “d“ -XPUT -H ”a:b”'); 589 | expect(result.method).to.equal('PUT'); 590 | result = Converter.convertCurlToRequest('curl https://domain.com --data “d“ -H ”a:b” -XPUT'); 591 | expect(result.method).to.equal('PUT'); 592 | 593 | // more than one -XMETHOD, last one gets the preference 594 | result = Converter.convertCurlToRequest('curl -XGET -XPUT https://domain.com'); 595 | expect(result.method).to.equal('PUT'); 596 | done(); 597 | }); 598 | 599 | it('[GitHub #8292]: should import body with --data-urlencode argument', function (done) { 600 | var result = Converter.convertCurlToRequest(`curl --location --request POST 'https://httpbin.org/post' 601 | --header 'accept: application/json' 602 | --header 'Content-Type: application/x-www-form-urlencoded' 603 | --data-urlencode 'test=test'`); 604 | expect(result.body).to.have.property('mode', 'urlencoded'); 605 | expect(result.body.urlencoded[0]).to.eql({ 606 | key: 'test', 607 | value: 'test', 608 | type: 'text' 609 | }); 610 | done(); 611 | }); 612 | 613 | it('[GitHub #8505] [GitHub #8953]: should correctly handle unicode characters present in data', function (done) { 614 | var result = Converter.convertCurlToRequest(`curl 'http://localhost:4000/graphql' \\ 615 | --data-binary $'[{"operationName":"someMutation","variables":{"aRequiredVar":"foo\\x78bar\\u{1064A9}"},"query":` + 616 | '"mutation someMutation($aRequiredVar: String\\u0021) {\\\\n mutateSomething(aRequiredVar: $aRequiredVar) ' + 617 | `{\\\\n message\\\\n __typename\\\\n }\\\\n}\\\\n"}]' \\ 618 | --compressed`); 619 | 620 | expect(result.body).to.have.property('mode', 'raw'); 621 | expect(result.body.raw).to.eql('[{\"operationName\":\"someMutation\",\"variables\":{\"aRequiredVar\":' + 622 | '\"fooxbar撩\"},\"query\":\"mutation someMutation($aRequiredVar: String!) {\\n mutateSomething(aRequiredVar: ' + 623 | '$aRequiredVar) {\\n message\\n __typename\\n }\\n}\\n\"}]'); 624 | done(); 625 | }); 626 | 627 | it('[GitHub #9391] [GitHub #10090]: should correctly handle escaped newlines present in data', function (done) { 628 | var result = Converter.convertCurlToRequest(`curl 'https://api.secretdomain.com/v3/login.awp' \\ 629 | -H 'authority: api.secretdomain.com' \\ 630 | -H 'accept: application/json, text/plain, */*' \\ 631 | -H 'content-type: application/x-www-form-urlencoded' \\ 632 | -H 'origin: https://www.secretdomain.com' \\ 633 | --data-raw $'data={\n "username": "someValue",\n "password": "somethingSecret",\n "token": "secret-token"\n}'\\ 634 | --compressed`); 635 | 636 | expect(result.body).to.have.property('mode', 'urlencoded'); 637 | expect(result.body.urlencoded[0].value).to.eql('{\n "username": "someValue",\n "password": "somethingSecret"' + 638 | ',\n "token": "secret-token"\n}'); 639 | expect(JSON.parse(result.body.urlencoded[0].value)).to.be.an.object; 640 | done(); 641 | }); 642 | 643 | it('[GitHub #10090]: should correctly handle escaped newlines present in data', function (done) { 644 | var result = Converter.convertCurlToRequest(`curl 'http://host' \\ 645 | --data-binary $'{\n "foo": "bar"\n}'`); 646 | 647 | expect(result.body).to.have.property('mode', 'raw'); 648 | expect(result.body.raw).to.eql('{\n "foo": "bar"\n}'); 649 | expect(JSON.parse(result.body.raw)).to.be.an.object; 650 | done(); 651 | }); 652 | 653 | it('[GitHub #4772]: should correctly handle escaped newlines present in urlencoded data', function (done) { 654 | var result = Converter.convertCurlToRequest(`curl 'https://api.secretdomain.com/v3/login.awp' --data-raw \\ 655 | $'data={\n "username": "someValue",\n "password": "somethingSecret",\n "token": "secret-token"\n}' \\ 656 | --compressed`); 657 | 658 | expect(result.body).to.have.property('mode', 'urlencoded'); 659 | expect(result.body.urlencoded.length).to.eql(1); 660 | expect(result.body.urlencoded[0].key).to.eql('data'); 661 | expect(result.body.urlencoded[0].value).to.eql('{\n \"username\": \"someValue\",\n \"password\": ' + 662 | '\"somethingSecret\",\n \"token\": \"secret-token\"\n}'); 663 | done(); 664 | }); 665 | 666 | it('[GitHub #7895]: should correctly handle raw form data with boundry separated body', function (done) { 667 | var result = Converter.convertCurlToRequest(`curl 'https://httpbin.org/post' 668 | -H 'Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7oJTsSWYoA2LdaPx' --data $'` + 669 | '------WebKitFormBoundary7oJTsSWYoA2LdaPx\r\nContent-Disposition: form-data; name="source"\r\n\r\ns\r\n' + 670 | '------WebKitFormBoundary7oJTsSWYoA2LdaPx\r\nContent-Disposition: form-data; name="files"; ' + 671 | 'filename="index.js"\r\n\r\nt\r\n------WebKitFormBoundary7oJTsSWYoA2LdaPx--\r\n\' --compressed'); 672 | 673 | expect(result.body).to.have.property('mode', 'formdata'); 674 | expect(result.body.formdata[0]).to.eql({ 675 | key: 'source', 676 | value: 's', 677 | type: 'text' 678 | }); 679 | expect(result.body.formdata[1]).to.eql({ 680 | key: 'files', 681 | value: 'index.js', 682 | type: 'file' 683 | }); 684 | done(); 685 | }); 686 | 687 | it('[GitHub #10068]: should correctly handle raw form data with boundry separated body', function (done) { 688 | var result = Converter.convertCurlToRequest(`curl --location --request POST 'https://httpbin.org/post' \\ 689 | --header 'Content-Type: multipart/form-data' \\ 690 | --form 'name="value"' \\ 691 | --form 'request="{\"hello\":\"world\"}"'`); 692 | 693 | expect(result.body).to.have.property('mode', 'formdata'); 694 | expect(result.body.formdata.length).to.eql(2); 695 | expect(result.body.formdata[0].key).to.eql('name'); 696 | expect(result.body.formdata[0].value).to.eql('value'); 697 | expect(result.body.formdata[1].key).to.eql('request'); 698 | expect(result.body.formdata[1].value).to.eql('{\"hello\":\"world\"}'); 699 | done(); 700 | }); 701 | 702 | it('[GitHub #5299]: should correctly import file references for formdata', function(done) { 703 | var result = Converter.convertCurlToRequest('curl -F "content=@/Users/John/file.txt" google.com'); 704 | expect(result.body).to.have.property('mode', 'formdata'); 705 | expect(result.body.formdata[0]).to.eql({ 706 | key: 'content', 707 | value: '/Users/John/file.txt', 708 | type: 'file' 709 | }); 710 | done(); 711 | }); 712 | 713 | it('[GitHub #8506]: should correctly add content type field for formdata', function(done) { 714 | var result = Converter.convertCurlToRequest(`curl --location --request POST 'https://httpbin.org/post' \\ 715 | --header 'Content-Type: multipart/form-data' \\ 716 | --form 'request={ "title": "My template" };type=application/json' \\ 717 | --form 'contentFile=@/tmp/archive.zip;type=application/octet-stream'`); 718 | 719 | expect(result.body).to.have.property('mode', 'formdata'); 720 | expect(result.body.formdata[0]).to.eql({ 721 | key: 'request', 722 | value: '{ "title": "My template" }', 723 | contentType: 'application/json', 724 | type: 'text' 725 | }); 726 | expect(result.body.formdata[1]).to.eql({ 727 | key: 'contentFile', 728 | value: '/tmp/archive.zip', 729 | contentType: 'application/octet-stream', 730 | type: 'file' 731 | }); 732 | done(); 733 | }); 734 | 735 | it('[Github #9941]: should correctly identify graphql queries', function(done) { 736 | var result = Converter.convertCurlToRequest(`curl -L 'https://countries.trevorblades.com' \\ 737 | -H 'Content-Type: application/json' \\ 738 | -d '{"query":"{\r\n countries {\r\n code\r\n name\r\n emoji\r\n }\r\n}","variables":{}}'`); 739 | 740 | expect(result.body).to.have.property('mode', 'graphql'); 741 | expect(result.body.graphql.query).to.eql('{\r\n countries {\r\n code\r\n name\r\n emoji\r\n }\r\n}'); 742 | expect(result.body.graphql.variables).to.eql(''); 743 | done(); 744 | }); 745 | 746 | it('[Github #12349]: should correctly convert graphql queries without operationName', function(done) { 747 | var result = Converter.convertCurlToRequest(`curl --location 'https://spacex-production.up.railway.app' \\ 748 | --header 'Content-Type: application/json' \\ 749 | --data '{"query":"query getCompanyData {\r\n company {\r\n ceo\r\n }\r\n}","variables":{}}'`); 750 | 751 | expect(result.body).to.have.property('mode', 'graphql'); 752 | expect(result.body.graphql.query).to.eql('query getCompanyData {\r\n company {\r\n ceo\r\n }\r\n}'); 753 | expect(result.body.graphql.variables).to.eql(''); 754 | expect(result.body.graphql).to.not.have.property('operationName'); 755 | done(); 756 | }); 757 | 758 | it('should convert a POST request and assign json language using content-type header', function(done) { 759 | var result = Converter.convertCurlToRequest(`curl -X POST -H "Content-Type: application/json" \\ 760 | -d \'{"key":"value"}\' https://www.example.com`); 761 | 762 | expect(result.body).to.eql({ 763 | mode: 'raw', 764 | raw: '{"key":"value"}', 765 | options: { raw: { language: 'json' } } 766 | }); 767 | 768 | done(); 769 | }); 770 | 771 | it('should convert a POST request and assign xml language using content-type header', function(done) { 772 | var result = Converter.convertCurlToRequest(`curl -X POST -H "Content-Type: application/xml" \\ 773 | -d \'value\' https://www.example.com`); 774 | 775 | expect(result.body).to.eql({ 776 | mode: 'raw', 777 | raw: 'value', 778 | options: { raw: { language: 'xml' } } 779 | }); 780 | 781 | done(); 782 | }); 783 | 784 | describe('[Github #8843]: It should recognize non-apostrophed ("...") url with multi-param', function() { 785 | it('in case where there is multiple params with & in between in the url (https)', function(done) { 786 | convert({ 787 | type: 'string', 788 | data: `curl -X GET \\ 789 | -H "User-Agent: Dalvik/2.1.0 (SM-A705FN Build/QP1A.190711.020)" \\ 790 | -H "Authorization: bearer XXX" \\ 791 | -H "Build: 4.6.4.459" \\ 792 | -H "Platform: Android" \\ 793 | -H "Accept-Language: tr-TR" \\ 794 | -H "Content-Type: application/json" \\ 795 | https://test.com/test/foo?bar=1&baz=2` 796 | }, function (err, result) { 797 | expect(result.result).to.equal(true); 798 | expect(result.output.length).to.equal(1); 799 | expect(result.output[0].type).to.equal('request'); 800 | const headerArr = result.output[0].data.header; 801 | expect(headerArr[0].key).to.equal('User-Agent'); 802 | expect(headerArr[0].value).to.equal('Dalvik/2.1.0 (SM-A705FN Build/QP1A.190711.020)'); 803 | expect(headerArr[headerArr.length - 1].key).to.equal('Content-Type'); 804 | expect(headerArr[headerArr.length - 1].value).to.equal('application/json'); 805 | expect(result.output[0].data.url).to.equal('https://test.com/test/foo?bar=1&baz=2'); 806 | done(); 807 | }); 808 | }); 809 | it('in case where there is multiple params with & in between in the url in apostrophes (https)', function(done) { 810 | convert({ 811 | type: 'string', 812 | data: `curl -X GET \\ 813 | -H "User-Agent: Dalvik/2.1.0 (SM-A705FN Build/QP1A.190711.020)" \\ 814 | -H "Authorization: bearer XXX" \\ 815 | -H "Build: 4.6.4.459" \\ 816 | -H "Platform: Android" \\ 817 | -H "Accept-Language: tr-TR" \\ 818 | -H "Content-Type: application/json" \\ 819 | "https://test.com/test/foo?bar=1&baz=2"` 820 | }, function (err, result) { 821 | expect(result.result).to.equal(true); 822 | expect(result.output.length).to.equal(1); 823 | expect(result.output[0].type).to.equal('request'); 824 | const headerArr = result.output[0].data.header; 825 | expect(headerArr[0].key).to.equal('User-Agent'); 826 | expect(headerArr[0].value).to.equal('Dalvik/2.1.0 (SM-A705FN Build/QP1A.190711.020)'); 827 | expect(headerArr[headerArr.length - 1].key).to.equal('Content-Type'); 828 | expect(headerArr[headerArr.length - 1].value).to.equal('application/json'); 829 | expect(result.output[0].data.url).to.equal('https://test.com/test/foo?bar=1&baz=2'); 830 | done(); 831 | }); 832 | }); 833 | it('in case where there is multiple params with & in between in the url (http)', function(done) { 834 | convert({ 835 | type: 'string', 836 | data: `curl -X GET \\ 837 | -H "User-Agent: Dalvik/2.1.0 (Linux; U; Android 10; SM-A705FN Build/QP1A.190711.020) Test/4.6.4.459" \\ 838 | -H "Authorization: bearer XXX" \\ 839 | -H "Build: 4.6.4.459" \\ 840 | -H "Platform: Android" \\ 841 | -H "Accept-Language: tr-TR" \\ 842 | -H "Content-Type: application/json" \\ 843 | http://test.com/test/foo?bar=1&baz=2` 844 | }, function (err, result) { 845 | expect(result.result).to.equal(true); 846 | expect(result.output.length).to.equal(1); 847 | expect(result.output[0].type).to.equal('request'); 848 | expect(result.output[0].data.url).to.equal('http://test.com/test/foo?bar=1&baz=2'); 849 | const headerArr = result.output[0].data.header; 850 | expect(headerArr[headerArr.length - 1].key).to.equal('Content-Type'); 851 | expect(headerArr[headerArr.length - 1].value).to.equal('application/json'); 852 | done(); 853 | }); 854 | }); 855 | it('in case where there is multiple params (3-4) with & in between in the url (https)', function(done) { 856 | convert({ 857 | type: 'string', 858 | data: `curl -X GET \\ 859 | -H "User-Agent: Dalvik/2.1.0 (Linux; U; Android 10; SM-A705FN Build/QP1A.190711.020) Test/4.6.4.459" \\ 860 | -H "Authorization: bearer XXX" \\ 861 | -H "Build: 4.6.4.459" \\ 862 | -H "Platform: Android" \\ 863 | -H "Accept-Language: tr-TR" \\ 864 | -H "Content-Type: application/json" \\ 865 | https://test.com/test/foo?bar=1&baz=2&bax=3` 866 | }, function (err, result) { 867 | expect(result.result).to.equal(true); 868 | expect(result.output.length).to.equal(1); 869 | expect(result.output[0].type).to.equal('request'); 870 | expect(result.output[0].data.url).to.equal('https://test.com/test/foo?bar=1&baz=2&bax=3'); 871 | const headerArr = result.output[0].data.header; 872 | expect(headerArr[headerArr.length - 1].key).to.equal('Content-Type'); 873 | expect(headerArr[headerArr.length - 1].value).to.equal('application/json'); 874 | // done(); 875 | }); 876 | convert({ 877 | type: 'string', 878 | data: `curl -X GET \\ 879 | -H "User-Agent: Dalvik/2.1.0 (Linux; U; Android 10; SM-A705FN Build/QP1A.190711.020) Test/4.6.4.459" \\ 880 | -H "Authorization: bearer XXX" \\ 881 | -H "Build: 4.6.4.459" \\ 882 | -H "Platform: Android" \\ 883 | -H "Accept-Language: tr-TR" \\ 884 | -H "Content-Type: application/json" \\ 885 | https://test.com/test/foo?bar=1&baz=2&bax=3&bay=4` 886 | }, function (err, result) { 887 | expect(result.result).to.equal(true); 888 | expect(result.output.length).to.equal(1); 889 | expect(result.output[0].type).to.equal('request'); 890 | expect(result.output[0].data.url).to.equal('https://test.com/test/foo?bar=1&baz=2&bax=3&bay=4'); 891 | const headerArr = result.output[0].data.header; 892 | expect(headerArr[headerArr.length - 1].key).to.equal('Content-Type'); 893 | expect(headerArr[headerArr.length - 1].value).to.equal('application/json'); 894 | done(); 895 | }); 896 | }); 897 | it('in case where there is multiple params with & in between in the url in apostrophes (http)', function(done) { 898 | convert({ 899 | type: 'string', 900 | data: `curl -X GET \\ 901 | -H "User-Agent: Dalvik/2.1.0 (Linux; U; Android 10; SM-A705FN Build/QP1A.190711.020) Test/4.6.4.459" \\ 902 | -H "Authorization: bearer XXX" \\ 903 | -H "Build: 4.6.4.459" \\ 904 | -H "Platform: Android" \\ 905 | -H "Accept-Language: tr-TR" \\ 906 | -H "Content-Type: application/json" \\ 907 | "http://test.com/test/foo?bar=1&baz=2"` 908 | }, function (err, result) { 909 | expect(result.result).to.equal(true); 910 | expect(result.output.length).to.equal(1); 911 | expect(result.output[0].type).to.equal('request'); 912 | expect(result.output[0].data.url).to.equal('http://test.com/test/foo?bar=1&baz=2'); 913 | const headerArr = result.output[0].data.header; 914 | expect(headerArr[headerArr.length - 1].key).to.equal('Content-Type'); 915 | expect(headerArr[headerArr.length - 1].value).to.equal('application/json'); 916 | done(); 917 | }); 918 | }); 919 | it('in case where there is multiple params with & in between in the url (www)', function(done) { 920 | convert({ 921 | type: 'string', 922 | data: `curl -X GET \\ 923 | -H "User-Agent: Dalvik/2.1.0 (Linux; U; Android 10; SM-A705FN Build/QP1A.190711.020) Test/4.6.4.459" \\ 924 | -H "Authorization: bearer XXX" \\ 925 | -H "Content-Type: application/json" \\ 926 | "www.test.com/test/foo?bar=1&baz=2"` 927 | }, function (err, result) { 928 | expect(result.result).to.equal(true); 929 | expect(result.output.length).to.equal(1); 930 | expect(result.output[0].type).to.equal('request'); 931 | expect(result.output[0].data.url).to.equal('www.test.com/test/foo?bar=1&baz=2'); 932 | const headerArr = result.output[0].data.header; 933 | expect(headerArr[headerArr.length - 1].key).to.equal('Content-Type'); 934 | expect(headerArr[headerArr.length - 1].value).to.equal('application/json'); 935 | done(); 936 | }); 937 | }); 938 | it('in case where there is multiple params with & in between in the url (without www)', function(done) { 939 | convert({ 940 | type: 'string', 941 | data: `curl -X GET \\ 942 | -H "User-Agent: Dalvik/2.1.0 (SM-A705FN Build/QP1A.190711.020)" \\ 943 | -H "Authorization: bearer XXX" \\ 944 | -H "Content-Type: application/json" \\ 945 | "test.com/test/foo?bar=1&baz=2"` 946 | }, function (err, result) { 947 | expect(result.result).to.equal(true); 948 | expect(result.output.length).to.equal(1); 949 | expect(result.output[0].type).to.equal('request'); 950 | const headerArr = result.output[0].data.header; 951 | expect(headerArr[0].key).to.equal('User-Agent'); 952 | expect(headerArr[0].value).to.equal('Dalvik/2.1.0 (SM-A705FN Build/QP1A.190711.020)'); 953 | expect(headerArr[headerArr.length - 1].key).to.equal('Content-Type'); 954 | expect(headerArr[headerArr.length - 1].value).to.equal('application/json'); 955 | expect(result.output[0].data.url).to.equal('test.com/test/foo?bar=1&baz=2'); 956 | done(); 957 | }); 958 | }); 959 | it('in case where there is multiple params with & in between in the url (direct param)', function(done) { 960 | convert({ 961 | type: 'string', 962 | data: `curl -X GET \\ 963 | -H "User-Agent: Dalvik/2.1.0 (Linux; U; Android 10; SM-A705FN Build/QP1A.190711.020) Test/4.6.4.459" \\ 964 | -H "Authorization: bearer XXX" \\ 965 | -H "Content-Type: application/json" \\ 966 | "test.com?bar=1&baz=2"` 967 | }, function (err, result) { 968 | expect(result.result).to.equal(true); 969 | expect(result.output.length).to.equal(1); 970 | expect(result.output[0].type).to.equal('request'); 971 | expect(result.output[0].data.url).to.equal('test.com?bar=1&baz=2'); 972 | const headerArr = result.output[0].data.header; 973 | expect(headerArr[headerArr.length - 1].key).to.equal('Content-Type'); 974 | expect(headerArr[headerArr.length - 1].value).to.equal('application/json'); 975 | done(); 976 | }); 977 | }); 978 | it('in case where there is multiple params with & in between in the url (param after route)', function(done) { 979 | convert({ 980 | type: 'string', 981 | data: `curl -X GET \\ 982 | -H "User-Agent: Dalvik/2.1.0 (Linux; U; Android 10; SM-A705FN Build/QP1A.190711.020) Test/4.6.4.459" \\ 983 | -H "Authorization: bearer XXX" \\ 984 | -H "Content-Type: application/json" \\ 985 | "test.com/?bar=1&baz=2"` 986 | }, function (err, result) { 987 | expect(result.result).to.equal(true); 988 | expect(result.output.length).to.equal(1); 989 | expect(result.output[0].type).to.equal('request'); 990 | expect(result.output[0].data.url).to.equal('test.com/?bar=1&baz=2'); 991 | const headerArr = result.output[0].data.header; 992 | expect(headerArr[headerArr.length - 1].key).to.equal('Content-Type'); 993 | expect(headerArr[headerArr.length - 1].value).to.equal('application/json'); 994 | done(); 995 | }); 996 | }); 997 | it('in case where there is a header with URL in it and the URL is unquoted', function(done) { 998 | convert({ 999 | type: 'string', 1000 | data: `curl -X GET \\ 1001 | -H "User-Agent: Dalvik/2.1.0 (Linux; U; Android 10; SM-A705FN Build/QP1A.190711.020) Test/4.6.4.459" \\ 1002 | -H "Authorization: bearer XXX" \\ 1003 | -H "Content-Type: application/json" \\ 1004 | -H "Referrer: test.com/?bar=1&baz=2" \\ 1005 | test.com/?bar=1&baz=2` 1006 | }, function (err, result) { 1007 | expect(result.result).to.equal(true); 1008 | expect(result.output.length).to.equal(1); 1009 | expect(result.output[0].type).to.equal('request'); 1010 | expect(result.output[0].data.url).to.equal('test.com/?bar=1&baz=2'); 1011 | const headerArr = result.output[0].data.header; 1012 | expect(headerArr[headerArr.length - 1].key).to.equal('Referrer'); 1013 | expect(headerArr[headerArr.length - 1].value).to.equal('test.com/?bar=1&baz=2'); 1014 | done(); 1015 | }); 1016 | }); 1017 | it('in case where there is a malformed URL with &(amp) in it, it should throw error', function(done) { 1018 | convert({ 1019 | type: 'string', 1020 | data: `curl -X GET \\ 1021 | -H "User-Agent: Dalvik/2.1.0 (Linux; U; Android 10; SM-A705FN Build/QP1A.190711.020) Test/4.6.4.459" \\ 1022 | -H "Authorization: bearer XXX" \\ 1023 | -H "Content-Type: application/json" \\ 1024 | test?bar=1&baz=2` 1025 | }, function (err, result) { 1026 | expect(result.result).to.equal(false); 1027 | expect(result.reason).to.equal('Please check your cURL string for malformed URL.'); 1028 | expect(result.error).to.have.property('message', result.reason); 1029 | done(); 1030 | }); 1031 | }); 1032 | it('in case where there is a malformed URL (variant 2) with &(amp) in it, it should throw error', function(done) { 1033 | convert({ 1034 | type: 'string', 1035 | data: `curl -X GET \\ 1036 | -H "User-Agent: Dalvik/2.1.0 (Linux; U; Android 10; SM-A705FN Build/QP1A.190711.020) Test/4.6.4.459" \\ 1037 | -H "Authorization: bearer XXX" \\ 1038 | -H "Content-Type: application/json" \\ 1039 | bar=1&baz=2` 1040 | }, function (err, result) { 1041 | expect(result.result).to.equal(false); 1042 | expect(result.reason).to.equal('Please check your cURL string for malformed URL.'); 1043 | expect(result.error).to.have.property('message', result.reason); 1044 | done(); 1045 | }); 1046 | }); 1047 | it(`in case where there is a malformed URL (variant 2) with &(amp) in it and no prefix space or 1048 | newline,it should throw error`, function(done) { 1049 | convert({ 1050 | type: 'string', 1051 | data: `curl -X GET \\ 1052 | -H "User-Agent: Dalvik/2.1.0 (Linux; U; Android 10; SM-A705FN Build/QP1A.190711.020) Test/4.6.4.459" \\ 1053 | -H "Authorization: bearer XXX" \\ 1054 | -H "Content-Type: application/json" \\ 1055 | "test.com/?bar=1&baz=2` 1056 | }, function (err, result) { 1057 | expect(result.result).to.equal(false); 1058 | expect(result.reason).to.equal('Please check your cURL string for malformed URL.'); 1059 | expect(result.error).to.have.property('message', result.reason); 1060 | done(); 1061 | }); 1062 | }); 1063 | it('properly formed large requests shouldn\'t hang the process', function(done) { 1064 | convert({ 1065 | type: 'string', 1066 | data: largeRequest 1067 | }, function (err, result) { 1068 | expect(result.result).to.equal(true); 1069 | expect(result.output.length).to.equal(1); 1070 | expect(result.output[0].type).to.equal('request'); 1071 | expect(result.output[0].data.url).to.equal('https://www.example.com/views/ajax?etc_category_tid=5&d=1'); 1072 | done(); 1073 | }); 1074 | }); 1075 | }); 1076 | 1077 | it('in case where there is a invalid character at the end it should safely generate request', function(done) { 1078 | convert({ 1079 | type: 'string', 1080 | data: `curl --location --request POST \\ 1081 | "postman-echo.com/post?qwerty=One" \\ 1082 | -H 'Cookie: sails.sid=s%3AGntztErGu9IDGjIBVu2-w7vTipGS3zsf.j9%2BHttqloZ2UJFwtSQbTx6tTTkOz2k6NkNq4NGCaDLI' \\ 1083 | ;` 1084 | }, function (err, result) { 1085 | expect(result.result).to.equal(true); 1086 | expect(result.output.length).to.equal(1); 1087 | expect(result.output[0].type).to.equal('request'); 1088 | expect(result.output[0].data.url).to.equal('postman-echo.com/post?qwerty=One'); 1089 | 1090 | const headerArr = result.output[0].data.header; 1091 | expect(headerArr[0].key).to.equal('Cookie'); 1092 | expect(headerArr[0].value).to.equal('sails.sid=s%3AGntztErGu9IDGjIBVu2-w7vTipGS3zsf.j9%2BHttqloZ2UJFwtSQbTx6tTTkOz2k6NkNq4NGCaDLI'); 1093 | done(); 1094 | }); 1095 | }); 1096 | 1097 | describe('[Github #5182]: It should correctly import cURL commands compatible with Windows cmd', function() { 1098 | 1099 | it('containing double quotes escaped with ^ (caret)', function(done) { 1100 | convert({ 1101 | type: 'string', 1102 | data: 'curl "https://trello.com/1/cards" --data-binary ' + 1103 | '"^{^\\^"name^\\^":^\\^"hello world^\\^",^\\^"pos^\\^":65535,^\\^"closed^\\^":false,' + 1104 | '^\\^"idLabels^\\^":^[^],^\\^"idMembers^\\^":^[^],^\\^"dateLastActivity^\\^":1536871503239,' + 1105 | '^\\^"idBoard^\\^":^\\^"5a84a94fc77d9f99cf9ecd8a^\\^",^\\^"idList^\\^":^\\^"a^\\^",' + 1106 | '^\\^"token^\\^":^\\^"a/L8nmd9rC5gyBYaBx6RVXGjHuMIRfMQS4b3p3zIhKWin8ejxTzJ5E5ERACxT2IILp^\\^"^}" --compressed' 1107 | }, function (err, result) { 1108 | expect(result.result).to.equal(true); 1109 | expect(result.output.length).to.equal(1); 1110 | expect(result.output[0].type).to.equal('request'); 1111 | expect(result.output[0].data.url).to.equal('https://trello.com/1/cards'); 1112 | done(); 1113 | }); 1114 | }); 1115 | 1116 | it('containing double quotes escaped with " (double quotes)', function(done) { 1117 | convert({ 1118 | type: 'string', 1119 | data: `curl "https://www.youtube.com/youtubei/v1/guide?key=321456467855697&prettyPrint=false" ^ 1120 | -H "authority: www.youtube.com" ^ 1121 | -H "sec-ch-ua: ^\\^"Not_A Brand^\\^";v=^\\^"99^\\^", ^\\^"Chromium^\\^";v=^\\^"109^\\^"" ^ 1122 | -H "sec-ch-ua-arch: ^\\^"arm^\\^"" ^ 1123 | -H "user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) (KHTML, like Gecko) Chrome/109.0.0.0" ^ 1124 | -H "content-type: application/json" ^ 1125 | --data-raw "^{^\\^"context^\\^":^{^\\^"client^\\^":^{^\\^"hl^\\^":^\\^"en^\\^"^}^},^\\^"state^\\^":true^}" ^ 1126 | --compressed 1127 | ` 1128 | }, function (err, result) { 1129 | expect(result.result).to.equal(true); 1130 | expect(result.output.length).to.equal(1); 1131 | expect(result.output[0].type).to.equal('request'); 1132 | const headerArr = result.output[0].data.header; 1133 | expect(headerArr[0].key).to.equal('authority'); 1134 | expect(headerArr[0].value).to.equal('www.youtube.com'); 1135 | expect(headerArr[1].key).to.equal('sec-ch-ua'); 1136 | expect(headerArr[1].value).to.equal( 1137 | '\"Not_A Brand\";v=\"99\", \"Chromium\";v=\"109\"'); 1138 | expect(headerArr[2].key).to.equal('sec-ch-ua-arch'); 1139 | expect(headerArr[2].value).to.equal('\"arm\"'); 1140 | expect(headerArr[3].key).to.equal('user-agent'); 1141 | expect(headerArr[3].value).to.equal( 1142 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) (KHTML, like Gecko) Chrome/109.0.0.0'); 1143 | expect(headerArr[4].key).to.equal('content-type'); 1144 | expect(headerArr[4].value).to.equal('application/json'); 1145 | expect(result.output[0].data.url).to.equal( 1146 | 'https://www.youtube.com/youtubei/v1/guide?key=321456467855697&prettyPrint=false'); 1147 | expect(result.output[0].data.body.mode).to.equal('raw'); 1148 | expect(result.output[0].data.body.raw).to.equal( 1149 | '{\"context\":{\"client\":{\"hl\":\"en\"}},\"state\":true}'); 1150 | done(); 1151 | }); 1152 | }); 1153 | }); 1154 | 1155 | describe('[Github #8296]: It should correctly generate request for form-data body', function() { 1156 | 1157 | it('containing form-data boundry with correct method', function(done) { 1158 | convert({ 1159 | type: 'string', 1160 | data: `curl 'https://httpbin.org/anything' \ 1161 | -H 'authority: httpbin.org' \ 1162 | -H 'accept: application/json, text/plain, */*' \ 1163 | -H 'content-type: multipart/form-data; boundary=----WebKitFormBoundaryDjpz6jUyMpfzNVCh' \ 1164 | --data-raw $'------WebKitFormBoundaryDjpz6jUyMpfzNVCh\r\nContent-Disposition: form-data; name="hello"\r\n\r\nworld\r\n------WebKitFormBoundaryDjpz6jUyMpfzNVCh\r\nContent-Disposition: form-data; name="contact[phone]"\r\n\r\n12345\r\n------WebKitFormBoundaryDjpz6jUyMpfzNVCh\r\nContent-Disposition: form-data; name="data"; filename="wsdl1.wsdl"\r\nContent-Type: application/octet-stream\r\n\r\n\r\n------WebKitFormBoundaryDjpz6jUyMpfzNVCh--\r\n' \ 1165 | --compressed 1166 | ` 1167 | }, function (err, result) { 1168 | expect(result.result).to.equal(true); 1169 | expect(result.output.length).to.equal(1); 1170 | expect(result.output[0].type).to.equal('request'); 1171 | expect(result.output[0].data.url).to.equal('https://httpbin.org/anything'); 1172 | expect(result.output[0].data.method).to.equal('POST'); 1173 | expect(result.output[0].data.body.mode).to.equal('formdata'); 1174 | expect(result.output[0].data.body.formdata).to.eql([ 1175 | { 1176 | key: 'hello', 1177 | value: 'world', 1178 | type: 'text' 1179 | }, 1180 | { 1181 | key: 'contact[phone]', 1182 | value: '12345', 1183 | type: 'text' 1184 | }, 1185 | { 1186 | key: 'data', 1187 | value: 'wsdl1.wsdl', 1188 | type: 'file' 1189 | } 1190 | ]); 1191 | done(); 1192 | }); 1193 | }); 1194 | 1195 | it('containing form params with correct method', function(done) { 1196 | convert({ 1197 | type: 'string', 1198 | data: `curl --location "https://httpbin.org/anything" \ 1199 | --form "hello=\"world\"" \ 1200 | --form "contact[phone]=\"12345\"" \ 1201 | --form "data=@\"wsdl1.wsdl\"" 1202 | ` 1203 | }, function (err, result) { 1204 | expect(result.result).to.equal(true); 1205 | expect(result.output.length).to.equal(1); 1206 | expect(result.output[0].type).to.equal('request'); 1207 | expect(result.output[0].data.url).to.equal('https://httpbin.org/anything'); 1208 | expect(result.output[0].data.method).to.equal('POST'); 1209 | expect(result.output[0].data.body.mode).to.equal('formdata'); 1210 | expect(result.output[0].data.body.formdata).to.eql([ 1211 | { 1212 | key: 'hello', 1213 | value: 'world', 1214 | type: 'text' 1215 | }, 1216 | { 1217 | key: 'contact[phone]', 1218 | value: '12345', 1219 | type: 'text' 1220 | }, 1221 | { 1222 | key: 'data', 1223 | value: 'wsdl1.wsdl', 1224 | type: 'file' 1225 | } 1226 | ]); 1227 | done(); 1228 | }); 1229 | }); 1230 | }); 1231 | 1232 | describe('It should correctly generate request for cURL with allowed bash operators', function() { 1233 | 1234 | it('containing "<" or/and ">" in URL', function(done) { 1235 | convert({ 1236 | type: 'string', 1237 | data: `curl https://httpbin.org/anything//team \ 1238 | -H 'authority: httpbin.org' 1239 | ` 1240 | }, function (err, result) { 1241 | expect(result.result).to.equal(true); 1242 | expect(result.output.length).to.equal(1); 1243 | expect(result.output[0].type).to.equal('request'); 1244 | expect(result.output[0].data.url).to.equal('https://httpbin.org/anything//team'); 1245 | expect(result.output[0].data.method).to.equal('GET'); 1246 | done(); 1247 | }); 1248 | }); 1249 | 1250 | it('containing "(" or/and ")" in URL', function(done) { 1251 | convert({ 1252 | type: 'string', 1253 | data: `curl https://httpbin.org/anything/(userId)/team \ 1254 | -H 'authority: httpbin.org' 1255 | ` 1256 | }, function (err, result) { 1257 | expect(result.result).to.equal(true); 1258 | expect(result.output.length).to.equal(1); 1259 | expect(result.output[0].type).to.equal('request'); 1260 | expect(result.output[0].data.url).to.equal('https://httpbin.org/anything/(userId)/team'); 1261 | expect(result.output[0].data.method).to.equal('GET'); 1262 | done(); 1263 | }); 1264 | }); 1265 | 1266 | it('containing allowed character with url option defined in URL', function(done) { 1267 | convert({ 1268 | type: 'string', 1269 | data: `curl --url https://httpbin.org/anything//team \ 1270 | -H 'authority: httpbin.org' 1271 | ` 1272 | }, function (err, result) { 1273 | expect(result.result).to.equal(true); 1274 | expect(result.output.length).to.equal(1); 1275 | expect(result.output[0].type).to.equal('request'); 1276 | expect(result.output[0].data.url).to.equal('https://httpbin.org/anything//team'); 1277 | expect(result.output[0].data.method).to.equal('GET'); 1278 | done(); 1279 | }); 1280 | }); 1281 | }); 1282 | 1283 | describe('It should correctly generate request for cURL with non-allowed bash operators without error', function() { 1284 | 1285 | it('containing non allowed operator "|" in URL', function(done) { 1286 | convert({ 1287 | type: 'string', 1288 | data: `curl https://httpbin.org/anything/user|id/team \ 1289 | -H 'authority: httpbin.org' 1290 | ` 1291 | }, function (err, result) { 1292 | expect(result.result).to.equal(true); 1293 | expect(result.output.length).to.equal(1); 1294 | expect(result.output[0].type).to.equal('request'); 1295 | expect(result.output[0].data.url).to.equal('https://httpbin.org/anything/user'); 1296 | expect(result.output[0].data.method).to.equal('GET'); 1297 | done(); 1298 | }); 1299 | }); 1300 | 1301 | it('containing non allowed operator "&" in URL host part', function(done) { 1302 | convert({ 1303 | type: 'string', 1304 | data: `curl https://httpbin.org/anything/user&id/team \ 1305 | -H 'authority: httpbin.org' 1306 | ` 1307 | }, function (err, result) { 1308 | expect(result.result).to.equal(true); 1309 | expect(result.output.length).to.equal(1); 1310 | expect(result.output[0].type).to.equal('request'); 1311 | expect(result.output[0].data.url).to.equal('https://httpbin.org/anything/user'); 1312 | expect(result.output[0].data.method).to.equal('GET'); 1313 | done(); 1314 | }); 1315 | }); 1316 | 1317 | it('containing "&" in URL query part', function(done) { 1318 | convert({ 1319 | type: 'string', 1320 | data: `curl https://httpbin.org/anything?hello=world&how=areyou \ 1321 | -H 'authority: httpbin.org' 1322 | ` 1323 | }, function (err, result) { 1324 | expect(result.result).to.equal(true); 1325 | expect(result.output.length).to.equal(1); 1326 | expect(result.output[0].type).to.equal('request'); 1327 | expect(result.output[0].data.url).to.equal('https://httpbin.org/anything?hello=world&how=areyou'); 1328 | expect(result.output[0].data.method).to.equal('GET'); 1329 | done(); 1330 | }); 1331 | }); 1332 | 1333 | it('containing non allowed operator ";" in URL', function(done) { 1334 | convert({ 1335 | type: 'string', 1336 | data: `curl https://httpbin.org/anything/user;id/team \ 1337 | -H 'authority: httpbin.org' 1338 | ` 1339 | }, function (err, result) { 1340 | expect(result.result).to.equal(true); 1341 | expect(result.output.length).to.equal(1); 1342 | expect(result.output[0].type).to.equal('request'); 1343 | expect(result.output[0].data.url).to.equal('https://httpbin.org/anything/user'); 1344 | expect(result.output[0].data.method).to.equal('GET'); 1345 | done(); 1346 | }); 1347 | }); 1348 | }); 1349 | 1350 | it('It should correctly generate request for cURL with special characters in URL without error', function(done) { 1351 | convert({ 1352 | type: 'string', 1353 | data: `curl https://httpbin.org/anything/!@$%^*-_=+.,\\{}[]/team \ 1354 | -H 'authority: httpbin.org' 1355 | ` 1356 | }, function (err, result) { 1357 | expect(result.result).to.equal(true); 1358 | expect(result.output.length).to.equal(1); 1359 | expect(result.output[0].type).to.equal('request'); 1360 | expect(result.output[0].data.url).to.equal('https://httpbin.org/anything/!@$%^*-_=+.,\{}[]/team'); 1361 | expect(result.output[0].data.method).to.equal('GET'); 1362 | done(); 1363 | }); 1364 | }); 1365 | 1366 | it('It should correctly generate request for cURL with extra arguments apart from URL without error', function(done) { 1367 | convert({ 1368 | type: 'string', 1369 | data: `curl https://httpbin.org/anything/team 5678 \ 1370 | -H 'authority: httpbin.org' 1371 | ` 1372 | }, function (err, result) { 1373 | expect(result.result).to.equal(true); 1374 | expect(result.output.length).to.equal(1); 1375 | expect(result.output[0].type).to.equal('request'); 1376 | expect(result.output[0].data.url).to.equal('https://httpbin.org/anything/team'); 1377 | expect(result.output[0].data.method).to.equal('GET'); 1378 | done(); 1379 | }); 1380 | }); 1381 | 1382 | it('It should correctly generate request for cURL with allowed operators not in URL correctly', function(done) { 1383 | convert({ 1384 | type: 'string', 1385 | data: `curl 'https://httpbin.org/anything' \ 1386 | -H 'authority: httpbin.org' \ 1387 | -H "user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) (KHTML, like Gecko) Chrome/109.0.0.0" \ 1388 | -H 'accept: application/json, text/plain, */*' \ 1389 | -H 'content-type: application/json' \ 1390 | --data "{\"context\":{\"client\":{\"hl\":\"en\"}},\"state\":true}" 1391 | ` 1392 | }, function (err, result) { 1393 | expect(result.result).to.equal(true); 1394 | expect(result.output.length).to.equal(1); 1395 | expect(result.output[0].type).to.equal('request'); 1396 | expect(result.output[0].data.url).to.equal('https://httpbin.org/anything'); 1397 | expect(result.output[0].data.method).to.equal('POST'); 1398 | 1399 | const headerArr = result.output[0].data.header; 1400 | expect(headerArr.length).to.equal(4); 1401 | expect(headerArr[0].key).to.equal('authority'); 1402 | expect(headerArr[0].value).to.equal('httpbin.org'); 1403 | expect(headerArr[1].key).to.equal('user-agent'); 1404 | expect(headerArr[1].value).to.equal('Mozilla/5.0 (Windows NT 10.0; Win64; x64) (KHTML, like Gecko) Chrome/109.0.0.0'); 1405 | expect(headerArr[2].key).to.equal('accept'); 1406 | expect(headerArr[2].value).to.equal('application/json, text/plain, */*'); 1407 | expect(headerArr[3].key).to.equal('content-type'); 1408 | expect(headerArr[3].value).to.equal('application/json'); 1409 | done(); 1410 | }); 1411 | }); 1412 | 1413 | describe('It should correctly generate request for cURL with various postman id formats in URL', function() { 1414 | 1415 | it('containing path variable with ":" character', function(done) { 1416 | convert({ 1417 | type: 'string', 1418 | data: `curl https://httpbin.org/team/:teamId/user/:userId \ 1419 | -H 'authority: httpbin.org' 1420 | ` 1421 | }, function (err, result) { 1422 | expect(result.result).to.equal(true); 1423 | expect(result.output.length).to.equal(1); 1424 | expect(result.output[0].type).to.equal('request'); 1425 | expect(result.output[0].data.url).to.equal('https://httpbin.org/team/:teamId/user/:userId'); 1426 | expect(result.output[0].data.method).to.equal('GET'); 1427 | done(); 1428 | }); 1429 | }); 1430 | 1431 | it('containing collection/environment variable in URL', function(done) { 1432 | convert({ 1433 | type: 'string', 1434 | data: `curl {{baseUrl}}/user/{{userId}} \ 1435 | -H 'authority: httpbin.org' 1436 | ` 1437 | }, function (err, result) { 1438 | expect(result.result).to.equal(true); 1439 | expect(result.output.length).to.equal(1); 1440 | expect(result.output[0].type).to.equal('request'); 1441 | expect(result.output[0].data.url).to.equal('{{baseUrl}}/user/{{userId}}'); 1442 | expect(result.output[0].data.method).to.equal('GET'); 1443 | done(); 1444 | }); 1445 | }); 1446 | }); 1447 | 1448 | describe('It should correctly generate request with cookie header', function() { 1449 | it('containing -b option', function(done) { 1450 | convert({ 1451 | type: 'string', 1452 | data: `curl 'https://httpbin.org/anything' \ 1453 | -b 'dashboard_beta=yes; postman-beta.track=default' \ 1454 | -H 'authority: httpbin.org' 1455 | --data "{\"context\":{\"client\":{\"hl\":\"en\"}},\"state\":true}" 1456 | ` 1457 | }, function (err, result) { 1458 | expect(result.result).to.equal(true); 1459 | expect(result.output.length).to.equal(1); 1460 | expect(result.output[0].type).to.equal('request'); 1461 | expect(result.output[0].data.url).to.equal('https://httpbin.org/anything'); 1462 | expect(result.output[0].data.method).to.equal('POST'); 1463 | 1464 | const headerArr = result.output[0].data.header; 1465 | expect(headerArr.length).to.equal(2); 1466 | expect(headerArr[0].key).to.equal('authority'); 1467 | expect(headerArr[0].value).to.equal('httpbin.org'); 1468 | expect(headerArr[1].key).to.equal('Cookie'); 1469 | expect(headerArr[1].value).to.equal('dashboard_beta=yes; postman-beta.track=default'); 1470 | done(); 1471 | }); 1472 | }); 1473 | 1474 | it('containing --cookie option', function(done) { 1475 | convert({ 1476 | type: 'string', 1477 | data: `curl 'https://httpbin.org/anything' \ 1478 | --cookie 'dashboard_beta=yes; postman-beta.track=default' \ 1479 | -H 'authority: httpbin.org' 1480 | --data "{\"context\":{\"client\":{\"hl\":\"en\"}},\"state\":true}" 1481 | ` 1482 | }, function (err, result) { 1483 | expect(result.result).to.equal(true); 1484 | expect(result.output.length).to.equal(1); 1485 | expect(result.output[0].type).to.equal('request'); 1486 | expect(result.output[0].data.url).to.equal('https://httpbin.org/anything'); 1487 | expect(result.output[0].data.method).to.equal('POST'); 1488 | 1489 | const headerArr = result.output[0].data.header; 1490 | expect(headerArr.length).to.equal(2); 1491 | expect(headerArr[0].key).to.equal('authority'); 1492 | expect(headerArr[0].value).to.equal('httpbin.org'); 1493 | expect(headerArr[1].key).to.equal('Cookie'); 1494 | expect(headerArr[1].value).to.equal('dashboard_beta=yes; postman-beta.track=default'); 1495 | done(); 1496 | }); 1497 | }); 1498 | 1499 | it('containing multiple cookies', function(done) { 1500 | convert({ 1501 | type: 'string', 1502 | data: `curl 'https://httpbin.org/anything' \ 1503 | -b 'dashboard_beta=yes; postman-beta.track=default' \ 1504 | -b 'name=JohnDoe' \ 1505 | -H 'authority: httpbin.org' \ 1506 | --data "{\"context\":{\"client\":{\"hl\":\"en\"}},\"state\":true}" 1507 | ` 1508 | }, function (err, result) { 1509 | expect(result.result).to.equal(true); 1510 | expect(result.output.length).to.equal(1); 1511 | expect(result.output[0].type).to.equal('request'); 1512 | expect(result.output[0].data.url).to.equal('https://httpbin.org/anything'); 1513 | expect(result.output[0].data.method).to.equal('POST'); 1514 | 1515 | const headerArr = result.output[0].data.header; 1516 | expect(headerArr.length).to.equal(3); 1517 | expect(headerArr[0].key).to.equal('authority'); 1518 | expect(headerArr[0].value).to.equal('httpbin.org'); 1519 | expect(headerArr[1].key).to.equal('Cookie'); 1520 | expect(headerArr[1].value).to.equal('dashboard_beta=yes; postman-beta.track=default'); 1521 | expect(headerArr[2].key).to.equal('Cookie'); 1522 | expect(headerArr[2].value).to.equal('name=JohnDoe'); 1523 | done(); 1524 | }); 1525 | }); 1526 | }); 1527 | }); 1528 | --------------------------------------------------------------------------------