github.com/icyphox/x@v0.0.355-0.20220311094250-029bd783e8b8/osx/file.go (about)

     1  package osx
     2  
     3  import (
     4  	"encoding/base64"
     5  	"io"
     6  	"net/url"
     7  	"os"
     8  
     9  	"github.com/hashicorp/go-retryablehttp"
    10  	"github.com/pkg/errors"
    11  
    12  	"github.com/ory/x/httpx"
    13  )
    14  
    15  type options struct {
    16  	disableFileLoader   bool
    17  	disableHTTPLoader   bool
    18  	disableBase64Loader bool
    19  	base64enc           *base64.Encoding
    20  	hc                  *retryablehttp.Client
    21  }
    22  
    23  type Option func(o *options)
    24  
    25  func (o *options) apply(opts []Option) *options {
    26  	for _, f := range opts {
    27  		f(o)
    28  	}
    29  	return o
    30  }
    31  
    32  func newOptions() *options {
    33  	return &options{
    34  		disableFileLoader:   false,
    35  		disableHTTPLoader:   false,
    36  		disableBase64Loader: false,
    37  		base64enc:           base64.RawURLEncoding,
    38  		hc:                  httpx.NewResilientClient(),
    39  	}
    40  }
    41  
    42  // WithDisabledFileLoader disables the file loader.
    43  func WithDisabledFileLoader() Option {
    44  	return func(o *options) {
    45  		o.disableFileLoader = true
    46  	}
    47  }
    48  
    49  // WithEnabledFileLoader enables the file loader.
    50  func WithEnabledFileLoader() Option {
    51  	return func(o *options) {
    52  		o.disableFileLoader = false
    53  	}
    54  }
    55  
    56  // WithDisabledHTTPLoader disables the HTTP loader.
    57  func WithDisabledHTTPLoader() Option {
    58  	return func(o *options) {
    59  		o.disableHTTPLoader = true
    60  	}
    61  }
    62  
    63  // WithEnabledHTTPLoader enables the HTTP loader.
    64  func WithEnabledHTTPLoader() Option {
    65  	return func(o *options) {
    66  		o.disableHTTPLoader = false
    67  	}
    68  }
    69  
    70  // WithDisabledBase64Loader disables the base64 loader.
    71  func WithDisabledBase64Loader() Option {
    72  	return func(o *options) {
    73  		o.disableBase64Loader = true
    74  	}
    75  }
    76  
    77  // WithEnabledBase64Loader disables the base64 loader.
    78  func WithEnabledBase64Loader() Option {
    79  	return func(o *options) {
    80  		o.disableBase64Loader = false
    81  	}
    82  }
    83  
    84  // WithBase64Encoding sets the base64 encoding.
    85  func WithBase64Encoding(enc *base64.Encoding) Option {
    86  	return func(o *options) {
    87  		o.base64enc = enc
    88  	}
    89  }
    90  
    91  //	WithHTTPClient sets the HTTP client.
    92  func WithHTTPClient(hc *retryablehttp.Client) Option {
    93  	return func(o *options) {
    94  		o.hc = hc
    95  	}
    96  }
    97  
    98  // RestrictedReadFile works similar to ReadFileFromAllSources but has all
    99  // sources disabled per default. You need to enable the loaders you wish to use
   100  // explicitly.
   101  func RestrictedReadFile(source string, opts ...Option) (bytes []byte, err error) {
   102  	o := newOptions()
   103  	o.disableFileLoader = true
   104  	o.disableBase64Loader = true
   105  	o.disableHTTPLoader = true
   106  	return readFile(source, o.apply(opts))
   107  }
   108  
   109  // ReadFileFromAllSources reads a file from base64, http, https, and file sources.
   110  //
   111  // Using options, you can disable individual loaders. For example, the following will
   112  // return an error:
   113  //
   114  //		ReadFileFromAllSources("https://foo.bar/baz.txt", WithDisabledHTTPLoader())
   115  //
   116  // Possible formats are:
   117  //
   118  // - file:///path/to/file
   119  // - https://host.com/path/to/file
   120  // - http://host.com/path/to/file
   121  // - base64://<base64 encoded string>
   122  //
   123  // For more options, check:
   124  //
   125  // - WithDisabledFileLoader
   126  // - WithDisabledHTTPLoader
   127  // - WithDisabledBase64Loader
   128  // - WithBase64Encoding
   129  // - WithHTTPClient
   130  func ReadFileFromAllSources(source string, opts ...Option) (bytes []byte, err error) {
   131  	return readFile(source, newOptions().apply(opts))
   132  }
   133  
   134  func readFile(source string, o *options) (bytes []byte, err error) {
   135  	parsed, err := url.ParseRequestURI(source)
   136  	if err != nil {
   137  		return nil, errors.Wrap(err, "failed to parse URL")
   138  	}
   139  
   140  	switch parsed.Scheme {
   141  	case "file":
   142  		if o.disableFileLoader {
   143  			return nil, errors.New("file loader disabled")
   144  		}
   145  
   146  		bytes, err = os.ReadFile(parsed.Host + parsed.Path)
   147  		if err != nil {
   148  			return nil, errors.Wrap(err, "unable to read the file")
   149  		}
   150  	case "http", "https":
   151  		if o.disableHTTPLoader {
   152  			return nil, errors.New("http(s) loader disabled")
   153  		}
   154  		resp, err := o.hc.Get(parsed.String())
   155  		if err != nil {
   156  			return nil, errors.Wrap(err, "unable to load remote file")
   157  		}
   158  		defer resp.Body.Close()
   159  
   160  		bytes, err = io.ReadAll(resp.Body)
   161  		if err != nil {
   162  			return nil, errors.Wrap(err, "unable to read the HTTP response body")
   163  		}
   164  	case "base64":
   165  		if o.disableBase64Loader {
   166  			return nil, errors.New("base64 loader disabled")
   167  		}
   168  
   169  		bytes, err = o.base64enc.DecodeString(parsed.Host + parsed.RawPath)
   170  		if err != nil {
   171  			return nil, errors.Wrap(err, "unable to base64 decode the location")
   172  		}
   173  	default:
   174  		return nil, errors.Errorf("unsupported source `%s`", parsed.Scheme)
   175  	}
   176  
   177  	return bytes, nil
   178  
   179  }