From 50becb1ca5b775f86829fb66b104a447a821eec0 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Wed, 24 May 2023 23:20:12 -0500 Subject: [PATCH 1/6] Update parse.go --- claat/parser/gdoc/parse.go | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/claat/parser/gdoc/parse.go b/claat/parser/gdoc/parse.go index e837a7b59..343f3a18e 100644 --- a/claat/parser/gdoc/parse.go +++ b/claat/parser/gdoc/parse.go @@ -697,12 +697,32 @@ func image(ds *docState) nodes.Node { errorAlt = "The domain of the requested iframe (" + u.Hostname() + ") has not been whitelisted." fmt.Fprint(os.Stderr, errorAlt+"\n") } + + var imageBytes []byte + var imageSrc string s := nodeAttr(ds.cur, "src") if s == "" { return nil + } else if strings.HasPrefix(s, "data:") { + _, data, ok := strings.Cut(s, ",") + if !ok { + fmt.Fprint(os.Stderr, "Failed to decode data URL: "+s+" \n") + return nil + } + b, err := base64.StdEncoding.DecodeString(data) + if err != nil { + fmt.Fprint(os.Stderr, "Failed to decode data URL: "+s+"\n"+err.Error()+"\n") + return nil + } + imageSrc = "" + imageBytes = b + } else { + imageSrc = s + imageBytes = []byte{} } n := nodes.NewImageNode(nodes.NewImageNodeOptions{ - Src: s, + Src: imageSrc, + Bytes: imageBytes, Width: styleFloatValue(ds.cur, "width"), }) n.MutateBlock(findBlockParent(ds.cur)) From 976cba94974b7d1395be57061e142346de38b2f4 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Wed, 24 May 2023 23:21:37 -0500 Subject: [PATCH 2/6] Update parse.go --- claat/parser/gdoc/parse.go | 1 + 1 file changed, 1 insertion(+) diff --git a/claat/parser/gdoc/parse.go b/claat/parser/gdoc/parse.go index 343f3a18e..53843a5b7 100644 --- a/claat/parser/gdoc/parse.go +++ b/claat/parser/gdoc/parse.go @@ -16,6 +16,7 @@ package gdoc import ( "bytes" + "encoding/base64" "fmt" "io" "net/url" From 3d4299ea4135d326f0fa53c08da3bf7338a973b2 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Wed, 24 May 2023 23:26:18 -0500 Subject: [PATCH 3/6] Update parse_test.go --- claat/parser/gdoc/parse_test.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/claat/parser/gdoc/parse_test.go b/claat/parser/gdoc/parse_test.go index 6a0ca2363..ec5b37619 100644 --- a/claat/parser/gdoc/parse_test.go +++ b/claat/parser/gdoc/parse_test.go @@ -16,6 +16,7 @@ package gdoc import ( "bytes" + "encoding/base64" "io" "reflect" "strings" @@ -295,6 +296,9 @@ func TestParseDoc(t *testing.T) {

[[import shared]]

alt text +

JPEG

+

GIF

+

PNG

icon.

https://www.youtube.com/watch?v=vid

@@ -405,6 +409,33 @@ func TestParseDoc(t *testing.T) { para.MutateBlock(true) content.Append(para) + bytes, _ := base64.StdEncoding.DecodeString("/9j/2wBDAP//////////////////////////////////////////////////////////////////////////////////////wAALCAABAAEBAREA/8QAFAABAAAAAAAAAAAAAAAAAAAAA//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAD8AN//Z") + img = nodes.NewImageNode(nodes.NewImageNodeOptions{ + Bytes: bytes, + Alt: "JPEG", + }) + para = nodes.NewListNode(img) + para.MutateBlock(true) + content.Append(para) + + bytes, _ = base64.StdEncoding.DecodeString("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7") + img = nodes.NewImageNode(nodes.NewImageNodeOptions{ + Bytes: bytes, + Alt: "GIF", + }) + para = nodes.NewListNode(img) + para.MutateBlock(true) + content.Append(para) + + bytes, _ = base64.StdEncoding.DecodeString("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=") + img = nodes.NewImageNode(nodes.NewImageNodeOptions{ + Bytes: bytes, + Alt: "PNG", + }) + para = nodes.NewListNode(img) + para.MutateBlock(true) + content.Append(para) + img = nodes.NewImageNode(nodes.NewImageNodeOptions{ Src: "https://host/small.png", Width: 25.5, From 6d7e1d3cfe2e4ccaf112895c00969a6308c93f43 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Wed, 24 May 2023 23:44:31 -0500 Subject: [PATCH 4/6] Update image_test.go --- claat/nodes/image_test.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/claat/nodes/image_test.go b/claat/nodes/image_test.go index 503827f74..c1665b140 100644 --- a/claat/nodes/image_test.go +++ b/claat/nodes/image_test.go @@ -1,11 +1,14 @@ package nodes import ( + "encoding/base64" "testing" "github.com/google/go-cmp/cmp" ) +var testBytes, _ = base64.StdEncoding.DecodeString("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7") + func TestNewImageNode(t *testing.T) { tests := []struct { name string @@ -19,7 +22,7 @@ func TestNewImageNode(t *testing.T) { }, }, { - name: "NonEmpty", + name: "StandardURL", inOpts: NewImageNodeOptions{ Src: "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png", Width: 1.0, @@ -34,6 +37,22 @@ func TestNewImageNode(t *testing.T) { Alt: "bar", }, }, + { + name: "DataURL", + inOpts: NewImageNodeOptions{ + Width: 1.0, + Title: "foo", + Alt: "bar", + Bytes: testBytes, + }, + out: &ImageNode{ + node: node{typ: NodeImage}, + Width: 1.0, + Title: "foo", + Alt: "bar", + Bytes: testBytes, + }, + }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { From a62f6c754b00abf698a7eecf79bd77327459ba8c Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Wed, 24 May 2023 23:50:20 -0500 Subject: [PATCH 5/6] Update fetch.go --- claat/fetch/fetch.go | 60 ++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/claat/fetch/fetch.go b/claat/fetch/fetch.go index 21dc1ceac..66ca877e7 100644 --- a/claat/fetch/fetch.go +++ b/claat/fetch/fetch.go @@ -229,7 +229,7 @@ func (f *Fetcher) SlurpImages(src, dir string, n []nodes.Node, images map[string for _, imageNode := range imageNodes { go func(imageNode *nodes.ImageNode) { url := imageNode.Src - file, err := f.slurpBytes(src, dir, url) + file, err := f.slurpBytes(src, dir, url, imageNode.Bytes) if err == nil { imageNode.Src = filepath.Join(util.ImgDirname, file) } @@ -251,40 +251,52 @@ func (f *Fetcher) SlurpImages(src, dir string, n []nodes.Node, images map[string return nil } -func (f *Fetcher) slurpBytes(codelabSrc, dir, imgURL string) (string, error) { - // images can be local in Markdown cases or remote. +func (f *Fetcher) slurpBytes(codelabSrc, dir, imgURL string, imgBytes []byte) (string, error) { + // images can be data URLs, local in Markdown cases or remote. // Only proceed a simple copy on local reference. var b []byte var ext string - u, err := url.Parse(imgURL) - if err != nil { - return "", err - } - - // If the codelab source is being downloaded from the network, then we should interpret - // the image URL in the same way. - srcUrl, err := url.Parse(codelabSrc) - if err == nil && srcUrl.Host != "" { - u = srcUrl.ResolveReference(u) - } + var err error - if u.Host == "" { - if imgURL, err = restrictPathToParent(imgURL, filepath.Dir(codelabSrc)); err != nil { - return "", err + if len(imgBytes) > 0 { + // Slurp bytes from image URL data. + b = imgBytes + if ext, err = imgExtFromBytes(b); err != nil { + return "", fmt.Errorf("Error reading image type: %v", err) } - if b, err = ioutil.ReadFile(imgURL); err != nil { + } else { + // Slurp bytes from local or remote URL. + u, err := url.Parse(imgURL) + if err != nil { return "", err } - ext = filepath.Ext(imgURL) - } else { - if b, err = f.slurpRemoteBytes(u.String(), 5); err != nil { - return "", fmt.Errorf("Error downloading image at %s: %v", u.String(), err) + + // If the codelab source is being downloaded from the network, then we should interpret + // the image URL in the same way. + srcURL, err := url.Parse(codelabSrc) + if err == nil && srcURL.Host != "" { + u = srcURL.ResolveReference(u) } - if ext, err = imgExtFromBytes(b); err != nil { - return "", fmt.Errorf("Error reading image type at %s: %v", u.String(), err) + + if u.Host == "" { + if imgURL, err = restrictPathToParent(imgURL, filepath.Dir(codelabSrc)); err != nil { + return "", err + } + if b, err = ioutil.ReadFile(imgURL); err != nil { + return "", err + } + ext = filepath.Ext(imgURL) + } else { + if b, err = f.slurpRemoteBytes(u.String(), 5); err != nil { + return "", fmt.Errorf("Error downloading image at %s: %v", u.String(), err) + } + if ext, err = imgExtFromBytes(b); err != nil { + return "", fmt.Errorf("Error reading image type at %s: %v", u.String(), err) + } } } + // Generate image file from slurped bytes. crc := crc64.Checksum(b, f.crcTable) file := fmt.Sprintf("%x%s", crc, ext) dst := filepath.Join(dir, file) From 9fff679b2c6a62a1e33b08795d77a7489316ff6e Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Wed, 24 May 2023 23:52:31 -0500 Subject: [PATCH 6/6] Update image.go --- claat/nodes/image.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/claat/nodes/image.go b/claat/nodes/image.go index b0fd5e7dd..7c966c42d 100644 --- a/claat/nodes/image.go +++ b/claat/nodes/image.go @@ -7,6 +7,7 @@ type NewImageNodeOptions struct { Width float32 Alt string Title string + Bytes []byte } // NewImageNode creates a new ImageNode with the given options. @@ -18,6 +19,7 @@ func NewImageNode(opts NewImageNodeOptions) *ImageNode { Width: opts.Width, Alt: opts.Alt, Title: opts.Title, + Bytes: opts.Bytes, } } @@ -28,11 +30,12 @@ type ImageNode struct { Width float32 Alt string Title string + Bytes []byte } // Empty returns true if its Src is zero, excluding space runes. func (in *ImageNode) Empty() bool { - return strings.TrimSpace(in.Src) == "" + return strings.TrimSpace(in.Src) == "" && len(in.Bytes) == 0 } // ImageNodes extracts everything except NodeImage nodes, recursively.