github.com/ratrocket/u-root@v0.0.0-20180201221235-1cf9f48ee2cf/pkg/pxe/schemes.go (about) 1 package pxe 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "net/http" 8 "net/url" 9 "os" 10 "path/filepath" 11 12 "pack.ag/tftp" 13 ) 14 15 var ( 16 // ErrNoSuchScheme is returned by Schemes.GetFile and 17 // Schemes.LazyGetFile if there is no registered FileScheme 18 // implementation for the given URI scheme. 19 ErrNoSuchScheme = errors.New("no such scheme") 20 ) 21 22 // FileScheme represents the implementation of a URI scheme and gives access to 23 // downloading files of that scheme. 24 // 25 // For example, an http FileScheme implementation would download files using 26 // the HTTP protocol. 27 type FileScheme interface { 28 // GetFile returns a reader that gives the contents of `u`. 29 // 30 // It may do so by downloading `u` and placing it in a buffer, or by 31 // returning an io.Reader that downloads the file. 32 GetFile(u *url.URL) (io.Reader, error) 33 } 34 35 var ( 36 // DefaultHTTPClient is the default HTTP FileScheme. 37 // 38 // It is not recommended to use this for HTTPS. We recommend creating an 39 // http.Client that accepts only a private pool of certificates. 40 DefaultHTTPClient = NewHTTPClient(http.DefaultClient) 41 42 // DefaultTFTPClient is the default TFTP FileScheme. 43 DefaultTFTPClient FileScheme 44 45 // DefaultSchemes are the schemes supported by PXE by default. 46 DefaultSchemes Schemes 47 ) 48 49 func init() { 50 c, err := tftp.NewClient() 51 if err != nil { 52 panic(fmt.Sprintf("tftp.NewClient failed: %v", err)) 53 } 54 DefaultTFTPClient = NewTFTPClient(c) 55 56 DefaultSchemes = Schemes{ 57 "tftp": NewCachedFileScheme(DefaultTFTPClient), 58 "http": NewCachedFileScheme(DefaultHTTPClient), 59 "file": NewCachedFileScheme(&LocalFileClient{}), 60 } 61 } 62 63 // Schemes is a map of URI scheme identifier -> implementation that can 64 // download a file for that scheme. 65 type Schemes map[string]FileScheme 66 67 // RegisterScheme calls DefaultSchemes.Register. 68 func RegisterScheme(scheme string, fs FileScheme) { 69 DefaultSchemes.Register(scheme, fs) 70 } 71 72 // Register registers a scheme identified by `scheme` to be `fs`. 73 func (s Schemes) Register(scheme string, fs FileScheme) { 74 s[scheme] = fs 75 } 76 77 func parseURI(uri string, wd *url.URL) (*url.URL, error) { 78 u, err := url.Parse(uri) 79 if err != nil { 80 return nil, err 81 } 82 83 if len(u.Scheme) == 0 { 84 u.Scheme = wd.Scheme 85 86 if len(u.Host) == 0 { 87 // If this is not there, it was likely just a path. 88 u.Host = wd.Host 89 u.Path = filepath.Join(wd.Path, filepath.Clean(u.Path)) 90 } 91 } 92 return u, nil 93 } 94 95 // GetFile downloads a file via DefaultSchemes. See Schemes.GetFile for 96 // details. 97 func GetFile(uri string, wd *url.URL) (io.Reader, error) { 98 return DefaultSchemes.GetFile(uri, wd) 99 } 100 101 // GetFile downloads the file with the given `uri`. `uri.Scheme` is used to 102 // select the FileScheme via `s`. 103 // 104 // If `s` does not contain a FileScheme for `uri.Scheme`, ErrNoSuchScheme is 105 // returned. 106 // 107 // If `uri` is just a relative path and not a full URI, `wd` is used as the 108 // "working directory" of that relative path; the resulting URI is roughly 109 // `path.Join(wd.String(), uri)`. 110 func (s Schemes) GetFile(uri string, wd *url.URL) (io.Reader, error) { 111 u, err := parseURI(uri, wd) 112 if err != nil { 113 return nil, err 114 } 115 116 fg, ok := s[u.Scheme] 117 if !ok { 118 return nil, ErrNoSuchScheme 119 } 120 return fg.GetFile(u) 121 } 122 123 // LazyGetFile calls LazyGetFile on DefaultSchemes. See Schemes.LazyGetFile. 124 func LazyGetFile(uri string, wd *url.URL) (io.Reader, error) { 125 return DefaultSchemes.LazyGetFile(uri, wd) 126 } 127 128 // LazyGetFile returns a reader that will download the file given by `uri` when 129 // Read is called, based on `uri`s scheme. See Schemes.GetFile for more 130 // details. 131 func (s Schemes) LazyGetFile(uri string, wd *url.URL) (io.Reader, error) { 132 u, err := parseURI(uri, wd) 133 if err != nil { 134 return nil, err 135 } 136 137 fg, ok := s[u.Scheme] 138 if !ok { 139 return nil, fmt.Errorf("could not get file based on scheme %q: no such scheme registered", u.Scheme) 140 } 141 142 return NewLazyOpener(func() (io.Reader, error) { 143 return fg.GetFile(u) 144 }), nil 145 } 146 147 // TFTPClient implements FileScheme for TFTP files. 148 type TFTPClient struct { 149 c *tftp.Client 150 } 151 152 // NewTFTPClient returns a new TFTP client based on the given tftp.Client. 153 func NewTFTPClient(c *tftp.Client) FileScheme { 154 return &TFTPClient{ 155 c: c, 156 } 157 } 158 159 // GetFile implements FileScheme.GetFile. 160 func (t *TFTPClient) GetFile(u *url.URL) (io.Reader, error) { 161 r, err := t.c.Get(u.String()) 162 if err != nil { 163 return nil, fmt.Errorf("failed to get %q: %v", u, err) 164 } 165 return r, nil 166 } 167 168 // HTTPClient implements FileScheme for HTTP files. 169 type HTTPClient struct { 170 c *http.Client 171 } 172 173 // NewHTTPClient returns a new HTTP FileScheme based on the given http.Client. 174 func NewHTTPClient(c *http.Client) *HTTPClient { 175 return &HTTPClient{ 176 c: c, 177 } 178 } 179 180 // GetFile implements FileScheme.GetFile. 181 func (h HTTPClient) GetFile(u *url.URL) (io.Reader, error) { 182 resp, err := h.c.Get(u.String()) 183 if err != nil { 184 return nil, fmt.Errorf("Could not download file %s: %v", u, err) 185 } 186 if resp.StatusCode != 200 { 187 return nil, fmt.Errorf("could not download file %s: response %v", u, resp) 188 } 189 return resp.Body, nil 190 } 191 192 // LocalFileClient implements FileScheme for files on disk. 193 type LocalFileClient struct{} 194 195 // GetFile implements FileScheme.GetFile. 196 func (lfs LocalFileClient) GetFile(u *url.URL) (io.Reader, error) { 197 return os.Open(filepath.Clean(u.Path)) 198 } 199 200 type cachedFile struct { 201 cr *CachingReader 202 err error 203 } 204 205 // CachedFileScheme implements FileScheme and caches files downloaded from a 206 // FileScheme. 207 type CachedFileScheme struct { 208 fs FileScheme 209 210 // cache is a map of URI string -> cached file or error object. 211 cache map[string]cachedFile 212 } 213 214 // NewCachedFileScheme returns a caching wrapper for the given FileScheme `fs`. 215 func NewCachedFileScheme(fs FileScheme) FileScheme { 216 return &CachedFileScheme{ 217 fs: fs, 218 cache: make(map[string]cachedFile), 219 } 220 } 221 222 // GetFile implements FileScheme.GetFile. 223 func (cc *CachedFileScheme) GetFile(u *url.URL) (io.Reader, error) { 224 uri := u.String() 225 if cf, ok := cc.cache[uri]; ok { 226 if cf.err != nil { 227 return nil, cf.err 228 } 229 return cf.cr.NewReader(), nil 230 } 231 232 r, err := cc.fs.GetFile(u) 233 if err != nil { 234 cc.cache[uri] = cachedFile{err: err} 235 return nil, err 236 } 237 cr := NewCachingReader(r) 238 cc.cache[uri] = cachedFile{cr: cr} 239 return cr.NewReader(), nil 240 }