Add support for encrypting preview image

This commit is contained in:
Tulir Asokan 2022-02-04 23:06:35 +02:00
parent d4334f5df8
commit 9fee8a50a4
3 changed files with 58 additions and 33 deletions

2
go.mod
View file

@ -10,7 +10,7 @@ require (
github.com/prometheus/client_golang v1.11.0 github.com/prometheus/client_golang v1.11.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/tidwall/gjson v1.13.0 github.com/tidwall/gjson v1.13.0
go.mau.fi/whatsmeow v0.0.0-20220204175019-e490de34933c go.mau.fi/whatsmeow v0.0.0-20220204210537-a425ddb0b16c
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 golang.org/x/image v0.0.0-20211028202545-6944b10bf410
google.golang.org/protobuf v1.27.1 google.golang.org/protobuf v1.27.1
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b

4
go.sum
View file

@ -140,8 +140,8 @@ github.com/tidwall/sjson v1.2.4 h1:cuiLzLnaMeBhRmEv00Lpk3tkYrcxpmbU81tAY4Dw0tc=
github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM= github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM=
go.mau.fi/libsignal v0.0.0-20211109153248-a67163214910 h1:9FFhG0OmkuMau5UEaTgiUQ+7cSbtbOQ7hiWKdN8OI3I= go.mau.fi/libsignal v0.0.0-20211109153248-a67163214910 h1:9FFhG0OmkuMau5UEaTgiUQ+7cSbtbOQ7hiWKdN8OI3I=
go.mau.fi/libsignal v0.0.0-20211109153248-a67163214910/go.mod h1:AufGrvVh+00Nc07Jm4hTquh7yleZyn20tKJI2wCPAKg= go.mau.fi/libsignal v0.0.0-20211109153248-a67163214910/go.mod h1:AufGrvVh+00Nc07Jm4hTquh7yleZyn20tKJI2wCPAKg=
go.mau.fi/whatsmeow v0.0.0-20220204175019-e490de34933c h1:AVSYHQ0N5n3buL+thypCk2jiltD+3+pUQ7oPVhC7I3w= go.mau.fi/whatsmeow v0.0.0-20220204210537-a425ddb0b16c h1:hwuZ1W55J2uSwm029dREAr6crSVa+i5VsF91ltK389k=
go.mau.fi/whatsmeow v0.0.0-20220204175019-e490de34933c/go.mod h1:8jUjOAi3xtGubxcZgG8uSHpAdyQXBRbWAfxkctX/4y4= go.mau.fi/whatsmeow v0.0.0-20220204210537-a425ddb0b16c/go.mod h1:8jUjOAi3xtGubxcZgG8uSHpAdyQXBRbWAfxkctX/4y4=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=

View file

@ -22,10 +22,13 @@ import (
"encoding/json" "encoding/json"
"image" "image"
"net/http" "net/http"
"strings"
"time"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
"maunium.net/go/mautrix/appservice" "maunium.net/go/mautrix/appservice"
"maunium.net/go/mautrix/crypto/attachment"
"maunium.net/go/mautrix/event" "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
@ -39,11 +42,14 @@ type BeeperLinkPreview struct {
Title string `json:"og:title,omitempty"` Title string `json:"og:title,omitempty"`
Type string `json:"og:type,omitempty"` Type string `json:"og:type,omitempty"`
Description string `json:"og:description,omitempty"` Description string `json:"og:description,omitempty"`
Image id.ContentURIString `json:"og:image,omitempty"`
ImageURL id.ContentURIString `json:"og:image,omitempty"`
ImageEncryption *event.EncryptedFileInfo `json:"beeper:image:encryption,omitempty"`
ImageSize int `json:"matrix:image:size,omitempty"`
ImageWidth int `json:"og:image:width,omitempty"` ImageWidth int `json:"og:image:width,omitempty"`
ImageHeight int `json:"og:image:height,omitempty"` ImageHeight int `json:"og:image:height,omitempty"`
ImageType string `json:"og:image:type,omitempty"`
MatchedURLFallback string `json:"matchedUrl"`
} }
func (portal *Portal) convertURLPreviewToBeeper(intent *appservice.IntentAPI, source *User, msg *waProto.ExtendedTextMessage) (output *BeeperLinkPreview) { func (portal *Portal) convertURLPreviewToBeeper(intent *appservice.IntentAPI, source *User, msg *waProto.ExtendedTextMessage) (output *BeeperLinkPreview) {
@ -57,7 +63,6 @@ func (portal *Portal) convertURLPreviewToBeeper(intent *appservice.IntentAPI, so
Title: msg.GetTitle(), Title: msg.GetTitle(),
Description: msg.GetDescription(), Description: msg.GetDescription(),
} }
output.MatchedURLFallback = output.MatchedURL
if len(output.CanonicalURL) == 0 { if len(output.CanonicalURL) == 0 {
output.CanonicalURL = output.MatchedURL output.CanonicalURL = output.MatchedURL
} }
@ -73,11 +78,6 @@ func (portal *Portal) convertURLPreviewToBeeper(intent *appservice.IntentAPI, so
thumbnailData = msg.JpegThumbnail thumbnailData = msg.JpegThumbnail
} }
if thumbnailData != nil { if thumbnailData != nil {
mxc, err := intent.UploadBytes(thumbnailData, http.DetectContentType(thumbnailData))
if err != nil {
portal.log.Warnfln("Failed to reupload thumbnail for link preview: %v", err)
} else {
output.Image = mxc.ContentURI.CUString()
output.ImageHeight = int(msg.GetThumbnailHeight()) output.ImageHeight = int(msg.GetThumbnailHeight())
output.ImageWidth = int(msg.GetThumbnailWidth()) output.ImageWidth = int(msg.GetThumbnailWidth())
if output.ImageHeight == 0 || output.ImageWidth == 0 { if output.ImageHeight == 0 || output.ImageWidth == 0 {
@ -87,6 +87,24 @@ func (portal *Portal) convertURLPreviewToBeeper(intent *appservice.IntentAPI, so
output.ImageWidth, output.ImageHeight = imageBounds.Max.X, imageBounds.Max.Y output.ImageWidth, output.ImageHeight = imageBounds.Max.X, imageBounds.Max.Y
} }
} }
output.ImageSize = len(thumbnailData)
output.ImageType = http.DetectContentType(thumbnailData)
uploadData, uploadMime := thumbnailData, output.ImageType
if portal.Encrypted {
crypto := attachment.NewEncryptedFile()
uploadData = crypto.Encrypt(uploadData)
uploadMime = "application/octet-stream"
output.ImageEncryption = &event.EncryptedFileInfo{EncryptedFile: *crypto}
}
resp, err := intent.UploadBytes(uploadData, uploadMime)
if err != nil {
portal.log.Warnfln("Failed to reupload thumbnail for link preview: %v", err)
} else {
if output.ImageEncryption != nil {
output.ImageEncryption.URL = resp.ContentURI.CUString()
} else {
output.ImageURL = resp.ContentURI.CUString()
}
} }
} }
if msg.GetPreviewType() == waProto.ExtendedTextMessage_VIDEO { if msg.GetPreviewType() == waProto.ExtendedTextMessage_VIDEO {
@ -102,16 +120,9 @@ func (portal *Portal) convertURLPreviewToWhatsApp(sender *User, evt *event.Event
return return
} }
var preview BeeperLinkPreview var preview BeeperLinkPreview
if err := json.Unmarshal([]byte(rawPreview.Raw), &preview); err != nil { if err := json.Unmarshal([]byte(rawPreview.Raw), &preview); err != nil || len(preview.MatchedURL) == 0 {
return return
} }
if len(preview.MatchedURL) == 0 {
if len(preview.MatchedURLFallback) == 0 {
return
} else {
preview.MatchedURL = preview.MatchedURLFallback
}
}
dest.MatchedText = &preview.MatchedURL dest.MatchedText = &preview.MatchedURL
if len(preview.CanonicalURL) > 0 { if len(preview.CanonicalURL) > 0 {
@ -123,13 +134,27 @@ func (portal *Portal) convertURLPreviewToWhatsApp(sender *User, evt *event.Event
if len(preview.Title) > 0 { if len(preview.Title) > 0 {
dest.Title = &preview.Title dest.Title = &preview.Title
} }
imageMXC := preview.Image.ParseOrIgnore() if strings.HasPrefix(preview.Type, "video.") {
dest.PreviewType = waProto.ExtendedTextMessage_VIDEO.Enum()
}
imageMXC := preview.ImageURL.ParseOrIgnore()
if preview.ImageEncryption != nil {
imageMXC = preview.ImageEncryption.URL.ParseOrIgnore()
}
if !imageMXC.IsEmpty() { if !imageMXC.IsEmpty() {
data, err := portal.MainIntent().DownloadBytes(imageMXC) data, err := portal.MainIntent().DownloadBytes(imageMXC)
if err != nil { if err != nil {
portal.log.Errorfln("Failed to download URL preview image %s: %v", preview.Image, err) portal.log.Errorfln("Failed to download URL preview image %s in %s: %v", preview.ImageURL, evt.ID, err)
return return
} }
if preview.ImageEncryption != nil {
data, err = preview.ImageEncryption.Decrypt(data)
if err != nil {
portal.log.Errorfln("Failed to decrypt URL preview image in %s: %v", evt.ID, err)
return
}
}
dest.MediaKeyTimestamp = proto.Int64(time.Now().Unix())
uploadResp, err := sender.Client.Upload(context.Background(), data, whatsmeow.MediaLinkThumbnail) uploadResp, err := sender.Client.Upload(context.Background(), data, whatsmeow.MediaLinkThumbnail)
if err != nil { if err != nil {
portal.log.Errorfln("Failed to upload URL preview thumbnail in %s: %v", evt.ID, err) portal.log.Errorfln("Failed to upload URL preview thumbnail in %s: %v", evt.ID, err)