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 }