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  }