Skip to content

Commit 95430ff

Browse files
committed
Resolve discussions
1 parent 6f27449 commit 95430ff

File tree

3 files changed

+199
-16
lines changed

3 files changed

+199
-16
lines changed

utils/image/base64converter.go

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,26 @@ import (
2020
)
2121

2222
const (
23-
pngFormat = "png"
24-
gifFormat = "gif"
25-
svgMimeType = "image/svg+xml"
26-
pngMimeType = "image/png"
27-
gifMimeType = "image/gif"
28-
jpegMimeType = "image/jpeg"
29-
widthAttr = "width"
30-
heightAttr = "height"
31-
defaultHTTPTimeout = 30 * time.Second
23+
pngFormat = "png"
24+
gifFormat = "gif"
25+
svgMimeType = "image/svg+xml"
26+
pngMimeType = "image/png"
27+
gifMimeType = "image/gif"
28+
jpegMimeType = "image/jpeg"
29+
widthAttr = "width"
30+
heightAttr = "height"
31+
defaultHTTPTimeout = 30 * time.Second
32+
svgDoubleQuotePattern = `%s="([^"]+)"`
33+
svgSingleQuotePattern = `%s='([^']+)'`
34+
svgTagPattern = `(?i)^\s*(?:<\?xml[^>]*>\s*)?(?:<!DOCTYPE[^>]*>\s*)?<svg\s`
3235
)
3336

3437
var (
35-
widthRegex = regexp.MustCompile(fmt.Sprintf(`%s=["']([^"']+)["']`, widthAttr))
36-
heightRegex = regexp.MustCompile(fmt.Sprintf(`%s=["']([^"']+)["']`, heightAttr))
37-
svgTagRegex = regexp.MustCompile(`(?i)^\s*(?:<\?xml[^>]*>\s*)?(?:<!DOCTYPE[^>]*>\s*)?<svg\s`)
38+
widthDoubleQuoteRegex = regexp.MustCompile(fmt.Sprintf(svgDoubleQuotePattern, widthAttr))
39+
widthSingleQuoteRegex = regexp.MustCompile(fmt.Sprintf(svgSingleQuotePattern, widthAttr))
40+
heightDoubleQuoteRegex = regexp.MustCompile(fmt.Sprintf(svgDoubleQuotePattern, heightAttr))
41+
heightSingleQuoteRegex = regexp.MustCompile(fmt.Sprintf(svgSingleQuotePattern, heightAttr))
42+
svgTagRegex = regexp.MustCompile(svgTagPattern)
3843
)
3944

