...

Source file src/github.com/magiconair/properties/load.go

Documentation: github.com/magiconair/properties

     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  }
   294  

View as plain text