1
0
mirror of https://github.com/taigrr/go-selfupdate synced 2025-01-18 04:33:12 -08:00

vendor via dep

This commit is contained in:
Mark Sanborn
2019-03-29 17:12:33 -07:00
parent bf295275fb
commit 4ac3c3274f
24 changed files with 1810 additions and 0 deletions

13
vendor/gopkg.in/inconshreveable/go-update.v0/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,13 @@
Copyright 2014 Alan Shreve
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.

37
vendor/gopkg.in/inconshreveable/go-update.v0/README.md generated vendored Normal file
View File

@@ -0,0 +1,37 @@
# go-update: Automatically update Go programs from the internet
go-update allows a program to update itself by replacing its executable file
with a new version. It provides the flexibility to implement different updating user experiences
like auto-updating, or manual user-initiated updates. It also boasts
advanced features like binary patching and code signing verification.
Updating your program to a new version is as easy as:
err, errRecover := update.New().FromUrl("http://release.example.com/2.0/myprogram")
if err != nil {
fmt.Printf("Update failed: %v\n", err)
}
## Documentation and API Reference
Comprehensive API documentation and code examples are available in the code documentation available on godoc.org:
[![GoDoc](https://godoc.org/github.com/inconshreveable/go-update?status.svg)](https://godoc.org/github.com/inconshreveable/go-update)
## Features
- Cross platform support (Windows too!)
- Binary patch application
- Checksum verification
- Code signing verification
- Support for updating arbitrary files
## [equinox.io](https://equinox.io)
go-update provides the primitives for building self-updating applications, but there a number of other challenges
involved in a complete updating solution such as hosting, code signing, update channels, gradual rollout,
dynamically computing binary patches, tracking update metrics like versions and failures, plus more.
I provide this service, a complete solution, free for open source projects, at [equinox.io](https://equinox.io).
## License
Apache

View File

@@ -0,0 +1,235 @@
package download
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"net/http"
"os"
"runtime"
)
type roundTripper struct {
RoundTripFn func(*http.Request) (*http.Response, error)
}
func (rt *roundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
return rt.RoundTripFn(r)
}
// Download encapsulates the state and parameters to download content
// from a URL which:
//
// - Publishes the percentage of the download completed to a channel.
// - May resume a previous download that was partially completed.
//
// Create an instance with the New() factory function.
type Download struct {
// net/http.Client to use when downloading the update.
// If nil, a default http.Client is used
HttpClient *http.Client
// As bytes are downloaded, they are written to Target.
// Download also uses the Target's Seek method to determine
// the size of partial-downloads so that it may properly
// request the remaining bytes to resume the download.
Target Target
// Progress returns the percentage of the download
// completed as an integer between 0 and 100
Progress chan (int)
// HTTP Method to use in the download request. Default is "GET"
Method string
// HTTP URL to issue the download request to
Url string
}
// New initializes a new Download object which will download
// the content from url into target.
func New(url string, target Target, httpClient *http.Client) *Download {
return &Download{
HttpClient: httpClient,
Progress: make(chan int),
Method: "GET",
Url: url,
Target: target,
}
}
// Get() downloads the content of a url to a target destination.
//
// Only HTTP/1.1 servers that implement the Range header support resuming a
// partially completed download.
//
// On success, the server must return 200 and the content, or 206 when resuming a partial download.
// If the HTTP server returns a 3XX redirect, it will be followed according to d.HttpClient's redirect policy.
//
func (d *Download) Get() (err error) {
// Close the progress channel whenever this function completes
defer close(d.Progress)
// determine the size of the download target to determine if we're resuming a partial download
offset, err := d.Target.Size()
if err != nil {
return
}
// create the download request
req, err := http.NewRequest(d.Method, d.Url, nil)
if err != nil {
return
}
// create an http client if one does not exist
if d.HttpClient == nil {
d.HttpClient = http.DefaultClient
}
// we have to add headers like this so they get used across redirects
trans := d.HttpClient.Transport
if trans == nil {
trans = http.DefaultTransport
}
d.HttpClient.Transport = &roundTripper{
RoundTripFn: func(r *http.Request) (*http.Response, error) {
// add header for download continuation
if offset > 0 {
r.Header.Add("Range", fmt.Sprintf("%d-", offset))
}
// ask for gzipped content so that net/http won't unzip it for us
// and destroy the content length header we need for progress calculations
r.Header.Add("Accept-Encoding", "gzip")
return trans.RoundTrip(r)
},
}
// issue the download request
resp, err := d.HttpClient.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
switch resp.StatusCode {
// ok
case 200, 206:
// server error
default:
err = fmt.Errorf("Non 2XX response when downloading update: %s", resp.Status)
return
}
// Determine how much we have to download
// net/http sets this to -1 when it is unknown
clength := resp.ContentLength
// Read the content from the response body
rd := resp.Body
// meter the rate at which we download content for
// progress reporting if we know how much to expect
if clength > 0 {
rd = &meteredReader{rd: rd, totalSize: clength, progress: d.Progress}
}
// Decompress the content if necessary
if resp.Header.Get("Content-Encoding") == "gzip" {
rd, err = gzip.NewReader(rd)
if err != nil {
return
}
}
// Download the update
_, err = io.Copy(d.Target, rd)
if err != nil {
return
}
return
}
// meteredReader wraps a ReadCloser. Calls to a meteredReader's Read() method
// publish updates to a progress channel with the percentage read so far.
type meteredReader struct {
rd io.ReadCloser
totalSize int64
progress chan int
totalRead int64
ticks int64
}
func (m *meteredReader) Close() error {
return m.rd.Close()
}
func (m *meteredReader) Read(b []byte) (n int, err error) {
chunkSize := (m.totalSize / 100) + 1
lenB := int64(len(b))
var nChunk int
for start := int64(0); start < lenB; start += int64(nChunk) {
end := start + chunkSize
if end > lenB {
end = lenB
}
nChunk, err = m.rd.Read(b[start:end])
n += nChunk
m.totalRead += int64(nChunk)
if m.totalRead > (m.ticks * chunkSize) {
m.ticks += 1
// try to send on channel, but don't block if it's full
select {
case m.progress <- int(m.ticks + 1):
default:
}
// give the progress channel consumer a chance to run
runtime.Gosched()
}
if err != nil {
return
}
}
return
}
// A Target is what you can supply to Download,
// it's just an io.Writer with a Size() method so that
// the a Download can "resume" an interrupted download
type Target interface {
io.Writer
Size() (int, error)
}
type FileTarget struct {
*os.File
}
func (t *FileTarget) Size() (int, error) {
if fi, err := t.File.Stat(); err != nil {
return 0, err
} else {
return int(fi.Size()), nil
}
}
type MemoryTarget struct {
bytes.Buffer
}
func (t *MemoryTarget) Size() (int, error) {
return t.Buffer.Len(), nil
}

View File

@@ -0,0 +1,7 @@
// +build !windows
package update
func hideFile(path string) error {
return nil
}

View File

@@ -0,0 +1,19 @@
package update
import (
"syscall"
"unsafe"
)
func hideFile(path string) error {
kernel32 := syscall.NewLazyDLL("kernel32.dll")
setFileAttributes := kernel32.NewProc("SetFileAttributesW")
r1, _, err := setFileAttributes.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(path))), 2)
if r1 == 0 {
return err
} else {
return nil
}
}

