view pkg/models/common.go @ 5377:d19fdf3d2099 extented-report

Add a string type that allows only runes that are safe of directory traversal.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Thu, 24 Jun 2021 22:13:48 +0200
parents 4847ac70103a
children 1222b777f51f
line wrap: on
line source

// This is Free Software under GNU Affero General Public License v >= 3.0
// without warranty, see README.md and license for details.
//
// SPDX-License-Identifier: AGPL-3.0-or-later
// License-Filename: LICENSES/AGPL-3.0.txt
//
// Copyright (C) 2018 by via donau
//   – Österreichische Wasserstraßen-Gesellschaft mbH
// Software engineering by Intevation GmbH
//
// Author(s):
//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>

package models

import (
	"database/sql/driver"
	"encoding/json"
	"errors"
	"fmt"
	"regexp"
	"strings"
	"time"

	"gemma.intevation.de/gemma/pkg/common"
)

var (
	errNoString    = errors.New("not a string")
	errNoByteSlice = errors.New("not a byte slice")
)

// WGS84 is the EPSG of the World Geodetic System 1984.
const WGS84 = 4326

type (
	Date struct{ time.Time }
	Time struct{ time.Time }

	// Country is a valid country 2 letter code.
	Country string
	// UniqueCountries is a list of unique countries.
	UniqueCountries []Country

	// SafePath should only contain chars that directory traversal safe.
	SafePath string
)

func (d Date) MarshalJSON() ([]byte, error) {
	return json.Marshal(d.Format(common.DateFormat))
}

func (d *Date) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}
	d2, err := time.Parse(common.DateFormat, s)
	if err == nil {
		*d = Date{d2}
	}
	return err
}

func (t Time) MarshalJSON() ([]byte, error) {
	return json.Marshal(t.Format(common.TimeFormat))
}

func (t *Time) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}
	t2, err := time.Parse(common.TimeFormat, s)
	if err == nil {
		*t = Time{t2}
	}
	return err
}

var (
	validCountries = []string{
		"AT", "BG", "DE", "HU", "HR",
		"MD", "RO", "RS", "SK", "UA",
	}
)

// Valid checks if the given country is a known one.
func (c Country) Valid() bool {
	for _, v := range validCountries {
		if string(c) == v {
			return true
		}
	}
	return false
}

// UnmarshalJSON ensures that the given string forms a valid
// two letter country code.
func (c *Country) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}
	u := strings.ToUpper(s)
	for _, v := range validCountries {
		if v == u {
			*c = Country(v)
			return nil
		}
	}
	return fmt.Errorf("'%s' is not a valid country", s)
}

// Value implements the driver.Valuer interface.
func (c Country) Value() (driver.Value, error) {
	return string(c), nil
}

// Scan implements the sql.Scanner interfaces.
func (c *Country) Scan(src interface{}) (err error) {
	if s, ok := src.(string); ok {
		*c = Country(s)
	} else {
		err = errNoString
	}
	return
}

func (uc *UniqueCountries) UnmarshalJSON(data []byte) error {
	var countries []Country
	if err := json.Unmarshal(data, &countries); err != nil {
		return err
	}
	unique := map[Country]struct{}{}
	for _, c := range countries {
		if _, found := unique[c]; found {
			return fmt.Errorf("country '%s' is not unique", string(c))
		}
		unique[c] = struct{}{}
	}
	*uc = countries
	return nil
}

func (uc UniqueCountries) String() string {
	var b strings.Builder
	for i, c := range uc {
		if i > 0 {
			b.WriteString(", ")
		}
		b.WriteString(string(c))
	}
	return b.String()
}

const SafePathExp = "[a-zA-Z0-9_-]+"

var safePathRegExp = regexp.MustCompile("^" + SafePathExp + "$")

func (sp SafePath) Valid() bool {
	return safePathRegExp.MatchString(string(sp))
}

// UnmarshalJSON ensures that the given string only consist
// of runes that are directory traversal safe.
func (sp *SafePath) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}
	if c := SafePath(s); c.Valid() {
		*sp = c
		return nil
	}
	return fmt.Errorf("'%s' is not a safe path", s)
}