1// Copyright 2018 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
5package properties
6
7// BUG(frank): Set() does not check for invalid unicode literals since this is currently handled by the lexer.
8// BUG(frank): Write() does not allow to configure the newline character. Therefore, on Windows LF is used.
9
10import (
11	"fmt"
12	"io"
13	"log"
14	"os"
15	"regexp"
16	"strconv"
17	"strings"
18	"time"
19	"unicode/utf8"
20)
21
22const maxExpansionDepth = 64
23
24// ErrorHandlerFunc defines the type of function which handles failures
25// of the MustXXX() functions. An error handler function must exit
26// the application after handling the error.
27type ErrorHandlerFunc func(error)
28
29// ErrorHandler is the function which handles failures of the MustXXX()
30// functions. The default is LogFatalHandler.
31var ErrorHandler ErrorHandlerFunc = LogFatalHandler
32
33// LogHandlerFunc defines the function prototype for logging errors.
34type LogHandlerFunc func(fmt string, args ...interface{})
35
36// LogPrintf defines a log handler which uses log.Printf.
37var LogPrintf LogHandlerFunc = log.Printf
38
39// LogFatalHandler handles the error by logging a fatal error and exiting.
40func LogFatalHandler(err error) {
41	log.Fatal(err)
42}
43
44// PanicHandler handles the error by panicking.
45func PanicHandler(err error) {
46	panic(err)
47}
48
49// -----------------------------------------------------------------------------
50
51// A Properties contains the key/value pairs from the properties input.
52// All values are stored in unexpanded form and are expanded at runtime
53type Properties struct {
54	// Pre-/Postfix for property expansion.
55	Prefix  string
56	Postfix string
57
58	// DisableExpansion controls the expansion of properties on Get()
59	// and the check for circular references on Set(). When set to
60	// true Properties behaves like a simple key/value store and does
61	// not check for circular references on Get() or on Set().
62	DisableExpansion bool
63
64	// Stores the key/value pairs
65	m map[string]string
66
67	// Stores the comments per key.
68	c map[string][]string
69
70	// Stores the keys in order of appearance.
71	k []string
72}
73
74// NewProperties creates a new Properties struct with the default
75// configuration for "${key}" expressions.
76func NewProperties() *Properties {
77	return &Properties{
78		Prefix:  "${",
79		Postfix: "}",
80		m:       map[string]string{},
81		c:       map[string][]string{},
82		k:       []string{},
83	}
84}
85
86// Load reads a buffer into the given Properties struct.
87func (p *Properties) Load(buf []byte, enc Encoding) error {
88	l := &Loader{Encoding: enc, DisableExpansion: p.DisableExpansion}
89	newProperties, err := l.LoadBytes(buf)
90	if err != nil {
91		return err
92	}
93	p.Merge(newProperties)
94	return nil
95}
96
97// Get returns the expanded value for the given key if exists.
98// Otherwise, ok is false.
99func (p *Properties) Get(key string) (value string, ok bool) {
100	v, ok := p.m[key]
101	if p.DisableExpansion {
102		return v, ok
103	}
104	if !ok {
105		return "", false
106	}
107
108	expanded, err := p.expand(key, v)
109
110	// we guarantee that the expanded value is free of
111	// circular references and malformed expressions
112	// so we panic if we still get an error here.
113	if err != nil {
114		ErrorHandler(fmt.Errorf("%s in %q", err, key+" = "+v))
115	}
116
117	return expanded, true
118}
119
120// MustGet returns the expanded value for the given key if exists.
121// Otherwise, it panics.
122func (p *Properties) MustGet(key string) string {
123	if v, ok := p.Get(key); ok {
124		return v
125	}
126	ErrorHandler(invalidKeyError(key))
127	panic("ErrorHandler should exit")
128}
129
130// ----------------------------------------------------------------------------
131
132// ClearComments removes the comments for all keys.
133func (p *Properties) ClearComments() {
134	p.c = map[string][]string{}
135}
136
137// ----------------------------------------------------------------------------
138
139// GetComment returns the last comment before the given key or an empty string.
140func (p *Properties) GetComment(key string) string {
141	comments, ok := p.c[key]
142	if !ok || len(comments) == 0 {
143		return ""
144	}
145	return comments[len(comments)-1]
146}
147
148// ----------------------------------------------------------------------------
149
150// GetComments returns all comments that appeared before the given key or nil.
151func (p *Properties) GetComments(key string) []string {
152	if comments, ok := p.c[key]; ok {
153		return comments
154	}
155	return nil
156}
157
158// ----------------------------------------------------------------------------
159
160// SetComment sets the comment for the key.
161func (p *Properties) SetComment(key, comment string) {
162	p.c[key] = []string{comment}
163}
164
165// ----------------------------------------------------------------------------
166
167// SetComments sets the comments for the key. If the comments are nil then
168// all comments for this key are deleted.
169func (p *Properties) SetComments(key string, comments []string) {
170	if comments == nil {
171		delete(p.c, key)
172		return
173	}
174	p.c[key] = comments
175}
176
177// ----------------------------------------------------------------------------
178
179// GetBool checks if the expanded value is one of '1', 'yes',
180// 'true' or 'on' if the key exists. The comparison is case-insensitive.
181// If the key does not exist the default value is returned.
182func (p *Properties) GetBool(key string, def bool) bool {
183	v, err := p.getBool(key)
184	if err != nil {
185		return def
186	}
187	return v
188}
189
190// MustGetBool checks if the expanded value is one of '1', 'yes',
191// 'true' or 'on' if the key exists. The comparison is case-insensitive.
192// If the key does not exist the function panics.
193func (p *Properties) MustGetBool(key string) bool {
194	v, err := p.getBool(key)
195	if err != nil {
196		ErrorHandler(err)
197	}
198	return v
199}
200
201func (p *Properties) getBool(key string) (value bool, err error) {
202	if v, ok := p.Get(key); ok {
203		return boolVal(v), nil
204	}
205	return false, invalidKeyError(key)
206}
207
208func boolVal(v string) bool {
209	v = strings.ToLower(v)
210	return v == "1" || v == "true" || v == "yes" || v == "on"
211}
212
213// ----------------------------------------------------------------------------
214
215// GetDuration parses the expanded value as an time.Duration (in ns) if the
216// key exists. If key does not exist or the value cannot be parsed the default
217// value is returned. In almost all cases you want to use GetParsedDuration().
218func (p *Properties) GetDuration(key string, def time.Duration) time.Duration {
219	v, err := p.getInt64(key)
220	if err != nil {
221		return def
222	}
223	return time.Duration(v)
224}
225
226// MustGetDuration parses the expanded value as an time.Duration (in ns) if
227// the key exists. If key does not exist or the value cannot be parsed the
228// function panics. In almost all cases you want to use MustGetParsedDuration().
229func (p *Properties) MustGetDuration(key string) time.Duration {
230	v, err := p.getInt64(key)
231	if err != nil {
232		ErrorHandler(err)
233	}
234	return time.Duration(v)
235}
236
237// ----------------------------------------------------------------------------
238
239// GetParsedDuration parses the expanded value with time.ParseDuration() if the key exists.
240// If key does not exist or the value cannot be parsed the default
241// value is returned.
242func (p *Properties) GetParsedDuration(key string, def time.Duration) time.Duration {
243	s, ok := p.Get(key)
244	if !ok {
245		return def
246	}
247	v, err := time.ParseDuration(s)
248	if err != nil {
249		return def
250	}
251	return v
252}
253
254// MustGetParsedDuration parses the expanded value with time.ParseDuration() if the key exists.
255// If key does not exist or the value cannot be parsed the function panics.
256func (p *Properties) MustGetParsedDuration(key string) time.Duration {
257	s, ok := p.Get(key)
258	if !ok {
259		ErrorHandler(invalidKeyError(key))
260	}
261	v, err := time.ParseDuration(s)
262	if err != nil {
263		ErrorHandler(err)
264	}
265	return v
266}
267
268// ----------------------------------------------------------------------------
269
270// GetFloat64 parses the expanded value as a float64 if the key exists.
271// If key does not exist or the value cannot be parsed the default
272// value is returned.
273func (p *Properties) GetFloat64(key string, def float64) float64 {
274	v, err := p.getFloat64(key)
275	if err != nil {
276		return def
277	}
278	return v
279}
280
281// MustGetFloat64 parses the expanded value as a float64 if the key exists.
282// If key does not exist or the value cannot be parsed the function panics.
283func (p *Properties) MustGetFloat64(key string) float64 {
284	v, err := p.getFloat64(key)
285	if err != nil {
286		ErrorHandler(err)
287	}
288	return v
289}
290
291func (p *Properties) getFloat64(key string) (value float64, err error) {
292	if v, ok := p.Get(key); ok {
293		value, err = strconv.ParseFloat(v, 64)
294		if err != nil {
295			return 0, err
296		}
297		return value, nil
298	}
299	return 0, invalidKeyError(key)
300}
301
302// ----------------------------------------------------------------------------
303
304// GetInt parses the expanded value as an int if the key exists.
305// If key does not exist or the value cannot be parsed the default
306// value is returned. If the value does not fit into an int the
307// function panics with an out of range error.
308func (p *Properties) GetInt(key string, def int) int {
309	v, err := p.getInt64(key)
310	if err != nil {
311		return def
312	}
313	return intRangeCheck(key, v)
314}
315
316// MustGetInt parses the expanded value as an int if the key exists.
317// If key does not exist or the value cannot be parsed the function panics.
318// If the value does not fit into an int the function panics with
319// an out of range error.
320func (p *Properties) MustGetInt(key string) int {
321	v, err := p.getInt64(key)
322	if err != nil {
323		ErrorHandler(err)
324	}
325	return intRangeCheck(key, v)
326}
327
328// ----------------------------------------------------------------------------
329
330// GetInt64 parses the expanded value as an int64 if the key exists.
331// If key does not exist or the value cannot be parsed the default
332// value is returned.
333func (p *Properties) GetInt64(key string, def int64) int64 {
334	v, err := p.getInt64(key)
335	if err != nil {
336		return def
337	}
338	return v
339}
340
341// MustGetInt64 parses the expanded value as an int if the key exists.
342// If key does not exist or the value cannot be parsed the function panics.
343func (p *Properties) MustGetInt64(key string) int64 {
344	v, err := p.getInt64(key)
345	if err != nil {
346		ErrorHandler(err)
347	}
348	return v
349}
350
351func (p *Properties) getInt64(key string) (value int64, err error) {
352	if v, ok := p.Get(key); ok {
353		value, err = strconv.ParseInt(v, 10, 64)
354		if err != nil {
355			return 0, err
356		}
357		return value, nil
358	}
359	return 0, invalidKeyError(key)
360}
361
362// ----------------------------------------------------------------------------
363
364// GetUint parses the expanded value as an uint if the key exists.
365// If key does not exist or the value cannot be parsed the default
366// value is returned. If the value does not fit into an int the
367// function panics with an out of range error.
368func (p *Properties) GetUint(key string, def uint) uint {
369	v, err := p.getUint64(key)
370	if err != nil {
371		return def
372	}
373	return uintRangeCheck(key, v)
374}
375
376// MustGetUint parses the expanded value as an int if the key exists.
377// If key does not exist or the value cannot be parsed the function panics.
378// If the value does not fit into an int the function panics with
379// an out of range error.
380func (p *Properties) MustGetUint(key string) uint {
381	v, err := p.getUint64(key)
382	if err != nil {
383		ErrorHandler(err)
384	}
385	return uintRangeCheck(key, v)
386}
387
388// ----------------------------------------------------------------------------
389
390// GetUint64 parses the expanded value as an uint64 if the key exists.
391// If key does not exist or the value cannot be parsed the default
392// value is returned.
393func (p *Properties) GetUint64(key string, def uint64) uint64 {
394	v, err := p.getUint64(key)
395	if err != nil {
396		return def
397	}
398	return v
399}
400
401// MustGetUint64 parses the expanded value as an int if the key exists.
402// If key does not exist or the value cannot be parsed the function panics.
403func (p *Properties) MustGetUint64(key string) uint64 {
404	v, err := p.getUint64(key)
405	if err != nil {
406		ErrorHandler(err)
407	}
408	return v
409}
410
411func (p *Properties) getUint64(key string) (value uint64, err error) {
412	if v, ok := p.Get(key); ok {
413		value, err = strconv.ParseUint(v, 10, 64)
414		if err != nil {
415			return 0, err
416		}
417		return value, nil
418	}
419	return 0, invalidKeyError(key)
420}
421
422// ----------------------------------------------------------------------------
423
424// GetString returns the expanded value for the given key if exists or
425// the default value otherwise.
426func (p *Properties) GetString(key, def string) string {
427	if v, ok := p.Get(key); ok {
428		return v
429	}
430	return def
431}
432
433// MustGetString returns the expanded value for the given key if exists or
434// panics otherwise.
435func (p *Properties) MustGetString(key string) string {
436	if v, ok := p.Get(key); ok {
437		return v
438	}
439	ErrorHandler(invalidKeyError(key))
440	panic("ErrorHandler should exit")
441}
442
443// ----------------------------------------------------------------------------
444
445// Filter returns a new properties object which contains all properties
446// for which the key matches the pattern.
447func (p *Properties) Filter(pattern string) (*Properties, error) {
448	re, err := regexp.Compile(pattern)
449	if err != nil {
450		return nil, err
451	}
452
453	return p.FilterRegexp(re), nil
454}
455
456// FilterRegexp returns a new properties object which contains all properties
457// for which the key matches the regular expression.
458func (p *Properties) FilterRegexp(re *regexp.Regexp) *Properties {
459	pp := NewProperties()
460	for _, k := range p.k {
461		if re.MatchString(k) {
462			// TODO(fs): we are ignoring the error which flags a circular reference.
463			// TODO(fs): since we are just copying a subset of keys this cannot happen (fingers crossed)
464			pp.Set(k, p.m[k])
465		}
466	}
467	return pp
468}
469
470// FilterPrefix returns a new properties object with a subset of all keys
471// with the given prefix.
472func (p *Properties) FilterPrefix(prefix string) *Properties {
473	pp := NewProperties()
474	for _, k := range p.k {
475		if strings.HasPrefix(k, prefix) {
476			// TODO(fs): we are ignoring the error which flags a circular reference.
477			// TODO(fs): since we are just copying a subset of keys this cannot happen (fingers crossed)
478			pp.Set(k, p.m[k])
479		}
480	}
481	return pp
482}
483
484// FilterStripPrefix returns a new properties object with a subset of all keys
485// with the given prefix and the prefix removed from the keys.
486func (p *Properties) FilterStripPrefix(prefix string) *Properties {
487	pp := NewProperties()
488	n := len(prefix)
489	for _, k := range p.k {
490		if len(k) > len(prefix) && strings.HasPrefix(k, prefix) {
491			// TODO(fs): we are ignoring the error which flags a circular reference.
492			// TODO(fs): since we are modifying keys I am not entirely sure whether we can create a circular reference
493			// TODO(fs): this function should probably return an error but the signature is fixed
494			pp.Set(k[n:], p.m[k])
495		}
496	}
497	return pp
498}
499
500// Len returns the number of keys.
501func (p *Properties) Len() int {
502	return len(p.m)
503}
504
505// Keys returns all keys in the same order as in the input.
506func (p *Properties) Keys() []string {
507	keys := make([]string, len(p.k))
508	copy(keys, p.k)
509	return keys
510}
511
512// Set sets the property key to the corresponding value.
513// If a value for key existed before then ok is true and prev
514// contains the previous value. If the value contains a
515// circular reference or a malformed expression then
516// an error is returned.
517// An empty key is silently ignored.
518func (p *Properties) Set(key, value string) (prev string, ok bool, err error) {
519	if key == "" {
520		return "", false, nil
521	}
522
523	// if expansion is disabled we allow circular references
524	if p.DisableExpansion {
525		prev, ok = p.Get(key)
526		p.m[key] = value
527		if !ok {
528			p.k = append(p.k, key)
529		}
530		return prev, ok, nil
531	}
532
533	// to check for a circular reference we temporarily need
534	// to set the new value. If there is an error then revert
535	// to the previous state. Only if all tests are successful
536	// then we add the key to the p.k list.
537	prev, ok = p.Get(key)
538	p.m[key] = value
539
540	// now check for a circular reference
541	_, err = p.expand(key, value)
542	if err != nil {
543
544		// revert to the previous state
545		if ok {
546			p.m[key] = prev
547		} else {
548			delete(p.m, key)
549		}
550
551		return "", false, err
552	}
553
554	if !ok {
555		p.k = append(p.k, key)
556	}
557
558	return prev, ok, nil
559}
560
561// SetValue sets property key to the default string value
562// as defined by fmt.Sprintf("%v").
563func (p *Properties) SetValue(key string, value interface{}) error {
564	_, _, err := p.Set(key, fmt.Sprintf("%v", value))
565	return err
566}
567
568// MustSet sets the property key to the corresponding value.
569// If a value for key existed before then ok is true and prev
570// contains the previous value. An empty key is silently ignored.
571func (p *Properties) MustSet(key, value string) (prev string, ok bool) {
572	prev, ok, err := p.Set(key, value)
573	if err != nil {
574		ErrorHandler(err)
575	}
576	return prev, ok
577}
578
579// String returns a string of all expanded 'key = value' pairs.
580func (p *Properties) String() string {
581	var s string
582	for _, key := range p.k {
583		value, _ := p.Get(key)
584		s = fmt.Sprintf("%s%s = %s\n", s, key, value)
585	}
586	return s
587}
588
589// Write writes all unexpanded 'key = value' pairs to the given writer.
590// Write returns the number of bytes written and any write error encountered.
591func (p *Properties) Write(w io.Writer, enc Encoding) (n int, err error) {
592	return p.WriteComment(w, "", enc)
593}
594
595// WriteComment writes all unexpanced 'key = value' pairs to the given writer.
596// If prefix is not empty then comments are written with a blank line and the
597// given prefix. The prefix should be either "# " or "! " to be compatible with
598// the properties file format. Otherwise, the properties parser will not be
599// able to read the file back in. It returns the number of bytes written and
600// any write error encountered.
601func (p *Properties) WriteComment(w io.Writer, prefix string, enc Encoding) (n int, err error) {
602	var x int
603
604	for _, key := range p.k {
605		value := p.m[key]
606
607		if prefix != "" {
608			if comments, ok := p.c[key]; ok {
609				// don't print comments if they are all empty
610				allEmpty := true
611				for _, c := range comments {
612					if c != "" {
613						allEmpty = false
614						break
615					}
616				}
617
618				if !allEmpty {
619					// add a blank line between entries but not at the top
620					if len(comments) > 0 && n > 0 {
621						x, err = fmt.Fprintln(w)
622						if err != nil {
623							return
624						}
625						n += x
626					}
627
628					for _, c := range comments {
629						x, err = fmt.Fprintf(w, "%s%s\n", prefix, encode(c, "", enc))
630						if err != nil {
631							return
632						}
633						n += x
634					}
635				}
636			}
637		}
638
639		x, err = fmt.Fprintf(w, "%s = %s\n", encode(key, " :", enc), encode(value, "", enc))
640		if err != nil {
641			return
642		}
643		n += x
644	}
645	return
646}
647
648// Map returns a copy of the properties as a map.
649func (p *Properties) Map() map[string]string {
650	m := make(map[string]string)
651	for k, v := range p.m {
652		m[k] = v
653	}
654	return m
655}
656
657// FilterFunc returns a copy of the properties which includes the values which passed all filters.
658func (p *Properties) FilterFunc(filters ...func(k, v string) bool) *Properties {
659	pp := NewProperties()
660outer:
661	for k, v := range p.m {
662		for _, f := range filters {
663			if !f(k, v) {
664				continue outer
665			}
666			pp.Set(k, v)
667		}
668	}
669	return pp
670}
671
672// ----------------------------------------------------------------------------
673
674// Delete removes the key and its comments.
675func (p *Properties) Delete(key string) {
676	delete(p.m, key)
677	delete(p.c, key)
678	newKeys := []string{}
679	for _, k := range p.k {
680		if k != key {
681			newKeys = append(newKeys, k)
682		}
683	}
684	p.k = newKeys
685}
686
687// Merge merges properties, comments and keys from other *Properties into p
688func (p *Properties) Merge(other *Properties) {
689	for k, v := range other.m {
690		p.m[k] = v
691	}
692	for k, v := range other.c {
693		p.c[k] = v
694	}
695
696outer:
697	for _, otherKey := range other.k {
698		for _, key := range p.k {
699			if otherKey == key {
700				continue outer
701			}
702		}
703		p.k = append(p.k, otherKey)
704	}
705}
706
707// ----------------------------------------------------------------------------
708
709// check expands all values and returns an error if a circular reference or
710// a malformed expression was found.
711func (p *Properties) check() error {
712	for key, value := range p.m {
713		if _, err := p.expand(key, value); err != nil {
714			return err
715		}
716	}
717	return nil
718}
719
720func (p *Properties) expand(key, input string) (string, error) {
721	// no pre/postfix -> nothing to expand
722	if p.Prefix == "" && p.Postfix == "" {
723		return input, nil
724	}
725
726	return expand(input, []string{key}, p.Prefix, p.Postfix, p.m)
727}
728
729// expand recursively expands expressions of '(prefix)key(postfix)' to their corresponding values.
730// The function keeps track of the keys that were already expanded and stops if it
731// detects a circular reference or a malformed expression of the form '(prefix)key'.
732func expand(s string, keys []string, prefix, postfix string, values map[string]string) (string, error) {
733	if len(keys) > maxExpansionDepth {
734		return "", fmt.Errorf("expansion too deep")
735	}
736
737	for {
738		start := strings.Index(s, prefix)
739		if start == -1 {
740			return s, nil
741		}
742
743		keyStart := start + len(prefix)
744		keyLen := strings.Index(s[keyStart:], postfix)
745		if keyLen == -1 {
746			return "", fmt.Errorf("malformed expression")
747		}
748
749		end := keyStart + keyLen + len(postfix) - 1
750		key := s[keyStart : keyStart+keyLen]
751
752		// fmt.Printf("s:%q pp:%q start:%d end:%d keyStart:%d keyLen:%d key:%q\n", s, prefix + "..." + postfix, start, end, keyStart, keyLen, key)
753
754		for _, k := range keys {
755			if key == k {
756				return "", fmt.Errorf("circular reference")
757			}
758		}
759
760		val, ok := values[key]
761		if !ok {
762			val = os.Getenv(key)
763		}
764		new_val, err := expand(val, append(keys, key), prefix, postfix, values)
765		if err != nil {
766			return "", err
767		}
768		s = s[:start] + new_val + s[end+1:]
769	}
770	return s, nil
771}
772
773// encode encodes a UTF-8 string to ISO-8859-1 and escapes some characters.
774func encode(s string, special string, enc Encoding) string {
775	switch enc {
776	case UTF8:
777		return encodeUtf8(s, special)
778	case ISO_8859_1:
779		return encodeIso(s, special)
780	default:
781		panic(fmt.Sprintf("unsupported encoding %v", enc))
782	}
783}
784
785func encodeUtf8(s string, special string) string {
786	v := ""
787	for pos := 0; pos < len(s); {
788		r, w := utf8.DecodeRuneInString(s[pos:])
789		pos += w
790		v += escape(r, special)
791	}
792	return v
793}
794
795func encodeIso(s string, special string) string {
796	var r rune
797	var w int
798	var v string
799	for pos := 0; pos < len(s); {
800		switch r, w = utf8.DecodeRuneInString(s[pos:]); {
801		case r < 1<<8: // single byte rune -> escape special chars only
802			v += escape(r, special)
803		case r < 1<<16: // two byte rune -> unicode literal
804			v += fmt.Sprintf("\\u%04x", r)
805		default: // more than two bytes per rune -> can't encode
806			v += "?"
807		}
808		pos += w
809	}
810	return v
811}
812
813func escape(r rune, special string) string {
814	switch r {
815	case '\f':
816		return "\\f"
817	case '\n':
818		return "\\n"
819	case '\r':
820		return "\\r"
821	case '\t':
822		return "\\t"
823	default:
824		if strings.ContainsRune(special, r) {
825			return "\\" + string(r)
826		}
827		return string(r)
828	}
829}
830
831func invalidKeyError(key string) error {
832	return fmt.Errorf("unknown property: %s", key)
833}
834