mirror of
https://github.com/diamondburned/cchat-gtk.git
synced 2024-12-23 20:56:42 +00:00
110 lines
2.4 KiB
Go
110 lines
2.4 KiB
Go
package json
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
var ErrUnsafePermission = errors.New("secrets.json file has unsafe permission")
|
|
|
|
type Provider struct {
|
|
dir string
|
|
}
|
|
|
|
func NewProvider(dir string) Provider {
|
|
return Provider{dir}
|
|
}
|
|
|
|
func (p Provider) open(service string, write bool) (*os.File, error) {
|
|
var flags int
|
|
if write {
|
|
// If file does not exist, then create a new one. Else, erase the file.
|
|
// The file can only be written onto.
|
|
flags = os.O_CREATE | os.O_WRONLY | os.O_TRUNC
|
|
} else {
|
|
// If file does not exist, then error out. The file can only be read
|
|
// from.
|
|
flags = os.O_RDONLY
|
|
}
|
|
|
|
// Make a filename using the given service.
|
|
var filename = fmt.Sprintf("%s_secret.json", SanitizeName(service))
|
|
|
|
f, err := os.OpenFile(filepath.Join(p.dir, filename), flags, 0600)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Failed to open file")
|
|
}
|
|
|
|
// Stat the file and verify that the permissions are safe.
|
|
s, err := f.Stat()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Failed to stat file")
|
|
}
|
|
if m := s.Mode(); m != 0600 {
|
|
return nil, fmt.Errorf("%s file has unsafe permission %04o", filename, m)
|
|
}
|
|
|
|
return f, nil
|
|
}
|
|
|
|
// Get unmarshals the service from the JSON secret file. It errors out if the
|
|
// secret file does not exist.
|
|
func (p Provider) Get(service string, v interface{}) error {
|
|
f, err := p.open(service, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
if err := json.NewDecoder(f).Decode(v); err != nil {
|
|
return errors.Wrap(err, "Failed to decode JSON")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Set writes the service's data into a JSON secret file.
|
|
func (p Provider) Set(service string, v interface{}) error {
|
|
f, err := p.open(service, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
// Create a new formatted encoder.
|
|
enc := json.NewEncoder(f)
|
|
enc.SetIndent("", "\n")
|
|
|
|
// Encode using created encoder.
|
|
if err := enc.Encode(v); err != nil {
|
|
return errors.Wrap(err, "Failed to encode JSON")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SanitizeName sanitizes the name so that it's safe to use as a filename.
|
|
func SanitizeName(name string) string {
|
|
// Escape all weird characters in a filename.
|
|
name = strings.Map(underscoreNonAlphanum, name)
|
|
|
|
// Lower-case everything.
|
|
name = strings.ToLower(name)
|
|
|
|
return name
|
|
}
|
|
|
|
func underscoreNonAlphanum(r rune) rune {
|
|
// especially does not cover slashes
|
|
if unicode.IsLetter(r) || unicode.IsNumber(r) {
|
|
return r
|
|
}
|
|
return '_'
|
|
}
|