4045
func ToBase64(imagePath, baseDir string, width, height int) (string, string, error) {
@@ -55,7 +60,7 @@ func ToBase64(imagePath, baseDir string, width, height int) (string, string, err
5560
}
5661

5762
if svgTagRegex.Match(content) {
58-
if err := validateSVGDimensions(content, width, height); err != nil {
63+
if err = validateSVGDimensions(content, width, height); err != nil {
5964
return "", "", err
6065
}
6166
return base64.StdEncoding.EncodeToString(content), svgMimeType, nil
@@ -66,7 +71,7 @@ func ToBase64(imagePath, baseDir string, width, height int) (string, string, err
6671
return "", "", fmt.Errorf("unsupported image format: %v", err)
6772
}
6873

69-
if err := validateImageDimensions(img, width, height); err != nil {
74+
if err = validateImageDimensions(img, width, height); err != nil {
7075
return "", "", err
7176
}
7277

@@ -125,8 +130,15 @@ func validateSVGDimensions(content []byte, maxWidth, maxHeight int) error {
125130
return nil
126131
}
127132

128-
widthMatch := widthRegex.FindStringSubmatch(contentStr)
129-
heightMatch := heightRegex.FindStringSubmatch(contentStr)
133+
widthMatch := widthDoubleQuoteRegex.FindStringSubmatch(contentStr)
134+
if len(widthMatch) == 0 {
135+
widthMatch = widthSingleQuoteRegex.FindStringSubmatch(contentStr)
136+
}
137+
138+
heightMatch := heightDoubleQuoteRegex.FindStringSubmatch(contentStr)
139+
if len(heightMatch) == 0 {
140+
heightMatch = heightSingleQuoteRegex.FindStringSubmatch(contentStr)
141+
}
130142

131143
var currentWidth, currentHeight int
132144
var hasWidth, hasHeight bool

utils/image/base64converter_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,45 @@ func TestValidateSVGDimensions_NoDimensions(t *testing.T) {
189189
require.NoError(t, err)
190190
}
191191

192+
func TestValidateSVGDimensions_SingleQuotes(t *testing.T) {
193+
svgContent := `<svg width='200' height='150' xmlns="http://www.w3.org/2000/svg">
194+
<rect width='200' height='150' fill="red"/>
195+
</svg>`
196+
197+
err := validateSVGDimensions([]byte(svgContent), 100, 100)
198+
require.Error(t, err)
199+
assert.Contains(t, err.Error(), "exceeds maximum allowed")
200+
}
201+
202+
func TestValidateSVGDimensions_MixedQuotes(t *testing.T) {
203+
svgContent := `<svg width="200" height='150' xmlns="http://www.w3.org/2000/svg">
204+
<rect width="200" height='150' fill="red"/>
205+
</svg>`
206+
207+
err := validateSVGDimensions([]byte(svgContent), 100, 100)
208+
require.Error(t, err)
209+
assert.Contains(t, err.Error(), "exceeds maximum allowed")
210+
}
211+
212+
func TestValidateSVGDimensions_SingleQuotesWithinLimits(t *testing.T) {
213+
svgContent := `<svg width='100' height='200' xmlns="http://www.w3.org/2000/svg">
214+
<rect width='100' height='200' fill="red"/>
215+
</svg>`
216+
217+
err := validateSVGDimensions([]byte(svgContent), 300, 400)
218+
require.NoError(t, err)
219+
}
220+
221+
func TestValidateSVGDimensions_MismatchedQuotes(t *testing.T) {
222+
svgContent := `<svg width='200" height="150' xmlns="http://www.w3.org/2000/svg">
223+
<rect width='200" height="150' fill="red"/>
224+
</svg>`
225+
226+
err := validateSVGDimensions([]byte(svgContent), 100, 100)
227+
require.Error(t, err)
228+
assert.Contains(t, err.Error(), "exceeds maximum allowed")
229+
}
230+
192231
func TestIsURL(t *testing.T) {
193232
tests := []struct {
194233
input string

utils/markdown/imagereplacer_test.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package markdown
33
import (
44
"image"
55
"image/png"
6+
"net/http"
7+
"net/http/httptest"
68
"os"
79
"path/filepath"
810
"testing"
@@ -204,3 +206,133 @@ func TestEmbedMarkdownImages_AvoidsCopyWhenAllImagesFail(t *testing.T) {
204206
// Verify it returns the same slice when all images fail to convert
205207
assert.Equal(t, &input[0], &result[0], "Should return the same slice when all conversions fail")
206208
}
209+
210+
func TestEmbedMarkdownImages_URLImage(t *testing.T) {
211+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
212+
img := createTestImage(50, 50)
213+
w.Header().Set("Content-Type", "image/png")
214+
if err := png.Encode(w, img); err != nil {
215+
t.Errorf("failed to encode PNG: %v", err)
216+
}
217+
}))
218+
defer server.Close()
219+
220+
content := `![test image](` + server.URL + `)`
221+
result, err := EmbedMarkdownImages([]byte(content), "", 0, 0)
222+
require.NoError(t, err)
223+
224+
assert.Contains(t, string(result), "data:image/png;base64,")
225+
assert.NotContains(t, string(result), "](`+server.URL+`)")
226+
}
227+
228+
func TestEmbedMarkdownImages_URLImageWithLimits(t *testing.T) {
229+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
230+
img := createTestImage(200, 150)
231+
w.Header().Set("Content-Type", "image/png")
232+
if err := png.Encode(w, img); err != nil {
233+
t.Errorf("failed to encode PNG: %v", err)
234+
}
235+
}))
236+
defer server.Close()
237+
238+
content := `![large image](` + server.URL + `)`
239+
result, err := EmbedMarkdownImages([]byte(content), "", 100, 100)
240+
require.NoError(t, err)
241+
242+
// Should keep original since image exceeds limits
243+
assert.Contains(t, string(result), "![large image]("+server.URL+")")
244+
assert.NotContains(t, string(result), "data:image/png;base64,")
245+
}
246+
247+
func TestEmbedMarkdownImages_URLImageError(t *testing.T) {
248+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
249+
w.WriteHeader(http.StatusNotFound)
250+
}))
251+
defer server.Close()
252+
253+
content := `![missing image](` + server.URL + `)`
254+
result, err := EmbedMarkdownImages([]byte(content), "", 0, 0)
255+
require.NoError(t, err)
256+
257+
// Should keep original since URL returns error
258+
assert.Contains(t, string(result), "![missing image]("+server.URL+")")
259+
assert.NotContains(t, string(result), "data:image/png;base64,")
260+
}
261+
262+
func TestEmbedMarkdownImages_MixedLocalAndURL(t *testing.T) {
263+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
264+
img := createTestImage(50, 50)
265+
w.Header().Set("Content-Type", "image/png")
266+
if err := png.Encode(w, img); err != nil {
267+
t.Errorf("failed to encode PNG: %v", err)
268+
}
269+
}))
270+
defer server.Close()
271+
272+
tempDir := t.TempDir()
273+
imgPath := filepath.Join(tempDir, "local.png")
274+
f, err := os.Create(imgPath)
275+
require.NoError(t, err)
276+
img := createTestImage(30, 30)
277+
require.NoError(t, png.Encode(f, img))
278+
require.NoError(t, f.Close())
279+
280+
content := `![local](local.png) and ![remote](` + server.URL + `)`
281+
result, err := EmbedMarkdownImages([]byte(content), tempDir, 0, 0)
282+
require.NoError(t, err)
283+
284+
// Both should be converted to data URIs
285+
assert.Contains(t, string(result), "data:image/png;base64,")
286+
assert.NotContains(t, string(result), "](local.png)")
287+
assert.NotContains(t, string(result), "]("+server.URL+")")
288+
}
289+
290+
func TestEmbedMarkdownImages_URLWithDifferentFormats(t *testing.T) {
291+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
292+
img := createTestImage(50, 50)
293+
w.Header().Set("Content-Type", "image/png")
294+
if err := png.Encode(w, img); err != nil {
295+
t.Errorf("failed to encode image: %v", err)
296+
}
297+
}))
298+
defer server.Close()
299+
300+
content := `![png image](` + server.URL + `)`
301+
result, err := EmbedMarkdownImages([]byte(content), "", 0, 0)
302+
require.NoError(t, err)
303+
304+
assert.Contains(t, string(result), "data:image/png;base64,")
305+
assert.NotContains(t, string(result), "]("+server.URL+")")
306+
}
307+
308+
func TestFindImageReferences_URLImages(t *testing.T) {
309+
content := `![local](./image.png) and ![remote](https://example.com/image.jpg)`
310+
311+
refs := findImageReferences(content)
312+
313+
require.Len(t, refs, 2)
314+
315+
// Check local image
316+
assert.Equal(t, "local", refs[0].AltText)
317+
assert.Equal(t, "./image.png", refs[0].ImagePath)
318+
319+
// Check URL image
320+
assert.Equal(t, "remote", refs[1].AltText)
321+
assert.Equal(t, "https://example.com/image.jpg", refs[1].ImagePath)
322+
}
323+
324+
func TestFindImageReferences_DataURLSkipped(t *testing.T) {
325+
content := `![data](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==) and ![url](https://example.com/image.png)`
326+
327+
refs := findImageReferences(content)
328+
329+
// Only URL should be found, data URL should be skipped
330+
require.Len(t, refs, 1)
331+
assert.Equal(t, "url", refs[0].AltText)
332+
assert.Equal(t, "https://example.com/image.png", refs[0].ImagePath)
333+
}
334+
335+
func createTestImage(width, height int) image.Image {
336+
img := image.NewRGBA(image.Rect(0, 0, width, height))
337+
return img
338+
}

0 commit comments

Comments
 (0)