├── .travis.yml
├── Godeps
├── _workspace
│ ├── .gitignore
│ └── src
│ │ ├── github.com
│ │ ├── kr
│ │ │ └── binarydist
│ │ │ │ ├── .gitignore
│ │ │ │ ├── testdata
│ │ │ │ ├── sample.new
│ │ │ │ ├── sample.old
│ │ │ │ └── sample.patch
│ │ │ │ ├── Readme.md
│ │ │ │ ├── sort_test.go
│ │ │ │ ├── doc.go
│ │ │ │ ├── bzip2.go
│ │ │ │ ├── seek.go
│ │ │ │ ├── License
│ │ │ │ ├── patch_test.go
│ │ │ │ ├── encoding.go
│ │ │ │ ├── common_test.go
│ │ │ │ ├── diff_test.go
│ │ │ │ ├── patch.go
│ │ │ │ └── diff.go
│ │ └── kardianos
│ │ │ └── osext
│ │ │ ├── README.md
│ │ │ ├── osext_plan9.go
│ │ │ ├── osext.go
│ │ │ ├── osext_windows.go
│ │ │ ├── osext_procfs.go
│ │ │ ├── LICENSE
│ │ │ ├── osext_sysctl.go
│ │ │ └── osext_test.go
│ │ └── gopkg.in
│ │ └── inconshreveable
│ │ └── go-update.v0
│ │ ├── hide_noop.go
│ │ ├── hide_windows.go
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── check
│ │ └── check.go
│ │ ├── download
│ │ └── download.go
│ │ ├── update_test.go
│ │ └── update.go
├── Readme
└── Godeps.json
├── main_test.go
├── selfupdate
├── selfupdate_test.go
└── selfupdate.go
├── .gitignore
├── LICENSE.md
├── README.md
└── main.go
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 |
--------------------------------------------------------------------------------
/Godeps/_workspace/.gitignore:
--------------------------------------------------------------------------------
1 | /pkg
2 | /bin
3 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/github.com/kr/binarydist/.gitignore:
--------------------------------------------------------------------------------
1 | test.*
2 |
--------------------------------------------------------------------------------
/main_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "testing"
4 |
5 | func TestUpdater(t *testing.T) {
6 | }
7 |
--------------------------------------------------------------------------------
/selfupdate/selfupdate_test.go:
--------------------------------------------------------------------------------
1 | package selfupdate
2 |
3 | import "testing"
4 |
5 | func TestUpdater(t *testing.T) {
6 | }
7 |
--------------------------------------------------------------------------------
/Godeps/Readme:
--------------------------------------------------------------------------------
1 | This directory tree is generated automatically by godep.
2 |
3 | Please do not edit.
4 |
5 | See https://github.com/tools/godep for more information.
6 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.new:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sqs/go-selfupdate/HEAD/Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.new
--------------------------------------------------------------------------------
/Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.old:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sqs/go-selfupdate/HEAD/Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.old
--------------------------------------------------------------------------------
/Godeps/_workspace/src/gopkg.in/inconshreveable/go-update.v0/hide_noop.go:
--------------------------------------------------------------------------------
1 | // +build !windows
2 |
3 | package update
4 |
5 | func hideFile(path string) error {
6 | return nil
7 | }
8 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.patch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sqs/go-selfupdate/HEAD/Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.patch
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Use glob syntax.
2 | syntax: glob
3 |
4 | *.DS_Store
5 | *.swp
6 | *.swo
7 | *.pyc
8 | *.php~
9 | *.orig
10 | *~
11 | *.db
12 | *.log
13 | public/*
14 | go-selfupdate
15 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/github.com/kr/binarydist/Readme.md:
--------------------------------------------------------------------------------
1 | # binarydist
2 |
3 | Package binarydist implements binary diff and patch as described on
4 | . It reads and writes files
5 | compatible with the tools there.
6 |
7 | Documentation at .
8 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/github.com/kardianos/osext/README.md:
--------------------------------------------------------------------------------
1 | ### Extensions to the "os" package.
2 |
3 | ## Find the current Executable and ExecutableFolder.
4 |
5 | There is sometimes utility in finding the current executable file
6 | that is running. This can be used for upgrading the current executable
7 | or finding resources located relative to the executable file.
8 |
9 | Multi-platform and supports:
10 | * Linux
11 | * OS X
12 | * Windows
13 | * Plan 9
14 | * BSDs.
15 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/gopkg.in/inconshreveable/go-update.v0/hide_windows.go:
--------------------------------------------------------------------------------
1 | package update
2 |
3 | import (
4 | "syscall"
5 | "unsafe"
6 | )
7 |
8 | func hideFile(path string) error {
9 | kernel32 := syscall.NewLazyDLL("kernel32.dll")
10 | setFileAttributes := kernel32.NewProc("SetFileAttributesW")
11 |
12 | r1, _, err := setFileAttributes.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(path))), 2)
13 |
14 | if r1 == 0 {
15 | return err
16 | } else {
17 | return nil
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Godeps/Godeps.json:
--------------------------------------------------------------------------------
1 | {
2 | "ImportPath": "github.com/sqs/go-selfupdate",
3 | "GoVersion": "go1.4.2",
4 | "Packages": [
5 | "./..."
6 | ],
7 | "Deps": [
8 | {
9 | "ImportPath": "github.com/kardianos/osext",
10 | "Rev": "8fef92e41e22a70e700a96b29f066cda30ea24ef"
11 | },
12 | {
13 | "ImportPath": "github.com/kr/binarydist",
14 | "Rev": "9955b0ab8708602d411341e55fffd7e0700f86bd"
15 | },
16 | {
17 | "ImportPath": "gopkg.in/inconshreveable/go-update.v0",
18 | "Rev": "d8b0b1d421aa1cbf392c05869f8abbc669bb7066"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/github.com/kardianos/osext/osext_plan9.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package osext
6 |
7 | import (
8 | "os"
9 | "strconv"
10 | "syscall"
11 | )
12 |
13 | func executable() (string, error) {
14 | f, err := os.Open("/proc/" + strconv.Itoa(os.Getpid()) + "/text")
15 | if err != nil {
16 | return "", err
17 | }
18 | defer f.Close()
19 | return syscall.Fd2path(int(f.Fd()))
20 | }
21 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/gopkg.in/inconshreveable/go-update.v0/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2014 Alan Shreve
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/github.com/kr/binarydist/sort_test.go:
--------------------------------------------------------------------------------
1 | package binarydist
2 |
3 | import (
4 | "bytes"
5 | "crypto/rand"
6 | "testing"
7 | )
8 |
9 | var sortT = [][]byte{
10 | mustRandBytes(1000),
11 | mustReadAll(mustOpen("test.old")),
12 | []byte("abcdefabcdef"),
13 | }
14 |
15 | func TestQsufsort(t *testing.T) {
16 | for _, s := range sortT {
17 | I := qsufsort(s)
18 | for i := 1; i < len(I); i++ {
19 | if bytes.Compare(s[I[i-1]:], s[I[i]:]) > 0 {
20 | t.Fatalf("unsorted at %d", i)
21 | }
22 | }
23 | }
24 | }
25 |
26 | func mustRandBytes(n int) []byte {
27 | b := make([]byte, n)
28 | _, err := rand.Read(b)
29 | if err != nil {
30 | panic(err)
31 | }
32 | return b
33 | }
34 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/github.com/kardianos/osext/osext.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // Extensions to the standard "os" package.
6 | package osext
7 |
8 | import "path/filepath"
9 |
10 | // Executable returns an absolute path that can be used to
11 | // re-invoke the current program.
12 | // It may not be valid after the current program exits.
13 | func Executable() (string, error) {
14 | p, err := executable()
15 | return filepath.Clean(p), err
16 | }
17 |
18 | // Returns same path as Executable, returns just the folder
19 | // path. Excludes the executable name.
20 | func ExecutableFolder() (string, error) {
21 | p, err := Executable()
22 | if err != nil {
23 | return "", err
24 | }
25 | folder, _ := filepath.Split(p)
26 | return folder, nil
27 | }
28 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/github.com/kr/binarydist/doc.go:
--------------------------------------------------------------------------------
1 | // Package binarydist implements binary diff and patch as described on
2 | // http://www.daemonology.net/bsdiff/. It reads and writes files
3 | // compatible with the tools there.
4 | package binarydist
5 |
6 | var magic = [8]byte{'B', 'S', 'D', 'I', 'F', 'F', '4', '0'}
7 |
8 | // File format:
9 | // 0 8 "BSDIFF40"
10 | // 8 8 X
11 | // 16 8 Y
12 | // 24 8 sizeof(newfile)
13 | // 32 X bzip2(control block)
14 | // 32+X Y bzip2(diff block)
15 | // 32+X+Y ??? bzip2(extra block)
16 | // with control block a set of triples (x,y,z) meaning "add x bytes
17 | // from oldfile to x bytes from the diff block; copy y bytes from the
18 | // extra block; seek forwards in oldfile by z bytes".
19 | type header struct {
20 | Magic [8]byte
21 | CtrlLen int64
22 | DiffLen int64
23 | NewSize int64
24 | }
25 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/github.com/kr/binarydist/bzip2.go:
--------------------------------------------------------------------------------
1 | package binarydist
2 |
3 | import (
4 | "io"
5 | "os/exec"
6 | )
7 |
8 | type bzip2Writer struct {
9 | c *exec.Cmd
10 | w io.WriteCloser
11 | }
12 |
13 | func (w bzip2Writer) Write(b []byte) (int, error) {
14 | return w.w.Write(b)
15 | }
16 |
17 | func (w bzip2Writer) Close() error {
18 | if err := w.w.Close(); err != nil {
19 | return err
20 | }
21 | return w.c.Wait()
22 | }
23 |
24 | // Package compress/bzip2 implements only decompression,
25 | // so we'll fake it by running bzip2 in another process.
26 | func newBzip2Writer(w io.Writer) (wc io.WriteCloser, err error) {
27 | var bw bzip2Writer
28 | bw.c = exec.Command("bzip2", "-c")
29 | bw.c.Stdout = w
30 |
31 | if bw.w, err = bw.c.StdinPipe(); err != nil {
32 | return nil, err
33 | }
34 |
35 | if err = bw.c.Start(); err != nil {
36 | return nil, err
37 | }
38 |
39 | return bw, nil
40 | }
41 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/github.com/kardianos/osext/osext_windows.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package osext
6 |
7 | import (
8 | "syscall"
9 | "unicode/utf16"
10 | "unsafe"
11 | )
12 |
13 | var (
14 | kernel = syscall.MustLoadDLL("kernel32.dll")
15 | getModuleFileNameProc = kernel.MustFindProc("GetModuleFileNameW")
16 | )
17 |
18 | // GetModuleFileName() with hModule = NULL
19 | func executable() (exePath string, err error) {
20 | return getModuleFileName()
21 | }
22 |
23 | func getModuleFileName() (string, error) {
24 | var n uint32
25 | b := make([]uint16, syscall.MAX_PATH)
26 | size := uint32(len(b))
27 |
28 | r0, _, e1 := getModuleFileNameProc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size))
29 | n = uint32(r0)
30 | if n == 0 {
31 | return "", e1
32 | }
33 | return string(utf16.Decode(b[0:n])), nil
34 | }
35 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/github.com/kr/binarydist/seek.go:
--------------------------------------------------------------------------------
1 | package binarydist
2 |
3 | import (
4 | "errors"
5 | )
6 |
7 | type seekBuffer struct {
8 | buf []byte
9 | pos int
10 | }
11 |
12 | func (b *seekBuffer) Write(p []byte) (n int, err error) {
13 | n = copy(b.buf[b.pos:], p)
14 | if n == len(p) {
15 | b.pos += n
16 | return n, nil
17 | }
18 | b.buf = append(b.buf, p[n:]...)
19 | b.pos += len(p)
20 | return len(p), nil
21 | }
22 |
23 | func (b *seekBuffer) Seek(offset int64, whence int) (ret int64, err error) {
24 | var abs int64
25 | switch whence {
26 | case 0:
27 | abs = offset
28 | case 1:
29 | abs = int64(b.pos) + offset
30 | case 2:
31 | abs = int64(len(b.buf)) + offset
32 | default:
33 | return 0, errors.New("binarydist: invalid whence")
34 | }
35 | if abs < 0 {
36 | return 0, errors.New("binarydist: negative position")
37 | }
38 | if abs >= 1<<31 {
39 | return 0, errors.New("binarydist: position out of range")
40 | }
41 | b.pos = int(abs)
42 | return abs, nil
43 | }
44 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/github.com/kardianos/osext/osext_procfs.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // +build linux netbsd openbsd solaris dragonfly
6 |
7 | package osext
8 |
9 | import (
10 | "errors"
11 | "fmt"
12 | "os"
13 | "runtime"
14 | "strings"
15 | )
16 |
17 | func executable() (string, error) {
18 | switch runtime.GOOS {
19 | case "linux":
20 | const deletedTag = " (deleted)"
21 | execpath, err := os.Readlink("/proc/self/exe")
22 | if err != nil {
23 | return execpath, err
24 | }
25 | execpath = strings.TrimSuffix(execpath, deletedTag)
26 | execpath = strings.TrimPrefix(execpath, deletedTag)
27 | return execpath, nil
28 | case "netbsd":
29 | return os.Readlink("/proc/curproc/exe")
30 | case "openbsd", "dragonfly":
31 | return os.Readlink("/proc/curproc/file")
32 | case "solaris":
33 | return os.Readlink(fmt.Sprintf("/proc/%d/path/a.out", os.Getpid()))
34 | }
35 | return "", errors.New("ExecPath not implemented for " + runtime.GOOS)
36 | }
37 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Mark Sanborn
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/github.com/kr/binarydist/License:
--------------------------------------------------------------------------------
1 | Copyright 2012 Keith Rarick
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/github.com/kr/binarydist/patch_test.go:
--------------------------------------------------------------------------------
1 | package binarydist
2 |
3 | import (
4 | "io/ioutil"
5 | "os"
6 | "os/exec"
7 | "testing"
8 | )
9 |
10 | func TestPatch(t *testing.T) {
11 | mustWriteRandFile("test.old", 1e3)
12 | mustWriteRandFile("test.new", 1e3)
13 |
14 | got, err := ioutil.TempFile("/tmp", "bspatch.")
15 | if err != nil {
16 | panic(err)
17 | }
18 | os.Remove(got.Name())
19 |
20 | err = exec.Command("bsdiff", "test.old", "test.new", "test.patch").Run()
21 | if err != nil {
22 | panic(err)
23 | }
24 |
25 | err = Patch(mustOpen("test.old"), got, mustOpen("test.patch"))
26 | if err != nil {
27 | t.Fatal("err", err)
28 | }
29 |
30 | ref, err := got.Seek(0, 2)
31 | if err != nil {
32 | panic(err)
33 | }
34 |
35 | t.Logf("got %d bytes", ref)
36 | if n := fileCmp(got, mustOpen("test.new")); n > -1 {
37 | t.Fatalf("produced different output at pos %d", n)
38 | }
39 | }
40 |
41 | func TestPatchHk(t *testing.T) {
42 | got, err := ioutil.TempFile("/tmp", "bspatch.")
43 | if err != nil {
44 | panic(err)
45 | }
46 | os.Remove(got.Name())
47 |
48 | err = Patch(mustOpen("testdata/sample.old"), got, mustOpen("testdata/sample.patch"))
49 | if err != nil {
50 | t.Fatal("err", err)
51 | }
52 |
53 | ref, err := got.Seek(0, 2)
54 | if err != nil {
55 | panic(err)
56 | }
57 |
58 | t.Logf("got %d bytes", ref)
59 | if n := fileCmp(got, mustOpen("testdata/sample.new")); n > -1 {
60 | t.Fatalf("produced different output at pos %d", n)
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/github.com/kr/binarydist/encoding.go:
--------------------------------------------------------------------------------
1 | package binarydist
2 |
3 | // SignMagLittleEndian is the numeric encoding used by the bsdiff tools.
4 | // It implements binary.ByteOrder using a sign-magnitude format
5 | // and little-endian byte order. Only methods Uint64 and String
6 | // have been written; the rest panic.
7 | type signMagLittleEndian struct{}
8 |
9 | func (signMagLittleEndian) Uint16(b []byte) uint16 { panic("unimplemented") }
10 |
11 | func (signMagLittleEndian) PutUint16(b []byte, v uint16) { panic("unimplemented") }
12 |
13 | func (signMagLittleEndian) Uint32(b []byte) uint32 { panic("unimplemented") }
14 |
15 | func (signMagLittleEndian) PutUint32(b []byte, v uint32) { panic("unimplemented") }
16 |
17 | func (signMagLittleEndian) Uint64(b []byte) uint64 {
18 | y := int64(b[0]) |
19 | int64(b[1])<<8 |
20 | int64(b[2])<<16 |
21 | int64(b[3])<<24 |
22 | int64(b[4])<<32 |
23 | int64(b[5])<<40 |
24 | int64(b[6])<<48 |
25 | int64(b[7]&0x7f)<<56
26 |
27 | if b[7]&0x80 != 0 {
28 | y = -y
29 | }
30 | return uint64(y)
31 | }
32 |
33 | func (signMagLittleEndian) PutUint64(b []byte, v uint64) {
34 | x := int64(v)
35 | neg := x < 0
36 | if neg {
37 | x = -x
38 | }
39 |
40 | b[0] = byte(x)
41 | b[1] = byte(x >> 8)
42 | b[2] = byte(x >> 16)
43 | b[3] = byte(x >> 24)
44 | b[4] = byte(x >> 32)
45 | b[5] = byte(x >> 40)
46 | b[6] = byte(x >> 48)
47 | b[7] = byte(x >> 56)
48 | if neg {
49 | b[7] |= 0x80
50 | }
51 | }
52 |
53 | func (signMagLittleEndian) String() string { return "signMagLittleEndian" }
54 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/github.com/kr/binarydist/common_test.go:
--------------------------------------------------------------------------------
1 | package binarydist
2 |
3 | import (
4 | "crypto/rand"
5 | "io"
6 | "io/ioutil"
7 | "os"
8 | )
9 |
10 | func mustOpen(path string) *os.File {
11 | f, err := os.Open(path)
12 | if err != nil {
13 | panic(err)
14 | }
15 |
16 | return f
17 | }
18 |
19 | func mustReadAll(r io.Reader) []byte {
20 | b, err := ioutil.ReadAll(r)
21 | if err != nil {
22 | panic(err)
23 | }
24 | return b
25 | }
26 |
27 | func fileCmp(a, b *os.File) int64 {
28 | sa, err := a.Seek(0, 2)
29 | if err != nil {
30 | panic(err)
31 | }
32 |
33 | sb, err := b.Seek(0, 2)
34 | if err != nil {
35 | panic(err)
36 | }
37 |
38 | if sa != sb {
39 | return sa
40 | }
41 |
42 | _, err = a.Seek(0, 0)
43 | if err != nil {
44 | panic(err)
45 | }
46 |
47 | _, err = b.Seek(0, 0)
48 | if err != nil {
49 | panic(err)
50 | }
51 |
52 | pa, err := ioutil.ReadAll(a)
53 | if err != nil {
54 | panic(err)
55 | }
56 |
57 | pb, err := ioutil.ReadAll(b)
58 | if err != nil {
59 | panic(err)
60 | }
61 |
62 | for i := range pa {
63 | if pa[i] != pb[i] {
64 | return int64(i)
65 | }
66 | }
67 | return -1
68 | }
69 |
70 | func mustWriteRandFile(path string, size int) *os.File {
71 | p := make([]byte, size)
72 | _, err := rand.Read(p)
73 | if err != nil {
74 | panic(err)
75 | }
76 |
77 | f, err := os.Create(path)
78 | if err != nil {
79 | panic(err)
80 | }
81 |
82 | _, err = f.Write(p)
83 | if err != nil {
84 | panic(err)
85 | }
86 |
87 | _, err = f.Seek(0, 0)
88 | if err != nil {
89 | panic(err)
90 | }
91 |
92 | return f
93 | }
94 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/github.com/kardianos/osext/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012 The Go Authors. All rights reserved.
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are
5 | met:
6 |
7 | * Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above
10 | copyright notice, this list of conditions and the following disclaimer
11 | in the documentation and/or other materials provided with the
12 | distribution.
13 | * Neither the name of Google Inc. nor the names of its
14 | contributors may be used to endorse or promote products derived from
15 | this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/github.com/kr/binarydist/diff_test.go:
--------------------------------------------------------------------------------
1 | package binarydist
2 |
3 | import (
4 | "bytes"
5 | "io/ioutil"
6 | "os"
7 | "os/exec"
8 | "testing"
9 | )
10 |
11 | var diffT = []struct {
12 | old *os.File
13 | new *os.File
14 | }{
15 | {
16 | old: mustWriteRandFile("test.old", 1e3),
17 | new: mustWriteRandFile("test.new", 1e3),
18 | },
19 | {
20 | old: mustOpen("testdata/sample.old"),
21 | new: mustOpen("testdata/sample.new"),
22 | },
23 | }
24 |
25 | func TestDiff(t *testing.T) {
26 | for _, s := range diffT {
27 | got, err := ioutil.TempFile("/tmp", "bspatch.")
28 | if err != nil {
29 | panic(err)
30 | }
31 | os.Remove(got.Name())
32 |
33 | exp, err := ioutil.TempFile("/tmp", "bspatch.")
34 | if err != nil {
35 | panic(err)
36 | }
37 |
38 | cmd := exec.Command("bsdiff", s.old.Name(), s.new.Name(), exp.Name())
39 | cmd.Stdout = os.Stdout
40 | err = cmd.Run()
41 | os.Remove(exp.Name())
42 | if err != nil {
43 | panic(err)
44 | }
45 |
46 | err = Diff(s.old, s.new, got)
47 | if err != nil {
48 | t.Fatal("err", err)
49 | }
50 |
51 | _, err = got.Seek(0, 0)
52 | if err != nil {
53 | panic(err)
54 | }
55 | gotBuf := mustReadAll(got)
56 | expBuf := mustReadAll(exp)
57 |
58 | if !bytes.Equal(gotBuf, expBuf) {
59 | t.Fail()
60 | t.Logf("diff %s %s", s.old.Name(), s.new.Name())
61 | t.Logf("%s: len(got) = %d", got.Name(), len(gotBuf))
62 | t.Logf("%s: len(exp) = %d", exp.Name(), len(expBuf))
63 | i := matchlen(gotBuf, expBuf)
64 | t.Logf("produced different output at pos %d; %d != %d", i, gotBuf[i], expBuf[i])
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/gopkg.in/inconshreveable/go-update.v0/README.md:
--------------------------------------------------------------------------------
1 | # go-update: Automatically update Go programs from the internet
2 |
3 | go-update allows a program to update itself by replacing its executable file
4 | with a new version. It provides the flexibility to implement different updating user experiences
5 | like auto-updating, or manual user-initiated updates. It also boasts
6 | advanced features like binary patching and code signing verification.
7 |
8 | Updating your program to a new version is as easy as:
9 |
10 | err, errRecover := update.New().FromUrl("http://release.example.com/2.0/myprogram")
11 | if err != nil {
12 | fmt.Printf("Update failed: %v\n", err)
13 | }
14 |
15 | ## Documentation and API Reference
16 |
17 | Comprehensive API documentation and code examples are available in the code documentation available on godoc.org:
18 |
19 | [](https://godoc.org/github.com/inconshreveable/go-update)
20 |
21 | ## Features
22 |
23 | - Cross platform support (Windows too!)
24 | - Binary patch application
25 | - Checksum verification
26 | - Code signing verification
27 | - Support for updating arbitrary files
28 |
29 | ## [equinox.io](https://equinox.io)
30 | go-update provides the primitives for building self-updating applications, but there a number of other challenges
31 | involved in a complete updating solution such as hosting, code signing, update channels, gradual rollout,
32 | dynamically computing binary patches, tracking update metrics like versions and failures, plus more.
33 |
34 | I provide this service, a complete solution, free for open source projects, at [equinox.io](https://equinox.io).
35 |
36 | ## License
37 | Apache
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | go-selfupdate
2 | =============
3 |
4 | [](https://godoc.org/github.com/sanbornm/go-selfupdate/selfupdate)
5 | [](https://travis-ci.org/sanbornm/go-selfupdate)
6 |
7 | Enable your Golang applications to self update. Inspired by Chrome based on Heroku's [hk](https://github.com/heroku/hk).
8 |
9 | ## Features
10 |
11 | * Tested on Mac, Linux, Arm, and Windows
12 | * Creates binary diffs with [bsdiff](http://www.daemonology.net/bsdiff/) allowing small incremental updates
13 | * Falls back to full binary update if diff fails to match SHA
14 |
15 | ## QuickStart
16 |
17 | ### Enable your App to Self Update
18 |
19 | var updater = &selfupdate.Updater{
20 | CurrentVersion: version,
21 | ApiURL: "http://updates.yourdomain.com/",
22 | BinURL: "http://updates.yourdownmain.com/",
23 | DiffURL: "http://updates.yourdomain.com/",
24 | Dir: "update/",
25 | CmdName: "myapp", // app name
26 | }
27 |
28 | if updater != nil {
29 | go updater.BackgroundRun()
30 | }
31 |
32 | ### Push Out and Update
33 |
34 | go-selfupdate myapp 1.2
35 |
36 | This will create a folder in your project called, *public* you can then rsync or transfer this to your webserver or S3.
37 |
38 | If you are cross compiling you can specify a directory:
39 |
40 | go-selfupdate /tmp/mybinares/ 1.2
41 |
42 | The directory should contain files with the name, $GOOS-$ARCH. Example:
43 |
44 | windows-386
45 | darwin-amd64
46 | linux-arm
47 |
48 | If you are using [goxc](https://github.com/laher/goxc) you can output the files with this naming format by specifying this config:
49 |
50 | "OutPath": "{{.Dest}}{{.PS}}{{.Version}}{{.PS}}{{.Os}}-{{.Arch}}",
51 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/github.com/kardianos/osext/osext_sysctl.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // +build darwin freebsd
6 |
7 | package osext
8 |
9 | import (
10 | "os"
11 | "path/filepath"
12 | "runtime"
13 | "syscall"
14 | "unsafe"
15 | )
16 |
17 | var initCwd, initCwdErr = os.Getwd()
18 |
19 | func executable() (string, error) {
20 | var mib [4]int32
21 | switch runtime.GOOS {
22 | case "freebsd":
23 | mib = [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1}
24 | case "darwin":
25 | mib = [4]int32{1 /* CTL_KERN */, 38 /* KERN_PROCARGS */, int32(os.Getpid()), -1}
26 | }
27 |
28 | n := uintptr(0)
29 | // Get length.
30 | _, _, errNum := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0)
31 | if errNum != 0 {
32 | return "", errNum
33 | }
34 | if n == 0 { // This shouldn't happen.
35 | return "", nil
36 | }
37 | buf := make([]byte, n)
38 | _, _, errNum = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0)
39 | if errNum != 0 {
40 | return "", errNum
41 | }
42 | if n == 0 { // This shouldn't happen.
43 | return "", nil
44 | }
45 | for i, v := range buf {
46 | if v == 0 {
47 | buf = buf[:i]
48 | break
49 | }
50 | }
51 | var err error
52 | execPath := string(buf)
53 | // execPath will not be empty due to above checks.
54 | // Try to get the absolute path if the execPath is not rooted.
55 | if execPath[0] != '/' {
56 | execPath, err = getAbs(execPath)
57 | if err != nil {
58 | return execPath, err
59 | }
60 | }
61 | // For darwin KERN_PROCARGS may return the path to a symlink rather than the
62 | // actual executable.
63 | if runtime.GOOS == "darwin" {
64 | if execPath, err = filepath.EvalSymlinks(execPath); err != nil {
65 | return execPath, err
66 | }
67 | }
68 | return execPath, nil
69 | }
70 |
71 | func getAbs(execPath string) (string, error) {
72 | if initCwdErr != nil {
73 | return execPath, initCwdErr
74 | }
75 | // The execPath may begin with a "../" or a "./" so clean it first.
76 | // Join the two paths, trailing and starting slashes undetermined, so use
77 | // the generic Join function.
78 | return filepath.Join(initCwd, filepath.Clean(execPath)), nil
79 | }
80 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/github.com/kr/binarydist/patch.go:
--------------------------------------------------------------------------------
1 | package binarydist
2 |
3 | import (
4 | "bytes"
5 | "compress/bzip2"
6 | "encoding/binary"
7 | "errors"
8 | "io"
9 | "io/ioutil"
10 | )
11 |
12 | var ErrCorrupt = errors.New("corrupt patch")
13 |
14 | // Patch applies patch to old, according to the bspatch algorithm,
15 | // and writes the result to new.
16 | func Patch(old io.Reader, new io.Writer, patch io.Reader) error {
17 | var hdr header
18 | err := binary.Read(patch, signMagLittleEndian{}, &hdr)
19 | if err != nil {
20 | return err
21 | }
22 | if hdr.Magic != magic {
23 | return ErrCorrupt
24 | }
25 | if hdr.CtrlLen < 0 || hdr.DiffLen < 0 || hdr.NewSize < 0 {
26 | return ErrCorrupt
27 | }
28 |
29 | ctrlbuf := make([]byte, hdr.CtrlLen)
30 | _, err = io.ReadFull(patch, ctrlbuf)
31 | if err != nil {
32 | return err
33 | }
34 | cpfbz2 := bzip2.NewReader(bytes.NewReader(ctrlbuf))
35 |
36 | diffbuf := make([]byte, hdr.DiffLen)
37 | _, err = io.ReadFull(patch, diffbuf)
38 | if err != nil {
39 | return err
40 | }
41 | dpfbz2 := bzip2.NewReader(bytes.NewReader(diffbuf))
42 |
43 | // The entire rest of the file is the extra block.
44 | epfbz2 := bzip2.NewReader(patch)
45 |
46 | obuf, err := ioutil.ReadAll(old)
47 | if err != nil {
48 | return err
49 | }
50 |
51 | nbuf := make([]byte, hdr.NewSize)
52 |
53 | var oldpos, newpos int64
54 | for newpos < hdr.NewSize {
55 | var ctrl struct{ Add, Copy, Seek int64 }
56 | err = binary.Read(cpfbz2, signMagLittleEndian{}, &ctrl)
57 | if err != nil {
58 | return err
59 | }
60 |
61 | // Sanity-check
62 | if newpos+ctrl.Add > hdr.NewSize {
63 | return ErrCorrupt
64 | }
65 |
66 | // Read diff string
67 | _, err = io.ReadFull(dpfbz2, nbuf[newpos:newpos+ctrl.Add])
68 | if err != nil {
69 | return ErrCorrupt
70 | }
71 |
72 | // Add old data to diff string
73 | for i := int64(0); i < ctrl.Add; i++ {
74 | if oldpos+i >= 0 && oldpos+i < int64(len(obuf)) {
75 | nbuf[newpos+i] += obuf[oldpos+i]
76 | }
77 | }
78 |
79 | // Adjust pointers
80 | newpos += ctrl.Add
81 | oldpos += ctrl.Add
82 |
83 | // Sanity-check
84 | if newpos+ctrl.Copy > hdr.NewSize {
85 | return ErrCorrupt
86 | }
87 |
88 | // Read extra string
89 | _, err = io.ReadFull(epfbz2, nbuf[newpos:newpos+ctrl.Copy])
90 | if err != nil {
91 | return ErrCorrupt
92 | }
93 |
94 | // Adjust pointers
95 | newpos += ctrl.Copy
96 | oldpos += ctrl.Seek
97 | }
98 |
99 | // Write the new file
100 | for len(nbuf) > 0 {
101 | n, err := new.Write(nbuf)
102 | if err != nil {
103 | return err
104 | }
105 | nbuf = nbuf[n:]
106 | }
107 |
108 | return nil
109 | }
110 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/github.com/kardianos/osext/osext_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // +build darwin linux freebsd netbsd windows
6 |
7 | package osext
8 |
9 | import (
10 | "bytes"
11 | "fmt"
12 | "io"
13 | "os"
14 | "os/exec"
15 | "path/filepath"
16 | "runtime"
17 | "testing"
18 | )
19 |
20 | const (
21 | executableEnvVar = "OSTEST_OUTPUT_EXECUTABLE"
22 |
23 | executableEnvValueMatch = "match"
24 | executableEnvValueDelete = "delete"
25 | )
26 |
27 | func TestExecutableMatch(t *testing.T) {
28 | ep, err := Executable()
29 | if err != nil {
30 | t.Fatalf("Executable failed: %v", err)
31 | }
32 |
33 | // fullpath to be of the form "dir/prog".
34 | dir := filepath.Dir(filepath.Dir(ep))
35 | fullpath, err := filepath.Rel(dir, ep)
36 | if err != nil {
37 | t.Fatalf("filepath.Rel: %v", err)
38 | }
39 | // Make child start with a relative program path.
40 | // Alter argv[0] for child to verify getting real path without argv[0].
41 | cmd := &exec.Cmd{
42 | Dir: dir,
43 | Path: fullpath,
44 | Env: []string{fmt.Sprintf("%s=%s", executableEnvVar, executableEnvValueMatch)},
45 | }
46 | out, err := cmd.CombinedOutput()
47 | if err != nil {
48 | t.Fatalf("exec(self) failed: %v", err)
49 | }
50 | outs := string(out)
51 | if !filepath.IsAbs(outs) {
52 | t.Fatalf("Child returned %q, want an absolute path", out)
53 | }
54 | if !sameFile(outs, ep) {
55 | t.Fatalf("Child returned %q, not the same file as %q", out, ep)
56 | }
57 | }
58 |
59 | func TestExecutableDelete(t *testing.T) {
60 | if runtime.GOOS != "linux" {
61 | t.Skip()
62 | }
63 | fpath, err := Executable()
64 | if err != nil {
65 | t.Fatalf("Executable failed: %v", err)
66 | }
67 |
68 | r, w := io.Pipe()
69 | stderrBuff := &bytes.Buffer{}
70 | stdoutBuff := &bytes.Buffer{}
71 | cmd := &exec.Cmd{
72 | Path: fpath,
73 | Env: []string{fmt.Sprintf("%s=%s", executableEnvVar, executableEnvValueDelete)},
74 | Stdin: r,
75 | Stderr: stderrBuff,
76 | Stdout: stdoutBuff,
77 | }
78 | err = cmd.Start()
79 | if err != nil {
80 | t.Fatalf("exec(self) start failed: %v", err)
81 | }
82 |
83 | tempPath := fpath + "_copy"
84 | _ = os.Remove(tempPath)
85 |
86 | err = copyFile(tempPath, fpath)
87 | if err != nil {
88 | t.Fatalf("copy file failed: %v", err)
89 | }
90 | err = os.Remove(fpath)
91 | if err != nil {
92 | t.Fatalf("remove running test file failed: %v", err)
93 | }
94 | err = os.Rename(tempPath, fpath)
95 | if err != nil {
96 | t.Fatalf("rename copy to previous name failed: %v", err)
97 | }
98 |
99 | w.Write([]byte{0})
100 | w.Close()
101 |
102 | err = cmd.Wait()
103 | if err != nil {
104 | t.Fatalf("exec wait failed: %v", err)
105 | }
106 |
107 | childPath := stderrBuff.String()
108 | if !filepath.IsAbs(childPath) {
109 | t.Fatalf("Child returned %q, want an absolute path", childPath)
110 | }
111 | if !sameFile(childPath, fpath) {
112 | t.Fatalf("Child returned %q, not the same file as %q", childPath, fpath)
113 | }
114 | }
115 |
116 | func sameFile(fn1, fn2 string) bool {
117 | fi1, err := os.Stat(fn1)
118 | if err != nil {
119 | return false
120 | }
121 | fi2, err := os.Stat(fn2)
122 | if err != nil {
123 | return false
124 | }
125 | return os.SameFile(fi1, fi2)
126 | }
127 | func copyFile(dest, src string) error {
128 | df, err := os.Create(dest)
129 | if err != nil {
130 | return err
131 | }
132 | defer df.Close()
133 |
134 | sf, err := os.Open(src)
135 | if err != nil {
136 | return err
137 | }
138 | defer sf.Close()
139 |
140 | _, err = io.Copy(df, sf)
141 | return err
142 | }
143 |
144 | func TestMain(m *testing.M) {
145 | env := os.Getenv(executableEnvVar)
146 | switch env {
147 | case "":
148 | os.Exit(m.Run())
149 | case executableEnvValueMatch:
150 | // First chdir to another path.
151 | dir := "/"
152 | if runtime.GOOS == "windows" {
153 | dir = filepath.VolumeName(".")
154 | }
155 | os.Chdir(dir)
156 | if ep, err := Executable(); err != nil {
157 | fmt.Fprint(os.Stderr, "ERROR: ", err)
158 | } else {
159 | fmt.Fprint(os.Stderr, ep)
160 | }
161 | case executableEnvValueDelete:
162 | bb := make([]byte, 1)
163 | var err error
164 | n, err := os.Stdin.Read(bb)
165 | if err != nil {
166 | fmt.Fprint(os.Stderr, "ERROR: ", err)
167 | os.Exit(2)
168 | }
169 | if n != 1 {
170 | fmt.Fprint(os.Stderr, "ERROR: n != 1, n == ", n)
171 | os.Exit(2)
172 | }
173 | if ep, err := Executable(); err != nil {
174 | fmt.Fprint(os.Stderr, "ERROR: ", err)
175 | } else {
176 | fmt.Fprint(os.Stderr, ep)
177 | }
178 | }
179 | os.Exit(0)
180 | }
181 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "compress/gzip"
6 | "crypto/sha256"
7 | "encoding/json"
8 | "flag"
9 | "fmt"
10 | "io"
11 | "io/ioutil"
12 | "log"
13 | "os"
14 | "path/filepath"
15 | "runtime"
16 |
17 | "github.com/kr/binarydist"
18 | )
19 |
20 | var version, genDir string
21 |
22 | type current struct {
23 | Version string
24 | Sha256 []byte
25 | }
26 |
27 | func generateSha256(path string) []byte {
28 | h := sha256.New()
29 | b, err := ioutil.ReadFile(path)
30 | if err != nil {
31 | fmt.Println(err)
32 | }
33 | h.Write(b)
34 | sum := h.Sum(nil)
35 | return sum
36 | //return base64.URLEncoding.EncodeToString(sum)
37 | }
38 |
39 | type gzReader struct {
40 | z, r io.ReadCloser
41 | }
42 |
43 | func (g *gzReader) Read(p []byte) (int, error) {
44 | return g.z.Read(p)
45 | }
46 |
47 | func (g *gzReader) Close() error {
48 | g.z.Close()
49 | return g.r.Close()
50 | }
51 |
52 | func newGzReader(r io.ReadCloser) io.ReadCloser {
53 | var err error
54 | g := new(gzReader)
55 | g.r = r
56 | g.z, err = gzip.NewReader(r)
57 | if err != nil {
58 | panic(err)
59 | }
60 | return g
61 | }
62 |
63 | func createUpdate(cmdName, path string, platform string) {
64 | c := current{Version: version, Sha256: generateSha256(path)}
65 |
66 | b, err := json.MarshalIndent(c, "", " ")
67 | if err != nil {
68 | fmt.Println("error:", err)
69 | }
70 |
71 | if err := os.MkdirAll(filepath.Join(genDir, platform), 0700); err != nil {
72 | log.Fatal(err)
73 | }
74 |
75 | err = ioutil.WriteFile(filepath.Join(genDir, platform, cmdName+".json"), b, 0755)
76 | if err != nil {
77 | panic(err)
78 | }
79 |
80 | os.MkdirAll(filepath.Join(genDir, version, platform), 0755)
81 |
82 | var buf bytes.Buffer
83 | w := gzip.NewWriter(&buf)
84 | f, err := ioutil.ReadFile(path)
85 | if err != nil {
86 | panic(err)
87 | }
88 | w.Write(f)
89 | w.Close() // You must close this first to flush the bytes to the buffer.
90 | err = ioutil.WriteFile(filepath.Join(genDir, version, platform, cmdName+".gz"), buf.Bytes(), 0755)
91 |
92 | files, err := ioutil.ReadDir(genDir)
93 | if err != nil {
94 | fmt.Println(err)
95 | }
96 |
97 | for _, file := range files {
98 | if file.IsDir() == false {
99 | continue
100 | }
101 | if file.Name() == version {
102 | continue
103 | }
104 |
105 | os.Mkdir(filepath.Join(genDir, file.Name(), version, platform), 0755)
106 |
107 | fName := filepath.Join(genDir, file.Name(), platform, cmdName+".gz")
108 | old, err := os.Open(fName)
109 | if err != nil {
110 | // Don't have an old release for this os/arch, continue on
111 | continue
112 | }
113 |
114 | fName = filepath.Join(genDir, version, platform, cmdName+".gz")
115 | newF, err := os.Open(fName)
116 | if err != nil {
117 | fmt.Fprintf(os.Stderr, "Can't open %s: error: %s\n", fName, err)
118 | os.Exit(1)
119 | }
120 |
121 | ar := newGzReader(old)
122 | defer ar.Close()
123 | br := newGzReader(newF)
124 | defer br.Close()
125 | patch := new(bytes.Buffer)
126 | if err := binarydist.Diff(ar, br, patch); err != nil {
127 | panic(err)
128 | }
129 | ioutil.WriteFile(filepath.Join(genDir, file.Name(), version, platform, cmdName), patch.Bytes(), 0755)
130 | }
131 | }
132 |
133 | func printUsage() {
134 | fmt.Println("")
135 | fmt.Println("Positional arguments:")
136 | fmt.Println("\tSingle platform: go-selfupdate myapp 1.2")
137 | fmt.Println("\tCross platform: go-selfupdate /tmp/mybinares/ 1.2")
138 | }
139 |
140 | func createBuildDir() {
141 | os.MkdirAll(genDir, 0755)
142 | }
143 |
144 | func main() {
145 | outputDirFlag := flag.String("o", "public", "Output directory for writing updates")
146 |
147 | cmdNameFlag := flag.String("cmd", "", "Name of command (program)")
148 |
149 | var defaultPlatform string
150 | goos := os.Getenv("GOOS")
151 | goarch := os.Getenv("GOARCH")
152 | if goos != "" && goarch != "" {
153 | defaultPlatform = goos + "-" + goarch
154 | } else {
155 | defaultPlatform = runtime.GOOS + "-" + runtime.GOARCH
156 | }
157 | platformFlag := flag.String("platform", defaultPlatform,
158 | "Target platform in the form OS-ARCH. Defaults to running os/arch or the combination of the environment variables GOOS and GOARCH if both are set.")
159 |
160 | flag.Parse()
161 | if flag.NArg() < 2 {
162 | flag.Usage()
163 | printUsage()
164 | os.Exit(0)
165 | }
166 |
167 | platform := *platformFlag
168 | cmdName := *cmdNameFlag
169 | appPath := flag.Arg(0)
170 | version = flag.Arg(1)
171 | genDir = *outputDirFlag
172 |
173 | createBuildDir()
174 |
175 | // If dir is given create update for each file
176 | files, err := ioutil.ReadDir(appPath)
177 | if err == nil {
178 | for _, file := range files {
179 | createUpdate(cmdName, filepath.Join(appPath, file.Name()), file.Name())
180 | }
181 | os.Exit(0)
182 | }
183 |
184 | createUpdate(cmdName, appPath, platform)
185 | }
186 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/gopkg.in/inconshreveable/go-update.v0/check/check.go:
--------------------------------------------------------------------------------
1 | package check
2 |
3 | import (
4 | "bytes"
5 | _ "crypto/sha512" // for tls cipher support
6 | "encoding/hex"
7 | "encoding/json"
8 | "fmt"
9 | "io/ioutil"
10 | "net/http"
11 | "runtime"
12 |
13 | "github.com/kardianos/osext"
14 | "gopkg.in/inconshreveable/go-update.v0"
15 | )
16 |
17 | type Initiative string
18 |
19 | const (
20 | INITIATIVE_NEVER Initiative = "never"
21 | INITIATIVE_AUTO = "auto"
22 | INITIATIVE_MANUAL = "manual"
23 | )
24 |
25 | var NoUpdateAvailable error = fmt.Errorf("No update available")
26 |
27 | type Params struct {
28 | // protocol version
29 | Version int `json:"version"`
30 | // identifier of the application to update
31 | AppId string `json:"app_id"`
32 | // version of the application updating itself
33 | AppVersion string `json:"app_version"`
34 | // operating system of target platform
35 | OS string `json:"-"`
36 | // hardware architecture of target platform
37 | Arch string `json:"-"`
38 | // application-level user identifier
39 | UserId string `json:"user_id"`
40 | // checksum of the binary to replace (used for returning diff patches)
41 | Checksum string `json:"checksum"`
42 | // release channel (empty string means 'stable')
43 | Channel string `json:"-"`
44 | // tags for custom update channels
45 | Tags map[string]string `json:"tags"`
46 | }
47 |
48 | type Result struct {
49 | up *update.Update
50 |
51 | // should the update be applied automatically/manually
52 | Initiative Initiative `json:"initiative"`
53 | // url where to download the updated application
54 | Url string `json:"url"`
55 | // a URL to a patch to apply
56 | PatchUrl string `json:"patch_url"`
57 | // the patch format (only bsdiff supported at the moment)
58 | PatchType update.PatchType `json:"patch_type"`
59 | // version of the new application
60 | Version string `json:"version"`
61 | // expected checksum of the new application
62 | Checksum string `json:"checksum"`
63 | // signature for verifying update authenticity
64 | Signature string `json:"signature"`
65 | }
66 |
67 | // CheckForUpdate makes an HTTP post to a URL with the JSON serialized
68 | // representation of Params. It returns the deserialized result object
69 | // returned by the remote endpoint or an error. If you do not set
70 | // OS/Arch, CheckForUpdate will populate them for you. Similarly, if
71 | // Version is 0, it will be set to 1. Lastly, if Checksum is the empty
72 | // string, it will be automatically be computed for the running program's
73 | // executable file.
74 | func (p *Params) CheckForUpdate(url string, up *update.Update) (*Result, error) {
75 | if p.Tags == nil {
76 | p.Tags = make(map[string]string)
77 | }
78 |
79 | if p.Channel == "" {
80 | p.Channel = "stable"
81 | }
82 |
83 | if p.OS == "" {
84 | p.OS = runtime.GOOS
85 | }
86 |
87 | if p.Arch == "" {
88 | p.Arch = runtime.GOARCH
89 | }
90 |
91 | if p.Version == 0 {
92 | p.Version = 1
93 | }
94 |
95 | // ignore errors auto-populating the checksum
96 | // if it fails, you just won't be able to patch
97 | if up.TargetPath == "" {
98 | p.Checksum = defaultChecksum()
99 | } else {
100 | checksum, err := update.ChecksumForFile(up.TargetPath)
101 | if err != nil {
102 | return nil, err
103 | }
104 | p.Checksum = hex.EncodeToString(checksum)
105 | }
106 |
107 | p.Tags["os"] = p.OS
108 | p.Tags["arch"] = p.Arch
109 | p.Tags["channel"] = p.Channel
110 |
111 | body, err := json.Marshal(p)
112 | if err != nil {
113 | return nil, err
114 | }
115 |
116 | client := up.HTTPClient
117 |
118 | if client == nil {
119 | client = &http.Client{}
120 | }
121 |
122 | resp, err := client.Post(url, "application/json", bytes.NewReader(body))
123 | if err != nil {
124 | return nil, err
125 | }
126 |
127 | // no content means no available update
128 | if resp.StatusCode == 204 {
129 | return nil, NoUpdateAvailable
130 | }
131 |
132 | defer resp.Body.Close()
133 | respBytes, err := ioutil.ReadAll(resp.Body)
134 | if err != nil {
135 | return nil, err
136 | }
137 |
138 | result := &Result{up: up}
139 | if err := json.Unmarshal(respBytes, result); err != nil {
140 | return nil, err
141 | }
142 |
143 | return result, nil
144 | }
145 |
146 | func (p *Params) CheckAndApplyUpdate(url string, up *update.Update) (result *Result, err error, errRecover error) {
147 | // check for an update
148 | result, err = p.CheckForUpdate(url, up)
149 | if err != nil {
150 | return
151 | }
152 |
153 | // run the available update
154 | err, errRecover = result.Update()
155 | return
156 | }
157 |
158 | func (r *Result) Update() (err error, errRecover error) {
159 | if r.Checksum != "" {
160 | r.up.Checksum, err = hex.DecodeString(r.Checksum)
161 | if err != nil {
162 | return
163 | }
164 | }
165 |
166 | if r.Signature != "" {
167 | r.up.Signature, err = hex.DecodeString(r.Signature)
168 | if err != nil {
169 | return
170 | }
171 | }
172 |
173 | if r.PatchType != "" {
174 | r.up.PatchType = r.PatchType
175 | }
176 |
177 | if r.Url == "" && r.PatchUrl == "" {
178 | err = fmt.Errorf("Result does not contain an update url or patch update url")
179 | return
180 | }
181 |
182 | if r.PatchUrl != "" {
183 | err, errRecover = r.up.FromUrl(r.PatchUrl)
184 | if err == nil {
185 | // success!
186 | return
187 | } else {
188 | // failed to update from patch URL, try with the whole thing
189 | if r.Url == "" || errRecover != nil {
190 | // we can't try updating from a URL with the full contents
191 | // in these cases, so fail
192 | return
193 | } else {
194 | r.up.PatchType = update.PATCHTYPE_NONE
195 | }
196 | }
197 | }
198 |
199 | // try updating from a URL with the full contents
200 | return r.up.FromUrl(r.Url)
201 | }
202 |
203 | func defaultChecksum() string {
204 | path, err := osext.Executable()
205 | if err != nil {
206 | return ""
207 | }
208 |
209 | checksum, err := update.ChecksumForFile(path)
210 | if err != nil {
211 | return ""
212 | }
213 |
214 | return hex.EncodeToString(checksum)
215 | }
216 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/gopkg.in/inconshreveable/go-update.v0/download/download.go:
--------------------------------------------------------------------------------
1 | package download
2 |
3 | import (
4 | "bytes"
5 | "compress/gzip"
6 | "fmt"
7 | "io"
8 | "net/http"
9 | "os"
10 | "runtime"
11 | )
12 |
13 | type roundTripper struct {
14 | RoundTripFn func(*http.Request) (*http.Response, error)
15 | }
16 |
17 | func (rt *roundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
18 | return rt.RoundTripFn(r)
19 | }
20 |
21 | // Download encapsulates the state and parameters to download content
22 | // from a URL which:
23 | //
24 | // - Publishes the percentage of the download completed to a channel.
25 | // - May resume a previous download that was partially completed.
26 | //
27 | // Create an instance with the New() factory function.
28 | type Download struct {
29 | // net/http.Client to use when downloading the update.
30 | // If nil, a default http.Client is used
31 | HttpClient *http.Client
32 |
33 | // As bytes are downloaded, they are written to Target.
34 | // Download also uses the Target's Seek method to determine
35 | // the size of partial-downloads so that it may properly
36 | // request the remaining bytes to resume the download.
37 | Target Target
38 |
39 | // Progress returns the percentage of the download
40 | // completed as an integer between 0 and 100
41 | Progress chan (int)
42 |
43 | // HTTP Method to use in the download request. Default is "GET"
44 | Method string
45 |
46 | // HTTP URL to issue the download request to
47 | Url string
48 | }
49 |
50 | // New initializes a new Download object which will download
51 | // the content from url into target.
52 | func New(url string, target Target, httpClient *http.Client) *Download {
53 | return &Download{
54 | HttpClient: httpClient,
55 | Progress: make(chan int),
56 | Method: "GET",
57 | Url: url,
58 | Target: target,
59 | }
60 | }
61 |
62 | // Get() downloads the content of a url to a target destination.
63 | //
64 | // Only HTTP/1.1 servers that implement the Range header support resuming a
65 | // partially completed download.
66 | //
67 | // On success, the server must return 200 and the content, or 206 when resuming a partial download.
68 | // If the HTTP server returns a 3XX redirect, it will be followed according to d.HttpClient's redirect policy.
69 | //
70 | func (d *Download) Get() (err error) {
71 | // Close the progress channel whenever this function completes
72 | defer close(d.Progress)
73 |
74 | // determine the size of the download target to determine if we're resuming a partial download
75 | offset, err := d.Target.Size()
76 | if err != nil {
77 | return
78 | }
79 |
80 | // create the download request
81 | req, err := http.NewRequest(d.Method, d.Url, nil)
82 | if err != nil {
83 | return
84 | }
85 |
86 | // create an http client if one does not exist
87 | if d.HttpClient == nil {
88 | d.HttpClient = http.DefaultClient
89 | }
90 |
91 | // we have to add headers like this so they get used across redirects
92 | trans := d.HttpClient.Transport
93 | if trans == nil {
94 | trans = http.DefaultTransport
95 | }
96 |
97 | d.HttpClient.Transport = &roundTripper{
98 | RoundTripFn: func(r *http.Request) (*http.Response, error) {
99 | // add header for download continuation
100 | if offset > 0 {
101 | r.Header.Add("Range", fmt.Sprintf("%d-", offset))
102 | }
103 |
104 | // ask for gzipped content so that net/http won't unzip it for us
105 | // and destroy the content length header we need for progress calculations
106 | r.Header.Add("Accept-Encoding", "gzip")
107 |
108 | return trans.RoundTrip(r)
109 | },
110 | }
111 |
112 | // issue the download request
113 | resp, err := d.HttpClient.Do(req)
114 | if err != nil {
115 | return
116 | }
117 | defer resp.Body.Close()
118 |
119 | switch resp.StatusCode {
120 | // ok
121 | case 200, 206:
122 |
123 | // server error
124 | default:
125 | err = fmt.Errorf("Non 2XX response when downloading update: %s", resp.Status)
126 | return
127 | }
128 |
129 | // Determine how much we have to download
130 | // net/http sets this to -1 when it is unknown
131 | clength := resp.ContentLength
132 |
133 | // Read the content from the response body
134 | rd := resp.Body
135 |
136 | // meter the rate at which we download content for
137 | // progress reporting if we know how much to expect
138 | if clength > 0 {
139 | rd = &meteredReader{rd: rd, totalSize: clength, progress: d.Progress}
140 | }
141 |
142 | // Decompress the content if necessary
143 | if resp.Header.Get("Content-Encoding") == "gzip" {
144 | rd, err = gzip.NewReader(rd)
145 | if err != nil {
146 | return
147 | }
148 | }
149 |
150 | // Download the update
151 | _, err = io.Copy(d.Target, rd)
152 | if err != nil {
153 | return
154 | }
155 |
156 | return
157 | }
158 |
159 | // meteredReader wraps a ReadCloser. Calls to a meteredReader's Read() method
160 | // publish updates to a progress channel with the percentage read so far.
161 | type meteredReader struct {
162 | rd io.ReadCloser
163 | totalSize int64
164 | progress chan int
165 | totalRead int64
166 | ticks int64
167 | }
168 |
169 | func (m *meteredReader) Close() error {
170 | return m.rd.Close()
171 | }
172 |
173 | func (m *meteredReader) Read(b []byte) (n int, err error) {
174 | chunkSize := (m.totalSize / 100) + 1
175 | lenB := int64(len(b))
176 |
177 | var nChunk int
178 | for start := int64(0); start < lenB; start += int64(nChunk) {
179 | end := start + chunkSize
180 | if end > lenB {
181 | end = lenB
182 | }
183 |
184 | nChunk, err = m.rd.Read(b[start:end])
185 |
186 | n += nChunk
187 | m.totalRead += int64(nChunk)
188 |
189 | if m.totalRead > (m.ticks * chunkSize) {
190 | m.ticks += 1
191 | // try to send on channel, but don't block if it's full
192 | select {
193 | case m.progress <- int(m.ticks + 1):
194 | default:
195 | }
196 |
197 | // give the progress channel consumer a chance to run
198 | runtime.Gosched()
199 | }
200 |
201 | if err != nil {
202 | return
203 | }
204 | }
205 |
206 | return
207 | }
208 |
209 | // A Target is what you can supply to Download,
210 | // it's just an io.Writer with a Size() method so that
211 | // the a Download can "resume" an interrupted download
212 | type Target interface {
213 | io.Writer
214 | Size() (int, error)
215 | }
216 |
217 | type FileTarget struct {
218 | *os.File
219 | }
220 |
221 | func (t *FileTarget) Size() (int, error) {
222 | if fi, err := t.File.Stat(); err != nil {
223 | return 0, err
224 | } else {
225 | return int(fi.Size()), nil
226 | }
227 | }
228 |
229 | type MemoryTarget struct {
230 | bytes.Buffer
231 | }
232 |
233 | func (t *MemoryTarget) Size() (int, error) {
234 | return t.Buffer.Len(), nil
235 | }
236 |
--------------------------------------------------------------------------------
/selfupdate/selfupdate.go:
--------------------------------------------------------------------------------
1 | // Update protocol:
2 | //
3 | // GET hk.heroku.com/hk/linux-amd64.json
4 | //
5 | // 200 ok
6 | // {
7 | // "Version": "2",
8 | // "Sha256": "..." // base64
9 | // }
10 | //
11 | // then
12 | //
13 | // GET hkpatch.s3.amazonaws.com/hk/1/2/linux-amd64
14 | //
15 | // 200 ok
16 | // [bsdiff data]
17 | //
18 | // or
19 | //
20 | // GET hkdist.s3.amazonaws.com/hk/2/linux-amd64.gz
21 | //
22 | // 200 ok
23 | // [gzipped executable data]
24 | //
25 | //
26 | package selfupdate
27 |
28 | import (
29 | "bytes"
30 | "compress/gzip"
31 | "crypto/sha256"
32 | "encoding/json"
33 | "errors"
34 | "fmt"
35 | "io"
36 | "io/ioutil"
37 | "log"
38 | "math/rand"
39 | "net/http"
40 | "os"
41 | "path/filepath"
42 | "runtime"
43 | "time"
44 |
45 | "github.com/kardianos/osext"
46 | "github.com/kr/binarydist"
47 | "gopkg.in/inconshreveable/go-update.v0"
48 | )
49 |
50 | const (
51 | upcktimePath = "cktime"
52 | plat = runtime.GOOS + "-" + runtime.GOARCH
53 | )
54 |
55 | const devValidTime = 7 * 24 * time.Hour
56 |
57 | var ErrHashMismatch = errors.New("new file hash mismatch after patch")
58 | var up = update.New()
59 |
60 | // Updater is the configuration and runtime data for doing an update.
61 | //
62 | // Note that ApiURL, BinURL and DiffURL should have the same value if all files are available at the same location.
63 | //
64 | // Example:
65 | //
66 | // updater := &selfupdate.Updater{
67 | // CurrentVersion: version,
68 | // ApiURL: "http://updates.yourdomain.com/",
69 | // BinURL: "http://updates.yourdownmain.com/",
70 | // DiffURL: "http://updates.yourdomain.com/",
71 | // Dir: "update/",
72 | // CmdName: "myapp", // app name
73 | // }
74 | // if updater != nil {
75 | // go updater.BackgroundRun()
76 | // }
77 | type Updater struct {
78 | CurrentVersion string // Currently running version.
79 | ApiURL string // Base URL for API requests (json files).
80 | CmdName string // Command name is appended to the ApiURL like http://apiurl/CmdName/. This represents one binary.
81 | BinURL string // Base URL for full binary downloads.
82 | DiffURL string // Base URL for diff downloads.
83 | Dir string // Directory to store selfupdate state.
84 | Info struct {
85 | Version string
86 | Sha256 []byte
87 | }
88 | }
89 |
90 | func (u *Updater) getExecRelativeDir(dir string) string {
91 | filename, _ := osext.Executable()
92 | path := filepath.Join(filepath.Dir(filename), dir)
93 | return path
94 | }
95 |
96 | // BackgroundRun starts the update check and apply cycle.
97 | func (u *Updater) BackgroundRun() error {
98 | os.MkdirAll(u.getExecRelativeDir(u.Dir), 0777)
99 | if u.wantUpdate() {
100 | if err := up.CanUpdate(); err != nil {
101 | // fail
102 | return err
103 | }
104 | //self, err := osext.Executable()
105 | //if err != nil {
106 | // fail update, couldn't figure out path to self
107 | //return
108 | //}
109 | // TODO(bgentry): logger isn't on Windows. Replace w/ proper error reports.
110 | if err := u.Update(); err != nil {
111 | return err
112 | }
113 | }
114 | return nil
115 | }
116 |
117 | func (u *Updater) wantUpdate() bool {
118 | path := u.getExecRelativeDir(u.Dir + upcktimePath)
119 | if u.CurrentVersion == "dev" || readTime(path).After(time.Now()) {
120 | return false
121 | }
122 | wait := 24*time.Hour + randDuration(24*time.Hour)
123 | return writeTime(path, time.Now().Add(wait))
124 | }
125 |
126 | func (u *Updater) Update() error {
127 | path, err := osext.Executable()
128 | if err != nil {
129 | return err
130 | }
131 | old, err := os.Open(path)
132 | if err != nil {
133 | return err
134 | }
135 | defer old.Close()
136 |
137 | err = u.Check()
138 | if err != nil {
139 | return err
140 | }
141 | if u.Info.Version == u.CurrentVersion {
142 | return nil
143 | }
144 | // TODO(sqs): reenable fetching just the patch
145 | /*bin, err := u.fetchAndVerifyPatch(old)
146 | if err != nil {
147 | if err == ErrHashMismatch {
148 | log.Println("update: hash mismatch from patched binary, downloading full binary")
149 | } else {
150 | if u.DiffURL != "" {
151 | log.Println("update: downloading full binary because patching binary failed:", err)
152 | }
153 | }*/
154 |
155 | bin, err := u.fetchAndVerifyFullBin()
156 | if err != nil {
157 | if err == ErrHashMismatch {
158 | log.Println("update: hash mismatch from full binary")
159 | } else {
160 | log.Println("update: fetching full binary failed:", err)
161 | }
162 | return err
163 | }
164 | //}
165 |
166 | // close the old binary before installing because on windows
167 | // it can't be renamed if a handle to the file is still open
168 | old.Close()
169 |
170 | err, errRecover := up.FromStream(bytes.NewBuffer(bin))
171 | if errRecover != nil {
172 | return fmt.Errorf("update and recovery errors: %q %q", err, errRecover)
173 | }
174 | if err != nil {
175 | return err
176 | }
177 | return nil
178 | }
179 |
180 | func (u *Updater) Check() error {
181 | r, err := fetch(u.ApiURL + u.CmdName + "/" + plat + "/" + u.CmdName + ".json")
182 | if err != nil {
183 | return err
184 | }
185 | defer r.Close()
186 | err = json.NewDecoder(r).Decode(&u.Info)
187 | if err != nil {
188 | return err
189 | }
190 | if len(u.Info.Sha256) != sha256.Size {
191 | return errors.New("bad cmd hash in info")
192 | }
193 | return nil
194 | }
195 |
196 | func (u *Updater) fetchAndVerifyPatch(old io.Reader) ([]byte, error) {
197 | bin, err := u.fetchAndApplyPatch(old)
198 | if err != nil {
199 | return nil, err
200 | }
201 | if !verifySha(bin, u.Info.Sha256) {
202 | return nil, ErrHashMismatch
203 | }
204 | return bin, nil
205 | }
206 |
207 | func (u *Updater) fetchAndApplyPatch(old io.Reader) ([]byte, error) {
208 | r, err := fetch(u.DiffURL + u.CmdName + "/" + u.CurrentVersion + "/" + u.Info.Version + "/" + plat + "/" + u.CmdName)
209 | if err != nil {
210 | return nil, err
211 | }
212 | defer r.Close()
213 | var buf bytes.Buffer
214 | err = binarydist.Patch(old, &buf, r)
215 | return buf.Bytes(), err
216 | }
217 |
218 | func (u *Updater) fetchAndVerifyFullBin() ([]byte, error) {
219 | bin, err := u.fetchBin()
220 | if err != nil {
221 | return nil, err
222 | }
223 | verified := verifySha(bin, u.Info.Sha256)
224 | if !verified {
225 | return nil, ErrHashMismatch
226 | }
227 | return bin, nil
228 | }
229 |
230 | func (u *Updater) fetchBin() ([]byte, error) {
231 | r, err := fetch(u.BinURL + u.CmdName + "/" + u.Info.Version + "/" + plat + "/" + u.CmdName + ".gz")
232 | if err != nil {
233 | return nil, err
234 | }
235 | defer r.Close()
236 | buf := new(bytes.Buffer)
237 | gz, err := gzip.NewReader(r)
238 | if err != nil {
239 | return nil, err
240 | }
241 | if _, err = io.Copy(buf, gz); err != nil {
242 | return nil, err
243 | }
244 |
245 | return buf.Bytes(), nil
246 | }
247 |
248 | // returns a random duration in [0,n).
249 | func randDuration(n time.Duration) time.Duration {
250 | return time.Duration(rand.Int63n(int64(n)))
251 | }
252 |
253 | func fetch(url string) (io.ReadCloser, error) {
254 | resp, err := http.Get(url)
255 | if err != nil {
256 | return nil, err
257 | }
258 | if resp.StatusCode != 200 {
259 | return nil, fmt.Errorf("bad http status from %s: %v", url, resp.Status)
260 | }
261 | return resp.Body, nil
262 | }
263 |
264 | func readTime(path string) time.Time {
265 | p, err := ioutil.ReadFile(path)
266 | if os.IsNotExist(err) {
267 | return time.Time{}
268 | }
269 | if err != nil {
270 | return time.Now().Add(1000 * time.Hour)
271 | }
272 | t, err := time.Parse(time.RFC3339, string(p))
273 | if err != nil {
274 | return time.Now().Add(1000 * time.Hour)
275 | }
276 | return t
277 | }
278 |
279 | func verifySha(bin []byte, sha []byte) bool {
280 | h := sha256.New()
281 | h.Write(bin)
282 | return bytes.Equal(h.Sum(nil), sha)
283 | }
284 |
285 | func writeTime(path string, t time.Time) bool {
286 | return ioutil.WriteFile(path, []byte(t.Format(time.RFC3339)), 0644) == nil
287 | }
288 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/github.com/kr/binarydist/diff.go:
--------------------------------------------------------------------------------
1 | package binarydist
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "io"
7 | "io/ioutil"
8 | )
9 |
10 | func swap(a []int, i, j int) { a[i], a[j] = a[j], a[i] }
11 |
12 | func split(I, V []int, start, length, h int) {
13 | var i, j, k, x, jj, kk int
14 |
15 | if length < 16 {
16 | for k = start; k < start+length; k += j {
17 | j = 1
18 | x = V[I[k]+h]
19 | for i = 1; k+i < start+length; i++ {
20 | if V[I[k+i]+h] < x {
21 | x = V[I[k+i]+h]
22 | j = 0
23 | }
24 | if V[I[k+i]+h] == x {
25 | swap(I, k+i, k+j)
26 | j++
27 | }
28 | }
29 | for i = 0; i < j; i++ {
30 | V[I[k+i]] = k + j - 1
31 | }
32 | if j == 1 {
33 | I[k] = -1
34 | }
35 | }
36 | return
37 | }
38 |
39 | x = V[I[start+length/2]+h]
40 | jj = 0
41 | kk = 0
42 | for i = start; i < start+length; i++ {
43 | if V[I[i]+h] < x {
44 | jj++
45 | }
46 | if V[I[i]+h] == x {
47 | kk++
48 | }
49 | }
50 | jj += start
51 | kk += jj
52 |
53 | i = start
54 | j = 0
55 | k = 0
56 | for i < jj {
57 | if V[I[i]+h] < x {
58 | i++
59 | } else if V[I[i]+h] == x {
60 | swap(I, i, jj+j)
61 | j++
62 | } else {
63 | swap(I, i, kk+k)
64 | k++
65 | }
66 | }
67 |
68 | for jj+j < kk {
69 | if V[I[jj+j]+h] == x {
70 | j++
71 | } else {
72 | swap(I, jj+j, kk+k)
73 | k++
74 | }
75 | }
76 |
77 | if jj > start {
78 | split(I, V, start, jj-start, h)
79 | }
80 |
81 | for i = 0; i < kk-jj; i++ {
82 | V[I[jj+i]] = kk - 1
83 | }
84 | if jj == kk-1 {
85 | I[jj] = -1
86 | }
87 |
88 | if start+length > kk {
89 | split(I, V, kk, start+length-kk, h)
90 | }
91 | }
92 |
93 | func qsufsort(obuf []byte) []int {
94 | var buckets [256]int
95 | var i, h int
96 | I := make([]int, len(obuf)+1)
97 | V := make([]int, len(obuf)+1)
98 |
99 | for _, c := range obuf {
100 | buckets[c]++
101 | }
102 | for i = 1; i < 256; i++ {
103 | buckets[i] += buckets[i-1]
104 | }
105 | copy(buckets[1:], buckets[:])
106 | buckets[0] = 0
107 |
108 | for i, c := range obuf {
109 | buckets[c]++
110 | I[buckets[c]] = i
111 | }
112 |
113 | I[0] = len(obuf)
114 | for i, c := range obuf {
115 | V[i] = buckets[c]
116 | }
117 |
118 | V[len(obuf)] = 0
119 | for i = 1; i < 256; i++ {
120 | if buckets[i] == buckets[i-1]+1 {
121 | I[buckets[i]] = -1
122 | }
123 | }
124 | I[0] = -1
125 |
126 | for h = 1; I[0] != -(len(obuf) + 1); h += h {
127 | var n int
128 | for i = 0; i < len(obuf)+1; {
129 | if I[i] < 0 {
130 | n -= I[i]
131 | i -= I[i]
132 | } else {
133 | if n != 0 {
134 | I[i-n] = -n
135 | }
136 | n = V[I[i]] + 1 - i
137 | split(I, V, i, n, h)
138 | i += n
139 | n = 0
140 | }
141 | }
142 | if n != 0 {
143 | I[i-n] = -n
144 | }
145 | }
146 |
147 | for i = 0; i < len(obuf)+1; i++ {
148 | I[V[i]] = i
149 | }
150 | return I
151 | }
152 |
153 | func matchlen(a, b []byte) (i int) {
154 | for i < len(a) && i < len(b) && a[i] == b[i] {
155 | i++
156 | }
157 | return i
158 | }
159 |
160 | func search(I []int, obuf, nbuf []byte, st, en int) (pos, n int) {
161 | if en-st < 2 {
162 | x := matchlen(obuf[I[st]:], nbuf)
163 | y := matchlen(obuf[I[en]:], nbuf)
164 |
165 | if x > y {
166 | return I[st], x
167 | } else {
168 | return I[en], y
169 | }
170 | }
171 |
172 | x := st + (en-st)/2
173 | if bytes.Compare(obuf[I[x]:], nbuf) < 0 {
174 | return search(I, obuf, nbuf, x, en)
175 | } else {
176 | return search(I, obuf, nbuf, st, x)
177 | }
178 | panic("unreached")
179 | }
180 |
181 | // Diff computes the difference between old and new, according to the bsdiff
182 | // algorithm, and writes the result to patch.
183 | func Diff(old, new io.Reader, patch io.Writer) error {
184 | obuf, err := ioutil.ReadAll(old)
185 | if err != nil {
186 | return err
187 | }
188 |
189 | nbuf, err := ioutil.ReadAll(new)
190 | if err != nil {
191 | return err
192 | }
193 |
194 | pbuf, err := diffBytes(obuf, nbuf)
195 | if err != nil {
196 | return err
197 | }
198 |
199 | _, err = patch.Write(pbuf)
200 | return err
201 | }
202 |
203 | func diffBytes(obuf, nbuf []byte) ([]byte, error) {
204 | var patch seekBuffer
205 | err := diff(obuf, nbuf, &patch)
206 | if err != nil {
207 | return nil, err
208 | }
209 | return patch.buf, nil
210 | }
211 |
212 | func diff(obuf, nbuf []byte, patch io.WriteSeeker) error {
213 | var lenf int
214 | I := qsufsort(obuf)
215 | db := make([]byte, len(nbuf))
216 | eb := make([]byte, len(nbuf))
217 | var dblen, eblen int
218 |
219 | var hdr header
220 | hdr.Magic = magic
221 | hdr.NewSize = int64(len(nbuf))
222 | err := binary.Write(patch, signMagLittleEndian{}, &hdr)
223 | if err != nil {
224 | return err
225 | }
226 |
227 | // Compute the differences, writing ctrl as we go
228 | pfbz2, err := newBzip2Writer(patch)
229 | if err != nil {
230 | return err
231 | }
232 | var scan, pos, length int
233 | var lastscan, lastpos, lastoffset int
234 | for scan < len(nbuf) {
235 | var oldscore int
236 | scan += length
237 | for scsc := scan; scan < len(nbuf); scan++ {
238 | pos, length = search(I, obuf, nbuf[scan:], 0, len(obuf))
239 |
240 | for ; scsc < scan+length; scsc++ {
241 | if scsc+lastoffset < len(obuf) &&
242 | obuf[scsc+lastoffset] == nbuf[scsc] {
243 | oldscore++
244 | }
245 | }
246 |
247 | if (length == oldscore && length != 0) || length > oldscore+8 {
248 | break
249 | }
250 |
251 | if scan+lastoffset < len(obuf) && obuf[scan+lastoffset] == nbuf[scan] {
252 | oldscore--
253 | }
254 | }
255 |
256 | if length != oldscore || scan == len(nbuf) {
257 | var s, Sf int
258 | lenf = 0
259 | for i := 0; lastscan+i < scan && lastpos+i < len(obuf); {
260 | if obuf[lastpos+i] == nbuf[lastscan+i] {
261 | s++
262 | }
263 | i++
264 | if s*2-i > Sf*2-lenf {
265 | Sf = s
266 | lenf = i
267 | }
268 | }
269 |
270 | lenb := 0
271 | if scan < len(nbuf) {
272 | var s, Sb int
273 | for i := 1; (scan >= lastscan+i) && (pos >= i); i++ {
274 | if obuf[pos-i] == nbuf[scan-i] {
275 | s++
276 | }
277 | if s*2-i > Sb*2-lenb {
278 | Sb = s
279 | lenb = i
280 | }
281 | }
282 | }
283 |
284 | if lastscan+lenf > scan-lenb {
285 | overlap := (lastscan + lenf) - (scan - lenb)
286 | s := 0
287 | Ss := 0
288 | lens := 0
289 | for i := 0; i < overlap; i++ {
290 | if nbuf[lastscan+lenf-overlap+i] == obuf[lastpos+lenf-overlap+i] {
291 | s++
292 | }
293 | if nbuf[scan-lenb+i] == obuf[pos-lenb+i] {
294 | s--
295 | }
296 | if s > Ss {
297 | Ss = s
298 | lens = i + 1
299 | }
300 | }
301 |
302 | lenf += lens - overlap
303 | lenb -= lens
304 | }
305 |
306 | for i := 0; i < lenf; i++ {
307 | db[dblen+i] = nbuf[lastscan+i] - obuf[lastpos+i]
308 | }
309 | for i := 0; i < (scan-lenb)-(lastscan+lenf); i++ {
310 | eb[eblen+i] = nbuf[lastscan+lenf+i]
311 | }
312 |
313 | dblen += lenf
314 | eblen += (scan - lenb) - (lastscan + lenf)
315 |
316 | err = binary.Write(pfbz2, signMagLittleEndian{}, int64(lenf))
317 | if err != nil {
318 | pfbz2.Close()
319 | return err
320 | }
321 |
322 | val := (scan - lenb) - (lastscan + lenf)
323 | err = binary.Write(pfbz2, signMagLittleEndian{}, int64(val))
324 | if err != nil {
325 | pfbz2.Close()
326 | return err
327 | }
328 |
329 | val = (pos - lenb) - (lastpos + lenf)
330 | err = binary.Write(pfbz2, signMagLittleEndian{}, int64(val))
331 | if err != nil {
332 | pfbz2.Close()
333 | return err
334 | }
335 |
336 | lastscan = scan - lenb
337 | lastpos = pos - lenb
338 | lastoffset = pos - scan
339 | }
340 | }
341 | err = pfbz2.Close()
342 | if err != nil {
343 | return err
344 | }
345 |
346 | // Compute size of compressed ctrl data
347 | l64, err := patch.Seek(0, 1)
348 | if err != nil {
349 | return err
350 | }
351 | hdr.CtrlLen = int64(l64 - 32)
352 |
353 | // Write compressed diff data
354 | pfbz2, err = newBzip2Writer(patch)
355 | if err != nil {
356 | return err
357 | }
358 | n, err := pfbz2.Write(db[:dblen])
359 | if err != nil {
360 | pfbz2.Close()
361 | return err
362 | }
363 | if n != dblen {
364 | pfbz2.Close()
365 | return io.ErrShortWrite
366 | }
367 | err = pfbz2.Close()
368 | if err != nil {
369 | return err
370 | }
371 |
372 | // Compute size of compressed diff data
373 | n64, err := patch.Seek(0, 1)
374 | if err != nil {
375 | return err
376 | }
377 | hdr.DiffLen = n64 - l64
378 |
379 | // Write compressed extra data
380 | pfbz2, err = newBzip2Writer(patch)
381 | if err != nil {
382 | return err
383 | }
384 | n, err = pfbz2.Write(eb[:eblen])
385 | if err != nil {
386 | pfbz2.Close()
387 | return err
388 | }
389 | if n != eblen {
390 | pfbz2.Close()
391 | return io.ErrShortWrite
392 | }
393 | err = pfbz2.Close()
394 | if err != nil {
395 | return err
396 | }
397 |
398 | // Seek to the beginning, write the header, and close the file
399 | _, err = patch.Seek(0, 0)
400 | if err != nil {
401 | return err
402 | }
403 | err = binary.Write(patch, signMagLittleEndian{}, &hdr)
404 | if err != nil {
405 | return err
406 | }
407 | return nil
408 | }
409 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/gopkg.in/inconshreveable/go-update.v0/update_test.go:
--------------------------------------------------------------------------------
1 | package update
2 |
3 | import (
4 | "bytes"
5 | "crypto"
6 | "crypto/rand"
7 | "crypto/rsa"
8 | "crypto/tls"
9 | "crypto/x509"
10 | "encoding/pem"
11 | "io/ioutil"
12 | "net"
13 | "net/http"
14 | "net/http/httptest"
15 | "os"
16 | "testing"
17 |
18 | "github.com/kr/binarydist"
19 | )
20 |
21 | var (
22 | oldFile = []byte{0xDE, 0xAD, 0xBE, 0xEF}
23 | newFile = []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}
24 | )
25 |
26 | func cleanup(path string) {
27 | os.Remove(path)
28 | }
29 |
30 | // we write with a separate name for each test so that we can run them in parallel
31 | func writeOldFile(path string, t *testing.T) {
32 | if err := ioutil.WriteFile(path, oldFile, 0777); err != nil {
33 | t.Fatalf("Failed to write file for testing preparation: %v", err)
34 | }
35 | }
36 |
37 | func validateUpdate(path string, err error, t *testing.T) {
38 | if err != nil {
39 | t.Fatalf("Failed to update: %v", err)
40 | }
41 |
42 | buf, err := ioutil.ReadFile(path)
43 | if err != nil {
44 | t.Fatalf("Failed to read file post-update: %v", err)
45 | }
46 |
47 | if !bytes.Equal(buf, newFile) {
48 | t.Fatalf("File was not updated! Bytes read: %v, Bytes expected: %v", buf, newFile)
49 | }
50 | }
51 |
52 | func TestFromStream(t *testing.T) {
53 | t.Parallel()
54 |
55 | fName := "TestFromStream"
56 | defer cleanup(fName)
57 | writeOldFile(fName, t)
58 |
59 | err, _ := New().Target(fName).FromStream(bytes.NewReader(newFile))
60 | validateUpdate(fName, err, t)
61 | }
62 |
63 | func TestFromFile(t *testing.T) {
64 | t.Parallel()
65 |
66 | fName := "TestFromFile"
67 | newFName := "NewTestFromFile"
68 | defer cleanup(fName)
69 | defer cleanup(newFName)
70 | writeOldFile(fName, t)
71 |
72 | if err := ioutil.WriteFile(newFName, newFile, 0777); err != nil {
73 | t.Fatalf("Failed to write file to update from: %v", err)
74 | }
75 |
76 | err, _ := New().Target(fName).FromFile(newFName)
77 | validateUpdate(fName, err, t)
78 | }
79 |
80 | func TestFromUrl(t *testing.T) {
81 | t.Parallel()
82 |
83 | fName := "TestFromUrl"
84 | defer cleanup(fName)
85 | writeOldFile(fName, t)
86 |
87 | l, err := net.Listen("tcp", ":0")
88 | if err != nil {
89 | t.Fatalf("Couldn't bind listener: %v", err)
90 | }
91 | addr := l.Addr().String()
92 |
93 | go http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
94 | w.Write(newFile)
95 | }))
96 |
97 | err, _ = New().Target(fName).FromUrl("http://" + addr)
98 | validateUpdate(fName, err, t)
99 | }
100 |
101 | func TestFromUrlCustomHTTPClient(t *testing.T) {
102 | t.Parallel()
103 |
104 | fName := "TestFromUrl"
105 | defer cleanup(fName)
106 | writeOldFile(fName, t)
107 |
108 | server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
109 | w.Write(newFile)
110 | }))
111 |
112 | server.StartTLS()
113 |
114 | noTLSClient := &http.Client{
115 | Transport: &http.Transport{
116 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
117 | },
118 | }
119 | testUpdate := &Update{HTTPClient: noTLSClient}
120 |
121 | err, _ := testUpdate.Target(fName).FromUrl(server.URL)
122 | validateUpdate(fName, err, t)
123 | }
124 |
125 | func TestVerifyChecksum(t *testing.T) {
126 | t.Parallel()
127 |
128 | fName := "TestVerifyChecksum"
129 | defer cleanup(fName)
130 | writeOldFile(fName, t)
131 |
132 | checksum, err := ChecksumForBytes(newFile)
133 | if err != nil {
134 | t.Fatalf("Failed to compute checksum: %v", err)
135 | }
136 |
137 | err, _ = New().Target(fName).VerifyChecksum(checksum).FromStream(bytes.NewReader(newFile))
138 | validateUpdate(fName, err, t)
139 | }
140 |
141 | func TestVerifyChecksumNegative(t *testing.T) {
142 | t.Parallel()
143 |
144 | fName := "TestVerifyChecksumNegative"
145 | defer cleanup(fName)
146 | writeOldFile(fName, t)
147 |
148 | badChecksum := []byte{0x0A, 0x0B, 0x0C, 0xFF}
149 | err, _ := New().Target(fName).VerifyChecksum(badChecksum).FromStream(bytes.NewReader(newFile))
150 | if err == nil {
151 | t.Fatalf("Failed to detect bad checksum!")
152 | }
153 | }
154 |
155 | func TestApplyPatch(t *testing.T) {
156 | t.Parallel()
157 |
158 | fName := "TestApplyPatch"
159 | defer cleanup(fName)
160 | writeOldFile(fName, t)
161 |
162 | patch := new(bytes.Buffer)
163 | err := binarydist.Diff(bytes.NewReader(oldFile), bytes.NewReader(newFile), patch)
164 | if err != nil {
165 | t.Fatalf("Failed to create patch: %v", err)
166 | }
167 |
168 | up := New().Target(fName).ApplyPatch(PATCHTYPE_BSDIFF)
169 | err, _ = up.FromStream(bytes.NewReader(patch.Bytes()))
170 | validateUpdate(fName, err, t)
171 | }
172 |
173 | func TestCorruptPatch(t *testing.T) {
174 | t.Parallel()
175 |
176 | fName := "TestCorruptPatch"
177 | defer cleanup(fName)
178 | writeOldFile(fName, t)
179 |
180 | badPatch := []byte{0x44, 0x38, 0x86, 0x3c, 0x4f, 0x8d, 0x26, 0x54, 0xb, 0x11, 0xce, 0xfe, 0xc1, 0xc0, 0xf8, 0x31, 0x38, 0xa0, 0x12, 0x1a, 0xa2, 0x57, 0x2a, 0xe1, 0x3a, 0x48, 0x62, 0x40, 0x2b, 0x81, 0x12, 0xb1, 0x21, 0xa5, 0x16, 0xed, 0x73, 0xd6, 0x54, 0x84, 0x29, 0xa6, 0xd6, 0xb2, 0x1b, 0xfb, 0xe6, 0xbe, 0x7b, 0x70}
181 | up := New().Target(fName).ApplyPatch(PATCHTYPE_BSDIFF)
182 | err, _ := up.FromStream(bytes.NewReader(badPatch))
183 | if err == nil {
184 | t.Fatalf("Failed to detect corrupt patch!")
185 | }
186 | }
187 |
188 | func TestVerifyChecksumPatchNegative(t *testing.T) {
189 | t.Parallel()
190 |
191 | fName := "TestVerifyChecksumPatchNegative"
192 | defer cleanup(fName)
193 | writeOldFile(fName, t)
194 |
195 | checksum, err := ChecksumForBytes(newFile)
196 | if err != nil {
197 | t.Fatalf("Failed to compute checksum: %v", err)
198 | }
199 |
200 | patch := new(bytes.Buffer)
201 | anotherFile := []byte{0x77, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66}
202 | err = binarydist.Diff(bytes.NewReader(oldFile), bytes.NewReader(anotherFile), patch)
203 | if err != nil {
204 | t.Fatalf("Failed to create patch: %v", err)
205 | }
206 |
207 | up := New().Target(fName).ApplyPatch(PATCHTYPE_BSDIFF).VerifyChecksum(checksum)
208 | err, _ = up.FromStream(bytes.NewReader(patch.Bytes()))
209 | if err == nil {
210 | t.Fatalf("Failed to detect patch to wrong file!")
211 | }
212 | }
213 |
214 | const publicKey = `-----BEGIN PUBLIC KEY-----
215 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxSWmu7trWKAwDFjiCN2D
216 | Tk2jj2sgcr/CMlI4cSSiIOHrXCFxP1I8i9PvQkd4hasXQrLbT5WXKrRGv1HKUKab
217 | b9ead+kD0kxk7i2bFYvKX43oq66IW0mOLTQBO7I9UyT4L7svcMD+HUQ2BqHoaQe4
218 | y20C59dPr9Dpcz8DZkdLsBV6YKF6Ieb3iGk8oRLMWNaUqPa8f1BGgxAkvPHcqDjT
219 | x4xRnjgTRRRlZvRtALHMUkIChgxDOhoEzKpGiqnX7HtMJfrhV6h0PAXNA4h9Kjv5
220 | 5fhJ08Rz7mmZmtH5JxTK5XTquo59sihSajR4bSjZbbkQ1uLkeFlY3eli3xdQ7Nrf
221 | fQIDAQAB
222 | -----END PUBLIC KEY-----`
223 |
224 | const privateKey = `-----BEGIN RSA PRIVATE KEY-----
225 | MIIEogIBAAKCAQEAxSWmu7trWKAwDFjiCN2DTk2jj2sgcr/CMlI4cSSiIOHrXCFx
226 | P1I8i9PvQkd4hasXQrLbT5WXKrRGv1HKUKabb9ead+kD0kxk7i2bFYvKX43oq66I
227 | W0mOLTQBO7I9UyT4L7svcMD+HUQ2BqHoaQe4y20C59dPr9Dpcz8DZkdLsBV6YKF6
228 | Ieb3iGk8oRLMWNaUqPa8f1BGgxAkvPHcqDjTx4xRnjgTRRRlZvRtALHMUkIChgxD
229 | OhoEzKpGiqnX7HtMJfrhV6h0PAXNA4h9Kjv55fhJ08Rz7mmZmtH5JxTK5XTquo59
230 | sihSajR4bSjZbbkQ1uLkeFlY3eli3xdQ7NrffQIDAQABAoIBAAkN+6RvrTR61voa
231 | Mvd5RQiZpEN4Bht/Fyo8gH8h0Zh1B9xJZOwlmMZLS5fdtHlfLEhR8qSrGDBL61vq
232 | I8KkhEsUufF78EL+YzxVN+Q7cWYGHIOWFokqza7hzpSxUQO6lPOMQ1eIZaNueJTB
233 | Zu07/47ISPPg/bXzgGVcpYlTCPTjUwKjtfyMqvX9AD7fIyYRm6zfE7EHj1J2sBFt
234 | Yz1OGELg6HfJwXfpnPfBvftD0hWGzJ78Bp71fPJe6n5gnqmSqRvrcXNWFnH/yqkN
235 | d6vPIxD6Z3LjvyZpkA7JillLva2L/zcIFhg4HZvQnWd8/PpDnUDonu36hcj4SC5j
236 | W4aVPLkCgYEA4XzNKWxqYcajzFGZeSxlRHupSAl2MT7Cc5085MmE7dd31wK2T8O4
237 | n7N4bkm/rjTbX85NsfWdKtWb6mpp8W3VlLP0rp4a/12OicVOkg4pv9LZDmY0sRlE
238 | YuDJk1FeCZ50UrwTZI3rZ9IhZHhkgVA6uWAs7tYndONkxNHG0pjqs4sCgYEA39MZ
239 | JwMqo3qsPntpgP940cCLflEsjS9hYNO3+Sv8Dq3P0HLVhBYajJnotf8VuU0fsQZG
240 | grmtVn1yThFbMq7X1oY4F0XBA+paSiU18c4YyUnwax2u4sw9U/Q9tmQUZad5+ueT
241 | qriMBwGv+ewO+nQxqvAsMUmemrVzrfwA5Oct+hcCgYAfiyXoNZJsOy2O15twqBVC
242 | j0oPGcO+/9iT89sg5lACNbI+EdMPNYIOVTzzsL1v0VUfAe08h++Enn1BPcG0VHkc
243 | ZFBGXTfJoXzfKQrkw7ZzbzuOGB4m6DH44xlP0oIlNlVvfX/5ASF9VJf3RiBJNsAA
244 | TsP6ZVr/rw/ZuL7nlxy+IQKBgDhL/HOXlE3yOQiuOec8WsNHTs7C1BXe6PtVxVxi
245 | 988pYK/pclL6zEq5G5NLSceF4obAMVQIJ9UtUGbabrncyGUo9UrFPLsjYvprSZo8
246 | YHegpVwL50UcYgCP2kXZ/ldjPIcjYDz8lhvdDMor2cidGTEJn9P11HLNWP9V91Ob
247 | 4jCZAoGAPNRSC5cC8iP/9j+s2/kdkfWJiNaolPYAUrmrkL6H39PYYZM5tnhaIYJV
248 | Oh9AgABamU0eb3p3vXTISClVgV7ifq1HyZ7BSUhMfaY2Jk/s3sUHCWFxPZe9sgEG
249 | KinIY/373KIkIV/5g4h2v1w330IWcfptxKcY/Er3DJr38f695GE=
250 | -----END RSA PRIVATE KEY-----`
251 |
252 | func sign(privatePEM string, source []byte, t *testing.T) []byte {
253 | block, _ := pem.Decode([]byte(privatePEM))
254 | if block == nil {
255 | t.Fatalf("Failed to parse private key PEM")
256 | }
257 |
258 | priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
259 | if err != nil {
260 | t.Fatalf("Failed to parse private key DER")
261 | }
262 |
263 | checksum, err := ChecksumForBytes(source)
264 | if err != nil {
265 | t.Fatalf("Failed to make checksum")
266 | }
267 |
268 | sig, err := rsa.SignPKCS1v15(rand.Reader, priv, crypto.SHA256, checksum)
269 | if err != nil {
270 | t.Fatalf("Failed to sign: %v", sig)
271 | }
272 |
273 | return sig
274 | }
275 |
276 | func TestVerifySignature(t *testing.T) {
277 | t.Parallel()
278 |
279 | fName := "TestVerifySignature"
280 | defer cleanup(fName)
281 | writeOldFile(fName, t)
282 |
283 | up, err := New().Target(fName).VerifySignatureWithPEM([]byte(publicKey))
284 | if err != nil {
285 | t.Fatalf("Could not parse public key: %v", err)
286 | }
287 |
288 | signature := sign(privateKey, newFile, t)
289 | err, _ = up.VerifySignature(signature).FromStream(bytes.NewReader(newFile))
290 | validateUpdate(fName, err, t)
291 | }
292 |
293 | func TestVerifyFailBadSignature(t *testing.T) {
294 | t.Parallel()
295 |
296 | fName := "TestVerifyFailBadSignature"
297 | defer cleanup(fName)
298 | writeOldFile(fName, t)
299 |
300 | up, err := New().Target(fName).VerifySignatureWithPEM([]byte(publicKey))
301 | if err != nil {
302 | t.Fatalf("Could not parse public key: %v", err)
303 | }
304 |
305 | badSig := []byte{0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA}
306 | err, _ = up.VerifySignature(badSig).FromStream(bytes.NewReader(newFile))
307 | if err == nil {
308 | t.Fatalf("Did not fail with bad signature")
309 | }
310 | }
311 |
312 | func TestVerifyFailNoSignature(t *testing.T) {
313 | t.Parallel()
314 |
315 | fName := "TestVerifySignatureWithPEM"
316 | defer cleanup(fName)
317 | writeOldFile(fName, t)
318 |
319 | up, err := New().Target(fName).VerifySignatureWithPEM([]byte(publicKey))
320 | if err != nil {
321 | t.Fatalf("Could not parse public key: %v", err)
322 | }
323 |
324 | err, _ = up.VerifySignature([]byte{}).FromStream(bytes.NewReader(newFile))
325 | if err == nil {
326 | t.Fatalf("Did not fail with empty signature")
327 | }
328 | }
329 |
330 | const wrongKey = `-----BEGIN RSA PRIVATE KEY-----
331 | MIIEpAIBAAKCAQEArKqjT+xOFJILe0CX7lKfQy52YwWLF9devYtLeUHTbPOueGLy
332 | 6CjrXJBrWIxNBxRd53y4dtgiMqCX6Gmmvuy8HnfbBuJjR2mcdEYo8UDy+aSVBQ6T
333 | /ND7Fd7KSzOruEFFzl2QFnZ/SrW/nsXdGyuF8l+YIwjZJRyV6StZkZ4ydOzOqUk9
334 | FXTeIkhX/Q7/jTETw7L3wxMyLgJAlV3lxDsPkMjxymngtbAIjwEjLsVeU+Prcz2e
335 | Ww34SZQ8qwzAdXieuDPryMwEsCcgc5NAKJFNL8TppYGDXOHI7CRXqHfNiJq2R+kQ
336 | LdxRvmfx8/iu4xM2hBzk4uSDS6TTn2AnWBm+cQIDAQABAoIBAFp//aUwaCRj/9yU
337 | GI3zhEJEIgz4pNTUL3YNgnuFwvlCJ9o1kreYavRTRdBdiSoCxM1GE7FGy3XZsoVA
338 | iwNbNaaKj6RmGD8f3b8b3u3EaxXp66mA4JQMPO5TnZgY9xJWM+5cH9+GMGXKKStg
339 | 7ekFwOkuraD/TEElYHWcIRAv6KZbc/YOIa6YDKi+1Gc7u0MeIvwqN7nwaBAoJKUE
340 | ZrJIfYKIViD/ZrCpgWN47C9x8w3ne7iiDrYoYct+0reC9LFlqwVBtDnyVx/q3upW
341 | zzczbNQagu3w0QgprDGhy0ZhDNxuylV3XBWTB+xBrFQgz6rD3LzUPywlbt0N7ZmD
342 | 936MVSECgYEA1IElCahF/+hC/OxFgy98DubAUDGmrvxWeZF3bvTseWZQp/gzxVS+
343 | SYumYyd2Ysx5+UjXQlVgR6BbDG13+DpSpZm6+MeWHBAR+KA2qCg009SDFv7l26/d
344 | xMT7lvIWz7ckQDb/+jvhF9HL2llyTN1Zex+n3XBeAMKNrPaubdEBFsUCgYEA0AIO
345 | tZMtzOpioAR1lGbwIguq04msDdrJNaY2TKrLeviJuQUw94fgL+3ULAPsiyxaU/Gv
346 | vln11R7aIp1SJ09T2UoFRbty+6SGRC56+Wh0pn5VnAi7aT6qdkYWhEjhqRHuXosf
347 | PYboXBuMwA0FBUTxWQL/lux2PZgvBkniYh5jI70CgYEAk9KmhhpFX2gdOT3OeRxO
348 | CzufaemwDqfAK97yGwBLg4OV9dJliQ6TNCvt+amY489jxfJSs3UafZjh3TpFKyq/
349 | FS1kb+y+0hSnu7EPdFhLr1N0QUndcb3b4iY48V7EWYgHspfP5y1CPsSVLvXr2eZc
350 | eZaiuhqReavczAXpfsDWJhUCgYEAwmUp2gfyhc+G3IVOXaLWSPseaxP+9/PAl6L+
351 | nCgCgqpEC+YOHUee/SwHXhtMtcR9pnX5CKyKUuLCehcM8C/y7N+AjerhSsw3rwDB
352 | bNVyLydiWrDOdU1bga1+3aI/QwK/AxyB1b5+6ZXVtKZ2SrZj2Aw1UZcr6eSQDhB+
353 | wbQkcwECgYBF13FMA6OOon992t9H3I+4KDgmz6G6mz3bVXSoFWfO1p/yXP04BzJl
354 | jtLFvFVTZdMs2o/wTd4SL6gYjx9mlOWwM8FblmjfiNSUVIyye33fRntEAr1n+FYI
355 | Xhv6aVnNdaGehGIqQxXFoGyiJxG3RYNkSwaTOamxY1V+ceLuO26n2Q==
356 | -----END RSA PRIVATE KEY-----`
357 |
358 | func TestVerifyFailWrongSignature(t *testing.T) {
359 | t.Parallel()
360 |
361 | fName := "TestVerifyFailWrongSignature"
362 | defer cleanup(fName)
363 | writeOldFile(fName, t)
364 |
365 | up, err := New().Target(fName).VerifySignatureWithPEM([]byte(publicKey))
366 | if err != nil {
367 | t.Fatalf("Could not parse public key: %v", err)
368 | }
369 |
370 | signature := sign(wrongKey, newFile, t)
371 | err, _ = up.VerifySignature(signature).FromStream(bytes.NewReader(newFile))
372 | if err == nil {
373 | t.Fatalf("Verified an update that was signed by an untrusted key!")
374 | }
375 | }
376 |
377 | func TestSignatureButNoPublicKey(t *testing.T) {
378 | t.Parallel()
379 |
380 | fName := "TestSignatureButNoPublicKey"
381 | defer cleanup(fName)
382 | writeOldFile(fName, t)
383 |
384 | sig := sign(privateKey, newFile, t)
385 | err, _ := New().Target(fName).VerifySignature(sig).FromStream(bytes.NewReader(newFile))
386 | if err == nil {
387 | t.Fatalf("Allowed an update with a signautre verification when no public key was specified!")
388 | }
389 | }
390 |
391 | func TestPublicKeyButNoSignature(t *testing.T) {
392 | t.Parallel()
393 |
394 | fName := "TestPublicKeyButNoSignature"
395 | defer cleanup(fName)
396 | writeOldFile(fName, t)
397 |
398 | up, err := New().Target(fName).VerifySignatureWithPEM([]byte(publicKey))
399 | if err != nil {
400 | t.Fatalf("Could not parse public key: %v", err)
401 | }
402 |
403 | err, _ = up.FromStream(bytes.NewReader(newFile))
404 | if err == nil {
405 | t.Fatalf("Allowed an update with no signautre when a public key was specified!")
406 | }
407 | }
408 |
--------------------------------------------------------------------------------
/Godeps/_workspace/src/gopkg.in/inconshreveable/go-update.v0/update.go:
--------------------------------------------------------------------------------
1 | /*
2 | go-update allows a program to update itself by replacing its executable file
3 | with a new version. It provides the flexibility to implement different updating user experiences
4 | like auto-updating, or manual user-initiated updates. It also boasts
5 | advanced features like binary patching and code signing verification.
6 |
7 | Updating your program to a new version is as easy as:
8 |
9 | err, errRecover := update.New().FromUrl("http://release.example.com/2.0/myprogram")
10 | if err != nil {
11 | fmt.Printf("Update failed: %v\n", err)
12 | }
13 |
14 | You may also choose to update from other data sources such as a file or an io.Reader:
15 |
16 | err, errRecover := update.New().FromFile("/path/to/update")
17 |
18 | Binary Diff Patching
19 |
20 | Binary diff updates are supported and easy to use:
21 |
22 | up := update.New().ApplyPatch(update.PATCHTYPE_BSDIFF)
23 | err, errRecover := up.FromUrl("http://release.example.com/2.0/mypatch")
24 |
25 | Checksum Verification
26 |
27 | You should also verify the checksum of new updates as well as verify
28 | the digital signature of an update. Note that even when you choose to apply
29 | a patch, the checksum is verified against the complete update after that patch
30 | has been applied.
31 |
32 | up := update.New().ApplyPatch(update.PATCHTYPE_BSDIFF).VerifyChecksum(checksum)
33 | err, errRecover := up.FromUrl("http://release.example.com/2.0/mypatch")
34 |
35 | Updating other files
36 |
37 | Updating arbitrary files is also supported. You may update files which are
38 | not the currently running program:
39 |
40 | up := update.New().Target("/usr/local/bin/some-program")
41 | err, errRecover := up.FromUrl("http://release.example.com/2.0/some-program")
42 |
43 | Code Signing
44 |
45 | Truly secure updates use code signing to verify that the update was issued by a trusted party.
46 | To do this, you'll need to generate a public/private key pair. You can do this with openssl,
47 | or the equinox.io client (https://equinox.io/client) can easily generate one for you:
48 |
49 | # with equinox client
50 | equinox genkey --private-key=private.pem --public-key=public.pem
51 |
52 | # with openssl
53 | openssl genrsa -out private.pem 2048
54 | openssl rsa -in private.pem -out public.pem -pubout
55 |
56 | Once you have your key pair, you can instruct your program to validate its updates
57 | with the public key:
58 |
59 | const publicKey = `-----BEGIN PUBLIC KEY-----
60 | ...
61 | -----END PUBLIC KEY-----`
62 |
63 | up, err := update.New().VerifySignatureWithPEM(publicKey)
64 | if err != nil {
65 | return fmt.Errorf("Bad public key: '%v': %v", publicKey, err)
66 | }
67 |
68 | Once you've configured your program this way, it will disallow all updates unless they
69 | are properly signed. You must now pass in the signature to verify with:
70 |
71 | up.VerifySignature(signature).FromUrl("http://dl.example.com/update")
72 |
73 | Error Handling and Recovery
74 |
75 | To perform an update, the process must be able to read its executable file and to write
76 | to the directory that contains its executable file. It can be useful to check whether the process
77 | has the necessary permissions to perform an update before trying to apply one. Use the
78 | CanUpdate call to provide a useful message to the user if the update can't proceed without
79 | elevated permissions:
80 |
81 | up := update.New().Target("/etc/hosts")
82 | err := up.CanUpdate()
83 | if err != nil {
84 | fmt.Printf("Can't update because: '%v'. Try as root or Administrator\n", err)
85 | return
86 | }
87 | err, errRecover := up.FromUrl("https://example.com/new/hosts")
88 |
89 | Although exceedingly unlikely, the update operation itself is not atomic and can fail
90 | in such a way that a user's computer is left in an inconsistent state. If that happens,
91 | go-update attempts to recover to leave the system in a good state. If the recovery step
92 | fails (even more unlikely), a second error, referred to as "errRecover" will be non-nil
93 | so that you may inform your users of the bad news. You should handle this case as shown
94 | here:
95 |
96 | err, errRecover := up.FromUrl("https://example.com/update")
97 | if err != nil {
98 | fmt.Printf("Update failed: %v\n", err)
99 | if errRecover != nil {
100 | fmt.Printf("Failed to recover bad update: %v!\n", errRecover)
101 | fmt.Printf("Program exectuable may be missing!\n")
102 | }
103 | }
104 |
105 | Subpackages
106 |
107 | Sub-package check contains the client functionality for a simple protocol for negotiating
108 | whether a new update is available, where it is, and the metadata needed for verifying it.
109 |
110 | Sub-package download contains functionality for downloading from an HTTP endpoint
111 | while outputting a progress meter and supports resuming partial downloads.
112 | */
113 | package update
114 |
115 | import (
116 | "bytes"
117 | "crypto"
118 | "crypto/rsa"
119 | "crypto/sha256"
120 | _ "crypto/sha512" // for tls cipher support
121 | "crypto/x509"
122 | "encoding/pem"
123 | "fmt"
124 | "io"
125 | "io/ioutil"
126 | "net/http"
127 | "os"
128 | "path/filepath"
129 |
130 | "github.com/kardianos/osext"
131 | "github.com/kr/binarydist"
132 | "gopkg.in/inconshreveable/go-update.v0/download"
133 | )
134 |
135 | // The type of a binary patch, if any. Only bsdiff is supported
136 | type PatchType string
137 |
138 | const (
139 | PATCHTYPE_BSDIFF PatchType = "bsdiff"
140 | PATCHTYPE_NONE = ""
141 | )
142 |
143 | type Update struct {
144 | // empty string means "path of the current executable"
145 | TargetPath string
146 |
147 | // type of patch to apply. PATCHTYPE_NONE means "not a patch"
148 | PatchType
149 |
150 | // sha256 checksum of the new binary to verify against
151 | Checksum []byte
152 |
153 | // public key to use for signature verification
154 | PublicKey *rsa.PublicKey
155 |
156 | // signature to use for signature verification
157 | Signature []byte
158 |
159 | // configurable http client can be passed to download
160 | HTTPClient *http.Client
161 | }
162 |
163 | func (u *Update) getPath() (string, error) {
164 | if u.TargetPath == "" {
165 | return osext.Executable()
166 | } else {
167 | return u.TargetPath, nil
168 | }
169 | }
170 |
171 | // New creates a new Update object.
172 | // A default update object assumes the complete binary
173 | // content will be used for update (not a patch) and that
174 | // the intended target is the running executable.
175 | //
176 | // Use this as the start of a chain of calls on the Update
177 | // object to build up your configuration. Example:
178 | //
179 | // up := update.New().ApplyPatch(update.PATCHTYPE_BSDIFF).VerifyChecksum(checksum)
180 | //
181 | func New() *Update {
182 | return &Update{
183 | TargetPath: "",
184 | PatchType: PATCHTYPE_NONE,
185 | }
186 | }
187 |
188 | // Target configures the update to update the file at the given path.
189 | // The emptry string means 'the executable file of the running program'.
190 | func (u *Update) Target(path string) *Update {
191 | u.TargetPath = path
192 | return u
193 | }
194 |
195 | // ApplyPatch configures the update to treat the contents of the update
196 | // as a patch to apply to the existing to target. You must specify the
197 | // format of the patch. Only PATCHTYPE_BSDIFF is supported at the moment.
198 | func (u *Update) ApplyPatch(patchType PatchType) *Update {
199 | u.PatchType = patchType
200 | return u
201 | }
202 |
203 | // VerifyChecksum configures the update to verify that the
204 | // the update has the given sha256 checksum.
205 | func (u *Update) VerifyChecksum(checksum []byte) *Update {
206 | u.Checksum = checksum
207 | return u
208 | }
209 |
210 | // VerifySignature configures the update to verify the given
211 | // signature of the update. You must also call one of the
212 | // VerifySignatureWith* functions to specify a public key
213 | // to use for verification.
214 | func (u *Update) VerifySignature(signature []byte) *Update {
215 | u.Signature = signature
216 | return u
217 | }
218 |
219 | // VerifySignatureWith configures the update to use the given RSA
220 | // public key to verify the update's signature. You must also call
221 | // VerifySignature() with a signature to check.
222 | //
223 | // You'll probably want to use VerifySignatureWithPEM instead of
224 | // parsing the public key yourself.
225 | func (u *Update) VerifySignatureWith(publicKey *rsa.PublicKey) *Update {
226 | u.PublicKey = publicKey
227 | return u
228 | }
229 |
230 | // VerifySignatureWithPEM configures the update to use the given PEM-formatted
231 | // RSA public key to verify the update's signature. You must also call
232 | // VerifySignature() with a signature to check.
233 | //
234 | // A PEM formatted public key typically begins with
235 | // -----BEGIN PUBLIC KEY-----
236 | func (u *Update) VerifySignatureWithPEM(publicKeyPEM []byte) (*Update, error) {
237 | block, _ := pem.Decode(publicKeyPEM)
238 | if block == nil {
239 | return u, fmt.Errorf("Couldn't parse PEM data")
240 | }
241 |
242 | pub, err := x509.ParsePKIXPublicKey(block.Bytes)
243 | if err != nil {
244 | return u, err
245 | }
246 |
247 | var ok bool
248 | u.PublicKey, ok = pub.(*rsa.PublicKey)
249 | if !ok {
250 | return u, fmt.Errorf("Public key isn't an RSA public key")
251 | }
252 |
253 | return u, nil
254 | }
255 |
256 | // FromUrl updates the target with the contents of the given URL.
257 | func (u *Update) FromUrl(url string) (err error, errRecover error) {
258 | target := new(download.MemoryTarget)
259 | err = download.New(url, target, u.HTTPClient).Get()
260 | if err != nil {
261 | return
262 | }
263 |
264 | return u.FromStream(target)
265 | }
266 |
267 | // FromFile updates the target the contents of the given file.
268 | func (u *Update) FromFile(path string) (err error, errRecover error) {
269 | // open the new updated contents
270 | fp, err := os.Open(path)
271 | if err != nil {
272 | return
273 | }
274 | defer fp.Close()
275 |
276 | // do the update
277 | return u.FromStream(fp)
278 | }
279 |
280 | // FromStream updates the target file with the contents of the supplied io.Reader.
281 | //
282 | // FromStream performs the following actions to ensure a safe cross-platform update:
283 | //
284 | // 1. If configured, applies the contents of the io.Reader as a binary patch.
285 | //
286 | // 2. If configured, computes the sha256 checksum and verifies it matches.
287 | //
288 | // 3. If configured, verifies the RSA signature with a public key.
289 | //
290 | // 4. Creates a new file, /path/to/.target.new with mode 0755 with the contents of the updated file
291 | //
292 | // 5. Renames /path/to/target to /path/to/.target.old
293 | //
294 | // 6. Renames /path/to/.target.new to /path/to/target
295 | //
296 | // 7. If the rename is successful, deletes /path/to/.target.old, returns no error
297 | //
298 | // 8. If the rename fails, attempts to rename /path/to/.target.old back to /path/to/target
299 | // If this operation fails, it is reported in the errRecover return value so as not to
300 | // mask the original error that caused the recovery attempt.
301 | //
302 | // On Windows, the removal of /path/to/.target.old always fails, so instead,
303 | // we just make the old file hidden instead.
304 | func (u *Update) FromStream(updateWith io.Reader) (err error, errRecover error) {
305 | updatePath, err := u.getPath()
306 | if err != nil {
307 | return
308 | }
309 |
310 | var newBytes []byte
311 | // apply a patch if requested
312 | switch u.PatchType {
313 | case PATCHTYPE_BSDIFF:
314 | newBytes, err = applyPatch(updateWith, updatePath)
315 | if err != nil {
316 | return
317 | }
318 | case PATCHTYPE_NONE:
319 | // no patch to apply, go on through
320 | newBytes, err = ioutil.ReadAll(updateWith)
321 | if err != nil {
322 | return
323 | }
324 | default:
325 | err = fmt.Errorf("Unrecognized patch type: %s", u.PatchType)
326 | return
327 | }
328 |
329 | // verify checksum if requested
330 | if u.Checksum != nil {
331 | if err = verifyChecksum(newBytes, u.Checksum); err != nil {
332 | return
333 | }
334 | }
335 |
336 | // verify signature if requested
337 | if u.Signature != nil || u.PublicKey != nil {
338 | if u.Signature == nil {
339 | err = fmt.Errorf("No public key specified to verify signature")
340 | return
341 | }
342 |
343 | if u.PublicKey == nil {
344 | err = fmt.Errorf("No signature to verify!")
345 | return
346 | }
347 |
348 | if err = verifySignature(newBytes, u.Signature, u.PublicKey); err != nil {
349 | return
350 | }
351 | }
352 |
353 | // get the directory the executable exists in
354 | updateDir := filepath.Dir(updatePath)
355 | filename := filepath.Base(updatePath)
356 |
357 | // Copy the contents of of newbinary to a the new executable file
358 | newPath := filepath.Join(updateDir, fmt.Sprintf(".%s.new", filename))
359 | fp, err := os.OpenFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
360 | if err != nil {
361 | return
362 | }
363 | defer fp.Close()
364 | _, err = io.Copy(fp, bytes.NewReader(newBytes))
365 |
366 | // if we don't call fp.Close(), windows won't let us move the new executable
367 | // because the file will still be "in use"
368 | fp.Close()
369 |
370 | // this is where we'll move the executable to so that we can swap in the updated replacement
371 | oldPath := filepath.Join(updateDir, fmt.Sprintf(".%s.old", filename))
372 |
373 | // delete any existing old exec file - this is necessary on Windows for two reasons:
374 | // 1. after a successful update, Windows can't remove the .old file because the process is still running
375 | // 2. windows rename operations fail if the destination file already exists
376 | _ = os.Remove(oldPath)
377 |
378 | // move the existing executable to a new file in the same directory
379 | err = os.Rename(updatePath, oldPath)
380 | if err != nil {
381 | return
382 | }
383 |
384 | // move the new exectuable in to become the new program
385 | err = os.Rename(newPath, updatePath)
386 |
387 | if err != nil {
388 | // copy unsuccessful
389 | errRecover = os.Rename(oldPath, updatePath)
390 | } else {
391 | // copy successful, remove the old binary
392 | errRemove := os.Remove(oldPath)
393 |
394 | // windows has trouble with removing old binaries, so hide it instead
395 | if errRemove != nil {
396 | _ = hideFile(oldPath)
397 | }
398 | }
399 |
400 | return
401 | }
402 |
403 | // CanUpdate() determines whether the process has the correct permissions to
404 | // perform the requested update. If the update can proceed, it returns nil, otherwise
405 | // it returns the error that would occur if an update were attempted.
406 | func (u *Update) CanUpdate() (err error) {
407 | // get the directory the file exists in
408 | path, err := u.getPath()
409 | if err != nil {
410 | return
411 | }
412 |
413 | fileDir := filepath.Dir(path)
414 | fileName := filepath.Base(path)
415 |
416 | // attempt to open a file in the file's directory
417 | newPath := filepath.Join(fileDir, fmt.Sprintf(".%s.new", fileName))
418 | fp, err := os.OpenFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
419 | if err != nil {
420 | return
421 | }
422 | fp.Close()
423 |
424 | _ = os.Remove(newPath)
425 | return
426 | }
427 |
428 | func applyPatch(patch io.Reader, updatePath string) ([]byte, error) {
429 | // open the file to update
430 | old, err := os.Open(updatePath)
431 | if err != nil {
432 | return nil, err
433 | }
434 | defer old.Close()
435 |
436 | // apply the patch
437 | applied := new(bytes.Buffer)
438 | if err = binarydist.Patch(old, applied, patch); err != nil {
439 | return nil, err
440 | }
441 |
442 | return applied.Bytes(), nil
443 | }
444 |
445 | func verifyChecksum(updated []byte, expectedChecksum []byte) error {
446 | checksum, err := ChecksumForBytes(updated)
447 | if err != nil {
448 | return err
449 | }
450 |
451 | if !bytes.Equal(expectedChecksum, checksum) {
452 | return fmt.Errorf("Updated file has wrong checksum. Expected: %x, got: %x", expectedChecksum, checksum)
453 | }
454 |
455 | return nil
456 | }
457 |
458 | // ChecksumForFile returns the sha256 checksum for the given file
459 | func ChecksumForFile(path string) ([]byte, error) {
460 | f, err := os.Open(path)
461 | if err != nil {
462 | return nil, err
463 | }
464 | defer f.Close()
465 |
466 | return ChecksumForReader(f)
467 | }
468 |
469 | // ChecksumForReader returns the sha256 checksum for the entire
470 | // contents of the given reader.
471 | func ChecksumForReader(rd io.Reader) ([]byte, error) {
472 | h := sha256.New()
473 | if _, err := io.Copy(h, rd); err != nil {
474 | return nil, err
475 | }
476 | return h.Sum(nil), nil
477 | }
478 |
479 | // ChecksumForBytes returns the sha256 checksum for the given bytes
480 | func ChecksumForBytes(source []byte) ([]byte, error) {
481 | return ChecksumForReader(bytes.NewReader(source))
482 | }
483 |
484 | func verifySignature(source, signature []byte, publicKey *rsa.PublicKey) error {
485 | checksum, err := ChecksumForBytes(source)
486 | if err != nil {
487 | return err
488 | }
489 |
490 | return rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, checksum, signature)
491 | }
492 |
--------------------------------------------------------------------------------