gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

load.go (9465B)


      1 // Copyright 2013-2022 Frank Schroeder. 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 properties
      6 
      7 import (
      8 	"fmt"
      9 	"io/ioutil"
     10 	"net/http"
     11 	"os"
     12 	"strings"
     13 )
     14 
     15 // Encoding specifies encoding of the input data.
     16 type Encoding uint
     17 
     18 const (
     19 	// utf8Default is a private placeholder for the zero value of Encoding to
     20 	// ensure that it has the correct meaning. UTF8 is the default encoding but
     21 	// was assigned a non-zero value which cannot be changed without breaking
     22 	// existing code. Clients should continue to use the public constants.
     23 	utf8Default Encoding = iota
     24 
     25 	// UTF8 interprets the input data as UTF-8.
     26 	UTF8
     27 
     28 	// ISO_8859_1 interprets the input data as ISO-8859-1.
     29 	ISO_8859_1
     30 )
     31 
     32 type Loader struct {
     33 	// Encoding determines how the data from files and byte buffers
     34 	// is interpreted. For URLs the Content-Type header is used
     35 	// to determine the encoding of the data.
     36 	Encoding Encoding
     37 
     38 	// DisableExpansion configures the property expansion of the
     39 	// returned property object. When set to true, the property values
     40 	// will not be expanded and the Property object will not be checked
     41 	// for invalid expansion expressions.
     42 	DisableExpansion bool
     43 
     44 	// IgnoreMissing configures whether missing files or URLs which return
     45 	// 404 are reported as errors. When set to true, missing files and 404
     46 	// status codes are not reported as errors.
     47 	IgnoreMissing bool
     48 }
     49 
     50 // Load reads a buffer into a Properties struct.
     51 func (l *Loader) LoadBytes(buf []byte) (*Properties, error) {
     52 	return l.loadBytes(buf, l.Encoding)
     53 }
     54 
     55 // LoadAll reads the content of multiple URLs or files in the given order into
     56 // a Properties struct. If IgnoreMissing is true then a 404 status code or
     57 // missing file will not be reported as error. Encoding sets the encoding for
     58 // files. For the URLs see LoadURL for the Content-Type header and the
     59 // encoding.
     60 func (l *Loader) LoadAll(names []string) (*Properties, error) {
     61 	all := NewProperties()
     62 	for _, name := range names {
     63 		n, err := expandName(name)
     64 		if err != nil {
     65 			return nil, err
     66 		}
     67 
     68 		var p *Properties
     69 		switch {
     70 		case strings.HasPrefix(n, "http://"):
     71 			p, err = l.LoadURL(n)
     72 		case strings.HasPrefix(n, "https://"):
     73 			p, err = l.LoadURL(n)
     74 		default:
     75 			p, err = l.LoadFile(n)
     76 		}
     77 		if err != nil {
     78 			return nil, err
     79 		}
     80 		all.Merge(p)
     81 	}
     82 
     83 	all.DisableExpansion = l.DisableExpansion
     84 	if all.DisableExpansion {
     85 		return all, nil
     86 	}
     87 	return all, all.check()
     88 }
     89 
     90 // LoadFile reads a file into a Properties struct.
     91 // If IgnoreMissing is true then a missing file will not be
     92 // reported as error.
     93 func (l *Loader) LoadFile(filename string) (*Properties, error) {
     94 	data, err := ioutil.ReadFile(filename)
     95 	if err != nil {
     96 		if l.IgnoreMissing && os.IsNotExist(err) {
     97 			LogPrintf("properties: %s not found. skipping", filename)
     98 			return NewProperties(), nil
     99 		}
    100 		return nil, err
    101 	}
    102 	return l.loadBytes(data, l.Encoding)
    103 }
    104 
    105 // LoadURL reads the content of the URL into a Properties struct.
    106 //
    107 // The encoding is determined via the Content-Type header which
    108 // should be set to 'text/plain'. If the 'charset' parameter is
    109 // missing, 'iso-8859-1' or 'latin1' the encoding is set to
    110 // ISO-8859-1. If the 'charset' parameter is set to 'utf-8' the
    111 // encoding is set to UTF-8. A missing content type header is
    112 // interpreted as 'text/plain; charset=utf-8'.
    113 func (l *Loader) LoadURL(url string) (*Properties, error) {
    114 	resp, err := http.Get(url)
    115 	if err != nil {
    116 		return nil, fmt.Errorf("properties: error fetching %q. %s", url, err)
    117 	}
    118 	defer resp.Body.Close()
    119 
    120 	if resp.StatusCode == 404 && l.IgnoreMissing {
    121 		LogPrintf("properties: %s returned %d. skipping", url, resp.StatusCode)
    122 		return NewProperties(), nil
    123 	}
    124 
    125 	if resp.StatusCode != 200 {
    126 		return nil, fmt.Errorf("properties: %s returned %d", url, resp.StatusCode)
    127 	}
    128 
    129 	body, err := ioutil.ReadAll(resp.Body)
    130 	if err != nil {
    131 		return nil, fmt.Errorf("properties: %s error reading response. %s", url, err)
    132 	}
    133 
    134 	ct := resp.Header.Get("Content-Type")
    135 	ct = strings.Join(strings.Fields(ct), "")
    136 	var enc Encoding
    137 	switch strings.ToLower(ct) {
    138 	case "text/plain", "text/plain;charset=iso-8859-1", "text/plain;charset=latin1":
    139 		enc = ISO_8859_1
    140 	case "", "text/plain;charset=utf-8":
    141 		enc = UTF8
    142 	default:
    143 		return nil, fmt.Errorf("properties: invalid content type %s", ct)
    144 	}
    145 
    146 	return l.loadBytes(body, enc)
    147 }
    148 
    149 func (l *Loader) loadBytes(buf []byte, enc Encoding) (*Properties, error) {
    150 	p, err := parse(convert(buf, enc))
    151 	if err != nil {
    152 		return nil, err
    153 	}
    154 	p.DisableExpansion = l.DisableExpansion
    155 	if p.DisableExpansion {
    156 		return p, nil
    157 	}
    158 	return p, p.check()
    159 }
    160 
    161 // Load reads a buffer into a Properties struct.
    162 func Load(buf []byte, enc Encoding) (*Properties, error) {
    163 	l := &Loader{Encoding: enc}
    164 	return l.LoadBytes(buf)
    165 }
    166 
    167 // LoadString reads an UTF8 string into a properties struct.
    168 func LoadString(s string) (*Properties, error) {
    169 	l := &Loader{Encoding: UTF8}
    170 	return l.LoadBytes([]byte(s))
    171 }
    172 
    173 // LoadMap creates a new Properties struct from a string map.
    174 func LoadMap(m map[string]string) *Properties {
    175 	p := NewProperties()
    176 	for k, v := range m {
    177 		p.Set(k, v)
    178 	}
    179 	return p
    180 }
    181 
    182 // LoadFile reads a file into a Properties struct.
    183 func LoadFile(filename string, enc Encoding) (*Properties, error) {
    184 	l := &Loader{Encoding: enc}
    185 	return l.LoadAll([]string{filename})
    186 }
    187 
    188 // LoadFiles reads multiple files in the given order into
    189 // a Properties struct. If 'ignoreMissing' is true then
    190 // non-existent files will not be reported as error.
    191 func LoadFiles(filenames []string, enc Encoding, ignoreMissing bool) (*Properties, error) {
    192 	l := &Loader{Encoding: enc, IgnoreMissing: ignoreMissing}
    193 	return l.LoadAll(filenames)
    194 }
    195 
    196 // LoadURL reads the content of the URL into a Properties struct.
    197 // See Loader#LoadURL for details.
    198 func LoadURL(url string) (*Properties, error) {
    199 	l := &Loader{Encoding: UTF8}
    200 	return l.LoadAll([]string{url})
    201 }
    202 
    203 // LoadURLs reads the content of multiple URLs in the given order into a
    204 // Properties struct. If IgnoreMissing is true then a 404 status code will
    205 // not be reported as error. See Loader#LoadURL for the Content-Type header
    206 // and the encoding.
    207 func LoadURLs(urls []string, ignoreMissing bool) (*Properties, error) {
    208 	l := &Loader{Encoding: UTF8, IgnoreMissing: ignoreMissing}
    209 	return l.LoadAll(urls)
    210 }
    211 
    212 // LoadAll reads the content of multiple URLs or files in the given order into a
    213 // Properties struct. If 'ignoreMissing' is true then a 404 status code or missing file will
    214 // not be reported as error. Encoding sets the encoding for files. For the URLs please see
    215 // LoadURL for the Content-Type header and the encoding.
    216 func LoadAll(names []string, enc Encoding, ignoreMissing bool) (*Properties, error) {
    217 	l := &Loader{Encoding: enc, IgnoreMissing: ignoreMissing}
    218 	return l.LoadAll(names)
    219 }
    220 
    221 // MustLoadString reads an UTF8 string into a Properties struct and
    222 // panics on error.
    223 func MustLoadString(s string) *Properties {
    224 	return must(LoadString(s))
    225 }
    226 
    227 // MustLoadFile reads a file into a Properties struct and
    228 // panics on error.
    229 func MustLoadFile(filename string, enc Encoding) *Properties {
    230 	return must(LoadFile(filename, enc))
    231 }
    232 
    233 // MustLoadFiles reads multiple files in the given order into
    234 // a Properties struct and panics on error. If 'ignoreMissing'
    235 // is true then non-existent files will not be reported as error.
    236 func MustLoadFiles(filenames []string, enc Encoding, ignoreMissing bool) *Properties {
    237 	return must(LoadFiles(filenames, enc, ignoreMissing))
    238 }
    239 
    240 // MustLoadURL reads the content of a URL into a Properties struct and
    241 // panics on error.
    242 func MustLoadURL(url string) *Properties {
    243 	return must(LoadURL(url))
    244 }
    245 
    246 // MustLoadURLs reads the content of multiple URLs in the given order into a
    247 // Properties struct and panics on error. If 'ignoreMissing' is true then a 404
    248 // status code will not be reported as error.
    249 func MustLoadURLs(urls []string, ignoreMissing bool) *Properties {
    250 	return must(LoadURLs(urls, ignoreMissing))
    251 }
    252 
    253 // MustLoadAll reads the content of multiple URLs or files in the given order into a
    254 // Properties struct. If 'ignoreMissing' is true then a 404 status code or missing file will
    255 // not be reported as error. Encoding sets the encoding for files. For the URLs please see
    256 // LoadURL for the Content-Type header and the encoding. It panics on error.
    257 func MustLoadAll(names []string, enc Encoding, ignoreMissing bool) *Properties {
    258 	return must(LoadAll(names, enc, ignoreMissing))
    259 }
    260 
    261 func must(p *Properties, err error) *Properties {
    262 	if err != nil {
    263 		ErrorHandler(err)
    264 	}
    265 	return p
    266 }
    267 
    268 // expandName expands ${ENV_VAR} expressions in a name.
    269 // If the environment variable does not exist then it will be replaced
    270 // with an empty string. Malformed expressions like "${ENV_VAR" will
    271 // be reported as error.
    272 func expandName(name string) (string, error) {
    273 	return expand(name, []string{}, "${", "}", make(map[string]string))
    274 }
    275 
    276 // Interprets a byte buffer either as an ISO-8859-1 or UTF-8 encoded string.
    277 // For ISO-8859-1 we can convert each byte straight into a rune since the
    278 // first 256 unicode code points cover ISO-8859-1.
    279 func convert(buf []byte, enc Encoding) string {
    280 	switch enc {
    281 	case utf8Default, UTF8:
    282 		return string(buf)
    283 	case ISO_8859_1:
    284 		runes := make([]rune, len(buf))
    285 		for i, b := range buf {
    286 			runes[i] = rune(b)
    287 		}
    288 		return string(runes)
    289 	default:
    290 		ErrorHandler(fmt.Errorf("unsupported encoding %v", enc))
    291 	}
    292 	panic("ErrorHandler should exit")
    293 }