1 // 2 // Copyright (c) SAS Institute Inc. 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 // 16 17 package signers 18 19 // Some package types can't be signed as a stream as-is, so we transform them 20 // into something else (a tarball) and upload that to the server. The server 21 // signs the tar, returns the signature blob, and the client inserts the blob 22 // into the original file. This mechanism can also be used for cases that don't 23 // need a transform on the upload but do need special processing on the result 24 // side. A default implementation that handles patching, copying, and 25 // overwriting is provided. 26 27 import ( 28 "fmt" 29 "io" 30 "io/ioutil" 31 "os" 32 33 "github.com/sassoftware/relic/lib/atomicfile" 34 "github.com/sassoftware/relic/lib/binpatch" 35 ) 36 37 type Transformer interface { 38 // Return a stream that will be uploaded to a remote server. This may be 39 // called multiple times in case of failover. 40 GetReader() (stream io.Reader, err error) 41 // Apply a HTTP response to the named destination file 42 Apply(dest, mimetype string, result io.Reader) error 43 } 44 45 // Return the transform for the given module if it has one, otherwise return 46 // the default transform. 47 func (s *Signer) GetTransform(f *os.File, opts SignOpts) (Transformer, error) { 48 if s != nil && s.Transform != nil { 49 return s.Transform(f, opts) 50 } 51 return fileProducer{f}, nil 52 } 53 54 func DefaultTransform(f *os.File) Transformer { 55 return fileProducer{f} 56 } 57 58 // Dummy implementation that sends the original file as a request, and 59 // either applies a binary patch or overwrites the whole file depending on the 60 // MIME type. 61 62 type fileProducer struct { 63 f *os.File 64 } 65 66 func (p fileProducer) GetReader() (io.Reader, error) { 67 if _, err := p.f.Seek(0, io.SeekStart); err != nil { 68 return nil, fmt.Errorf("failed to seek input file: %s", err) 69 } 70 return p.f, nil 71 } 72 73 // If the response is a binpatch, apply it. Otherwise overwrite the destination 74 // file with the response 75 func (p fileProducer) Apply(dest, mimetype string, result io.Reader) error { 76 if mimetype == binpatch.MimeType { 77 return ApplyBinPatch(p.f, dest, result) 78 } 79 f, err := atomicfile.WriteAny(dest) 80 if err != nil { 81 return err 82 } 83 if _, err := io.Copy(f, result); err != nil { 84 return err 85 } 86 p.f.Close() 87 return f.Commit() 88 } 89 90 func ApplyBinPatch(src *os.File, dest string, result io.Reader) error { 91 blob, err := ioutil.ReadAll(result) 92 if err != nil { 93 return err 94 } 95 patch, err := binpatch.Load(blob) 96 if err != nil { 97 return err 98 } 99 return patch.Apply(src, dest) 100 } 101