chore: split app to commons and web (squash commit)
This commit is contained in:
@@ -0,0 +1,851 @@
|
||||
// @ts-check
|
||||
// ^^^ Enables Type Checking by the TypeScript compiler
|
||||
|
||||
import { makeRESTRequest, rawKeyValueEntriesToString } from "@hoppscotch/data"
|
||||
import { parseCurlToHoppRESTReq } from ".."
|
||||
|
||||
const samples = [
|
||||
{
|
||||
command: `
|
||||
curl --request GET \
|
||||
--url https://echo.hoppscotch.io/ \
|
||||
--header 'content-type: application/x-www-form-urlencoded' \
|
||||
--data a=b \
|
||||
--data c=d
|
||||
`,
|
||||
response: makeRESTRequest({
|
||||
method: "GET",
|
||||
name: "Untitled request",
|
||||
endpoint: "https://echo.hoppscotch.io/",
|
||||
auth: { authType: "none", authActive: true },
|
||||
body: {
|
||||
contentType: "application/x-www-form-urlencoded",
|
||||
body: rawKeyValueEntriesToString([
|
||||
{
|
||||
active: true,
|
||||
key: "a",
|
||||
value: "b",
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
key: "c",
|
||||
value: "d",
|
||||
},
|
||||
]),
|
||||
},
|
||||
headers: [],
|
||||
params: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
{
|
||||
command: `
|
||||
curl 'http://avs:def@127.0.0.1:8000/api/admin/crm/brand/4'
|
||||
-X PUT
|
||||
-H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0'
|
||||
-H 'Accept: application/json, text/plain, */*'
|
||||
-H 'Accept-Language: en'
|
||||
--compressed
|
||||
-H 'Content-Type: application/hal+json;charset=utf-8'
|
||||
-H 'Origin: http://localhost:3012'
|
||||
-H 'Connection: keep-alive'
|
||||
-H 'Referer: http://localhost:3012/crm/company/4'
|
||||
--data-raw '{"id":4,"crm_company_id":4,"industry_primary_id":2,"industry_head_id":2,"industry_body_id":2,"code":"01","barcode":"222010101","summary":"Healt-Seasoning-Basic-Hori-Kello","name":"Kellolaa","sub_code":"01","sub_name":"Hori","created_at":"2020-06-08 08:50:02","updated_at":"2020-06-08 08:50:02","company":4,"primary":{"id":2,"code":"2","name":"Healt","created_at":"2020-05-19 07:05:02","updated_at":"2020-05-19 07:09:28"},"head":{"id":2,"code":"2","name":"Seasoning","created_at":"2020-04-14 19:34:33","updated_at":"2020-04-14 19:34:33"},"body":{"id":2,"code":"2","name":"Basic","created_at":"2020-04-14 19:33:54","updated_at":"2020-04-14 19:33:54"},"contacts":[]}'
|
||||
`,
|
||||
response: makeRESTRequest({
|
||||
method: "PUT",
|
||||
name: "Untitled request",
|
||||
endpoint: "http://127.0.0.1:8000/api/admin/crm/brand/4",
|
||||
auth: {
|
||||
authType: "basic",
|
||||
authActive: true,
|
||||
username: "avs",
|
||||
password: "def",
|
||||
},
|
||||
body: {
|
||||
contentType: "application/hal+json",
|
||||
body: `{
|
||||
"id": 4,
|
||||
"crm_company_id": 4,
|
||||
"industry_primary_id": 2,
|
||||
"industry_head_id": 2,
|
||||
"industry_body_id": 2,
|
||||
"code": "01",
|
||||
"barcode": "222010101",
|
||||
"summary": "Healt-Seasoning-Basic-Hori-Kello",
|
||||
"name": "Kellolaa",
|
||||
"sub_code": "01",
|
||||
"sub_name": "Hori",
|
||||
"created_at": "2020-06-08 08:50:02",
|
||||
"updated_at": "2020-06-08 08:50:02",
|
||||
"company": 4,
|
||||
"primary": {
|
||||
"id": 2,
|
||||
"code": "2",
|
||||
"name": "Healt",
|
||||
"created_at": "2020-05-19 07:05:02",
|
||||
"updated_at": "2020-05-19 07:09:28"
|
||||
},
|
||||
"head": {
|
||||
"id": 2,
|
||||
"code": "2",
|
||||
"name": "Seasoning",
|
||||
"created_at": "2020-04-14 19:34:33",
|
||||
"updated_at": "2020-04-14 19:34:33"
|
||||
},
|
||||
"body": {
|
||||
"id": 2,
|
||||
"code": "2",
|
||||
"name": "Basic",
|
||||
"created_at": "2020-04-14 19:33:54",
|
||||
"updated_at": "2020-04-14 19:33:54"
|
||||
},
|
||||
"contacts": []
|
||||
}`,
|
||||
},
|
||||
headers: [
|
||||
{
|
||||
active: true,
|
||||
key: "User-Agent",
|
||||
value:
|
||||
"Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0",
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
key: "Accept",
|
||||
value: "application/json, text/plain, */*",
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
key: "Accept-Language",
|
||||
value: "en",
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
key: "Origin",
|
||||
value: "http://localhost:3012",
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
key: "Connection",
|
||||
value: "keep-alive",
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
key: "Referer",
|
||||
value: "http://localhost:3012/crm/company/4",
|
||||
},
|
||||
],
|
||||
params: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
{
|
||||
command: `curl google.com`,
|
||||
response: makeRESTRequest({
|
||||
method: "GET",
|
||||
name: "Untitled request",
|
||||
endpoint: "https://google.com/",
|
||||
auth: { authType: "none", authActive: true },
|
||||
body: {
|
||||
contentType: null,
|
||||
body: null,
|
||||
},
|
||||
headers: [],
|
||||
params: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
{
|
||||
command: `curl -X POST -d '{"foo":"bar"}' http://localhost:1111/hello/world/?bar=baz&buzz`,
|
||||
response: makeRESTRequest({
|
||||
method: "POST",
|
||||
name: "Untitled request",
|
||||
endpoint: "http://localhost:1111/hello/world/?buzz",
|
||||
auth: { authType: "none", authActive: true },
|
||||
body: {
|
||||
contentType: "application/json",
|
||||
body: `{\n "foo": "bar"\n}`,
|
||||
},
|
||||
headers: [],
|
||||
params: [
|
||||
{
|
||||
active: true,
|
||||
key: "bar",
|
||||
value: "baz",
|
||||
},
|
||||
],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
{
|
||||
command: `curl --get -d "tool=curl" -d "age=old" https://example.com`,
|
||||
response: makeRESTRequest({
|
||||
method: "GET",
|
||||
name: "Untitled request",
|
||||
endpoint: "https://example.com/",
|
||||
auth: { authType: "none", authActive: true },
|
||||
body: {
|
||||
contentType: null,
|
||||
body: null,
|
||||
},
|
||||
headers: [],
|
||||
params: [
|
||||
{
|
||||
active: true,
|
||||
key: "tool",
|
||||
value: "curl",
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
key: "age",
|
||||
value: "old",
|
||||
},
|
||||
],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
{
|
||||
command: `curl -F hello=hello2 -F hello3=@hello4.txt bing.com`,
|
||||
response: makeRESTRequest({
|
||||
method: "POST",
|
||||
name: "Untitled request",
|
||||
endpoint: "https://bing.com/",
|
||||
auth: { authType: "none", authActive: true },
|
||||
body: {
|
||||
contentType: "multipart/form-data",
|
||||
body: [
|
||||
{
|
||||
active: true,
|
||||
isFile: false,
|
||||
key: "hello",
|
||||
value: "hello2",
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
isFile: false,
|
||||
key: "hello3",
|
||||
value: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
headers: [],
|
||||
params: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
{
|
||||
command:
|
||||
"curl -X GET localhost -H 'Accept: application/json' --user root:toor",
|
||||
response: makeRESTRequest({
|
||||
method: "GET",
|
||||
name: "Untitled request",
|
||||
endpoint: "http://localhost/",
|
||||
auth: {
|
||||
authType: "basic",
|
||||
authActive: true,
|
||||
username: "root",
|
||||
password: "toor",
|
||||
},
|
||||
body: {
|
||||
contentType: null,
|
||||
body: null,
|
||||
},
|
||||
params: [],
|
||||
headers: [
|
||||
{
|
||||
active: true,
|
||||
key: "Accept",
|
||||
value: "application/json",
|
||||
},
|
||||
],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
{
|
||||
command:
|
||||
"curl -X GET localhost --header 'Authorization: Basic dXNlcjpwYXNz'",
|
||||
response: makeRESTRequest({
|
||||
method: "GET",
|
||||
name: "Untitled request",
|
||||
endpoint: "http://localhost/",
|
||||
auth: {
|
||||
authType: "basic",
|
||||
authActive: true,
|
||||
username: "user",
|
||||
password: "pass",
|
||||
},
|
||||
body: {
|
||||
contentType: null,
|
||||
body: null,
|
||||
},
|
||||
params: [],
|
||||
headers: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
{
|
||||
command:
|
||||
"curl -X GET localhost:9900 --header 'Authorization: Basic 77898dXNlcjpwYXNz'",
|
||||
response: makeRESTRequest({
|
||||
method: "GET",
|
||||
name: "Untitled request",
|
||||
endpoint: "http://localhost:9900/",
|
||||
auth: {
|
||||
authType: "none",
|
||||
authActive: true,
|
||||
},
|
||||
body: {
|
||||
contentType: null,
|
||||
body: null,
|
||||
},
|
||||
params: [],
|
||||
headers: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
{
|
||||
command:
|
||||
"curl -X GET localhost --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'",
|
||||
response: makeRESTRequest({
|
||||
method: "GET",
|
||||
name: "Untitled request",
|
||||
endpoint: "http://localhost/",
|
||||
auth: {
|
||||
authType: "bearer",
|
||||
authActive: true,
|
||||
token:
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
|
||||
},
|
||||
body: {
|
||||
contentType: null,
|
||||
body: null,
|
||||
},
|
||||
params: [],
|
||||
headers: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
{
|
||||
command: `curl --get -I -d "tool=curl" -d "platform=hoppscotch" -d"io" https://hoppscotch.io`,
|
||||
response: makeRESTRequest({
|
||||
method: "HEAD",
|
||||
name: "Untitled request",
|
||||
endpoint: "https://hoppscotch.io/?io",
|
||||
auth: {
|
||||
authActive: true,
|
||||
authType: "none",
|
||||
},
|
||||
body: {
|
||||
contentType: null,
|
||||
body: null,
|
||||
},
|
||||
params: [
|
||||
{
|
||||
active: true,
|
||||
key: "tool",
|
||||
value: "curl",
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
key: "platform",
|
||||
value: "hoppscotch",
|
||||
},
|
||||
],
|
||||
headers: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
{
|
||||
command: `curl 'https://someshadywebsite.com/questionable/path/?and=params&so&stay=tuned&' \
|
||||
-H 'user-agent: Mozilla/5.0' \
|
||||
-H 'accept: text/html' \
|
||||
-H $'cookie: cookie-cookie' \
|
||||
--data $'------WebKitFormBoundaryj3oufpIISPa2DP7c\\r\\nContent-Disposition: form-data; name="EmailAddress"\\r\\n\\r\\ntest@test.com\\r\\n------WebKitFormBoundaryj3oufpIISPa2DP7c\\r\\nContent-Disposition: form-data; name="Entity"\\r\\n\\r\\n1\\r\\n------WebKitFormBoundaryj3oufpIISPa2DP7c--\\r\\n'`,
|
||||
response: makeRESTRequest({
|
||||
method: "POST",
|
||||
name: "Untitled request",
|
||||
endpoint: "https://someshadywebsite.com/questionable/path/?so",
|
||||
auth: {
|
||||
authActive: true,
|
||||
authType: "none",
|
||||
},
|
||||
body: {
|
||||
contentType: "multipart/form-data",
|
||||
body: [
|
||||
{
|
||||
active: true,
|
||||
isFile: false,
|
||||
key: "EmailAddress",
|
||||
value: "test@test.com",
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
isFile: false,
|
||||
key: "Entity",
|
||||
value: "1",
|
||||
},
|
||||
],
|
||||
},
|
||||
params: [
|
||||
{
|
||||
active: true,
|
||||
key: "and",
|
||||
value: "params",
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
key: "stay",
|
||||
value: "tuned",
|
||||
},
|
||||
],
|
||||
headers: [
|
||||
{
|
||||
active: true,
|
||||
key: "user-agent",
|
||||
value: "Mozilla/5.0",
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
key: "accept",
|
||||
value: "text/html",
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
key: "cookie",
|
||||
value: "cookie-cookie",
|
||||
},
|
||||
],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
{
|
||||
command:
|
||||
"curl localhost -H 'content-type: multipart/form-data; boundary=------------------------d74496d66958873e' --data '-----------------------------d74496d66958873e\\r\\nContent-Disposition: form-data; name=\"file\"; filename=\"test.txt\"\\r\\nContent-Type: text/plain\\r\\n\\r\\nHello World\\r\\n\\r\\n-----------------------------d74496d66958873e--\\r\\n'",
|
||||
response: makeRESTRequest({
|
||||
method: "POST",
|
||||
name: "Untitled request",
|
||||
endpoint: "http://localhost/",
|
||||
auth: {
|
||||
authActive: true,
|
||||
authType: "none",
|
||||
},
|
||||
body: {
|
||||
contentType: "multipart/form-data",
|
||||
body: [
|
||||
{
|
||||
active: true,
|
||||
isFile: false,
|
||||
key: "file",
|
||||
value: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
params: [],
|
||||
headers: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
{
|
||||
command: `curl 'https://hoppscotch.io/' \
|
||||
-H 'authority: hoppscotch.io' \
|
||||
-H 'sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="98", "Google Chrome";v="98"' \
|
||||
-H 'accept: */*' \
|
||||
-H 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36' \
|
||||
-H 'sec-ch-ua-platform: "Windows"' \
|
||||
-H 'accept-language: en-US,en;q=0.9,ml;q=0.8' \
|
||||
--compressed`,
|
||||
response: makeRESTRequest({
|
||||
method: "GET",
|
||||
name: "Untitled request",
|
||||
endpoint: "https://hoppscotch.io/",
|
||||
auth: { authType: "none", authActive: true },
|
||||
body: {
|
||||
contentType: null,
|
||||
body: null,
|
||||
},
|
||||
params: [],
|
||||
headers: [
|
||||
{
|
||||
active: true,
|
||||
key: "authority",
|
||||
value: "hoppscotch.io",
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
key: "sec-ch-ua",
|
||||
value:
|
||||
'" Not A;Brand";v="99", "Chromium";v="98", "Google Chrome";v="98"',
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
key: "accept",
|
||||
value: "*/*",
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
key: "user-agent",
|
||||
value:
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36",
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
key: "sec-ch-ua-platform",
|
||||
value: '"Windows"',
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
key: "accept-language",
|
||||
value: "en-US,en;q=0.9,ml;q=0.8",
|
||||
},
|
||||
],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
{
|
||||
command: `curl --request GET \
|
||||
--url 'https://echo.hoppscotch.io/?hello=there' \
|
||||
--header 'content-type: application/x-www-form-urlencoded' \
|
||||
--header 'something: other-thing' \
|
||||
--data a=b \
|
||||
--data c=d`,
|
||||
response: makeRESTRequest({
|
||||
method: "GET",
|
||||
name: "Untitled request",
|
||||
endpoint: "https://echo.hoppscotch.io/",
|
||||
auth: { authType: "none", authActive: true },
|
||||
body: {
|
||||
contentType: "application/x-www-form-urlencoded",
|
||||
body: rawKeyValueEntriesToString([
|
||||
{
|
||||
key: "a",
|
||||
value: "b",
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
key: "c",
|
||||
value: "d",
|
||||
active: true,
|
||||
},
|
||||
]),
|
||||
},
|
||||
params: [
|
||||
{
|
||||
active: true,
|
||||
key: "hello",
|
||||
value: "there",
|
||||
},
|
||||
],
|
||||
headers: [
|
||||
{
|
||||
active: true,
|
||||
key: "something",
|
||||
value: "other-thing",
|
||||
},
|
||||
],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
{
|
||||
command: `curl --request POST \
|
||||
--url 'https://echo.hoppscotch.io/?hello=there' \
|
||||
--header 'content-type: multipart/form-data' \
|
||||
--header 'something: other-thing' \
|
||||
--form a=b \
|
||||
--form c=d`,
|
||||
response: makeRESTRequest({
|
||||
name: "Untitled request",
|
||||
endpoint: "https://echo.hoppscotch.io/",
|
||||
method: "POST",
|
||||
auth: { authType: "none", authActive: true },
|
||||
headers: [
|
||||
{
|
||||
active: true,
|
||||
key: "something",
|
||||
value: "other-thing",
|
||||
},
|
||||
],
|
||||
body: {
|
||||
contentType: "multipart/form-data",
|
||||
body: [
|
||||
{
|
||||
active: true,
|
||||
isFile: false,
|
||||
key: "a",
|
||||
value: "b",
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
isFile: false,
|
||||
key: "c",
|
||||
value: "d",
|
||||
},
|
||||
],
|
||||
},
|
||||
params: [
|
||||
{
|
||||
active: true,
|
||||
key: "hello",
|
||||
value: "there",
|
||||
},
|
||||
],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
{
|
||||
command: "curl 'muxueqz.top/skybook.html'",
|
||||
response: makeRESTRequest({
|
||||
name: "Untitled request",
|
||||
endpoint: "https://muxueqz.top/skybook.html",
|
||||
method: "GET",
|
||||
auth: { authType: "none", authActive: true },
|
||||
headers: [],
|
||||
body: { contentType: null, body: null },
|
||||
params: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
{
|
||||
command: "curl -F abcd=efghi",
|
||||
response: makeRESTRequest({
|
||||
name: "Untitled request",
|
||||
endpoint: "https://echo.hoppscotch.io/",
|
||||
method: "POST",
|
||||
auth: { authType: "none", authActive: true },
|
||||
headers: [],
|
||||
body: {
|
||||
contentType: "multipart/form-data",
|
||||
body: [
|
||||
{
|
||||
active: true,
|
||||
isFile: false,
|
||||
key: "abcd",
|
||||
value: "efghi",
|
||||
},
|
||||
],
|
||||
},
|
||||
params: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
{
|
||||
command: "curl 127.0.0.1 -X custommethod",
|
||||
response: makeRESTRequest({
|
||||
name: "Untitled request",
|
||||
endpoint: "http://127.0.0.1/",
|
||||
method: "CUSTOMMETHOD",
|
||||
auth: { authType: "none", authActive: true },
|
||||
headers: [],
|
||||
body: {
|
||||
contentType: null,
|
||||
body: null,
|
||||
},
|
||||
params: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
{
|
||||
command: "curl echo.hoppscotch.io -A pinephone",
|
||||
response: makeRESTRequest({
|
||||
name: "Untitled request",
|
||||
endpoint: "https://echo.hoppscotch.io/",
|
||||
method: "GET",
|
||||
auth: { authType: "none", authActive: true },
|
||||
headers: [
|
||||
{
|
||||
active: true,
|
||||
key: "User-Agent",
|
||||
value: "pinephone",
|
||||
},
|
||||
],
|
||||
body: {
|
||||
contentType: null,
|
||||
body: null,
|
||||
},
|
||||
params: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
{
|
||||
command: "curl echo.hoppscotch.io -G",
|
||||
response: makeRESTRequest({
|
||||
name: "Untitled request",
|
||||
endpoint: "https://echo.hoppscotch.io/",
|
||||
method: "GET",
|
||||
auth: { authType: "none", authActive: true },
|
||||
headers: [],
|
||||
body: {
|
||||
contentType: null,
|
||||
body: null,
|
||||
},
|
||||
params: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
{
|
||||
command: `curl --get -I -d "tool=hopp" https://example.org`,
|
||||
response: makeRESTRequest({
|
||||
name: "Untitled request",
|
||||
endpoint: "https://example.org/",
|
||||
method: "HEAD",
|
||||
auth: { authType: "none", authActive: true },
|
||||
headers: [],
|
||||
body: {
|
||||
contentType: null,
|
||||
body: null,
|
||||
},
|
||||
params: [
|
||||
{
|
||||
active: true,
|
||||
key: "tool",
|
||||
value: "hopp",
|
||||
},
|
||||
],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
{
|
||||
command: `curl google.com -u userx`,
|
||||
response: makeRESTRequest({
|
||||
method: "GET",
|
||||
name: "Untitled request",
|
||||
endpoint: "https://google.com/",
|
||||
auth: {
|
||||
authType: "basic",
|
||||
authActive: true,
|
||||
username: "userx",
|
||||
password: "",
|
||||
},
|
||||
body: {
|
||||
contentType: null,
|
||||
body: null,
|
||||
},
|
||||
params: [],
|
||||
headers: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
{
|
||||
command: `curl google.com -H "Authorization"`,
|
||||
response: makeRESTRequest({
|
||||
method: "GET",
|
||||
name: "Untitled request",
|
||||
endpoint: "https://google.com/",
|
||||
auth: {
|
||||
authType: "none",
|
||||
authActive: true,
|
||||
},
|
||||
body: {
|
||||
contentType: null,
|
||||
body: null,
|
||||
},
|
||||
params: [],
|
||||
headers: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
{
|
||||
command: `curl \`
|
||||
google.com -H "content-type: application/json"`,
|
||||
response: makeRESTRequest({
|
||||
method: "GET",
|
||||
name: "Untitled request",
|
||||
endpoint: "https://google.com/",
|
||||
auth: {
|
||||
authType: "none",
|
||||
authActive: true,
|
||||
},
|
||||
body: {
|
||||
contentType: null,
|
||||
body: null,
|
||||
},
|
||||
params: [],
|
||||
headers: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
{
|
||||
command: `curl 192.168.0.24:8080/ping`,
|
||||
response: makeRESTRequest({
|
||||
method: "GET",
|
||||
name: "Untitled request",
|
||||
endpoint: "http://192.168.0.24:8080/ping",
|
||||
auth: {
|
||||
authType: "none",
|
||||
authActive: true,
|
||||
},
|
||||
body: {
|
||||
contentType: null,
|
||||
body: null,
|
||||
},
|
||||
params: [],
|
||||
headers: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
{
|
||||
command: `curl https://example.com -d "alpha=beta&request_id=4"`,
|
||||
response: makeRESTRequest({
|
||||
method: "POST",
|
||||
name: "Untitled request",
|
||||
endpoint: "https://example.com/",
|
||||
auth: {
|
||||
authType: "none",
|
||||
authActive: true,
|
||||
},
|
||||
body: {
|
||||
contentType: "application/x-www-form-urlencoded",
|
||||
body: rawKeyValueEntriesToString([
|
||||
{
|
||||
active: true,
|
||||
key: "alpha",
|
||||
value: "beta",
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
key: "request_id",
|
||||
value: "4",
|
||||
},
|
||||
]),
|
||||
},
|
||||
params: [],
|
||||
headers: [],
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
}),
|
||||
},
|
||||
]
|
||||
|
||||
describe("Parse curl command to Hopp REST Request", () => {
|
||||
for (const [i, { command, response }] of samples.entries()) {
|
||||
test(`for sample #${i + 1}:\n\n${command}`, () => {
|
||||
expect(parseCurlToHoppRESTReq(command)).toEqual(response)
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,158 @@
|
||||
import { detectContentType } from "../sub_helpers/contentParser"
|
||||
|
||||
describe("detect content type", () => {
|
||||
test("should return null for blank input", () => {
|
||||
expect(detectContentType("")).toBe(null)
|
||||
})
|
||||
|
||||
describe("application/json", () => {
|
||||
test('should return text/plain for "{"', () => {
|
||||
expect(detectContentType("{")).toBe("text/plain")
|
||||
})
|
||||
|
||||
test('should return application/json for "{}"', () => {
|
||||
expect(detectContentType("{}")).toBe("application/json")
|
||||
})
|
||||
|
||||
test("should return application/json for valid json data", () => {
|
||||
expect(
|
||||
detectContentType(`
|
||||
{
|
||||
"body": "some text",
|
||||
"name": "interesting name",
|
||||
"code": [1, 5, 6, 2]
|
||||
}
|
||||
`)
|
||||
).toBe("application/json")
|
||||
})
|
||||
})
|
||||
|
||||
describe("application/xml", () => {
|
||||
test("should return text/html for XML data without XML declaration", () => {
|
||||
expect(
|
||||
detectContentType(`
|
||||
<book category="cooking">
|
||||
<title lang="en">Everyday Italian</title>
|
||||
<author>Giada De Laurentiis</author>
|
||||
<year>2005</year>
|
||||
<price>30.00</price>
|
||||
</book>
|
||||
`)
|
||||
).toBe("text/html")
|
||||
})
|
||||
|
||||
test("should return application/xml for valid XML data", () => {
|
||||
expect(
|
||||
detectContentType(`
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<book category="cooking">
|
||||
<title lang="en">Everyday Italian</title>
|
||||
<author>Giada De Laurentiis</author>
|
||||
<year>2005</year>
|
||||
<price>30.00</price>
|
||||
</book>
|
||||
`)
|
||||
).toBe("text/html")
|
||||
})
|
||||
|
||||
test("should return text/html for invalid XML data", () => {
|
||||
expect(
|
||||
detectContentType(`
|
||||
<book category="cooking">
|
||||
<title lang="en">Everyday Italian
|
||||
<abcd>Giada De Laurentiis</abcd>
|
||||
<year>2005</year>
|
||||
<price>30.00</price>
|
||||
`)
|
||||
).toBe("text/html")
|
||||
})
|
||||
})
|
||||
|
||||
describe("text/html", () => {
|
||||
test("should return text/html for valid HTML data", () => {
|
||||
expect(
|
||||
detectContentType(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Page Title</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>This is a Heading</h1>
|
||||
<p>This is a paragraph.</p>
|
||||
</body>
|
||||
</html>
|
||||
`)
|
||||
).toBe("text/html")
|
||||
})
|
||||
|
||||
test("should return text/html for invalid HTML data", () => {
|
||||
expect(
|
||||
detectContentType(`
|
||||
<head>
|
||||
<title>Page Title</title>
|
||||
<body>
|
||||
<h1>This is a Heading</h1>
|
||||
</body>
|
||||
</html>
|
||||
`)
|
||||
).toBe("text/html")
|
||||
})
|
||||
|
||||
test("should return text/html for unmatched tag", () => {
|
||||
expect(detectContentType("</html>")).toBe("text/html")
|
||||
})
|
||||
|
||||
test("should return text/plain for no valid tags in input", () => {
|
||||
expect(detectContentType("</html")).toBe("text/plain")
|
||||
})
|
||||
})
|
||||
|
||||
describe("application/x-www-form-urlencoded", () => {
|
||||
test("should return application/x-www-form-urlencoded for valid data", () => {
|
||||
expect(detectContentType("hello=world&hopp=scotch")).toBe(
|
||||
"application/x-www-form-urlencoded"
|
||||
)
|
||||
})
|
||||
|
||||
test("should return application/x-www-form-urlencoded for empty pair", () => {
|
||||
expect(detectContentType("hello=world&hopp=scotch&")).toBe(
|
||||
"application/x-www-form-urlencoded"
|
||||
)
|
||||
})
|
||||
|
||||
test("should return application/x-www-form-urlencoded for dangling param", () => {
|
||||
expect(detectContentType("hello=world&hoppscotch")).toBe(
|
||||
"application/x-www-form-urlencoded"
|
||||
)
|
||||
})
|
||||
|
||||
test('should return text/plain for "="', () => {
|
||||
expect(detectContentType("=")).toBe("text/plain")
|
||||
})
|
||||
|
||||
test("should return application/x-www-form-urlencoded for no value field", () => {
|
||||
expect(detectContentType("hello=")).toBe(
|
||||
"application/x-www-form-urlencoded"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("multipart/form-data", () => {
|
||||
test("should return multipart/form-data for valid data", () => {
|
||||
expect(
|
||||
detectContentType(
|
||||
`------WebKitFormBoundaryj3oufpIISPa2DP7c\\r\\nContent-Disposition: form-data; name="EmailAddress"\\r\\n\\r\\ntest@test.com\\r\\n------WebKitFormBoundaryj3oufpIISPa2DP7c\\r\\nContent-Disposition: form-data; name="Entity"\\r\\n\\r\\n1\\r\\n------WebKitFormBoundaryj3oufpIISPa2DP7c--\\r\\n`
|
||||
)
|
||||
).toBe("multipart/form-data")
|
||||
})
|
||||
|
||||
test("should return application/x-www-form-urlencoded for data with only one boundary", () => {
|
||||
expect(
|
||||
detectContentType(
|
||||
`\\r\\nContent-Disposition: form-data; name="EmailAddress"\\r\\n\\r\\ntest@test.com\\r\\n\\r\\nContent-Disposition: form-data; name="Entity"\\r\\n\\r\\n1\\r\\n------WebKitFormBoundaryj3oufpIISPa2DP7c--\\r\\n`
|
||||
)
|
||||
).toBe("application/x-www-form-urlencoded")
|
||||
})
|
||||
})
|
||||
})
|
||||
185
packages/hoppscotch-common/src/helpers/curl/curlparser.ts
Normal file
185
packages/hoppscotch-common/src/helpers/curl/curlparser.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* the direct import from yargs-parser uses fs which is a built in node module,
|
||||
* just adding the /browser import as a fix for now, which does not have type info on DefinitelyTyped.
|
||||
* remove/update this comment before merging the vue3 port.
|
||||
*/
|
||||
import parser from "yargs-parser/browser"
|
||||
import * as O from "fp-ts/Option"
|
||||
import * as A from "fp-ts/Array"
|
||||
import { pipe, flow } from "fp-ts/function"
|
||||
import {
|
||||
FormDataKeyValue,
|
||||
HoppRESTReqBody,
|
||||
makeRESTRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import { getAuthObject } from "./sub_helpers/auth"
|
||||
import { getHeaders, recordToHoppHeaders } from "./sub_helpers/headers"
|
||||
// import { getCookies } from "./sub_helpers/cookies"
|
||||
import { getQueries } from "./sub_helpers/queries"
|
||||
import { getMethod } from "./sub_helpers/method"
|
||||
import { concatParams, getURLObject } from "./sub_helpers/url"
|
||||
import { preProcessCurlCommand } from "./sub_helpers/preproc"
|
||||
import { getBody, getFArgumentMultipartData } from "./sub_helpers/body"
|
||||
import { getDefaultRESTRequest } from "~/newstore/RESTSession"
|
||||
import {
|
||||
objHasProperty,
|
||||
objHasArrayProperty,
|
||||
} from "~/helpers/functional/object"
|
||||
|
||||
const defaultRESTReq = getDefaultRESTRequest()
|
||||
|
||||
export const parseCurlCommand = (curlCommand: string) => {
|
||||
// const isDataBinary = curlCommand.includes(" --data-binary")
|
||||
// const compressed = !!parsedArguments.compressed
|
||||
|
||||
curlCommand = preProcessCurlCommand(curlCommand)
|
||||
const parsedArguments = parser(curlCommand)
|
||||
|
||||
const headerObject = getHeaders(parsedArguments)
|
||||
const { headers } = headerObject
|
||||
let { rawContentType } = headerObject
|
||||
const hoppHeaders = pipe(
|
||||
headers,
|
||||
O.fromPredicate(() => Object.keys(headers).length > 0),
|
||||
O.map(recordToHoppHeaders),
|
||||
O.getOrElse(() => defaultRESTReq.headers)
|
||||
)
|
||||
|
||||
const method = getMethod(parsedArguments)
|
||||
// const cookies = getCookies(parsedArguments)
|
||||
const urlObject = getURLObject(parsedArguments)
|
||||
const auth = getAuthObject(parsedArguments, headers, urlObject)
|
||||
|
||||
let rawData: string | string[] = pipe(
|
||||
parsedArguments,
|
||||
O.fromPredicate(objHasArrayProperty("d", "string")),
|
||||
O.map((args) => args.d),
|
||||
O.altW(() =>
|
||||
pipe(
|
||||
parsedArguments,
|
||||
O.fromPredicate(objHasProperty("d", "string")),
|
||||
O.map((args) => args.d)
|
||||
)
|
||||
),
|
||||
O.getOrElseW(() => "")
|
||||
)
|
||||
|
||||
let body: HoppRESTReqBody["body"] = ""
|
||||
let contentType: HoppRESTReqBody["contentType"] =
|
||||
defaultRESTReq.body.contentType
|
||||
let hasBodyBeenParsed = false
|
||||
|
||||
let { queries, danglingParams } = getQueries(
|
||||
Array.from(urlObject.searchParams.entries())
|
||||
)
|
||||
|
||||
const stringToPair = flow(
|
||||
decodeURIComponent,
|
||||
(pair) => <[string, string]>pair.split("=", 2)
|
||||
)
|
||||
const pairs = pipe(
|
||||
rawData,
|
||||
O.fromPredicate(Array.isArray),
|
||||
O.map(A.map(stringToPair)),
|
||||
O.alt(() =>
|
||||
pipe(
|
||||
rawData,
|
||||
O.fromPredicate((s) => s.length > 0),
|
||||
O.map(() => [stringToPair(rawData as string)])
|
||||
)
|
||||
),
|
||||
O.getOrElseW(() => undefined)
|
||||
)
|
||||
|
||||
if (objHasProperty("G", "boolean")(parsedArguments) && !!pairs) {
|
||||
const newQueries = getQueries(pairs)
|
||||
queries = [...queries, ...newQueries.queries]
|
||||
danglingParams = [...danglingParams, ...newQueries.danglingParams]
|
||||
hasBodyBeenParsed = true
|
||||
} else if (
|
||||
rawContentType.includes("application/x-www-form-urlencoded") &&
|
||||
!!pairs &&
|
||||
Array.isArray(rawData)
|
||||
) {
|
||||
body = pairs.map((p) => p.join(": ")).join("\n") || null
|
||||
contentType = "application/x-www-form-urlencoded"
|
||||
hasBodyBeenParsed = true
|
||||
}
|
||||
|
||||
const urlString = concatParams(urlObject, danglingParams)
|
||||
|
||||
let multipartUploads: Record<string, string> = pipe(
|
||||
O.of(parsedArguments),
|
||||
O.chain(getFArgumentMultipartData),
|
||||
O.match(
|
||||
() => ({}),
|
||||
(args) => {
|
||||
hasBodyBeenParsed = true
|
||||
rawContentType = "multipart/form-data"
|
||||
return args
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
if (!hasBodyBeenParsed) {
|
||||
if (typeof rawData !== "string") {
|
||||
rawData = rawData.join("")
|
||||
}
|
||||
const bodyObject = getBody(rawData, rawContentType, contentType)
|
||||
|
||||
if (O.isSome(bodyObject)) {
|
||||
const bodyObjectValue = bodyObject.value
|
||||
|
||||
if (bodyObjectValue.type === "FORMDATA") {
|
||||
multipartUploads = bodyObjectValue.body
|
||||
} else {
|
||||
body = bodyObjectValue.body.body
|
||||
contentType = bodyObjectValue.body
|
||||
.contentType as HoppRESTReqBody["contentType"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const finalBody: HoppRESTReqBody = pipe(
|
||||
body,
|
||||
O.fromNullable,
|
||||
O.filter((b) => b.length > 0),
|
||||
O.map((b) => <HoppRESTReqBody>{ body: b, contentType }),
|
||||
O.alt(() =>
|
||||
pipe(
|
||||
multipartUploads,
|
||||
O.of,
|
||||
O.map((m) => Object.entries(m)),
|
||||
O.filter((m) => m.length > 0),
|
||||
O.map(
|
||||
flow(
|
||||
A.map(
|
||||
([key, value]) =>
|
||||
<FormDataKeyValue>{
|
||||
active: true,
|
||||
isFile: false,
|
||||
key,
|
||||
value,
|
||||
}
|
||||
),
|
||||
(b) =>
|
||||
<HoppRESTReqBody>{ body: b, contentType: "multipart/form-data" }
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
O.getOrElse(() => defaultRESTReq.body)
|
||||
)
|
||||
|
||||
return makeRESTRequest({
|
||||
name: defaultRESTReq.name,
|
||||
endpoint: urlString,
|
||||
method: (method || defaultRESTReq.method).toUpperCase(),
|
||||
params: queries ?? defaultRESTReq.params,
|
||||
headers: hoppHeaders,
|
||||
preRequestScript: defaultRESTReq.preRequestScript,
|
||||
testScript: defaultRESTReq.testScript,
|
||||
auth,
|
||||
body: finalBody,
|
||||
})
|
||||
}
|
||||
5
packages/hoppscotch-common/src/helpers/curl/index.ts
Normal file
5
packages/hoppscotch-common/src/helpers/curl/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { flow } from "fp-ts/function"
|
||||
import { cloneDeep } from "lodash-es"
|
||||
import { parseCurlCommand } from "./curlparser"
|
||||
|
||||
export const parseCurlToHoppRESTReq = flow(parseCurlCommand, cloneDeep)
|
||||
116
packages/hoppscotch-common/src/helpers/curl/sub_helpers/auth.ts
Normal file
116
packages/hoppscotch-common/src/helpers/curl/sub_helpers/auth.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { HoppRESTAuth } from "@hoppscotch/data"
|
||||
import parser from "yargs-parser"
|
||||
import * as O from "fp-ts/Option"
|
||||
import * as S from "fp-ts/string"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { getDefaultRESTRequest } from "~/newstore/RESTSession"
|
||||
import { objHasProperty } from "~/helpers/functional/object"
|
||||
|
||||
const defaultRESTReq = getDefaultRESTRequest()
|
||||
|
||||
const getAuthFromAuthHeader = (headers: Record<string, string>) =>
|
||||
pipe(
|
||||
headers.Authorization,
|
||||
O.fromNullable,
|
||||
O.map((a) => a.split(" ")),
|
||||
O.filter((a) => a.length > 1),
|
||||
O.chain((kv) =>
|
||||
O.fromNullable(
|
||||
(() => {
|
||||
switch (kv[0].toLowerCase()) {
|
||||
case "bearer":
|
||||
return <HoppRESTAuth>{
|
||||
authActive: true,
|
||||
authType: "bearer",
|
||||
token: kv[1],
|
||||
}
|
||||
case "basic": {
|
||||
const [username, password] = pipe(
|
||||
O.tryCatch(() => atob(kv[1])),
|
||||
O.map(S.split(":")),
|
||||
// can have a username with no password
|
||||
O.filter((arr) => arr.length > 0),
|
||||
O.map(
|
||||
([username, password]) =>
|
||||
<[string, string]>[username, password]
|
||||
),
|
||||
O.getOrElse(() => ["", ""])
|
||||
)
|
||||
|
||||
if (!username) return undefined
|
||||
|
||||
return <HoppRESTAuth>{
|
||||
authActive: true,
|
||||
authType: "basic",
|
||||
username,
|
||||
password: password ?? "",
|
||||
}
|
||||
}
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
})()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
const getAuthFromParsedArgs = (parsedArguments: parser.Arguments) =>
|
||||
pipe(
|
||||
parsedArguments,
|
||||
O.fromPredicate(objHasProperty("u", "string")),
|
||||
O.chain((args) =>
|
||||
pipe(
|
||||
args.u,
|
||||
S.split(":"),
|
||||
// can have a username with no password
|
||||
O.fromPredicate((arr) => arr.length > 0 && arr[0].length > 0),
|
||||
O.map(
|
||||
([username, password]) => <[string, string]>[username, password ?? ""]
|
||||
)
|
||||
)
|
||||
),
|
||||
O.map(
|
||||
([username, password]) =>
|
||||
<HoppRESTAuth>{
|
||||
authActive: true,
|
||||
authType: "basic",
|
||||
username,
|
||||
password,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
const getAuthFromURLObject = (urlObject: URL) =>
|
||||
pipe(
|
||||
urlObject,
|
||||
(url) => [url.username, url.password ?? ""],
|
||||
// can have a username with no password
|
||||
O.fromPredicate(([username]) => !!username && username.length > 0),
|
||||
O.map(
|
||||
([username, password]) =>
|
||||
<HoppRESTAuth>{
|
||||
authActive: true,
|
||||
authType: "basic",
|
||||
username,
|
||||
password,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
/**
|
||||
* Preference order:
|
||||
* - Auth headers
|
||||
* - --user or -u argument
|
||||
* - Creds provided along with URL
|
||||
*/
|
||||
export const getAuthObject = (
|
||||
parsedArguments: parser.Arguments,
|
||||
headers: Record<string, string>,
|
||||
urlObject: URL
|
||||
): HoppRESTAuth =>
|
||||
pipe(
|
||||
getAuthFromAuthHeader(headers),
|
||||
O.alt(() => getAuthFromParsedArgs(parsedArguments)),
|
||||
O.alt(() => getAuthFromURLObject(urlObject)),
|
||||
O.getOrElse(() => defaultRESTReq.auth)
|
||||
)
|
||||
169
packages/hoppscotch-common/src/helpers/curl/sub_helpers/body.ts
Normal file
169
packages/hoppscotch-common/src/helpers/curl/sub_helpers/body.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import parser from "yargs-parser"
|
||||
import { pipe, flow } from "fp-ts/function"
|
||||
import * as O from "fp-ts/Option"
|
||||
import * as A from "fp-ts/Array"
|
||||
import * as RNEA from "fp-ts/ReadonlyNonEmptyArray"
|
||||
import * as S from "fp-ts/string"
|
||||
import {
|
||||
HoppRESTReqBody,
|
||||
HoppRESTReqBodyFormData,
|
||||
ValidContentTypes,
|
||||
knownContentTypes,
|
||||
} from "@hoppscotch/data"
|
||||
import { detectContentType, parseBody } from "./contentParser"
|
||||
import { tupleToRecord } from "~/helpers/functional/record"
|
||||
import {
|
||||
objHasProperty,
|
||||
objHasArrayProperty,
|
||||
} from "~/helpers/functional/object"
|
||||
|
||||
type BodyReturnType =
|
||||
| { type: "FORMDATA"; body: Record<string, string> }
|
||||
| {
|
||||
type: "NON_FORMDATA"
|
||||
body: Exclude<HoppRESTReqBody, HoppRESTReqBodyFormData>
|
||||
}
|
||||
|
||||
/** Parses body based on the content type
|
||||
* @param rData Raw data
|
||||
* @param cType Sanitized content type
|
||||
* @returns Option of parsed body of type string | Record<string, string>
|
||||
*/
|
||||
const getBodyFromContentType =
|
||||
(rData: string, cType: HoppRESTReqBody["contentType"]) => (rct: string) =>
|
||||
pipe(
|
||||
cType,
|
||||
O.fromPredicate((ctype) => ctype === "multipart/form-data"),
|
||||
O.chain(() =>
|
||||
pipe(
|
||||
// pass rawContentType for boundary ascertion
|
||||
parseBody(rData, cType, rct),
|
||||
O.filter((parsedBody) => typeof parsedBody !== "string")
|
||||
)
|
||||
),
|
||||
O.alt(() =>
|
||||
pipe(
|
||||
parseBody(rData, cType),
|
||||
O.filter(
|
||||
(parsedBody) =>
|
||||
typeof parsedBody === "string" && parsedBody.length > 0
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
const getContentTypeFromRawContentType = (rawContentType: string) =>
|
||||
pipe(
|
||||
rawContentType,
|
||||
O.fromPredicate((rct) => rct.length > 0),
|
||||
// get everything before semi-colon
|
||||
O.map(flow(S.toLowerCase, S.split(";"), RNEA.head)),
|
||||
// if rawContentType is valid, cast it to contentType type
|
||||
O.filter((ct) => Object.keys(knownContentTypes).includes(ct)),
|
||||
O.map((ct) => ct as HoppRESTReqBody["contentType"])
|
||||
)
|
||||
|
||||
const getContentTypeFromRawData = (rawData: string) =>
|
||||
pipe(
|
||||
rawData,
|
||||
O.fromPredicate((rd) => rd.length > 0),
|
||||
O.map(detectContentType)
|
||||
)
|
||||
|
||||
export const getBody = (
|
||||
rawData: string,
|
||||
rawContentType: string,
|
||||
contentType: HoppRESTReqBody["contentType"]
|
||||
): O.Option<BodyReturnType> => {
|
||||
return pipe(
|
||||
O.Do,
|
||||
|
||||
O.bind("cType", () =>
|
||||
pipe(
|
||||
// get provided content-type
|
||||
contentType,
|
||||
O.fromNullable,
|
||||
// or figure it out
|
||||
O.alt(() => getContentTypeFromRawContentType(rawContentType)),
|
||||
O.alt(() => getContentTypeFromRawData(rawData))
|
||||
)
|
||||
),
|
||||
|
||||
O.bind("rData", () =>
|
||||
pipe(
|
||||
rawData,
|
||||
O.fromPredicate(() => rawData.length > 0)
|
||||
)
|
||||
),
|
||||
|
||||
O.bind("ctBody", ({ cType, rData }) =>
|
||||
pipe(rawContentType, getBodyFromContentType(rData, cType))
|
||||
),
|
||||
|
||||
O.map(({ cType, ctBody }) =>
|
||||
typeof ctBody === "string"
|
||||
? {
|
||||
type: "NON_FORMDATA",
|
||||
body: {
|
||||
body: ctBody,
|
||||
contentType: cType as Exclude<
|
||||
ValidContentTypes,
|
||||
"multipart/form-data"
|
||||
>,
|
||||
},
|
||||
}
|
||||
: { type: "FORMDATA", body: ctBody }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses and structures multipart/form-data from -F argument of curl command
|
||||
* @param parsedArguments Parsed Arguments object
|
||||
* @returns Option of Record<string, string> type containing key-value pairs of multipart/form-data
|
||||
*/
|
||||
export function getFArgumentMultipartData(
|
||||
parsedArguments: parser.Arguments
|
||||
): O.Option<Record<string, string>> {
|
||||
// --form or -F multipart data
|
||||
|
||||
return pipe(
|
||||
parsedArguments,
|
||||
// make it an array if not already
|
||||
O.fromPredicate(objHasProperty("F", "string")),
|
||||
O.map((args) => [args.F]),
|
||||
O.alt(() =>
|
||||
pipe(
|
||||
parsedArguments,
|
||||
O.fromPredicate(objHasArrayProperty("F", "string")),
|
||||
O.map((args) => args.F)
|
||||
)
|
||||
),
|
||||
O.chain(
|
||||
flow(
|
||||
A.map(S.split("=")),
|
||||
// can only have a key and no value
|
||||
O.fromPredicate((fArgs) => fArgs.length > 0),
|
||||
O.map(
|
||||
flow(
|
||||
A.map(([k, v]) =>
|
||||
pipe(
|
||||
parsedArguments,
|
||||
// form-string option allows for "@" and "<" prefixes
|
||||
// without them being considered as files
|
||||
O.fromPredicate(objHasProperty("form-string", "boolean")),
|
||||
O.match(
|
||||
// leave the value field empty for files
|
||||
() => [k, v[0] === "@" || v[0] === "<" ? "" : v],
|
||||
() => [k, v]
|
||||
)
|
||||
)
|
||||
),
|
||||
A.map(([k, v]) => [k, v] as [string, string]),
|
||||
tupleToRecord
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
import { HoppRESTReqBody } from "@hoppscotch/data"
|
||||
import * as O from "fp-ts/Option"
|
||||
import * as RA from "fp-ts/ReadonlyArray"
|
||||
import * as S from "fp-ts/string"
|
||||
import { pipe, flow } from "fp-ts/function"
|
||||
import { tupleToRecord } from "~/helpers/functional/record"
|
||||
import { safeParseJSON } from "~/helpers/functional/json"
|
||||
import { optionChoose } from "~/helpers/functional/option"
|
||||
|
||||
const isJSON = flow(safeParseJSON, O.isSome)
|
||||
|
||||
const isXML = (rawData: string) =>
|
||||
pipe(
|
||||
rawData,
|
||||
O.fromPredicate(() => /<\/?[a-zA-Z][\s\S]*>/i.test(rawData)),
|
||||
O.chain(prettifyXml),
|
||||
O.isSome
|
||||
)
|
||||
|
||||
const isHTML = (rawData: string) =>
|
||||
pipe(
|
||||
rawData,
|
||||
O.fromPredicate(() => /<\/?[a-zA-Z][\s\S]*>/i.test(rawData)),
|
||||
O.isSome
|
||||
)
|
||||
|
||||
const isFormData = (rawData: string) =>
|
||||
pipe(
|
||||
rawData.match(/^-{2,}[A-Za-z0-9]+\\r\\n/),
|
||||
O.fromNullable,
|
||||
O.filter((boundaryMatch) => boundaryMatch.length > 0),
|
||||
O.isSome
|
||||
)
|
||||
|
||||
const isXWWWFormUrlEncoded = (rawData: string) =>
|
||||
pipe(
|
||||
rawData,
|
||||
O.fromPredicate((rd) => /([^&=]+)=([^&=]*)/.test(rd)),
|
||||
O.isSome
|
||||
)
|
||||
|
||||
/**
|
||||
* Detects the content type of the input string
|
||||
* @param rawData String for which content type is to be detected
|
||||
* @returns Content type of the data
|
||||
*/
|
||||
export const detectContentType = (
|
||||
rawData: string
|
||||
): HoppRESTReqBody["contentType"] =>
|
||||
pipe(
|
||||
rawData,
|
||||
optionChoose([
|
||||
[(rd) => !rd, null],
|
||||
[isJSON, "application/json" as const],
|
||||
[isFormData, "multipart/form-data" as const],
|
||||
[isXML, "application/xml" as const],
|
||||
[isHTML, "text/html" as const],
|
||||
[isXWWWFormUrlEncoded, "application/x-www-form-urlencoded" as const],
|
||||
]),
|
||||
O.getOrElseW(() => "text/plain" as const)
|
||||
)
|
||||
|
||||
const multipartFunctions = {
|
||||
getBoundary(rawData: string, rawContentType: string | undefined) {
|
||||
return pipe(
|
||||
rawContentType,
|
||||
O.fromNullable,
|
||||
O.filter((rct) => rct.length > 0),
|
||||
O.match(
|
||||
() => this.getBoundaryFromRawData(rawData),
|
||||
(rct) => this.getBoundaryFromRawContentType(rawData, rct)
|
||||
)
|
||||
)
|
||||
},
|
||||
|
||||
getBoundaryFromRawData(rawData: string) {
|
||||
return pipe(
|
||||
rawData.match(/(-{2,}[A-Za-z0-9]+)\\r\\n/g),
|
||||
O.fromNullable,
|
||||
O.filter((boundaryMatch) => boundaryMatch.length > 0),
|
||||
O.map((matches) => matches[0].slice(0, -4))
|
||||
)
|
||||
},
|
||||
|
||||
getBoundaryFromRawContentType(rawData: string, rawContentType: string) {
|
||||
return pipe(
|
||||
rawContentType.match(/boundary=(.+)/),
|
||||
O.fromNullable,
|
||||
O.filter((boundaryContentMatch) => boundaryContentMatch.length > 1),
|
||||
O.filter((matches) =>
|
||||
rawData.replaceAll("\\r\\n", "").endsWith("--" + matches[1] + "--")
|
||||
),
|
||||
O.map((matches) => "--" + matches[1])
|
||||
)
|
||||
},
|
||||
|
||||
splitUsingBoundaryAndNewLines(rawData: string, boundary: string) {
|
||||
return pipe(
|
||||
rawData,
|
||||
S.split(RegExp(`${boundary}-*`)),
|
||||
RA.filter((p) => p !== "" && p.includes("name")),
|
||||
RA.map((p) =>
|
||||
pipe(
|
||||
p.replaceAll(/\\r\\n+/g, "\\r\\n"),
|
||||
S.split("\\r\\n"),
|
||||
RA.filter((q) => q !== "")
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
|
||||
getNameValuePair(pair: readonly string[]) {
|
||||
return pipe(
|
||||
pair,
|
||||
O.fromPredicate((p) => p.length > 1),
|
||||
O.chain((pair) => O.fromNullable(pair[0].match(/ name="(\w+)"/))),
|
||||
O.filter((nameMatch) => nameMatch.length > 0),
|
||||
O.chain((nameMatch) =>
|
||||
pipe(
|
||||
nameMatch[0],
|
||||
S.replace(/"/g, ""),
|
||||
S.split("="),
|
||||
O.fromPredicate((q) => q.length === 2),
|
||||
O.map(
|
||||
(nameArr) =>
|
||||
[nameArr[1], pair[0].includes("filename") ? "" : pair[1]] as [
|
||||
string,
|
||||
string
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const getFormDataBody = (rawData: string, rawContentType: string | undefined) =>
|
||||
pipe(
|
||||
multipartFunctions.getBoundary(rawData, rawContentType),
|
||||
O.map((boundary) =>
|
||||
pipe(
|
||||
multipartFunctions.splitUsingBoundaryAndNewLines(rawData, boundary),
|
||||
RA.filterMap((p) => multipartFunctions.getNameValuePair(p)),
|
||||
RA.toArray
|
||||
)
|
||||
),
|
||||
|
||||
O.filter((arr) => arr.length > 0),
|
||||
O.map(tupleToRecord)
|
||||
)
|
||||
|
||||
const getHTMLBody = flow(formatHTML, O.of)
|
||||
|
||||
const getXMLBody = (rawData: string) =>
|
||||
pipe(
|
||||
rawData,
|
||||
prettifyXml,
|
||||
O.alt(() => O.some(rawData))
|
||||
)
|
||||
|
||||
const getFormattedJSON = flow(
|
||||
safeParseJSON,
|
||||
O.map((parsedJSON) => JSON.stringify(parsedJSON, null, 2)),
|
||||
O.getOrElse(() => "{ }")
|
||||
)
|
||||
|
||||
const getXWWWFormUrlEncodedBody = flow(
|
||||
decodeURIComponent,
|
||||
(decoded) => decoded.match(/(([^&=]+)=?([^&=]*))/g),
|
||||
O.fromNullable,
|
||||
O.map((pairs) => pairs.map((p) => p.replace("=", ": ")).join("\n"))
|
||||
)
|
||||
|
||||
/**
|
||||
* Parses provided string according to the content type
|
||||
* @param rawData Data to be parsed
|
||||
* @param contentType Content type of the data
|
||||
* @param rawContentType Optional parameter required for multipart/form-data
|
||||
* @returns Option of parsed body as string or Record object for multipart/form-data
|
||||
*/
|
||||
export function parseBody(
|
||||
rawData: string,
|
||||
contentType: HoppRESTReqBody["contentType"],
|
||||
rawContentType?: string
|
||||
): O.Option<string | Record<string, string>> {
|
||||
switch (contentType) {
|
||||
case "application/hal+json":
|
||||
case "application/ld+json":
|
||||
case "application/vnd.api+json":
|
||||
case "application/json":
|
||||
return O.some(getFormattedJSON(rawData))
|
||||
|
||||
case "application/x-www-form-urlencoded":
|
||||
return getXWWWFormUrlEncodedBody(rawData)
|
||||
|
||||
case "multipart/form-data":
|
||||
return getFormDataBody(rawData, rawContentType)
|
||||
|
||||
case "text/html":
|
||||
return getHTMLBody(rawData)
|
||||
|
||||
case "application/xml":
|
||||
return getXMLBody(rawData)
|
||||
|
||||
case "text/plain":
|
||||
default:
|
||||
return O.some(rawData)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatter Functions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Prettifies XML string
|
||||
* @param sourceXml The string to format
|
||||
* @returns Indented XML string (uses spaces)
|
||||
*/
|
||||
function prettifyXml(sourceXml: string) {
|
||||
return pipe(
|
||||
O.tryCatch(() => {
|
||||
const xmlDoc = new DOMParser().parseFromString(
|
||||
sourceXml,
|
||||
"application/xml"
|
||||
)
|
||||
|
||||
if (xmlDoc.querySelector("parsererror")) {
|
||||
throw new Error("Unstructured Body")
|
||||
}
|
||||
|
||||
const xsltDoc = new DOMParser().parseFromString(
|
||||
[
|
||||
// describes how we want to modify the XML - indent everything
|
||||
'<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">',
|
||||
' <xsl:strip-space elements="*"/>',
|
||||
' <xsl:template match="para[content-style][not(text())]">', // change to just text() to strip space in text nodes
|
||||
' <xsl:value-of select="normalize-space(.)"/>',
|
||||
" </xsl:template>",
|
||||
' <xsl:template match="node()|@*">',
|
||||
' <xsl:copy><xsl:apply-templates select="node()|@*"/></xsl:copy>',
|
||||
" </xsl:template>",
|
||||
' <xsl:output indent="yes"/>',
|
||||
"</xsl:stylesheet>",
|
||||
].join("\n"),
|
||||
"application/xml"
|
||||
)
|
||||
|
||||
const xsltProcessor = new XSLTProcessor()
|
||||
xsltProcessor.importStylesheet(xsltDoc)
|
||||
const resultDoc = xsltProcessor.transformToDocument(xmlDoc)
|
||||
const resultXml = new XMLSerializer().serializeToString(resultDoc)
|
||||
|
||||
return resultXml
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Prettifies HTML string
|
||||
* @param htmlString The string to format
|
||||
* @returns Indented HTML string (uses spaces)
|
||||
*/
|
||||
function formatHTML(htmlString: string) {
|
||||
const tab = " "
|
||||
let result = ""
|
||||
let indent = ""
|
||||
const emptyTags = [
|
||||
"area",
|
||||
"base",
|
||||
"br",
|
||||
"col",
|
||||
"embed",
|
||||
"hr",
|
||||
"img",
|
||||
"input",
|
||||
"link",
|
||||
"meta",
|
||||
"param",
|
||||
"source",
|
||||
"track",
|
||||
"wbr",
|
||||
]
|
||||
|
||||
const spl = htmlString.split(/>\s*</)
|
||||
spl.forEach((element) => {
|
||||
if (element.match(/^\/\w/)) {
|
||||
indent = indent.substring(tab.length)
|
||||
}
|
||||
|
||||
result += indent + "<" + element + ">\n"
|
||||
|
||||
if (
|
||||
element.match(/^<?\w[^>]*[^/]$/) &&
|
||||
!emptyTags.includes(element.match(/^([a-z]*)/i)?.at(1) || "")
|
||||
) {
|
||||
indent += tab
|
||||
}
|
||||
})
|
||||
|
||||
return result.substring(1, result.length - 2)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import parser from "yargs-parser"
|
||||
import * as cookie from "cookie"
|
||||
import * as O from "fp-ts/Option"
|
||||
import * as S from "fp-ts/string"
|
||||
import { pipe, flow } from "fp-ts/function"
|
||||
import { objHasProperty } from "~/helpers/functional/object"
|
||||
|
||||
export function getCookies(parsedArguments: parser.Arguments) {
|
||||
return pipe(
|
||||
parsedArguments,
|
||||
O.fromPredicate(objHasProperty("cookie", "string")),
|
||||
|
||||
O.map((args) => args.cookie),
|
||||
|
||||
O.alt(() =>
|
||||
pipe(
|
||||
parsedArguments,
|
||||
O.fromPredicate(objHasProperty("b", "string")),
|
||||
O.map((args) => args.b)
|
||||
)
|
||||
),
|
||||
|
||||
O.map(flow(S.replace(/^cookie: /i, ""), cookie.parse)),
|
||||
|
||||
O.getOrElse(() => ({}))
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import parser from "yargs-parser"
|
||||
import { pipe, flow } from "fp-ts/function"
|
||||
import { HoppRESTHeader } from "@hoppscotch/data"
|
||||
import * as A from "fp-ts/Array"
|
||||
import * as S from "fp-ts/string"
|
||||
import * as O from "fp-ts/Option"
|
||||
import { tupleToRecord } from "~/helpers/functional/record"
|
||||
import {
|
||||
objHasProperty,
|
||||
objHasArrayProperty,
|
||||
} from "~/helpers/functional/object"
|
||||
|
||||
const getHeaderPair = flow(
|
||||
S.split(": "),
|
||||
// must have a key and a value
|
||||
O.fromPredicate((arr) => arr.length === 2),
|
||||
O.map(([k, v]) => [k.trim(), v?.trim() ?? ""] as [string, string])
|
||||
)
|
||||
|
||||
export function getHeaders(parsedArguments: parser.Arguments) {
|
||||
let headers: Record<string, string> = {}
|
||||
|
||||
headers = pipe(
|
||||
parsedArguments,
|
||||
// make it an array if not already
|
||||
O.fromPredicate(objHasProperty("H", "string")),
|
||||
O.map((args) => [args.H]),
|
||||
O.alt(() =>
|
||||
pipe(
|
||||
parsedArguments,
|
||||
O.fromPredicate(objHasArrayProperty("H", "string")),
|
||||
O.map((args) => args.H)
|
||||
)
|
||||
),
|
||||
O.map(
|
||||
flow(
|
||||
A.map(getHeaderPair),
|
||||
A.filterMap((a) => a),
|
||||
tupleToRecord
|
||||
)
|
||||
),
|
||||
O.getOrElseW(() => ({}))
|
||||
)
|
||||
|
||||
if (
|
||||
objHasProperty("A", "string")(parsedArguments) ||
|
||||
objHasProperty("user-agent", "string")(parsedArguments)
|
||||
)
|
||||
headers["User-Agent"] = parsedArguments.A ?? parsedArguments["user-agent"]
|
||||
|
||||
const rawContentType =
|
||||
headers["Content-Type"] ?? headers["content-type"] ?? ""
|
||||
|
||||
return {
|
||||
headers,
|
||||
rawContentType,
|
||||
}
|
||||
}
|
||||
|
||||
export const recordToHoppHeaders = (
|
||||
headers: Record<string, string>
|
||||
): HoppRESTHeader[] =>
|
||||
pipe(
|
||||
Object.keys(headers),
|
||||
A.map((key) => ({
|
||||
key,
|
||||
value: headers[key],
|
||||
active: true,
|
||||
})),
|
||||
A.filter(
|
||||
(header) =>
|
||||
header.key !== "Authorization" &&
|
||||
header.key !== "content-type" &&
|
||||
header.key !== "Content-Type"
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,68 @@
|
||||
import parser from "yargs-parser"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as O from "fp-ts/Option"
|
||||
import * as R from "fp-ts/Refinement"
|
||||
import { getDefaultRESTRequest } from "~/newstore/RESTSession"
|
||||
import {
|
||||
objHasProperty,
|
||||
objHasArrayProperty,
|
||||
} from "~/helpers/functional/object"
|
||||
|
||||
const defaultRESTReq = getDefaultRESTRequest()
|
||||
|
||||
const getMethodFromXArg = (parsedArguments: parser.Arguments) =>
|
||||
pipe(
|
||||
parsedArguments,
|
||||
O.fromPredicate(objHasProperty("X", "string")),
|
||||
O.map((args) => args.X.trim()),
|
||||
O.chain((xarg) =>
|
||||
pipe(
|
||||
O.fromNullable(
|
||||
xarg.match(/GET|POST|PUT|PATCH|DELETE|HEAD|CONNECT|OPTIONS|TRACE/i)
|
||||
),
|
||||
O.alt(() => O.fromNullable(xarg.match(/[a-zA-Z]+/)))
|
||||
)
|
||||
),
|
||||
O.map((method) => method[0])
|
||||
)
|
||||
|
||||
const getMethodByDeduction = (parsedArguments: parser.Arguments) => {
|
||||
if (
|
||||
pipe(
|
||||
objHasProperty("T", "string"),
|
||||
R.or(objHasProperty("upload-file", "string"))
|
||||
)(parsedArguments)
|
||||
)
|
||||
return O.some("put")
|
||||
else if (
|
||||
pipe(
|
||||
objHasProperty("I", "boolean"),
|
||||
R.or(objHasProperty("head", "boolean"))
|
||||
)(parsedArguments)
|
||||
)
|
||||
return O.some("head")
|
||||
else if (objHasProperty("G", "boolean")(parsedArguments)) return O.some("get")
|
||||
else if (
|
||||
pipe(
|
||||
objHasProperty("d", "string"),
|
||||
R.or(objHasArrayProperty("d", "string")),
|
||||
R.or(objHasProperty("F", "string")),
|
||||
R.or(objHasArrayProperty("F", "string"))
|
||||
)(parsedArguments)
|
||||
)
|
||||
return O.some("POST")
|
||||
else return O.none
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method type from X argument in curl string or
|
||||
* find it out through other arguments
|
||||
* @param parsedArguments Parsed Arguments object
|
||||
* @returns Method string
|
||||
*/
|
||||
export const getMethod = (parsedArguments: parser.Arguments): string =>
|
||||
pipe(
|
||||
getMethodFromXArg(parsedArguments),
|
||||
O.alt(() => getMethodByDeduction(parsedArguments)),
|
||||
O.getOrElse(() => defaultRESTReq.method)
|
||||
)
|
||||
@@ -0,0 +1,70 @@
|
||||
import { pipe, flow } from "fp-ts/function"
|
||||
import * as S from "fp-ts/string"
|
||||
import * as O from "fp-ts/Option"
|
||||
import * as A from "fp-ts/Array"
|
||||
|
||||
const replaceables: { [key: string]: string } = {
|
||||
"--request": "-X",
|
||||
"--header": "-H",
|
||||
"--url": "",
|
||||
"--form": "-F",
|
||||
"--data-raw": "--data",
|
||||
"--data": "-d",
|
||||
"--data-ascii": "-d",
|
||||
"--data-binary": "-d",
|
||||
"--user": "-u",
|
||||
"--get": "-G",
|
||||
}
|
||||
|
||||
const paperCuts = flow(
|
||||
// remove '\' and newlines
|
||||
S.replace(/ ?\\ ?$/gm, " "),
|
||||
S.replace(/\n/g, " "),
|
||||
// remove all $ symbols from start of argument values
|
||||
S.replace(/\$'/g, "'"),
|
||||
S.replace(/\$"/g, '"'),
|
||||
S.trim
|
||||
)
|
||||
|
||||
// replace --zargs option with -Z
|
||||
const replaceLongOptions = (curlCmd: string) =>
|
||||
pipe(Object.keys(replaceables), A.reduce(curlCmd, replaceFunction))
|
||||
|
||||
const replaceFunction = (curlCmd: string, r: string) =>
|
||||
pipe(
|
||||
curlCmd,
|
||||
O.fromPredicate(
|
||||
() => r.includes("data") || r.includes("form") || r.includes("header")
|
||||
),
|
||||
O.map(S.replace(RegExp(`[ \t]${r}(["' ])`, "g"), ` ${replaceables[r]}$1`)),
|
||||
O.alt(() =>
|
||||
pipe(
|
||||
curlCmd,
|
||||
S.replace(RegExp(`[ \t]${r}(["' ])`), ` ${replaceables[r]}$1`),
|
||||
O.of
|
||||
)
|
||||
),
|
||||
O.getOrElse(() => "")
|
||||
)
|
||||
|
||||
// yargs parses -XPOST as separate arguments. just prescreen for it.
|
||||
const prescreenXArgs = flow(
|
||||
S.replace(
|
||||
/ -X(GET|POST|PUT|PATCH|DELETE|HEAD|CONNECT|OPTIONS|TRACE)/,
|
||||
" -X $1"
|
||||
),
|
||||
S.trim
|
||||
)
|
||||
|
||||
/**
|
||||
* Sanitizes and makes curl string processable
|
||||
* @param curlCommand Raw curl command string
|
||||
* @returns Processed curl command string
|
||||
*/
|
||||
export const preProcessCurlCommand = (curlCommand: string) =>
|
||||
pipe(
|
||||
curlCommand,
|
||||
O.fromPredicate((curlCmd) => curlCmd.length > 0),
|
||||
O.map(flow(paperCuts, replaceLongOptions, prescreenXArgs)),
|
||||
O.getOrElse(() => "")
|
||||
)
|
||||
@@ -0,0 +1,43 @@
|
||||
import { pipe, flow } from "fp-ts/function"
|
||||
import * as O from "fp-ts/Option"
|
||||
import * as A from "fp-ts/Array"
|
||||
import * as Sep from "fp-ts/Separated"
|
||||
import { HoppRESTParam } from "@hoppscotch/data"
|
||||
|
||||
const isDangling = ([, value]: [string, string]) => !value
|
||||
|
||||
/**
|
||||
* Converts queries to HoppRESTParam format and separates dangling ones
|
||||
* @param params Array of key value pairs of queries
|
||||
* @returns Object containing separated queries and dangling queries
|
||||
*/
|
||||
export function getQueries(params: Array<[string, string]>): {
|
||||
queries: Array<HoppRESTParam>
|
||||
danglingParams: Array<string>
|
||||
} {
|
||||
return pipe(
|
||||
params,
|
||||
O.of,
|
||||
O.map(
|
||||
flow(
|
||||
A.partition(isDangling),
|
||||
Sep.bimap(
|
||||
A.map(([key, value]) => ({
|
||||
key,
|
||||
value,
|
||||
active: true,
|
||||
})),
|
||||
A.map(([key]) => key)
|
||||
),
|
||||
(sep) => ({
|
||||
queries: sep.left,
|
||||
danglingParams: sep.right,
|
||||
})
|
||||
)
|
||||
),
|
||||
O.getOrElseW(() => ({
|
||||
queries: [],
|
||||
danglingParams: [],
|
||||
}))
|
||||
)
|
||||
}
|
||||
112
packages/hoppscotch-common/src/helpers/curl/sub_helpers/url.ts
Normal file
112
packages/hoppscotch-common/src/helpers/curl/sub_helpers/url.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import parser from "yargs-parser"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as O from "fp-ts/Option"
|
||||
import * as A from "fp-ts/Array"
|
||||
import { getDefaultRESTRequest } from "~/newstore/RESTSession"
|
||||
import { stringArrayJoin } from "~/helpers/functional/array"
|
||||
|
||||
const defaultRESTReq = getDefaultRESTRequest()
|
||||
|
||||
const getProtocolFromURL = (url: string) =>
|
||||
pipe(
|
||||
// get the base URL
|
||||
/^([^\s:@]+:[^\s:@]+@)?([^:/\s]+)([:]*)/.exec(url),
|
||||
O.fromNullable,
|
||||
O.filter((burl) => burl.length > 1),
|
||||
O.map((burl) => burl[2]),
|
||||
// set protocol to http for local URLs
|
||||
O.map((burl) =>
|
||||
burl === "localhost" ||
|
||||
burl === "2130706433" ||
|
||||
/127(\.0){0,2}\.1/.test(burl) ||
|
||||
/0177(\.0){0,2}\.1/.test(burl) ||
|
||||
/0x7f(\.0){0,2}\.1/.test(burl) ||
|
||||
/192\.168(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){2}/.test(burl) ||
|
||||
/10(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}/.test(burl)
|
||||
? "http://" + url
|
||||
: "https://" + url
|
||||
)
|
||||
)
|
||||
|
||||
/**
|
||||
* Checks if the URL is valid using the URL constructor
|
||||
* @param urlString URL string (with protocol)
|
||||
* @returns boolean whether the URL is valid using the inbuilt URL class
|
||||
*/
|
||||
const isURLValid = (urlString: string) =>
|
||||
pipe(
|
||||
O.tryCatch(() => new URL(urlString)),
|
||||
O.isSome
|
||||
)
|
||||
|
||||
/**
|
||||
* Checks and returns URL object for the valid URL
|
||||
* @param urlText Raw URL string provided by argument parser
|
||||
* @returns Option of URL object
|
||||
*/
|
||||
const parseURL = (urlText: string | number) =>
|
||||
pipe(
|
||||
urlText,
|
||||
O.fromNullable,
|
||||
// preprocess url string
|
||||
O.map((u) => u.toString().replaceAll(/[^a-zA-Z0-9_\-./?&=:@%+#,;\s]/g, "")),
|
||||
O.filter((u) => u.length > 0),
|
||||
O.chain((u) =>
|
||||
pipe(
|
||||
u,
|
||||
// check if protocol is available
|
||||
O.fromPredicate(
|
||||
(url: string) => /^[^:\s]+(?=:\/\/)/.exec(url) !== null
|
||||
),
|
||||
O.alt(() => getProtocolFromURL(u))
|
||||
)
|
||||
),
|
||||
O.filter(isURLValid),
|
||||
O.map((u) => new URL(u))
|
||||
)
|
||||
|
||||
/**
|
||||
* Processes URL string and returns the URL object
|
||||
* @param parsedArguments Parsed Arguments object
|
||||
* @returns URL object
|
||||
*/
|
||||
export function getURLObject(parsedArguments: parser.Arguments) {
|
||||
return pipe(
|
||||
// contains raw url strings
|
||||
parsedArguments._.slice(1),
|
||||
A.findFirstMap(parseURL),
|
||||
// no url found
|
||||
O.getOrElse(() => new URL(defaultRESTReq.endpoint))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins dangling params to origin
|
||||
* @param urlObject URL object containing origin and pathname
|
||||
* @param danglingParams Keys of params with empty values
|
||||
* @returns origin string concatenated with dangling paramas
|
||||
*/
|
||||
export function concatParams(urlObject: URL, danglingParams: string[]) {
|
||||
return pipe(
|
||||
O.Do,
|
||||
|
||||
O.bind("originString", () =>
|
||||
pipe(
|
||||
urlObject.origin,
|
||||
O.fromPredicate((h) => h !== "")
|
||||
)
|
||||
),
|
||||
|
||||
O.map(({ originString }) =>
|
||||
pipe(
|
||||
danglingParams,
|
||||
O.fromPredicate((dp) => dp.length > 0),
|
||||
O.map(stringArrayJoin("&")),
|
||||
O.map((h) => originString + (urlObject.pathname || "") + "?" + h),
|
||||
O.getOrElse(() => originString + (urlObject.pathname || ""))
|
||||
)
|
||||
),
|
||||
|
||||
O.getOrElse(() => defaultRESTReq.endpoint)
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user