1package packr 2 3import ( 4 "bytes" 5 "compress/gzip" 6 "io/ioutil" 7 "net/http" 8 "os" 9 "path" 10 "path/filepath" 11 "runtime" 12 "strings" 13 14 "github.com/pkg/errors" 15) 16 17var ( 18 // ErrResOutsideBox gets returned in case of the requested resources being outside the box 19 ErrResOutsideBox = errors.New("Can't find a resource outside the box") 20) 21 22// NewBox returns a Box that can be used to 23// retrieve files from either disk or the embedded 24// binary. 25func NewBox(path string) Box { 26 var cd string 27 if !filepath.IsAbs(path) { 28 _, filename, _, _ := runtime.Caller(1) 29 cd = filepath.Dir(filename) 30 } 31 32 // this little hack courtesy of the `-cover` flag!! 33 cov := filepath.Join("_test", "_obj_test") 34 cd = strings.Replace(cd, string(filepath.Separator)+cov, "", 1) 35 if !filepath.IsAbs(cd) && cd != "" { 36 cd = filepath.Join(GoPath(), "src", cd) 37 } 38 39 return Box{ 40 Path: path, 41 callingDir: cd, 42 data: map[string][]byte{}, 43 } 44} 45 46// Box represent a folder on a disk you want to 47// have access to in the built Go binary. 48type Box struct { 49 Path string 50 callingDir string 51 data map[string][]byte 52 directories map[string]bool 53} 54 55// AddString converts t to a byteslice and delegates to AddBytes to add to b.data 56func (b Box) AddString(path string, t string) { 57 b.AddBytes(path, []byte(t)) 58} 59 60// AddBytes sets t in b.data by the given path 61func (b Box) AddBytes(path string, t []byte) { 62 b.data[path] = t 63} 64 65// String of the file asked for or an empty string. 66func (b Box) String(name string) string { 67 return string(b.Bytes(name)) 68} 69 70// MustString returns either the string of the requested 71// file or an error if it can not be found. 72func (b Box) MustString(name string) (string, error) { 73 bb, err := b.MustBytes(name) 74 return string(bb), err 75} 76 77// Bytes of the file asked for or an empty byte slice. 78func (b Box) Bytes(name string) []byte { 79 bb, _ := b.MustBytes(name) 80 return bb 81} 82 83// MustBytes returns either the byte slice of the requested 84// file or an error if it can not be found. 85func (b Box) MustBytes(name string) ([]byte, error) { 86 f, err := b.find(name) 87 if err == nil { 88 bb := &bytes.Buffer{} 89 bb.ReadFrom(f) 90 return bb.Bytes(), err 91 } 92 return nil, err 93} 94 95// Has returns true if the resource exists in the box 96func (b Box) Has(name string) bool { 97 _, err := b.find(name) 98 if err != nil { 99 return false 100 } 101 return true 102} 103 104func (b Box) decompress(bb []byte) []byte { 105 reader, err := gzip.NewReader(bytes.NewReader(bb)) 106 if err != nil { 107 return bb 108 } 109 data, err := ioutil.ReadAll(reader) 110 if err != nil { 111 return bb 112 } 113 return data 114} 115 116func (b Box) find(name string) (File, error) { 117 if bb, ok := b.data[name]; ok { 118 return newVirtualFile(name, bb), nil 119 } 120 if b.directories == nil { 121 b.indexDirectories() 122 } 123 124 cleanName := filepath.ToSlash(filepath.Clean(name)) 125 // Ensure name is not outside the box 126 if strings.HasPrefix(cleanName, "../") { 127 return nil, ErrResOutsideBox 128 } 129 // Absolute name is considered as relative to the box root 130 cleanName = strings.TrimPrefix(cleanName, "/") 131 132 // Try to get the resource from the box 133 if _, ok := data[b.Path]; ok { 134 if bb, ok := data[b.Path][cleanName]; ok { 135 bb = b.decompress(bb) 136 return newVirtualFile(cleanName, bb), nil 137 } 138 if _, ok := b.directories[cleanName]; ok { 139 return newVirtualDir(cleanName), nil 140 } 141 if filepath.Ext(cleanName) != "" { 142 // The Handler created by http.FileSystem checks for those errors and 143 // returns http.StatusNotFound instead of http.StatusInternalServerError. 144 return nil, os.ErrNotExist 145 } 146 return nil, os.ErrNotExist 147 } 148 149 // Not found in the box virtual fs, try to get it from the file system 150 cleanName = filepath.FromSlash(cleanName) 151 p := filepath.Join(b.callingDir, b.Path, cleanName) 152 return fileFor(p, cleanName) 153} 154 155// Open returns a File using the http.File interface 156func (b Box) Open(name string) (http.File, error) { 157 return b.find(name) 158} 159 160// List shows "What's in the box?" 161func (b Box) List() []string { 162 var keys []string 163 164 if b.data == nil || len(b.data) == 0 { 165 b.Walk(func(path string, info File) error { 166 finfo, _ := info.FileInfo() 167 if !finfo.IsDir() { 168 keys = append(keys, finfo.Name()) 169 } 170 return nil 171 }) 172 } else { 173 for k := range b.data { 174 keys = append(keys, k) 175 } 176 } 177 return keys 178} 179 180func (b *Box) indexDirectories() { 181 b.directories = map[string]bool{} 182 if _, ok := data[b.Path]; ok { 183 for name := range data[b.Path] { 184 prefix, _ := path.Split(name) 185 // Even on Windows the suffix appears to be a / 186 prefix = strings.TrimSuffix(prefix, "/") 187 b.directories[prefix] = true 188 } 189 } 190} 191 192func fileFor(p string, name string) (File, error) { 193 fi, err := os.Stat(p) 194 if err != nil { 195 return nil, err 196 } 197 if fi.IsDir() { 198 return newVirtualDir(p), nil 199 } 200 if bb, err := ioutil.ReadFile(p); err == nil { 201 return newVirtualFile(name, bb), nil 202 } 203 return nil, os.ErrNotExist 204} 205