From 973fbb09254e30709a45ae9c7b08a710cb822fd8 Mon Sep 17 00:00:00 2001 From: Gowtham Munukutla Date: Wed, 24 Feb 2021 18:39:25 +0530 Subject: [PATCH] Disallow operating on binary files in gist --- go.mod | 1 + go.sum | 2 ++ pkg/cmd/gist/create/create.go | 15 ++++++++++++-- pkg/cmd/gist/edit/edit.go | 9 ++++++++ pkg/cmd/gist/edit/edit_test.go | 1 - pkg/cmd/gist/shared/shared.go | 32 +++++++++++++++++++++++++++++ pkg/cmd/gist/shared/shared_test.go | 31 ++++++++++++++++++++++++++++ pkg/cmd/gist/view/view.go | 33 +++++++++++++++++++++++++++++- 8 files changed, 120 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 2ddd3697b..c2cc17338 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/cli/safeexec v1.0.0 github.com/cpuguy83/go-md2man/v2 v2.0.0 github.com/enescakir/emoji v1.0.0 + github.com/gabriel-vasile/mimetype v1.1.2 github.com/google/go-cmp v0.5.2 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/go-version v1.2.1 diff --git a/go.sum b/go.sum index dfbd5af86..ad31293d6 100644 --- a/go.sum +++ b/go.sum @@ -74,6 +74,8 @@ github.com/enescakir/emoji v1.0.0/go.mod h1:Bt1EKuLnKDTYpLALApstIkAjdDrS/8IAgTkK github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/gabriel-vasile/mimetype v1.1.2 h1:gaPnPcNor5aZSVCJVSGipcpbgMWiAAj9z182ocSGbHU= +github.com/gabriel-vasile/mimetype v1.1.2/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= diff --git a/pkg/cmd/gist/create/create.go b/pkg/cmd/gist/create/create.go index fe6fac748..901074734 100644 --- a/pkg/cmd/gist/create/create.go +++ b/pkg/cmd/gist/create/create.go @@ -30,8 +30,7 @@ type CreateOptions struct { Public bool Filenames []string FilenameOverride string - - WebMode bool + WebMode bool HttpClient func() (*http.Client, error) } @@ -164,6 +163,8 @@ func processFiles(stdin io.ReadCloser, filenameOverride string, filenames []stri var filename string var content []byte var err error + var isBinary bool + if f == "-" { if filenameOverride != "" { filename = filenameOverride @@ -180,6 +181,16 @@ func processFiles(stdin io.ReadCloser, filenameOverride string, filenames []stri if err != nil { return fs, fmt.Errorf("failed to read file %s: %w", f, err) } + + isBinary, err = shared.IsBinaryContents(content) + if err != nil { + return nil, err + } + + if isBinary { + return nil, fmt.Errorf("binary file not supported") + } + filename = path.Base(f) } diff --git a/pkg/cmd/gist/edit/edit.go b/pkg/cmd/gist/edit/edit.go index 27df4a104..39fe08674 100644 --- a/pkg/cmd/gist/edit/edit.go +++ b/pkg/cmd/gist/edit/edit.go @@ -148,6 +148,15 @@ func editRun(opts *EditOptions) error { return fmt.Errorf("gist has no file %q", filename) } + isBinary, err := shared.IsBinaryContents([]byte(gist.Files[filename].Content)) + if err != nil { + return err + } + + if isBinary { + return fmt.Errorf("Editing binary files not supported") + } + editorCommand, err := cmdutil.DetermineEditor(opts.Config) if err != nil { return err diff --git a/pkg/cmd/gist/edit/edit_test.go b/pkg/cmd/gist/edit/edit_test.go index 73a53e523..71250d5e5 100644 --- a/pkg/cmd/gist/edit/edit_test.go +++ b/pkg/cmd/gist/edit/edit_test.go @@ -301,7 +301,6 @@ func Test_editRun(t *testing.T) { io.SetStdoutTTY(!tt.nontty) io.SetStdinTTY(!tt.nontty) tt.opts.IO = io - tt.opts.Selector = "1234" tt.opts.Config = func() (config.Config, error) { diff --git a/pkg/cmd/gist/shared/shared.go b/pkg/cmd/gist/shared/shared.go index 56bb3bb20..97539a78b 100644 --- a/pkg/cmd/gist/shared/shared.go +++ b/pkg/cmd/gist/shared/shared.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/gabriel-vasile/mimetype" "net/http" "net/url" "strings" @@ -147,3 +148,34 @@ pagination: return gists, nil } + +func IsBinaryFile(file string) (bool, error) { + detectedMime, err := mimetype.DetectFile(file) + if err != nil { + return false, err + } + + isBinary := true + + for mime := detectedMime; mime != nil; mime = mime.Parent() { + if mime.Is("text/plain") { + isBinary = false + } + } + + return isBinary, nil +} + +func IsBinaryContents(contents []byte) (bool, error) { + detectedMime := mimetype.Detect(contents) + + isBinary := true + + for mime := detectedMime; mime != nil; mime = mime.Parent() { + if mime.Is("text/plain") { + isBinary = false + } + } + + return isBinary, nil +} diff --git a/pkg/cmd/gist/shared/shared_test.go b/pkg/cmd/gist/shared/shared_test.go index 0f80db690..23dcaa64e 100644 --- a/pkg/cmd/gist/shared/shared_test.go +++ b/pkg/cmd/gist/shared/shared_test.go @@ -50,3 +50,34 @@ func Test_GetGistIDFromURL(t *testing.T) { }) } } + +func TestIsBinaryContents(t *testing.T) { + tests := []struct { + fileContent []byte + want bool + }{ + { + want: false, + fileContent: []byte("package main"), + }, + { + want: true, + fileContent: []byte{239, 191, 189, 239, 191, 189, 239, 191, 189, 239, + 191, 189, 239, 191, 189, 16, 74, 70, 73, 70, 239, 191, 189, 1, 1, 1, + 1, 44, 1, 44, 239, 191, 189, 239, 191, 189, 239, 191, 189, 239, 191, + 189, 239, 191, 189, 67, 239, 191, 189, 8, 6, 6, 7, 6, 5, 8, 7, 7, 7, + 9, 9, 8, 10, 12, 20, 10, 12, 11, 11, 12, 25, 18, 19, 15, 20, 29, 26, + 31, 30, 29, 26, 28, 28, 32, 36, 46, 39, 32, 34, 44, 35, 28, 28, 40, + 55, 41, 44, 48, 49, 52, 52, 52, 31, 39, 57, 61, 56, 50, 60, 46, 51, + 52, 50, 239, 191, 189, 239, 191, 189, 239, 191, 189, 67, 1, 9, 9, 9, 12}, + }, + } + + for _, tt := range tests { + isBinary, err := IsBinaryContents(tt.fileContent) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, tt.want, isBinary) + } +} diff --git a/pkg/cmd/gist/view/view.go b/pkg/cmd/gist/view/view.go index 176d565ef..8e665ea4f 100644 --- a/pkg/cmd/gist/view/view.go +++ b/pkg/cmd/gist/view/view.go @@ -118,6 +118,15 @@ func viewRun(opts *ViewOptions) error { defer opts.IO.StopPager() render := func(gf *shared.GistFile) error { + isBinary, err := shared.IsBinaryContents([]byte(gf.Content)) + if err != nil { + return err + } + + if isBinary { + gf.Content = "Skipping rendering binary content..." + } + if strings.Contains(gf.Type, "markdown") && !opts.Raw { rendered, err := markdown.Render(gf.Content, markdownStyle, "") if err != nil { @@ -134,6 +143,7 @@ func viewRun(opts *ViewOptions) error { _, err := fmt.Fprint(opts.IO.Out, "\n") return err } + return nil } @@ -142,6 +152,16 @@ func viewRun(opts *ViewOptions) error { if !ok { return fmt.Errorf("gist has no such file: %q", opts.Filename) } + + isBinary, err := shared.IsBinaryContents([]byte(gistFile.Content)) + if err != nil { + return err + } + + if isBinary { + return fmt.Errorf("Error: file contents is binary") + } + return render(gistFile) } @@ -154,8 +174,19 @@ func viewRun(opts *ViewOptions) error { for fn := range gist.Files { filenames = append(filenames, fn) } - sort.Strings(filenames) + if len(filenames) == 1 { + isBinary, err := shared.IsBinaryContents([]byte(gist.Files[filenames[0]].Content)) + if err != nil { + return err + } + + if isBinary { + return fmt.Errorf("Error: file contents is binary") + } + } + + sort.Strings(filenames) if opts.ListFiles { for _, fn := range filenames { fmt.Fprintln(opts.IO.Out, fn)