├── .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 | [![GoDoc](https://godoc.org/github.com/inconshreveable/go-update?status.svg)](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 | [![GoDoc](https://godoc.org/github.com/sanbornm/go-selfupdate/selfupdate?status.svg)](https://godoc.org/github.com/sanbornm/go-selfupdate/selfupdate) 5 | [![Build Status](https://travis-ci.org/sanbornm/go-selfupdate.svg?branch=master)](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 | --------------------------------------------------------------------------------