package protocol import ( "bytes" "fmt" "io/ioutil" "net/http" neturl "net/url" "strings" "github.com/keighl/metabolize" "go.uber.org/zap" "golang.org/x/net/html" "github.com/status-im/status-go/images" "github.com/status-im/status-go/protocol/common" "github.com/status-im/status-go/protocol/protobuf" ) type OpenGraphMetadata struct { Title string `json:"title" meta:"og:title"` Description string `json:"description" meta:"og:description"` ThumbnailURL string `json:"thumbnailUrl" meta:"og:image"` } // OpenGraphUnfurler should be preferred over OEmbedUnfurler because oEmbed // gives back a JSON response with a "html" field that's supposed to be embedded // in an iframe (hardly useful for existing Status' clients). type OpenGraphUnfurler struct { url *neturl.URL logger *zap.Logger httpClient *http.Client } func NewOpenGraphUnfurler(URL *neturl.URL, logger *zap.Logger, httpClient *http.Client) *OpenGraphUnfurler { return &OpenGraphUnfurler{ url: URL, logger: logger, httpClient: httpClient, } } func GetFavicon(bodyBytes []byte) string { htmlTokens := html.NewTokenizer(bytes.NewBuffer(bodyBytes)) loop: for { tt := htmlTokens.Next() switch tt { case html.ErrorToken: break loop case html.StartTagToken: t := htmlTokens.Token() if t.Data != "link" { continue } isIcon := false href := "" for _, attr := range t.Attr { k := attr.Key v := attr.Val if k == "rel" && (v == "icon" || v == "shortcut icon") { isIcon = true } else if k == "href" && (strings.Contains(v, ".ico") || strings.Contains(v, ".png") || strings.Contains(v, ".svg")) { href = v } } if isIcon && href != "" { return href } } } return "" } func (u *OpenGraphUnfurler) Unfurl() (*common.LinkPreview, error) { preview := newDefaultLinkPreview(u.url) preview.Type = protobuf.UnfurledLink_LINK headers := map[string]string{ "accept": headerAcceptText, "accept-language": headerAcceptLanguage, "user-agent": headerUserAgent, } bodyBytes, err := fetchBody(u.logger, u.httpClient, u.url.String(), headers) if err != nil { return preview, err } var ogMetadata OpenGraphMetadata err = metabolize.Metabolize(ioutil.NopCloser(bytes.NewBuffer(bodyBytes)), &ogMetadata) if err != nil { return preview, fmt.Errorf("failed to parse OpenGraph data") } faviconPath := GetFavicon(bodyBytes) t, err := fetchImage(u.logger, u.httpClient, faviconPath, false) if err != nil { u.logger.Info("failed to fetch favicon", zap.String("url", u.url.String()), zap.Error(err)) } else { preview.Favicon.DataURI = t.DataURI } // There are URLs like https://wikipedia.org/ that don't have an OpenGraph // title tag, but article pages do. In the future, we can fallback to the // website's title by using the