github.com/craftyguy/u-root@v1.0.0/pkg/pxe/schemes.go (about) 1 // Copyright 2017-2018 the u-root 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 pxe 6 7 import ( 8 "errors" 9 "fmt" 10 "io" 11 "net/http" 12 "net/url" 13 "os" 14 "path/filepath" 15 16 "github.com/u-root/u-root/pkg/uio" 17 "pack.ag/tftp" 18 ) 19 20 var ( 21 // ErrNoSuchScheme is returned by Schemes.GetFile and 22 // Schemes.LazyGetFile if there is no registered FileScheme 23 // implementation for the given URL scheme. 24 ErrNoSuchScheme = errors.New("no such scheme") 25 ) 26 27 // FileScheme represents the implementation of a URL scheme and gives access to 28 // downloading files of that scheme. 29 // 30 // For example, an http FileScheme implementation would download files using 31 // the HTTP protocol. 32 type FileScheme interface { 33 // GetFile returns a reader that gives the contents of `u`. 34 // 35 // It may do so by downloading `u` and placing it in a buffer, or by 36 // returning an io.ReaderAt that downloads the file. 37 GetFile(u *url.URL) (io.ReaderAt, error) 38 } 39 40 var ( 41 // DefaultHTTPClient is the default HTTP FileScheme. 42 // 43 // It is not recommended to use this for HTTPS. We recommend creating an 44 // http.Client that accepts only a private pool of certificates. 45 DefaultHTTPClient = NewHTTPClient(http.DefaultClient) 46 47 // DefaultTFTPClient is the default TFTP FileScheme. 48 DefaultTFTPClient = NewTFTPClient() 49 50 // DefaultSchemes are the schemes supported by PXE by default. 51 DefaultSchemes = Schemes{ 52 "tftp": DefaultTFTPClient, 53 "http": DefaultHTTPClient, 54 "file": &LocalFileClient{}, 55 } 56 ) 57 58 // URLError is an error involving URLs. 59 type URLError struct { 60 URL *url.URL 61 Err error 62 } 63 64 // Error implements error.Error. 65 func (s *URLError) Error() string { 66 return fmt.Sprintf("encountered error %v with %q", s.Err, s.URL) 67 } 68 69 // IsURLError returns true iff err is a URLError. 70 func IsURLError(err error) bool { 71 _, ok := err.(*URLError) 72 return ok 73 } 74 75 // Schemes is a map of URL scheme identifier -> implementation that can 76 // download a file for that scheme. 77 type Schemes map[string]FileScheme 78 79 // RegisterScheme calls DefaultSchemes.Register. 80 func RegisterScheme(scheme string, fs FileScheme) { 81 DefaultSchemes.Register(scheme, fs) 82 } 83 84 // Register registers a scheme identified by `scheme` to be `fs`. 85 func (s Schemes) Register(scheme string, fs FileScheme) { 86 s[scheme] = fs 87 } 88 89 // GetFile downloads a file via DefaultSchemes. See Schemes.GetFile for 90 // details. 91 func GetFile(u *url.URL) (io.ReaderAt, error) { 92 return DefaultSchemes.GetFile(u) 93 } 94 95 // GetFile downloads the file with the given `u`. `u.Scheme` is used to 96 // select the FileScheme via `s`. 97 // 98 // If `s` does not contain a FileScheme for `u.Scheme`, ErrNoSuchScheme is 99 // returned. 100 func (s Schemes) GetFile(u *url.URL) (io.ReaderAt, error) { 101 fg, ok := s[u.Scheme] 102 if !ok { 103 return nil, &URLError{URL: u, Err: ErrNoSuchScheme} 104 } 105 r, err := fg.GetFile(u) 106 if err != nil { 107 return nil, &URLError{URL: u, Err: err} 108 } 109 return r, nil 110 } 111 112 // LazyGetFile calls LazyGetFile on DefaultSchemes. See Schemes.LazyGetFile. 113 func LazyGetFile(u *url.URL) (io.ReaderAt, error) { 114 return DefaultSchemes.LazyGetFile(u) 115 } 116 117 // LazyGetFile returns a reader that will download the file given by `u` when 118 // Read is called, based on `u`s scheme. See Schemes.GetFile for more 119 // details. 120 func (s Schemes) LazyGetFile(u *url.URL) (io.ReaderAt, error) { 121 fg, ok := s[u.Scheme] 122 if !ok { 123 return nil, &URLError{URL: u, Err: ErrNoSuchScheme} 124 } 125 126 return uio.NewLazyOpenerAt(func() (io.ReaderAt, error) { 127 r, err := fg.GetFile(u) 128 if err != nil { 129 return nil, &URLError{URL: u, Err: err} 130 } 131 return r, nil 132 }), nil 133 } 134 135 // TFTPClient implements FileScheme for TFTP files. 136 type TFTPClient struct { 137 opts []tftp.ClientOpt 138 } 139 140 // NewTFTPClient returns a new TFTP client based on the given tftp.ClientOpt. 141 func NewTFTPClient(opts ...tftp.ClientOpt) FileScheme { 142 return &TFTPClient{ 143 opts: opts, 144 } 145 } 146 147 // GetFile implements FileScheme.GetFile. 148 func (t *TFTPClient) GetFile(u *url.URL) (io.ReaderAt, error) { 149 // TODO(hugelgupf): These clients are basically stateless, except for 150 // the options. Figure out whether you actually have to re-establish 151 // this connection every time. Audit the TFTP library. 152 c, err := tftp.NewClient(t.opts...) 153 if err != nil { 154 return nil, err 155 } 156 157 r, err := c.Get(u.String()) 158 if err != nil { 159 return nil, err 160 } 161 return uio.NewCachingReader(r), nil 162 } 163 164 // HTTPClient implements FileScheme for HTTP files. 165 type HTTPClient struct { 166 c *http.Client 167 } 168 169 // NewHTTPClient returns a new HTTP FileScheme based on the given http.Client. 170 func NewHTTPClient(c *http.Client) *HTTPClient { 171 return &HTTPClient{ 172 c: c, 173 } 174 } 175 176 // GetFile implements FileScheme.GetFile. 177 func (h HTTPClient) GetFile(u *url.URL) (io.ReaderAt, error) { 178 resp, err := h.c.Get(u.String()) 179 if err != nil { 180 return nil, err 181 } 182 183 if resp.StatusCode != 200 { 184 return nil, fmt.Errorf("HTTP server responded with code %d, want 200: response %v", resp.StatusCode, resp) 185 } 186 return uio.NewCachingReader(resp.Body), nil 187 } 188 189 // LocalFileClient implements FileScheme for files on disk. 190 type LocalFileClient struct{} 191 192 // GetFile implements FileScheme.GetFile. 193 func (lfs LocalFileClient) GetFile(u *url.URL) (io.ReaderAt, error) { 194 return os.Open(filepath.Clean(u.Path)) 195 }