aboutsummaryrefslogtreecommitdiffhomepage
path: root/reader/icon/finder.go
blob: 2a5beb5e4c811f952ebd1655a6067a5fb42ae28f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
// Copyright 2017 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.

package icon

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"

	"github.com/miniflux/miniflux/helper"
	"github.com/miniflux/miniflux/http"
	"github.com/miniflux/miniflux/model"
	"github.com/miniflux/miniflux/url"

	"github.com/PuerkitoBio/goquery"
)

// FindIcon try to find the website's icon.
func FindIcon(websiteURL string) (*model.Icon, error) {
	rootURL := url.RootURL(websiteURL)
	client := http.NewClient(rootURL)
	response, err := client.Get()
	if err != nil {
		return nil, fmt.Errorf("unable to download website index page: %v", err)
	}

	if response.HasServerFailure() {
		return nil, fmt.Errorf("unable to download website index page: status=%d", response.StatusCode)
	}

	iconURL, err := parseDocument(rootURL, response.Body)
	if err != nil {
		return nil, err
	}

	log.Println("[FindIcon] Fetching icon =>", iconURL)
	icon, err := downloadIcon(iconURL)
	if err != nil {
		return nil, err
	}

	return icon, nil
}

func parseDocument(websiteURL string, data io.Reader) (string, error) {
	queries := []string{
		"link[rel='shortcut icon']",
		"link[rel='Shortcut Icon']",
		"link[rel='icon shortcut']",
		"link[rel='icon']",
	}

	doc, err := goquery.NewDocumentFromReader(data)
	if err != nil {
		return "", fmt.Errorf("unable to read document: %v", err)
	}

	var iconURL string
	for _, query := range queries {
		doc.Find(query).Each(func(i int, s *goquery.Selection) {
			if href, exists := s.Attr("href"); exists {
				iconURL = href
			}
		})

		if iconURL != "" {
			break
		}
	}

	if iconURL == "" {
		iconURL = url.RootURL(websiteURL) + "favicon.ico"
	} else {
		iconURL, _ = url.AbsoluteURL(websiteURL, iconURL)
	}

	return iconURL, nil
}

func downloadIcon(iconURL string) (*model.Icon, error) {
	client := http.NewClient(iconURL)
	response, err := client.Get()
	if err != nil {
		return nil, fmt.Errorf("unable to download iconURL: %v", err)
	}

	if response.HasServerFailure() {
		return nil, fmt.Errorf("unable to download icon: status=%d", response.StatusCode)
	}

	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, fmt.Errorf("unable to read downloaded icon: %v", err)
	}

	if len(body) == 0 {
		return nil, fmt.Errorf("downloaded icon is empty, iconURL=%s", iconURL)
	}

	icon := &model.Icon{
		Hash:     helper.HashFromBytes(body),
		MimeType: response.ContentType,
		Content:  body,
	}

	return icon, nil
}