diff --git a/cmd/decode.go b/cmd/decode.go new file mode 100644 index 0000000..ab53b62 --- /dev/null +++ b/cmd/decode.go @@ -0,0 +1,85 @@ +package cmd + +import ( + "bufio" + "bytes" + "compress/zlib" + "encoding/base64" + "fmt" + "io" + "net/url" + "os" + "path" + "strings" + + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +func Decode(_ *cobra.Command, args []string) { + input := args[0] + if input == "-" { + reader := bufio.NewReader(os.Stdin) + DecodeFromReader(reader) + } else { + DecodeFromInput(input) + } +} + +func DecodeFromReader(reader io.Reader) { + text, err := GetTextFromReader(reader) + if err != nil { + exit(err) + } + result, err := DecodeInput(text) + if err != nil { + exit(err) + } + fmt.Println(result) +} + +func DecodeFromInput(input string) { + result, err := DecodeInput(input) + if err != nil { + exit(err) + } + fmt.Println(result) +} + +// takes a string encoded using deflate + base64 format and returns a decoded string +func DecodeInput(input string) (string, error) { + // special case to extract the encoded diagram from a URL (GET request) + // expected format is: https://kroki.io/diagram/format/encoded + if strings.HasPrefix(input, "https://") || strings.HasPrefix(input, "http://") { + inputUrl, _ := getUrl(input) + if inputUrl != nil { + // get the last part of the URL + input = path.Base(inputUrl.Path) + } + } + result, err := base64.URLEncoding.DecodeString(input) + if err != nil { + return "", errors.Wrap(err, "fail to decode the input") + } + reader, err := zlib.NewReader(bytes.NewReader(result)) + if err != nil { + return "", errors.Wrap(err, "fail to create the reader") + } + out := new(strings.Builder) + _, _ = io.Copy(out, reader) + return out.String(), nil +} + +func getUrl(input string) (*url.URL, error) { + _, err := url.ParseRequestURI(input) + if err != nil { + return nil, err + } + + u, err := url.Parse(input) + if err != nil || u.Scheme == "" || u.Host == "" { + return u, errors.New("invalid URL") + } + + return u, nil +} diff --git a/cmd/decode_test.go b/cmd/decode_test.go new file mode 100644 index 0000000..3463d13 --- /dev/null +++ b/cmd/decode_test.go @@ -0,0 +1,36 @@ +package cmd + +import ( + "bytes" + "testing" +) + +func TestDecodeFromReader(t *testing.T) { + cases := []struct { + input string + expected string + }{ + { + input: "eNpKyUwvSizIUHBXqPZIzcnJ17ULzy_KSakFBAAA__9sQAjG", + expected: "digraph G {Hello->World}\n", + }, + { + input: "https://kroki.io/graphviz/svg/eNpKyUwvSizIUHBXqPZIzcnJ17ULzy_KSakFBAAA__9sQAjG", + expected: "digraph G {Hello->World}\n", + }, + { + input: "http://localhost:8000/graphviz/svg/eNpKyUwvSizIUHBXqPZIzcnJ17ULzy_KSakFBAAA__9sQAjG", + expected: "digraph G {Hello->World}\n", + }, + } + for _, c := range cases { + buf := bytes.NewBuffer([]byte("")) + buf.Write([]byte(c.input)) + result := CaptureOutput(func() { + DecodeFromReader(buf) + }) + if result != c.expected { + t.Errorf("DecodeFromReader error\nexpected: %s\nactual: %s", c.expected, result) + } + } +} diff --git a/cmd/root.go b/cmd/root.go index a3eba38..d3f38aa 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -30,6 +30,13 @@ var encodeCmd = &cobra.Command{ Run: Encode, } +var decodeCmd = &cobra.Command{ + Use: "decode input", + Short: "Decode an encoded (deflate + base64) diagram", + Args: cobra.ExactArgs(1), + Run: Decode, +} + var versionCmd = &cobra.Command{ Use: "version", Short: "Print the version of kroki", @@ -56,6 +63,7 @@ func init() { RootCmd.AddCommand(versionCmd) RootCmd.AddCommand(convertCmd) RootCmd.AddCommand(encodeCmd) + RootCmd.AddCommand(decodeCmd) SetupConfig() diff --git a/go.sum b/go.sum index c5b5d8c..9c0e687 100644 --- a/go.sum +++ b/go.sum @@ -40,6 +40,8 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuzutech/kroki-go v0.5.0 h1:XWmxmG22/7PXsqt1TihYjYqmAcs3AO3JtvuwGF20Ehs= github.com/yuzutech/kroki-go v0.5.0/go.mod h1:Cys0gjXdlViePdrZ4AKMUyX4yVIAoIhMBhvpYPJahrk= +github.com/yuzutech/kroki-go v0.5.0 h1:XWmxmG22/7PXsqt1TihYjYqmAcs3AO3JtvuwGF20Ehs= +github.com/yuzutech/kroki-go v0.5.0/go.mod h1:Cys0gjXdlViePdrZ4AKMUyX4yVIAoIhMBhvpYPJahrk= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=