finder_test.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package icon // import "miniflux.app/v2/internal/reader/icon"
  4. import (
  5. "bytes"
  6. "encoding/base64"
  7. "image"
  8. "strings"
  9. "testing"
  10. "miniflux.app/v2/internal/model"
  11. )
  12. func TestParseImageDataURL(t *testing.T) {
  13. iconURL := "data:image/webp;base64,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"
  14. icon, err := parseImageDataURL(iconURL)
  15. if err != nil {
  16. t.Fatalf(`We should be able to parse valid data URL: %v`, err)
  17. }
  18. if icon.MimeType != "image/webp" {
  19. t.Fatal(`Invalid mime type parsed`)
  20. }
  21. if icon.Hash == "" {
  22. t.Fatal(`Image hash should be computed`)
  23. }
  24. }
  25. func TestParseImageDataURLWithNoEncoding(t *testing.T) {
  26. iconURL := `data:image/webp,%3Ch1%3EHello%2C%20World%21%3C%2Fh1%3E`
  27. icon, err := parseImageDataURL(iconURL)
  28. if err != nil {
  29. t.Fatalf(`We should be able to parse valid data URL: %v`, err)
  30. }
  31. if icon.MimeType != "image/webp" {
  32. t.Fatal(`Invalid mime type parsed`)
  33. }
  34. if string(icon.Content) == "Hello, World!" {
  35. t.Fatal(`Value should be URL-decoded`)
  36. }
  37. if icon.Hash == "" {
  38. t.Fatal(`Image hash should be computed`)
  39. }
  40. }
  41. func TestParseImageWithRawSVGEncodedInUTF8(t *testing.T) {
  42. iconURL := `data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 456 456'><circle></circle></svg>`
  43. icon, err := parseImageDataURL(iconURL)
  44. if err != nil {
  45. t.Fatalf(`We should be able to parse valid data URL: %v`, err)
  46. }
  47. if icon.MimeType != "image/svg+xml" {
  48. t.Fatal(`Invalid mime type parsed`)
  49. }
  50. if icon.Hash == "" {
  51. t.Fatal(`Image hash should be computed`)
  52. }
  53. if string(icon.Content) != `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 456 456'><circle></circle></svg>` {
  54. t.Fatal(`Invalid SVG content`)
  55. }
  56. }
  57. func TestParseImageDataURLWithNoMediaTypeAndNoEncoding(t *testing.T) {
  58. iconURL := `data:,Hello%2C%20World%21`
  59. _, err := parseImageDataURL(iconURL)
  60. if err == nil {
  61. t.Fatal(`We should detect invalid mime type`)
  62. }
  63. }
  64. func TestParseInvalidImageDataURLWithBadMimeType(t *testing.T) {
  65. _, err := parseImageDataURL("data:text/plain;base64,blob")
  66. if err == nil {
  67. t.Fatal(`We should detect invalid mime type`)
  68. }
  69. }
  70. func TestParseInvalidImageDataURLWithUnsupportedEncoding(t *testing.T) {
  71. _, err := parseImageDataURL("data:image/png;base32,blob")
  72. if err == nil {
  73. t.Fatal(`We should detect unsupported encoding`)
  74. }
  75. }
  76. func TestParseInvalidImageDataURLWithNoData(t *testing.T) {
  77. _, err := parseImageDataURL("data:image/png;base64,")
  78. if err == nil {
  79. t.Fatal(`We should detect invalid encoded data`)
  80. }
  81. }
  82. func TestParseInvalidImageDataURL(t *testing.T) {
  83. _, err := parseImageDataURL("data:image/jpeg")
  84. if err == nil {
  85. t.Fatal(`We should detect malformed image data URL`)
  86. }
  87. }
  88. func TestParseInvalidImageDataURLWithWrongPrefix(t *testing.T) {
  89. _, err := parseImageDataURL("data,test")
  90. if err == nil {
  91. t.Fatal(`We should detect malformed image data URL`)
  92. }
  93. }
  94. func TestParseDocumentWithWhitespaceIconURL(t *testing.T) {
  95. html := `<link rel="shortcut icon" href="
  96. /static/img/favicon.ico
  97. ">`
  98. iconURLs, err := findIconURLsFromHTMLDocument(strings.NewReader(html), "text/html")
  99. if err != nil {
  100. t.Fatal(err)
  101. }
  102. if len(iconURLs) != 1 {
  103. t.Fatalf(`Invalid number of icon URLs, got %d`, len(iconURLs))
  104. }
  105. if iconURLs[0] != "/static/img/favicon.ico" {
  106. t.Errorf(`Invalid icon URL, got %q`, iconURLs[0])
  107. }
  108. }
  109. func TestResizeIconSmallGif(t *testing.T) {
  110. data, err := base64.StdEncoding.DecodeString("R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==")
  111. if err != nil {
  112. t.Fatal(err)
  113. }
  114. icon := model.Icon{
  115. Content: data,
  116. MimeType: "image/gif",
  117. }
  118. if !bytes.Equal(icon.Content, resizeIcon(&icon).Content) {
  119. t.Fatalf("Converted gif smaller than 16x16")
  120. }
  121. }
  122. func TestResizeIconPng(t *testing.T) {
  123. data, err := base64.StdEncoding.DecodeString("iVBORw0KGgoAAAANSUhEUgAAACEAAAAhCAYAAABX5MJvAAAALUlEQVR42u3OMQEAAAgDoJnc6BpjDyRgcrcpGwkJCQkJCQkJCQkJCQkJCYmyB7NfUj/Kk4FkAAAAAElFTkSuQmCC")
  124. if err != nil {
  125. t.Fatal(err)
  126. }
  127. icon := model.Icon{
  128. Content: data,
  129. MimeType: "image/png",
  130. }
  131. resizedIcon := resizeIcon(&icon)
  132. if bytes.Equal(data, resizedIcon.Content) {
  133. t.Fatalf("Didn't convert png of 33x33")
  134. }
  135. config, _, err := image.DecodeConfig(bytes.NewReader(resizedIcon.Content))
  136. if err != nil {
  137. t.Fatalf("Couln't decode resulting png: %v", err)
  138. }
  139. if config.Height != 32 || config.Width != 32 {
  140. t.Fatalf("Was expecting an image of 16x16, got %dx%d", config.Width, config.Height)
  141. }
  142. }
  143. func TestResizeInvalidImage(t *testing.T) {
  144. icon := model.Icon{
  145. Content: []byte("invalid data"),
  146. MimeType: "image/gif",
  147. }
  148. if !bytes.Equal(icon.Content, resizeIcon(&icon).Content) {
  149. t.Fatalf("Tried to convert an invalid image")
  150. }
  151. }