casdoor/object/user_avatar_favicon.go

256 lines
5.0 KiB
Go

// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"bytes"
"fmt"
"net/http"
"strings"
"github.com/casdoor/casdoor/util"
"golang.org/x/net/html"
)
type Link struct {
Rel string
Sizes string
Href string
}
func GetFaviconUrl(htmlStr string) (string, error) {
doc, err := html.Parse(strings.NewReader(htmlStr))
if err != nil {
return "", err
}
var links []Link
findLinks(doc, &links)
if len(links) == 0 {
return "", fmt.Errorf("no Favicon links found")
}
chosenLink := chooseFaviconLink(links)
if chosenLink == nil {
return "", fmt.Errorf("unable to determine favicon URL")
}
return chosenLink.Href, nil
}
func findLinks(n *html.Node, links *[]Link) {
if n.Type == html.ElementNode && n.Data == "link" {
link := parseLink(n)
if link != nil {
*links = append(*links, *link)
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
findLinks(c, links)
}
}
func parseLink(n *html.Node) *Link {
var link Link
for _, attr := range n.Attr {
switch attr.Key {
case "rel":
link.Rel = attr.Val
case "sizes":
link.Sizes = attr.Val
case "href":
link.Href = attr.Val
}
}
if link.Href != "" {
return &link
}
return nil
}
func chooseFaviconLink(links []Link) *Link {
var appleTouchLinks []Link
var shortcutLinks []Link
var iconLinks []Link
for _, link := range links {
switch link.Rel {
case "apple-touch-icon":
appleTouchLinks = append(appleTouchLinks, link)
case "shortcut icon":
shortcutLinks = append(shortcutLinks, link)
case "icon":
iconLinks = append(iconLinks, link)
}
}
if len(appleTouchLinks) > 0 {
return chooseFaviconLinkBySizes(appleTouchLinks)
}
if len(shortcutLinks) > 0 {
return chooseFaviconLinkBySizes(shortcutLinks)
}
if len(iconLinks) > 0 {
return chooseFaviconLinkBySizes(iconLinks)
}
return nil
}
func chooseFaviconLinkBySizes(links []Link) *Link {
if len(links) == 1 {
return &links[0]
}
var chosenLink *Link
for _, link := range links {
link := link
if chosenLink == nil || compareSizes(link.Sizes, chosenLink.Sizes) > 0 {
chosenLink = &link
}
}
return chosenLink
}
func compareSizes(sizes1, sizes2 string) int {
if sizes1 == sizes2 {
return 0
}
size1 := parseSize(sizes1)
size2 := parseSize(sizes2)
if size1 == nil {
return -1
}
if size2 == nil {
return 1
}
if size1[0] == size2[0] {
return size1[1] - size2[1]
}
return size1[0] - size2[0]
}
func parseSize(sizes string) []int {
size := strings.Split(sizes, "x")
if len(size) != 2 {
return nil
}
var result []int
for _, s := range size {
val := strings.TrimSpace(s)
if len(val) > 0 {
num := 0
for i := 0; i < len(val); i++ {
if val[i] >= '0' && val[i] <= '9' {
num = num*10 + int(val[i]-'0')
} else {
break
}
}
result = append(result, num)
}
}
if len(result) == 2 {
return result
}
return nil
}
var publicEmailDomains = map[string]int{
"gmail.com": 1,
"163.com": 1,
"qq.com": 1,
"yahoo.com": 1,
"hotmail.com": 1,
"outlook.com": 1,
"icloud.com": 1,
"mail.com": 1,
"aol.com": 1,
"live.com": 1,
"yandex.com": 1,
"yahoo.co.jp": 1,
"yahoo.co.in": 1,
"yahoo.co.uk": 1,
"me.com": 1,
"msn.com": 1,
"comcast.net": 1,
"sbcglobal.net": 1,
"verizon.net": 1,
"earthlink.net": 1,
"cox.net": 1,
"rediffmail.com": 1,
"in.com": 1,
"hotmail.co.uk": 1,
"hotmail.fr": 1,
"zoho.com": 1,
"gmx.com": 1,
"gmx.de": 1,
"gmx.net": 1,
}
func isPublicEmailDomain(domain string) bool {
_, exists := publicEmailDomains[domain]
return exists
}
func getFaviconFileBuffer(client *http.Client, email string) (*bytes.Buffer, string, error) {
tokens := strings.Split(email, "@")
domain := tokens[1]
if isPublicEmailDomain(domain) {
return nil, "", nil
}
htmlUrl := fmt.Sprintf("https://%s", domain)
buffer, _, err := downloadImage(client, htmlUrl)
if err != nil {
return nil, "", err
}
faviconUrl := ""
if buffer != nil {
faviconUrl, err = GetFaviconUrl(buffer.String())
if err != nil {
fmt.Printf("getFaviconFileBuffer() error, faviconUrl is empty, error = %s\n", err.Error())
} else {
if !strings.HasPrefix(faviconUrl, "http") {
faviconUrl = util.UrlJoin(htmlUrl, faviconUrl)
}
}
}
if faviconUrl == "" {
faviconUrl = fmt.Sprintf("https://%s/favicon.ico", domain)
}
return downloadImage(client, faviconUrl)
}