210 lines
5.9 KiB
Go
210 lines
5.9 KiB
Go
|
package server
|
||
|
|
||
|
import (
|
||
|
"database/sql"
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"net/http"
|
||
|
|
||
|
"github.com/golang/protobuf/proto"
|
||
|
"go.uber.org/zap"
|
||
|
|
||
|
"github.com/status-im/status-go/images"
|
||
|
"github.com/status-im/status-go/protocol/common"
|
||
|
"github.com/status-im/status-go/protocol/protobuf"
|
||
|
)
|
||
|
|
||
|
func getThumbnailPayload(db *sql.DB, logger *zap.Logger, msgID string, thumbnailURL string) ([]byte, error) {
|
||
|
var payload []byte
|
||
|
|
||
|
var result []byte
|
||
|
err := db.QueryRow(`SELECT unfurled_links FROM user_messages WHERE id = ?`, msgID).Scan(&result)
|
||
|
if err != nil {
|
||
|
return payload, fmt.Errorf("could not find message with message-id '%s': %w", msgID, err)
|
||
|
}
|
||
|
|
||
|
var links []*protobuf.UnfurledLink
|
||
|
err = json.Unmarshal(result, &links)
|
||
|
if err != nil {
|
||
|
return payload, fmt.Errorf("failed to unmarshal protobuf.UrlPreview: %w", err)
|
||
|
}
|
||
|
|
||
|
for _, p := range links {
|
||
|
if p.Url == thumbnailURL {
|
||
|
payload = p.ThumbnailPayload
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return payload, nil
|
||
|
}
|
||
|
|
||
|
func handleLinkPreviewThumbnail(db *sql.DB, logger *zap.Logger) http.HandlerFunc {
|
||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
params := r.URL.Query()
|
||
|
parsed := ParseImageParams(logger, params)
|
||
|
|
||
|
if parsed.MessageID == "" {
|
||
|
http.Error(w, "missing query parameter 'message-id'", http.StatusBadRequest)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if parsed.URL == "" {
|
||
|
http.Error(w, "missing query parameter 'url'", http.StatusBadRequest)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
thumbnail, err := getThumbnailPayload(db, logger, parsed.MessageID, parsed.URL)
|
||
|
if err != nil {
|
||
|
logger.Error("failed to get thumbnail", zap.String("msgID", parsed.MessageID))
|
||
|
http.Error(w, "failed to get thumbnail", http.StatusInternalServerError)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
mimeType, err := images.GetMimeType(thumbnail)
|
||
|
if err != nil {
|
||
|
http.Error(w, "mime type not supported", http.StatusNotImplemented)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
w.Header().Set("Content-Type", "image/"+mimeType)
|
||
|
w.Header().Set("Cache-Control", "no-store")
|
||
|
|
||
|
_, err = w.Write(thumbnail)
|
||
|
if err != nil {
|
||
|
logger.Error("failed to write response", zap.Error(err))
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func getStatusLinkPreviewImage(p *protobuf.UnfurledStatusLink, imageID common.MediaServerImageID) ([]byte, error) {
|
||
|
|
||
|
switch imageID {
|
||
|
case common.MediaServerContactIcon:
|
||
|
contact := p.GetContact()
|
||
|
if contact == nil {
|
||
|
return nil, fmt.Errorf("this is not a contact link")
|
||
|
}
|
||
|
if contact.Icon == nil {
|
||
|
return nil, fmt.Errorf("contact icon is empty")
|
||
|
}
|
||
|
return contact.Icon.Payload, nil
|
||
|
|
||
|
case common.MediaServerCommunityIcon:
|
||
|
community := p.GetCommunity()
|
||
|
if community == nil {
|
||
|
return nil, fmt.Errorf("this is not a community link")
|
||
|
}
|
||
|
if community.Icon == nil {
|
||
|
return nil, fmt.Errorf("community icon is empty")
|
||
|
}
|
||
|
return community.Icon.Payload, nil
|
||
|
|
||
|
case common.MediaServerCommunityBanner:
|
||
|
community := p.GetCommunity()
|
||
|
if community == nil {
|
||
|
return nil, fmt.Errorf("this is not a community link")
|
||
|
}
|
||
|
if community.Banner == nil {
|
||
|
return nil, fmt.Errorf("community banner is empty")
|
||
|
}
|
||
|
return community.Banner.Payload, nil
|
||
|
|
||
|
case common.MediaServerChannelCommunityIcon:
|
||
|
channel := p.GetChannel()
|
||
|
if channel == nil {
|
||
|
return nil, fmt.Errorf("this is not a community channel link")
|
||
|
}
|
||
|
if channel.Community == nil {
|
||
|
return nil, fmt.Errorf("channel community is empty")
|
||
|
}
|
||
|
if channel.Community.Icon == nil {
|
||
|
return nil, fmt.Errorf("channel community icon is empty")
|
||
|
}
|
||
|
return channel.Community.Icon.Payload, nil
|
||
|
|
||
|
case common.MediaServerChannelCommunityBanner:
|
||
|
channel := p.GetChannel()
|
||
|
if channel == nil {
|
||
|
return nil, fmt.Errorf("this is not a community channel link")
|
||
|
}
|
||
|
if channel.Community == nil {
|
||
|
return nil, fmt.Errorf("channel community is empty")
|
||
|
}
|
||
|
if channel.Community.Banner == nil {
|
||
|
return nil, fmt.Errorf("channel community banner is empty")
|
||
|
}
|
||
|
return channel.Community.Banner.Payload, nil
|
||
|
}
|
||
|
|
||
|
return nil, fmt.Errorf("value not supported")
|
||
|
}
|
||
|
|
||
|
func getStatusLinkPreviewThumbnail(db *sql.DB, messageID string, URL string, imageID common.MediaServerImageID) ([]byte, int, error) {
|
||
|
var messageLinks []byte
|
||
|
err := db.QueryRow(`SELECT unfurled_status_links FROM user_messages WHERE id = ?`, messageID).Scan(&messageLinks)
|
||
|
if err != nil {
|
||
|
return nil, http.StatusBadRequest, fmt.Errorf("could not find message with message-id '%s': %w", messageID, err)
|
||
|
}
|
||
|
|
||
|
var links protobuf.UnfurledStatusLinks
|
||
|
err = proto.Unmarshal(messageLinks, &links)
|
||
|
if err != nil {
|
||
|
return nil, http.StatusInternalServerError, fmt.Errorf("failed to unmarshal protobuf.UrlPreview: %w", err)
|
||
|
}
|
||
|
|
||
|
for _, p := range links.UnfurledStatusLinks {
|
||
|
if p.Url == URL {
|
||
|
thumbnailPayload, err := getStatusLinkPreviewImage(p, imageID)
|
||
|
if err != nil {
|
||
|
return nil, http.StatusBadRequest, fmt.Errorf("invalid query parameter 'image-id' value: %w", err)
|
||
|
}
|
||
|
return thumbnailPayload, http.StatusOK, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil, http.StatusBadRequest, fmt.Errorf("no link preview found for given url")
|
||
|
}
|
||
|
|
||
|
func handleStatusLinkPreviewThumbnail(db *sql.DB, logger *zap.Logger) http.HandlerFunc {
|
||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
params := r.URL.Query()
|
||
|
parsed := ParseImageParams(logger, params)
|
||
|
|
||
|
if parsed.MessageID == "" {
|
||
|
http.Error(w, "missing query parameter 'message-id'", http.StatusBadRequest)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if parsed.URL == "" {
|
||
|
http.Error(w, "missing query parameter 'url'", http.StatusBadRequest)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if parsed.ImageID == "" {
|
||
|
http.Error(w, "missing query parameter 'image-id'", http.StatusBadRequest)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
thumbnail, httpsStatusCode, err := getStatusLinkPreviewThumbnail(db, parsed.MessageID, parsed.URL, common.MediaServerImageID(parsed.ImageID))
|
||
|
if err != nil {
|
||
|
http.Error(w, err.Error(), httpsStatusCode)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
mimeType, err := images.GetMimeType(thumbnail)
|
||
|
if err != nil {
|
||
|
http.Error(w, "mime type not supported", http.StatusNotImplemented)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
w.Header().Set("Content-Type", "image/"+mimeType)
|
||
|
w.Header().Set("Cache-Control", "no-store")
|
||
|
|
||
|
_, err = w.Write(thumbnail)
|
||
|
if err != nil {
|
||
|
logger.Error("failed to write response", zap.Error(err))
|
||
|
}
|
||
|
}
|
||
|
}
|