forked from fynelabs/selfupdate
-
Notifications
You must be signed in to change notification settings - Fork 0
/
http_source.go
154 lines (128 loc) · 3.55 KB
/
http_source.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
package selfupdate
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"runtime"
"text/template"
)
// HTTPSource provide a Source that will download the update from a HTTP url.
// It is expecting the signature file to be served at ${URL}.ed25519
type HTTPSource struct {
client *http.Client
baseURL string
}
var _ Source = (*HTTPSource)(nil)
type platform struct {
OS string
Arch string
Ext string
Executable string
}
// NewHTTPSource provide a selfupdate.Source that will fetch the specified base URL
// for update and signature using the http.Client provided. To help into providing
// cross platform application, the base is actually a Go Template string where the
// following parameter are recognized:
// {{.OS}} will be filled by the runtime OS name
// {{.Arch}} will be filled by the runtime Arch name
// {{.Ext}} will be filled by the executable expected extension for the OS
// As an example the following string `http://localhost/myapp-{{.OS}}-{{.Arch}}{{.Ext}}`
// would fetch on Windows AMD64 the following URL: `http://localhost/myapp-windows-amd64.exe`
// and on Linux AMD64: `http://localhost/myapp-linux-amd64`.
func NewHTTPSource(client *http.Client, base string) Source {
if client == nil {
client = http.DefaultClient
}
base = replaceURLTemplate(base)
return &HTTPSource{client: client, baseURL: base}
}
// Get will return if it succeed an io.ReaderCloser to the new executable being downloaded and its length
func (h *HTTPSource) Get(v *Version) (io.ReadCloser, int64, error) {
request, err := http.NewRequest("GET", h.baseURL, nil)
if err != nil {
return nil, 0, err
}
if v != nil {
if !v.Date.IsZero() {
request.Header.Add("If-Modified-Since", v.Date.Format(http.TimeFormat))
}
}
response, err := h.client.Do(request)
if err != nil {
return nil, 0, err
}
return response.Body, response.ContentLength, nil
}
// GetSignature will return the content of ${URL}.ed25519
func (h *HTTPSource) GetSignature() ([64]byte, error) {
resp, err := h.client.Get(h.baseURL + ".ed25519")
if err != nil {
return [64]byte{}, err
}
defer resp.Body.Close()
if resp.ContentLength != 64 {
return [64]byte{}, fmt.Errorf("ed25519 signature must be 64 bytes long and was %v", resp.ContentLength)
}
writer := bytes.NewBuffer(make([]byte, 0, 64))
n, err := io.Copy(writer, resp.Body)
if err != nil {
return [64]byte{}, err
}
if n != 64 {
return [64]byte{}, fmt.Errorf("ed25519 signature must be 64 bytes long and was %v", n)
}
r := [64]byte{}
copy(r[:], writer.Bytes())
return r, nil
}
// LatestVersion will return the URL Last-Modified time
func (h *HTTPSource) LatestVersion() (*Version, error) {
resp, err := h.client.Head(h.baseURL)
if err != nil {
return nil, err
}
lastModified := resp.Header.Get("Last-Modified")
if lastModified == "" {
return nil, fmt.Errorf("no Last-Modified served")
}
t, err := http.ParseTime(lastModified)
if err != nil {
return nil, err
}
return &Version{Date: t}, nil
}
func replaceURLTemplate(base string) string {
ext := ""
if runtime.GOOS == "windows" {
ext = ".exe"
}
p := platform{
OS: runtime.GOOS,
Arch: runtime.GOARCH,
Ext: ext,
}
exe, err := ExecutableRealPath()
if err != nil {
exe = filepath.Base(os.Args[0])
} else {
exe = filepath.Base(exe)
}
if runtime.GOOS == "windows" {
p.Executable = exe[:len(exe)-len(".exe")]
} else {
p.Executable = exe
}
t, err := template.New("platform").Parse(base)
if err != nil {
return base
}
buf := &bytes.Buffer{}
err = t.Execute(buf, p)
if err != nil {
return base
}
return buf.String()
}