491
vendor/gopkg.in/inconshreveable/go-update.v0/update.go generated vendored Normal file
View File

@@ -0,0 +1,491 @@
/*
go-update allows a program to update itself by replacing its executable file
with a new version. It provides the flexibility to implement different updating user experiences
like auto-updating, or manual user-initiated updates. It also boasts
advanced features like binary patching and code signing verification.
Updating your program to a new version is as easy as:
err, errRecover := update.New().FromUrl("http://release.example.com/2.0/myprogram")
if err != nil {
fmt.Printf("Update failed: %v\n", err)
}
You may also choose to update from other data sources such as a file or an io.Reader:
err, errRecover := update.New().FromFile("/path/to/update")
Binary Diff Patching
Binary diff updates are supported and easy to use:
up := update.New().ApplyPatch(update.PATCHTYPE_BSDIFF)
err, errRecover := up.FromUrl("http://release.example.com/2.0/mypatch")
Checksum Verification
You should also verify the checksum of new updates as well as verify
the digital signature of an update. Note that even when you choose to apply
a patch, the checksum is verified against the complete update after that patch
has been applied.
up := update.New().ApplyPatch(update.PATCHTYPE_BSDIFF).VerifyChecksum(checksum)
err, errRecover := up.FromUrl("http://release.example.com/2.0/mypatch")
Updating other files
Updating arbitrary files is also supported. You may update files which are
not the currently running program:
up := update.New().Target("/usr/local/bin/some-program")
err, errRecover := up.FromUrl("http://release.example.com/2.0/some-program")
Code Signing
Truly secure updates use code signing to verify that the update was issued by a trusted party.
To do this, you'll need to generate a public/private key pair. You can do this with openssl,
or the equinox.io client (https://equinox.io/client) can easily generate one for you:
# with equinox client
equinox genkey --private-key=private.pem --public-key=public.pem
# with openssl
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -out public.pem -pubout
Once you have your key pair, you can instruct your program to validate its updates
with the public key:
const publicKey = `-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----`
up, err := update.New().VerifySignatureWithPEM(publicKey)
if err != nil {
return fmt.Errorf("Bad public key: '%v': %v", publicKey, err)
}
Once you've configured your program this way, it will disallow all updates unless they
are properly signed. You must now pass in the signature to verify with:
up.VerifySignature(signature).FromUrl("http://dl.example.com/update")
Error Handling and Recovery
To perform an update, the process must be able to read its executable file and to write
to the directory that contains its executable file. It can be useful to check whether the process
has the necessary permissions to perform an update before trying to apply one. Use the
CanUpdate call to provide a useful message to the user if the update can't proceed without
elevated permissions:
up := update.New().Target("/etc/hosts")
err := up.CanUpdate()
if err != nil {
fmt.Printf("Can't update because: '%v'. Try as root or Administrator\n", err)
return
}
err, errRecover := up.FromUrl("https://example.com/new/hosts")
Although exceedingly unlikely, the update operation itself is not atomic and can fail
in such a way that a user's computer is left in an inconsistent state. If that happens,
go-update attempts to recover to leave the system in a good state. If the recovery step
fails (even more unlikely), a second error, referred to as "errRecover" will be non-nil
so that you may inform your users of the bad news. You should handle this case as shown
here:
err, errRecover := up.FromUrl("https://example.com/update")
if err != nil {
fmt.Printf("Update failed: %v\n", err)
if errRecover != nil {
fmt.Printf("Failed to recover bad update: %v!\n", errRecover)
fmt.Printf("Program exectuable may be missing!\n")
}
}
Subpackages
Sub-package check contains the client functionality for a simple protocol for negotiating
whether a new update is available, where it is, and the metadata needed for verifying it.
Sub-package download contains functionality for downloading from an HTTP endpoint
while outputting a progress meter and supports resuming partial downloads.
*/
package update
import (
"bytes"
"crypto"
"crypto/rsa"
"crypto/sha256"
_ "crypto/sha512" // for tls cipher support
"crypto/x509"
"encoding/pem"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"github.com/kardianos/osext"
"github.com/kr/binarydist"
"gopkg.in/inconshreveable/go-update.v0/download"
)
// The type of a binary patch, if any. Only bsdiff is supported
type PatchType string
const (
PATCHTYPE_BSDIFF PatchType = "bsdiff"
PATCHTYPE_NONE = ""
)
type Update struct {
// empty string means "path of the current executable"
TargetPath string
// type of patch to apply. PATCHTYPE_NONE means "not a patch"
PatchType
// sha256 checksum of the new binary to verify against
Checksum []byte
// public key to use for signature verification
PublicKey *rsa.PublicKey
// signature to use for signature verification
Signature []byte
// configurable http client can be passed to download
HTTPClient *http.Client
}
func (u *Update) getPath() (string, error) {
if u.TargetPath == "" {
return osext.Executable()
} else {
return u.TargetPath, nil
}
}
// New creates a new Update object.
// A default update object assumes the complete binary
// content will be used for update (not a patch) and that
// the intended target is the running executable.
//
// Use this as the start of a chain of calls on the Update
// object to build up your configuration. Example:
//
// up := update.New().ApplyPatch(update.PATCHTYPE_BSDIFF).VerifyChecksum(checksum)
//
func New() *Update {
return &Update{
TargetPath: "",
PatchType: PATCHTYPE_NONE,
}
}
// Target configures the update to update the file at the given path.
// The emptry string means 'the executable file of the running program'.
func (u *Update) Target(path string) *Update {
u.TargetPath = path
return u
}
// ApplyPatch configures the update to treat the contents of the update
// as a patch to apply to the existing to target. You must specify the
// format of the patch. Only PATCHTYPE_BSDIFF is supported at the moment.
func (u *Update) ApplyPatch(patchType PatchType) *Update {
u.PatchType = patchType
return u
}
// VerifyChecksum configures the update to verify that the
// the update has the given sha256 checksum.
func (u *Update) VerifyChecksum(checksum []byte) *Update {
u.Checksum = checksum
return u
}
// VerifySignature configures the update to verify the given
// signature of the update. You must also call one of the
// VerifySignatureWith* functions to specify a public key
// to use for verification.
func (u *Update) VerifySignature(signature []byte) *Update {
u.Signature = signature
return u
}
// VerifySignatureWith configures the update to use the given RSA
// public key to verify the update's signature. You must also call
// VerifySignature() with a signature to check.
//
// You'll probably want to use VerifySignatureWithPEM instead of
// parsing the public key yourself.
func (u *Update) VerifySignatureWith(publicKey *rsa.PublicKey) *Update {
u.PublicKey = publicKey
return u
}
// VerifySignatureWithPEM configures the update to use the given PEM-formatted
// RSA public key to verify the update's signature. You must also call
// VerifySignature() with a signature to check.
//
// A PEM formatted public key typically begins with
// -----BEGIN PUBLIC KEY-----
func (u *Update) VerifySignatureWithPEM(publicKeyPEM []byte) (*Update, error) {
block, _ := pem.Decode(publicKeyPEM)
if block == nil {
return u, fmt.Errorf("Couldn't parse PEM data")
}
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return u, err
}
var ok bool
u.PublicKey, ok = pub.(*rsa.PublicKey)
if !ok {
return u, fmt.Errorf("Public key isn't an RSA public key")
}
return u, nil
}
// FromUrl updates the target with the contents of the given URL.
func (u *Update) FromUrl(url string) (err error, errRecover error) {
target := new(download.MemoryTarget)
err = download.New(url, target, u.HTTPClient).Get()
if err != nil {
return
}
return u.FromStream(target)
}
// FromFile updates the target the contents of the given file.
func (u *Update) FromFile(path string) (err error, errRecover error) {
// open the new updated contents
fp, err := os.Open(path)
if err != nil {
return
}
defer fp.Close()
// do the update
return u.FromStream(fp)
}
// FromStream updates the target file with the contents of the supplied io.Reader.
//
// FromStream performs the following actions to ensure a safe cross-platform update:
//
// 1. If configured, applies the contents of the io.Reader as a binary patch.
//
// 2. If configured, computes the sha256 checksum and verifies it matches.
//
// 3. If configured, verifies the RSA signature with a public key.
//
// 4. Creates a new file, /path/to/.target.new with mode 0755 with the contents of the updated file
//
// 5. Renames /path/to/target to /path/to/.target.old
//
// 6. Renames /path/to/.target.new to /path/to/target
//
// 7. If the rename is successful, deletes /path/to/.target.old, returns no error
//
// 8. If the rename fails, attempts to rename /path/to/.target.old back to /path/to/target
// If this operation fails, it is reported in the errRecover return value so as not to
// mask the original error that caused the recovery attempt.
//
// On Windows, the removal of /path/to/.target.old always fails, so instead,
// we just make the old file hidden instead.
func (u *Update) FromStream(updateWith io.Reader) (err error, errRecover error) {
updatePath, err := u.getPath()
if err != nil {
return
}
var newBytes []byte
// apply a patch if requested
switch u.PatchType {
case PATCHTYPE_BSDIFF:
newBytes, err = applyPatch(updateWith, updatePath)
if err != nil {
return
}
case PATCHTYPE_NONE:
// no patch to apply, go on through
newBytes, err = ioutil.ReadAll(updateWith)
if err != nil {
return
}
default:
err = fmt.Errorf("Unrecognized patch type: %s", u.PatchType)
return
}
// verify checksum if requested
if u.Checksum != nil {
if err = verifyChecksum(newBytes, u.Checksum); err != nil {
return
}
}
// verify signature if requested
if u.Signature != nil || u.PublicKey != nil {
if u.Signature == nil {
err = fmt.Errorf("No public key specified to verify signature")
return
}
if u.PublicKey == nil {
err = fmt.Errorf("No signature to verify!")
return
}
if err = verifySignature(newBytes, u.Signature, u.PublicKey); err != nil {
return
}
}
// get the directory the executable exists in
updateDir := filepath.Dir(updatePath)
filename := filepath.Base(updatePath)
// Copy the contents of of newbinary to a the new executable file
newPath := filepath.Join(updateDir, fmt.Sprintf(".%s.new", filename))
fp, err := os.OpenFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
if err != nil {
return
}
defer fp.Close()
_, err = io.Copy(fp, bytes.NewReader(newBytes))
// if we don't call fp.Close(), windows won't let us move the new executable
// because the file will still be "in use"
fp.Close()
// this is where we'll move the executable to so that we can swap in the updated replacement
oldPath := filepath.Join(updateDir, fmt.Sprintf(".%s.old", filename))
// delete any existing old exec file - this is necessary on Windows for two reasons:
// 1. after a successful update, Windows can't remove the .old file because the process is still running
// 2. windows rename operations fail if the destination file already exists
_ = os.Remove(oldPath)
// move the existing executable to a new file in the same directory
err = os.Rename(updatePath, oldPath)
if err != nil {
return
}
// move the new exectuable in to become the new program
err = os.Rename(newPath, updatePath)
if err != nil {
// copy unsuccessful
errRecover = os.Rename(oldPath, updatePath)
} else {
// copy successful, remove the old binary
errRemove := os.Remove(oldPath)
// windows has trouble with removing old binaries, so hide it instead
if errRemove != nil {
_ = hideFile(oldPath)
}
}
return
}
// CanUpdate() determines whether the process has the correct permissions to
// perform the requested update. If the update can proceed, it returns nil, otherwise
// it returns the error that would occur if an update were attempted.
func (u *Update) CanUpdate() (err error) {
// get the directory the file exists in
path, err := u.getPath()
if err != nil {
return
}
fileDir := filepath.Dir(path)
fileName := filepath.Base(path)
// attempt to open a file in the file's directory
newPath := filepath.Join(fileDir, fmt.Sprintf(".%s.new", fileName))
fp, err := os.OpenFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
if err != nil {
return
}
fp.Close()
_ = os.Remove(newPath)
return
}
func applyPatch(patch io.Reader, updatePath string) ([]byte, error) {
// open the file to update
old, err := os.Open(updatePath)
if err != nil {
return nil, err
}
defer old.Close()
// apply the patch
applied := new(bytes.Buffer)
if err = binarydist.Patch(old, applied, patch); err != nil {
return nil, err
}
return applied.Bytes(), nil
}
func verifyChecksum(updated []byte, expectedChecksum []byte) error {
checksum, err := ChecksumForBytes(updated)
if err != nil {
return err
}
if !bytes.Equal(expectedChecksum, checksum) {
return fmt.Errorf("Updated file has wrong checksum. Expected: %x, got: %x", expectedChecksum, checksum)
}
return nil
}
// ChecksumForFile returns the sha256 checksum for the given file
func ChecksumForFile(path string) ([]byte, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return ChecksumForReader(f)
}
// ChecksumForReader returns the sha256 checksum for the entire
// contents of the given reader.
func ChecksumForReader(rd io.Reader) ([]byte, error) {
h := sha256.New()
if _, err := io.Copy(h, rd); err != nil {
return nil, err
}
return h.Sum(nil), nil
}
// ChecksumForBytes returns the sha256 checksum for the given bytes
func ChecksumForBytes(source []byte) ([]byte, error) {
return ChecksumForReader(bytes.NewReader(source))
}
func verifySignature(source, signature []byte, publicKey *rsa.PublicKey) error {
checksum, err := ChecksumForBytes(source)
if err != nil {
return err
}
return rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, checksum, signature)
}