forked from sanbornm/go-selfupdate
/
check.go
215 lines (181 loc) · 4.87 KB
/
check.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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
package check
import (
"bytes"
_ "crypto/sha512" // for tls cipher support
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"runtime"
"github.com/kardianos/osext"
"gopkg.in/inconshreveable/go-update.v0"
)
type Initiative string
const (
INITIATIVE_NEVER Initiative = "never"
INITIATIVE_AUTO = "auto"
INITIATIVE_MANUAL = "manual"
)
var NoUpdateAvailable error = fmt.Errorf("No update available")
type Params struct {
// protocol version
Version int `json:"version"`
// identifier of the application to update
AppId string `json:"app_id"`
// version of the application updating itself
AppVersion string `json:"app_version"`
// operating system of target platform
OS string `json:"-"`
// hardware architecture of target platform
Arch string `json:"-"`
// application-level user identifier
UserId string `json:"user_id"`
// checksum of the binary to replace (used for returning diff patches)
Checksum string `json:"checksum"`
// release channel (empty string means 'stable')
Channel string `json:"-"`
// tags for custom update channels
Tags map[string]string `json:"tags"`
}
type Result struct {
up *update.Update
// should the update be applied automatically/manually
Initiative Initiative `json:"initiative"`
// url where to download the updated application
Url string `json:"url"`
// a URL to a patch to apply
PatchUrl string `json:"patch_url"`
// the patch format (only bsdiff supported at the moment)
PatchType update.PatchType `json:"patch_type"`
// version of the new application
Version string `json:"version"`
// expected checksum of the new application
Checksum string `json:"checksum"`
// signature for verifying update authenticity
Signature string `json:"signature"`
}
// CheckForUpdate makes an HTTP post to a URL with the JSON serialized
// representation of Params. It returns the deserialized result object
// returned by the remote endpoint or an error. If you do not set
// OS/Arch, CheckForUpdate will populate them for you. Similarly, if
// Version is 0, it will be set to 1. Lastly, if Checksum is the empty
// string, it will be automatically be computed for the running program's
// executable file.
func (p *Params) CheckForUpdate(url string, up *update.Update) (*Result, error) {
if p.Tags == nil {
p.Tags = make(map[string]string)
}
if p.Channel == "" {
p.Channel = "stable"
}
if p.OS == "" {
p.OS = runtime.GOOS
}
if p.Arch == "" {
p.Arch = runtime.GOARCH
}
if p.Version == 0 {
p.Version = 1
}
// ignore errors auto-populating the checksum
// if it fails, you just won't be able to patch
if up.TargetPath == "" {
p.Checksum = defaultChecksum()
} else {
checksum, err := update.ChecksumForFile(up.TargetPath)
if err != nil {
return nil, err
}
p.Checksum = hex.EncodeToString(checksum)
}
p.Tags["os"] = p.OS
p.Tags["arch"] = p.Arch
p.Tags["channel"] = p.Channel
body, err := json.Marshal(p)
if err != nil {
return nil, err
}
client := up.HTTPClient
if client == nil {
client = &http.Client{}
}
resp, err := client.Post(url, "application/json", bytes.NewReader(body))
if err != nil {
return nil, err
}
// no content means no available update
if resp.StatusCode == 204 {
return nil, NoUpdateAvailable
}
defer resp.Body.Close()
respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
result := &Result{up: up}
if err := json.Unmarshal(respBytes, result); err != nil {
return nil, err
}
return result, nil
}
func (p *Params) CheckAndApplyUpdate(url string, up *update.Update) (result *Result, err error, errRecover error) {
// check for an update
result, err = p.CheckForUpdate(url, up)
if err != nil {
return
}
// run the available update
err, errRecover = result.Update()
return
}
func (r *Result) Update() (err error, errRecover error) {
if r.Checksum != "" {
r.up.Checksum, err = hex.DecodeString(r.Checksum)
if err != nil {
return
}
}
if r.Signature != "" {
r.up.Signature, err = hex.DecodeString(r.Signature)
if err != nil {
return
}
}
if r.PatchType != "" {
r.up.PatchType = r.PatchType
}
if r.Url == "" && r.PatchUrl == "" {
err = fmt.Errorf("Result does not contain an update url or patch update url")
return
}
if r.PatchUrl != "" {
err, errRecover = r.up.FromUrl(r.PatchUrl)
if err == nil {
// success!
return
} else {
// failed to update from patch URL, try with the whole thing
if r.Url == "" || errRecover != nil {
// we can't try updating from a URL with the full contents
// in these cases, so fail
return
} else {
r.up.PatchType = update.PATCHTYPE_NONE
}
}
}
// try updating from a URL with the full contents
return r.up.FromUrl(r.Url)
}
func defaultChecksum() string {
path, err := osext.Executable()
if err != nil {
return ""
}
checksum, err := update.ChecksumForFile(path)
if err != nil {
return ""
}
return hex.EncodeToString(checksum)
}