-
Notifications
You must be signed in to change notification settings - Fork 0
/
favicon-list.go
166 lines (151 loc) · 4.24 KB
/
favicon-list.go
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package favicons
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"github.com/mrmelon54/png2ico"
"image/png"
"io"
"net/http"
)
// FaviconList contains the ico, png and svg icons for separate favicons
type FaviconList struct {
Ico *FaviconImage // can be generated from png with wrapper
Png *FaviconImage // can be generated from svg with inkscape
Svg *FaviconImage
}
var ErrInvalidFaviconExtension = errors.New("invalid favicon extension")
// ProduceForExt outputs the bytes for the ico/png/svg icon and the HTTP
// Content-Type header to output.
func (l *FaviconList) ProduceForExt(ext string) (raw []byte, contentType string, err error) {
switch ext {
case ".ico":
contentType = "image/x-icon"
raw, err = l.ProduceIco()
case ".png":
contentType = "image/png"
raw, err = l.ProducePng()
case ".svg":
contentType = "image/svg+xml"
raw, err = l.ProduceSvg()
default:
err = ErrInvalidFaviconExtension
}
return
}
// ProduceIco outputs the bytes of the ico icon or an error
func (l *FaviconList) ProduceIco() ([]byte, error) {
if l.Ico == nil {
return nil, ErrFaviconNotFound
}
return l.Ico.Raw, nil
}
// ProducePng outputs the bytes of the png icon or an error
func (l *FaviconList) ProducePng() ([]byte, error) {
if l.Png == nil {
return nil, ErrFaviconNotFound
}
return l.Png.Raw, nil
}
// ProduceSvg outputs the bytes of the svg icon or an error
func (l *FaviconList) ProduceSvg() ([]byte, error) {
if l.Svg == nil {
return nil, ErrFaviconNotFound
}
return l.Svg.Raw, nil
}
// PreProcess takes an input of the svg2png conversion function and outputs
// an error if the SVG, PNG or ICO fails to download or generate
func (l *FaviconList) PreProcess(convert func(in []byte) ([]byte, error)) error {
var err error
// SVG
if l.Svg != nil {
// download SVG
l.Svg.Raw, err = getFaviconViaRequest(l.Svg.Url)
if err != nil {
return fmt.Errorf("favicons: failed to fetch SVG icon: %w", err)
}
l.Svg.Hash = hex.EncodeToString(sha256.New().Sum(l.Svg.Raw))
}
// PNG
if l.Png != nil {
// download PNG
l.Png.Raw, err = getFaviconViaRequest(l.Png.Url)
if err != nil {
return fmt.Errorf("favicons: failed to fetch PNG icon: %w", err)
}
} else if l.Svg != nil {
// generate PNG from SVG
l.Png = &FaviconImage{}
l.Png.Raw, err = convert(l.Svg.Raw)
if err != nil {
return fmt.Errorf("favicons: failed to generate PNG icon: %w", err)
}
}
// ICO
if l.Ico != nil {
// download ICO
l.Ico.Raw, err = getFaviconViaRequest(l.Ico.Url)
if err != nil {
return fmt.Errorf("favicons: failed to fetch ICO icon: %w", err)
}
} else if l.Png != nil {
// generate ICO from PNG
l.Ico = &FaviconImage{}
decode, err := png.Decode(bytes.NewReader(l.Png.Raw))
if err != nil {
return fmt.Errorf("favicons: failed to decode PNG icon: %w", err)
}
b := decode.Bounds()
l.Ico.Raw, err = png2ico.ConvertPngToIco(l.Png.Raw, b.Dx(), b.Dy())
if err != nil {
return fmt.Errorf("favicons: failed to generate ICO icon: %w", err)
}
}
// generate sha256 hashes for svg, png and ico
l.genSha256()
return nil
}
// genSha256 generates sha256 hashes
func (l *FaviconList) genSha256() {
if l.Svg != nil {
l.Svg.Hash = genSha256(l.Svg.Raw)
}
if l.Png != nil {
l.Png.Hash = genSha256(l.Png.Raw)
}
if l.Ico != nil {
l.Ico.Hash = genSha256(l.Ico.Raw)
}
}
// getFaviconViaRequest uses the standard http request library to download
// icons, outputs the raw bytes from the download or an error.
var getFaviconViaRequest = func(url string) ([]byte, error) {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, fmt.Errorf("favicons: Failed to send request '%s': %w", url, err)
}
req.Header.Set("X-Violet-Raw-Favicon", "1")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("favicons: failed to do request '%s': %w", url, err)
}
rawBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("favicons: failed to read response '%s': %w", url, err)
}
return rawBody, nil
}
// genSha256 generates a sha256 hash as a hex encoded string
func genSha256(in []byte) string {
// create sha256 generator and write to it
h := sha256.New()
_, err := h.Write(in)
if err != nil {
return ""
}
// encode as hex
return hex.EncodeToString(h.Sum(nil))
}