Skip to content

Commit

Permalink
feat: Unfurl image URLs (#3901)
Browse files Browse the repository at this point in the history
This commit adds support for unfurling static image URLs (not GIFs, not animated WebPs), such as https://placehold.co/[email protected]. It also compresses images before returning them as data URIs to clients.

About compression: the compression strategy leverages the existing function images.CompressToFileLimits. A more comprehensive logic to consider the possibility of multiple image URLs being unfurled simultaneously is yet to be implemented.

Closes #3761
  • Loading branch information
ilmotta authored Aug 21, 2023
1 parent 6f1c9af commit 084d4ba
Show file tree
Hide file tree
Showing 25 changed files with 697 additions and 456 deletions.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.163.12
0.163.13
174 changes: 87 additions & 87 deletions appdatabase/migrations/bindata.go

Large diffs are not rendered by default.

108 changes: 54 additions & 54 deletions appdatabase/migrationsprevnodecfg/bindata.go

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions images/adjust.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const (
idealTargetImageSize = 50000
)

var DefaultBounds = FileSizeLimits{Ideal: idealTargetImageSize, Max: resizeTargetImageSize}

func OpenAndAdjustImage(inputImage CroppedImage, crop bool) ([]byte, error) {
file, err := os.Open(inputImage.ImagePath)
if err != nil {
Expand Down Expand Up @@ -43,8 +45,7 @@ func OpenAndAdjustImage(inputImage CroppedImage, crop bool) ([]byte, error) {
}

bb := bytes.NewBuffer([]byte{})
err = CompressToFileLimits(bb, img, FileSizeLimits{Ideal: idealTargetImageSize, Max: resizeTargetImageSize})

err = CompressToFileLimits(bb, img, DefaultBounds)
if err != nil {
return nil, err
}
Expand Down
30 changes: 15 additions & 15 deletions images/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func Decode(fileName string) (image.Image, error) {
return nil, err
}

return decodeImageData(fb, file)
return DecodeImageData(fb, file)
}

func DecodeFromURL(path string) (image.Image, error) {
Expand All @@ -57,7 +57,7 @@ func DecodeFromURL(path string) (image.Image, error) {
return nil, err
}

return decodeImageData(bodyBytes, bytes.NewReader(bodyBytes))
return DecodeImageData(bodyBytes, bytes.NewReader(bodyBytes))
}

func prepareFileForDecode(file *os.File) ([]byte, error) {
Expand All @@ -77,7 +77,7 @@ func prepareFileForDecode(file *os.File) ([]byte, error) {
return fb, nil
}

func decodeImageData(buf []byte, r io.Reader) (img image.Image, err error) {
func DecodeImageData(buf []byte, r io.Reader) (img image.Image, err error) {
switch GetType(buf) {
case JPEG:
img, err = jpeg.Decode(r)
Expand All @@ -101,13 +101,13 @@ func decodeImageData(buf []byte, r io.Reader) (img image.Image, err error) {

func GetType(buf []byte) ImageType {
switch {
case isJpeg(buf):
case IsJpeg(buf):
return JPEG
case isPng(buf):
case IsPng(buf):
return PNG
case isGif(buf):
case IsGif(buf):
return GIF
case isWebp(buf):
case IsWebp(buf):
return WEBP
default:
return UNKNOWN
Expand All @@ -116,38 +116,38 @@ func GetType(buf []byte) ImageType {

func GetMimeType(buf []byte) (string, error) {
switch {
case isJpeg(buf):
case IsJpeg(buf):
return "jpeg", nil
case isPng(buf):
case IsPng(buf):
return "png", nil
case isGif(buf):
case IsGif(buf):
return "gif", nil
case isWebp(buf):
case IsWebp(buf):
return "webp", nil
default:
return "", errors.New("image format not supported")
}
}

func isJpeg(buf []byte) bool {
func IsJpeg(buf []byte) bool {
return len(buf) > 2 &&
buf[0] == 0xFF &&
buf[1] == 0xD8 &&
buf[2] == 0xFF
}

func isPng(buf []byte) bool {
func IsPng(buf []byte) bool {
return len(buf) > 3 &&
buf[0] == 0x89 && buf[1] == 0x50 &&
buf[2] == 0x4E && buf[3] == 0x47
}

func isGif(buf []byte) bool {
func IsGif(buf []byte) bool {
return len(buf) > 2 &&
buf[0] == 0x47 && buf[1] == 0x49 && buf[2] == 0x46
}

func isWebp(buf []byte) bool {
func IsWebp(buf []byte) bool {
return len(buf) > 11 &&
buf[8] == 0x57 && buf[9] == 0x45 &&
buf[10] == 0x42 && buf[11] == 0x50
Expand Down
6 changes: 3 additions & 3 deletions mailserver/migrations/bindata.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 13 additions & 13 deletions multiaccounts/migrations/bindata.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions protocol/anonmetrics/migrations/migrations.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 19 additions & 8 deletions protocol/common/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,12 @@ type LinkPreviewThumbnail struct {
}

type LinkPreview struct {
URL string `json:"url"`
Hostname string `json:"hostname"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
Thumbnail LinkPreviewThumbnail `json:"thumbnail,omitempty"`
Type protobuf.UnfurledLink_LinkType `json:"type"`
URL string `json:"url"`
Hostname string `json:"hostname"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
Thumbnail LinkPreviewThumbnail `json:"thumbnail,omitempty"`
}

const EveryoneMentionTag = "0x00001"
Expand Down Expand Up @@ -758,10 +759,18 @@ func (m *Message) LoadImage() error {
return nil
}

func isValidLinkPreviewThumbnail(thumbnail LinkPreviewThumbnail) bool {
return (thumbnail.DataURI == "" && thumbnail.Width == 0 && thumbnail.Height == 0) ||
(thumbnail.DataURI != "" && thumbnail.Width > 0 && thumbnail.Height > 0)
}

func isValidLinkPreviewForProto(preview LinkPreview) bool {
return preview.Title != "" && preview.URL != "" &&
((preview.Thumbnail.DataURI == "" && preview.Thumbnail.Width == 0 && preview.Thumbnail.Height == 0) ||
(preview.Thumbnail.DataURI != "" && preview.Thumbnail.Width > 0 && preview.Thumbnail.Height > 0))
switch preview.Type {
case protobuf.UnfurledLink_IMAGE:
return preview.URL != "" && isValidLinkPreviewThumbnail(preview.Thumbnail)
default: // Validate as a link type by default.
return preview.Title != "" && preview.URL != "" && isValidLinkPreviewThumbnail(preview.Thumbnail)
}
}

// ConvertLinkPreviewsToProto expects previews to be correctly sent by the
Expand Down Expand Up @@ -791,6 +800,7 @@ func (m *Message) ConvertLinkPreviewsToProto() ([]*protobuf.UnfurledLink, error)
}

ul := &protobuf.UnfurledLink{
Type: preview.Type,
Url: preview.URL,
Title: preview.Title,
Description: preview.Description,
Expand Down Expand Up @@ -827,6 +837,7 @@ func (m *Message) ConvertFromProtoToLinkPreviews(makeMediaServerURL func(msgID s
Description: link.Description,
Hostname: hostname,
Title: link.Title,
Type: link.Type,
URL: link.Url,
}
if payload := link.GetThumbnailPayload(); payload != nil {
Expand Down
5 changes: 5 additions & 0 deletions protocol/common/message_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ func TestConvertLinkPreviewsToProto(t *testing.T) {
msg := Message{
LinkPreviews: []LinkPreview{
{
Type: protobuf.UnfurledLink_LINK,
Description: "GitHub is where people build software.",
Hostname: "github.com",
Title: "Build software better, together",
Expand All @@ -155,6 +156,7 @@ func TestConvertLinkPreviewsToProto(t *testing.T) {

l := unfurledLinks[0]
validPreview := msg.LinkPreviews[0]
require.Equal(t, validPreview.Type, l.Type)
require.Equal(t, validPreview.Description, l.Description)
require.Equal(t, validPreview.Title, l.Title)
require.Equal(t, uint32(validPreview.Thumbnail.Width), l.ThumbnailWidth)
Expand Down Expand Up @@ -194,6 +196,7 @@ func TestConvertFromProtoToLinkPreviews(t *testing.T) {
l := &protobuf.UnfurledLink{
Description: "GitHub is where people build software.",
Title: "Build software better, together",
Type: protobuf.UnfurledLink_LINK,
Url: "https://github.com",
ThumbnailPayload: []byte(""),
ThumbnailWidth: 100,
Expand All @@ -213,6 +216,7 @@ func TestConvertFromProtoToLinkPreviews(t *testing.T) {
previews := msg.ConvertFromProtoToLinkPreviews(urlMaker)
require.Len(t, previews, 1)
p := previews[0]
require.Equal(t, l.Type, p.Type)
require.Equal(t, "github.com", p.Hostname)
require.Equal(t, l.Description, p.Description)
require.Equal(t, l.Url, p.URL)
Expand Down Expand Up @@ -262,6 +266,7 @@ func TestMarshalMessageJSON(t *testing.T) {
From: "0x04c51631b3354242d5a56f044c3b7703bcc001e8c725c4706928b3fac3c2a12ec9019e1e224d487f5c893389405bcec998bc687307f290a569d6a97d24b711bca8",
LinkPreviews: []LinkPreview{
{
Type: protobuf.UnfurledLink_LINK,
Description: "GitHub is where people build software.",
Hostname: "github.com",
Title: "Build software better, together",
Expand Down
Loading

0 comments on commit 084d4ba

Please sign in to comment.