diff --git a/app.go b/app.go index 72cab12..7e23c11 100644 --- a/app.go +++ b/app.go @@ -51,12 +51,49 @@ func MakeRequest(c *http.Client, return body, resp, nil } -type RequestResult struct { +func HeadersToStr(h *http.Header) string { + headersArr := []string{} + for k, v := range *h { + vals := strings.Join(v, "; ") + headersArr = append(headersArr, k+": "+vals) + } + + result := strings.Join(headersArr, "\n") + return result +} + +type RequestFE struct { Body string HeadersStr string Error string } +type RequestResult struct { + Method string + URL string + ReqHeaders string + RequestBody string + Body string + HeadersStr string + Error string +} + +func (a *App) RunCurl(curl string) RequestResult { + res := RequestResult{} + r, ok := Parse(curl) + if !ok { + res.Error = "Unable to parse curl" + return res + } + + res = a.MakeRequest(r.Url, r.Method, r.Body, r.Header.ToString()) + res.ReqHeaders = r.Header.ToString() + res.Method = r.Method + res.URL = r.Url + res.RequestBody = r.Body + return res +} + // Greet returns a greeting for the given name func (a *App) MakeRequest( urlIn string, @@ -64,7 +101,11 @@ func (a *App) MakeRequest( body string, headers string, ) RequestResult { - result := RequestResult{} + result := RequestResult{ + URL: urlIn, + Method: method, + RequestBody: body, + } rbody := bytes.NewBuffer([]byte(body)) r, err := http.NewRequest(method, urlIn, rbody) if err != nil { @@ -86,13 +127,7 @@ func (a *App) MakeRequest( return result } - headersArr := []string{} - for k, v := range httpResp.Header { - vals := strings.Join(v, "; ") - headersArr = append(headersArr, k+": "+vals) - } - - result.HeadersStr = strings.Join(headersArr, "\n") + result.HeadersStr = HeadersToStr(&httpResp.Header) b := bytes.NewBuffer(make([]byte, 0, len(res))) err = json.Indent(b, res, "\n", " ") if err != nil { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ec28100..ebab3a9 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,12 +1,15 @@ import {useState} from 'react'; import './App.css'; -import {MakeRequest} from "../wailsjs/go/main/App"; +import {MakeRequest, RunCurl} from "../wailsjs/go/main/App"; import Button from '@mui/material/Button'; import TextField from '@mui/material/TextField'; import Divider from '@mui/material/Divider'; import Grid from '@mui/material/Unstable_Grid2'; // Grid version 2 -import MenuItem from '@mui/material/MenuItem'; // Grid version 2 -import Select from '@mui/material/Select'; // Grid version 2 +import MenuItem from '@mui/material/MenuItem'; +import Modal from '@mui/material/Modal'; +import Select from '@mui/material/Select'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; import { ThemeProvider, createTheme } from '@mui/material/styles'; import { Experimental_CssVarsProvider as CssVarsProvider, @@ -22,29 +25,80 @@ const darkTheme = createTheme({ function App() { const [resultText, setResultText] = useState(""); + const [curlBody, setCurlBody] = useState(""); const [reqMethod, setReqMethod] = useState("GET"); const [errorText, setErrorText] = useState(""); const [reqBody, setReqBody] = useState(""); const [reqHeaders, setReqHeaders] = useState(""); const [resultHeader, setResultHeader] = useState(""); const [url, setURL] = useState(''); + const [open, setOpen] = useState(false); const updateURL = (e: any) => setURL(e.target.value); const updateResultText = (result: any) => { - setResultHeader(result.HeadersStr) + setResultHeader(result.HeadersStr) setResultText(result.Body) setErrorText(result.Error) }; + const updateCurlResult = (result: any) => { + setURL(result.URL) + setReqBody(result.RequestBody) + setReqHeaders(result.ReqHeaders) + setReqMethod(result.Method) + handleClose() + } + const handleOpen = () => setOpen(true); + const handleClose = () => setOpen(false); + const style = { + position: 'absolute' as 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: 800, + bgcolor: 'background.paper', + p: 4, + }; const updateMethod = (e: any) => setReqMethod(e.target.value); const updateBody = (e:any) => setReqBody(e.target.value) const updateReqHeaders = (e:any) => setReqHeaders(e.target.value) + const updateCurlBody = (e:any) => setCurlBody(e.target.value) function makeRequest() { MakeRequest(url, reqMethod, reqBody, reqHeaders).then(updateResultText); } + + function importCurl() { + console.log("importing curl") + RunCurl(curlBody).then((result: any)=>{ + updateResultText(result) + updateCurlResult(result) + }) + } return ( + + + + Enter Curl + + + + + + + + + + + + - - + @@ -63,7 +117,7 @@ function App() { Request Headers - diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts index 5a41220..525dc47 100755 --- a/frontend/wailsjs/go/main/App.d.ts +++ b/frontend/wailsjs/go/main/App.d.ts @@ -3,3 +3,5 @@ import {main} from '../models'; export function MakeRequest(arg1:string,arg2:string,arg3:string,arg4:string):Promise; + +export function RunCurl(arg1:string):Promise; diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js index 533085a..4b3dbea 100755 --- a/frontend/wailsjs/go/main/App.js +++ b/frontend/wailsjs/go/main/App.js @@ -5,3 +5,7 @@ export function MakeRequest(arg1, arg2, arg3, arg4) { return window['go']['main']['App']['MakeRequest'](arg1, arg2, arg3, arg4); } + +export function RunCurl(arg1) { + return window['go']['main']['App']['RunCurl'](arg1); +} diff --git a/go.mod b/go.mod index 19e5157..55d2337 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,10 @@ module changeme go 1.18 -require github.com/wailsapp/wails/v2 v2.5.1 +require ( + github.com/mattn/go-shellwords v1.0.12 + github.com/wailsapp/wails/v2 v2.5.1 +) require ( github.com/bep/debounce v1.2.1 // indirect diff --git a/go.sum b/go.sum index aac8803..6f12fff 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,8 @@ github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHR github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 h1:acNfDZXmm28D2Yg/c3ALnZStzNaZMSagpbr96vY6Zjc= github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= diff --git a/parse_curl.go b/parse_curl.go new file mode 100644 index 0000000..f41b7ce --- /dev/null +++ b/parse_curl.go @@ -0,0 +1,192 @@ +package main + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "strings" + + "github.com/mattn/go-shellwords" +) + +type Header map[string]string + +func (h *Header) ToString() string { + headersArr := []string{} + for k, v := range *h { + headersArr = append(headersArr, k+": "+v) + } + + return strings.Join(headersArr, "\n") +} + +type Request struct { + Method string `json:"method"` + Url string `json:"url"` + Header Header `json:"header"` + Body string `json:"body"` +} + +func (r *Request) ToJson(format bool) string { + buffer := &bytes.Buffer{} + encoder := json.NewEncoder(buffer) + encoder.SetEscapeHTML(false) + + if format { + encoder.SetIndent("", " ") + } + _ = encoder.Encode(r) + + return string(buffer.Bytes()) +} + +func Parse(curl string) (*Request, bool) { + if strings.Index(curl, "curl ") != 0 { + return nil, false + } + + // https://github.com/mattn/go-shellwords + // https://github.com/tj/parse-curl.js + args, err := shellwords.Parse(curl) + if err != nil { + return nil, false + } + + args = rewrite(args) + request := &Request{Method: "GET", Header: Header{}} + state := "" + + for _, arg := range args { + switch true { + case isUrl(arg): + request.Url = arg + + case arg == "-A" || arg == "--user-agent": + state = "user-agent" + + case arg == "-H" || arg == "--header": + state = "header" + + case arg == "-d" || arg == "--data" || arg == "--data-ascii" || arg == "--data-raw": + state = "data" + + case arg == "-u" || arg == "--user": + state = "user" + + case arg == "-I" || arg == "--head": + request.Method = "HEAD" + + case arg == "-X" || arg == "--request": + state = "method" + + case arg == "-b" || arg == "--cookie": + state = "cookie" + + case len(arg) > 0: + switch state { + case "header": + fields := parseField(arg) + request.Header[fields[0]] = strings.TrimSpace(fields[1]) + state = "" + + case "user-agent": + request.Header["User-Agent"] = arg + state = "" + + case "data": + if request.Method == "GET" || request.Method == "HEAD" { + request.Method = "POST" + } + + if !hasContentType(*request) { + request.Header["Content-Type"] = "application/x-www-form-urlencoded" + } + + if len(request.Body) == 0 { + request.Body = arg + } else { + request.Body = request.Body + "&" + arg + } + + state = "" + + case "user": + request.Header["Authorization"] = "Basic " + base64.StdEncoding.EncodeToString([]byte(arg)) + state = "" + + case "method": + request.Method = arg + state = "" + + case "cookie": + request.Header["Cookie"] = arg + state = "" + + default: + break + } + } + + } + + // format json body + if value, ok := request.Header["Content-Type"]; ok && value == "application/json" { + decoder := json.NewDecoder(strings.NewReader(request.Body)) + jsonData := make(map[string]interface{}) + if err := decoder.Decode(&jsonData); err == nil { + buffer := &bytes.Buffer{} + encoder := json.NewEncoder(buffer) + encoder.SetEscapeHTML(false) + if err = encoder.Encode(jsonData); err == nil { + request.Body = strings.ReplaceAll(buffer.String(), "\n", "") + } + } + } + + return request, true +} + +func rewrite(args []string) []string { + res := make([]string, 0) + + for _, arg := range args { + + arg = strings.TrimSpace(arg) + + if arg == "\n" { + continue + } + + if strings.Contains(arg, "\n") { + arg = strings.ReplaceAll(arg, "\n", "") + } + + // split request method + if strings.Index(arg, "-X") == 0 { + res = append(res, arg[0:2]) + res = append(res, arg[2:]) + } else { + res = append(res, arg) + } + } + + return res +} + +func isUrl(url string) bool { + return strings.HasPrefix(url, "http://") || + strings.HasPrefix(url, "https://") +} + +func parseField(arg string) []string { + index := strings.Index(arg, ":") + return []string{arg[0:index], arg[index+2:]} +} + +func hasContentType(request Request) bool { + if _, ok := request.Header["Content-Type"]; ok { + return true + } + + return false +}