<commit_before>package wsl

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strings"
	"time"
)

type WSL struct {
	Config *Config
	db     *sql.DB
}

func New(confFile string) (*WSL, error) {
	config, err := NewConfig(confFile)
	if err != nil {
		return nil, err
	}
	return &WSL{
		Config: config,
	}, nil
}

func (this *WSL) connectToDb() error {
	if this.db == nil {
		db, err := sql.Open(this.Config.DbType, this.Config.DbUrl)
		if err != nil {
			return err
		}
		this.db = db
	}
	return nil
}

func (this *WSL) Start() {
	err := this.connectToDb()
	if err != nil {
		log.Println(err)
		return
	}

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		if this.Config.Cors {
			w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
			w.Header().Set("Access-Control-Allow-Credentials", "true")
			w.Header().Set("Access-Control-Allow-Methods", r.Header.Get("Access-Control-Request-Method"))
			w.Header().Set("Access-Control-Allow-Headers", r.Header.Get("Access-Control-Request-Headers"))
		}

		urlPath := strings.Split(r.URL.Path, "/")
		if len(urlPath) < 2 {
			log.Println("Invalid URL.")
			return
		}
		qID := urlPath[1]

		if script, ok := this.Config.Scripts[qID]; ok {
			sepIndex := strings.LastIndex(r.RemoteAddr, ":")
			clientIp := r.RemoteAddr[0:sepIndex]
			clientIp = strings.Replace(strings.Replace(clientIp, "[", "", -1), "]", "", -1)

			r.ParseForm()

			params := valuesToMap(&r.Form)

			params["__client_ip"] = clientIp

			result, err := this.exec(this.db, script, params)
			if err != nil {
				log.Println(err)
				return
			}
			jsonData, err := json.Marshal(result)
			jsonString := string(jsonData)
			w.Header().Set("Content-Type", "application/json; charset=utf-8")
			fmt.Fprint(w, jsonString)
		} else {
			log.Println("Invalid script:", qID)
			return
		}
	})

	if this.Config.httpEnabled() {
		srv := &http.Server{
			Addr:         this.Config.HttpAddr,
			WriteTimeout: 15 * time.Second,
			ReadTimeout:  15 * time.Second,
		}
		go func() {
			fmt.Println(fmt.Sprint("Listening on http://", this.Config.HttpAddr, "/"))
			log.Fatal(srv.ListenAndServe())
		}()
	}

	if this.Config.httpsEnabled() {
		srvs := &http.Server{
			Addr:         this.Config.HttpsAddr,
			WriteTimeout: 15 * time.Second,
			ReadTimeout:  15 * time.Second,
		}
		go func() {
			fmt.Println(fmt.Sprint("Listening on https://", this.Config.HttpsAddr, "/"))
			log.Fatal(srvs.ListenAndServeTLS(this.Config.CertFile, this.Config.KeyFile))
		}()
	}
}
<commit_msg>Update.<commit_after>package wsl

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strings"
	"time"
)

type WSL struct {
	Config *Config
	db     *sql.DB
}

func New(confFile string) (*WSL, error) {
	config, err := NewConfig(confFile)
	if err != nil {
		return nil, err
	}
	return &WSL{
		Config: config,
	}, nil
}

func (this *WSL) connectToDb() error {
	if this.db == nil {
		db, err := sql.Open(this.Config.DbType, this.Config.DbUrl)
		if err != nil {
			return err
		}
		this.db = db
	}
	return nil
}

func (this *WSL) Start() {
	err := this.connectToDb()
	if err != nil {
		log.Println(err)
		return
	}

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		if this.Config.Cors {
			w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
			w.Header().Set("Access-Control-Allow-Credentials", "true")
			w.Header().Set("Access-Control-Allow-Methods", r.Header.Get("Access-Control-Request-Method"))
			w.Header().Set("Access-Control-Allow-Headers", r.Header.Get("Access-Control-Request-Headers"))
		}

		urlPath := strings.Split(r.URL.Path, "/")
		if len(urlPath) < 2 {
			log.Println("Invalid URL.")
			return
		}
		qID := urlPath[1]

		if script, ok := this.Config.Scripts[qID]; ok {
			sepIndex := strings.LastIndex(r.RemoteAddr, ":")
			clientIp := r.RemoteAddr[0:sepIndex]
			clientIp = strings.Replace(strings.Replace(clientIp, "[", "", -1), "]", "", -1)

			r.ParseForm()
			params := valuesToMap(&r.Form)

			params["__client_ip"] = clientIp

			result, err := this.exec(this.db, script, params)
			if err != nil {
				log.Println(err)
				return
			}
			jsonData, err := json.Marshal(result)
			jsonString := string(jsonData)
			w.Header().Set("Content-Type", "application/json; charset=utf-8")
			fmt.Fprint(w, jsonString)
		} else {
			log.Println("Invalid script:", qID)
			return
		}
	})

	if this.Config.httpEnabled() {
		srv := &http.Server{
			Addr:         this.Config.HttpAddr,
			WriteTimeout: 15 * time.Second,
			ReadTimeout:  15 * time.Second,
		}
		go func() {
			fmt.Println(fmt.Sprint("Listening on http://", this.Config.HttpAddr, "/"))
			log.Fatal(srv.ListenAndServe())
		}()
	}

	if this.Config.httpsEnabled() {
		srvs := &http.Server{
			Addr:         this.Config.HttpsAddr,
			WriteTimeout: 15 * time.Second,
			ReadTimeout:  15 * time.Second,
		}
		go func() {
			fmt.Println(fmt.Sprint("Listening on https://", this.Config.HttpsAddr, "/"))
			log.Fatal(srvs.ListenAndServeTLS(this.Config.CertFile, this.Config.KeyFile))
		}()
	}
}
<|endoftext|>


<commit_before>package gokeepasslib

import (
	"crypto/rand"
	"encoding/base64"
	"encoding/xml"
	"errors"

	w "github.com/tobischo/gokeepasslib/wrappers"
)

// MetaData is the structure for the metadata headers at the top of kdbx files,
// it contains things like the name of the database
type MetaData struct {
	Generator                  string         `xml:"Generator"`
	HeaderHash                 string         `xml:"HeaderHash"`
	DatabaseName               string         `xml:"DatabaseName"`
	DatabaseNameChanged        *w.TimeWrapper `xml:"DatabaseNameChanged"`
	DatabaseDescription        string         `xml:"DatabaseDescription"`
	DatabaseDescriptionChanged *w.TimeWrapper `xml:"DatabaseDescriptionChanged"`
	DefaultUserName            string         `xml:"DefaultUserName"`
	DefaultUserNameChanged     *w.TimeWrapper `xml:"DefaultUserNameChanged"`
	MaintenanceHistoryDays     int64          `xml:"MaintenanceHistoryDays"`
	Color                      string         `xml:"Color"`
	MasterKeyChanged           *w.TimeWrapper `xml:"MasterKeyChanged"`
	MasterKeyChangeRec         int64          `xml:"MasterKeyChangeRec"`
	MasterKeyChangeForce       int64          `xml:"MasterKeyChangeForce"`
	MemoryProtection           MemProtection  `xml:"MemoryProtection"`
	RecycleBinEnabled          w.BoolWrapper  `xml:"RecycleBinEnabled"`
	RecycleBinUUID             UUID           `xml:"RecycleBinUUID"`
	RecycleBinChanged          *w.TimeWrapper `xml:"RecycleBinChanged"`
	EntryTemplatesGroup        string         `xml:"EntryTemplatesGroup"`
	EntryTemplatesGroupChanged *w.TimeWrapper `xml:"EntryTemplatesGroupChanged"`
	HistoryMaxItems            int64          `xml:"HistoryMaxItems"`
	HistoryMaxSize             int64          `xml:"HistoryMaxSize"`
	LastSelectedGroup          string         `xml:"LastSelectedGroup"`
	LastTopVisibleGroup        string         `xml:"LastTopVisibleGroup"`
	Binaries                   Binaries       `xml:"Binaries>Binary"`
	CustomData                 []CustomData   `xml:"CustomData>Item"`
}

// MemProtection is a structure containing settings for MemoryProtection
type MemProtection struct {
	ProtectTitle    w.BoolWrapper `xml:"ProtectTitle"`
	ProtectUserName w.BoolWrapper `xml:"ProtectUserName"`
	ProtectPassword w.BoolWrapper `xml:"ProtectPassword"`
	ProtectURL      w.BoolWrapper `xml:"ProtectURL"`
	ProtectNotes    w.BoolWrapper `xml:"ProtectNotes"`
}

// RootData stores the actual content of a database (all enteries sorted into groups and the recycle bin)
type RootData struct {
	Groups         []Group             `xml:"Group"`
	DeletedObjects []DeletedObjectData `xml:"DeletedObjects>DeletedObject"`
}

// Group is a structure to store entries in their named groups for organization
type Group struct {
	UUID                    UUID          `xml:"UUID"`
	Name                    string        `xml:"Name"`
	Notes                   string        `xml:"Notes"`
	IconID                  int64         `xml:"IconID"`
	Times                   TimeData      `xml:"Times"`
	IsExpanded              w.BoolWrapper `xml:"IsExpanded"`
	DefaultAutoTypeSequence string        `xml:"DefaultAutoTypeSequence"`
	EnableAutoType          w.BoolWrapper `xml:"EnableAutoType"`
	EnableSearching         w.BoolWrapper `xml:"EnableSearching"`
	LastTopVisibleEntry     string        `xml:"LastTopVisibleEntry"`
	Entries                 []Entry       `xml:"Entry,omitempty"`
	Groups                  []Group       `xml:"Group,omitempty"`
}

// UUID stores a universal identifier for each group+entry
type UUID [16]byte

// TimeData contains all metadata related to times for groups and entries
// e.g. the last modification time or the creation time
type TimeData struct {
	CreationTime         *w.TimeWrapper `xml:"CreationTime"`
	LastModificationTime *w.TimeWrapper `xml:"LastModificationTime"`
	LastAccessTime       *w.TimeWrapper `xml:"LastAccessTime"`
	ExpiryTime           *w.TimeWrapper `xml:"ExpiryTime"`
	Expires              w.BoolWrapper  `xml:"Expires"`
	UsageCount           int64          `xml:"UsageCount"`
	LocationChanged      *w.TimeWrapper `xml:"LocationChanged"`
}

// Entry is the structure which holds information about a parsed entry in a keepass database
type Entry struct {
	UUID            UUID              `xml:"UUID"`
	IconID          int64             `xml:"IconID"`
	ForegroundColor string            `xml:"ForegroundColor"`
	BackgroundColor string            `xml:"BackgroundColor"`
	OverrideURL     string            `xml:"OverrideURL"`
	Tags            string            `xml:"Tags"`
	Times           TimeData          `xml:"Times"`
	Values          []ValueData       `xml:"String,omitempty"`
	AutoType        AutoTypeData      `xml:"AutoType"`
	Histories       []History         `xml:"History"`
	Binaries        []BinaryReference `xml:"Binary,omitempty"`
}

// History stores information about changes made to an entry,
// in the form of a list of previous versions of that entry
type History struct {
	Entries []Entry `xml:"Entry"`
}

// ValueData is a structure containing key value pairs of information stored in an entry
type ValueData struct {
	Key   string `xml:"Key"`
	Value V      `xml:"Value"`
}

// V is a wrapper for the content of a value, so that it can store whether it is protected
type V struct {
	Content   string        `xml:",chardata"`
	Protected w.BoolWrapper `xml:"Protected,attr,omitempty"`
}

// AutoTypeData is a structure containing auto type settings of an entry
type AutoTypeData struct {
	Enabled                 w.BoolWrapper        `xml:"Enabled"`
	DataTransferObfuscation int64                `xml:"DataTransferObfuscation"`
	Association             *AutoTypeAssociation `xml:"Association,omitempty"`
}

// AutoTypeAssociation is a structure that store the keystroke sequence of a window for AutoTypeData
type AutoTypeAssociation struct {
	Window            string `xml:"Window"`
	KeystrokeSequence string `xml:"KeystrokeSequence"`
}

// DeletedObjectData is the structure for a deleted object
type DeletedObjectData struct {
	XMLName      xml.Name       `xml:"DeletedObject"`
	UUID         UUID           `xml:"UUID"`
	DeletionTime *w.TimeWrapper `xml:"DeletionTime"`
}

// CustomData is the structure for plugins custom data
type CustomData struct {
	XMLName xml.Name `xml:"Item"`
	Key     string   `xml:"Key"`
	Value   string   `xml:"Value"`
}

// NewMetaData creates a MetaData struct with some defaults set
func NewMetaData() *MetaData {
	now := w.Now()

	return &MetaData{
		MasterKeyChanged:       &now,
		MasterKeyChangeRec:     -1,
		MasterKeyChangeForce:   -1,
		HistoryMaxItems:        10,
		HistoryMaxSize:         6291456, // 6 MB
		MaintenanceHistoryDays: 365,
	}
}

// NewRootData returns a RootData struct with good defaults
func NewRootData() *RootData {
	root := new(RootData)
	group := NewGroup()
	group.Name = "NewDatabase"
	entry := NewEntry()
	entry.Values = append(entry.Values, ValueData{Key: "Title", Value: V{Content: "Sample Entry"}})
	group.Entries = append(group.Entries, entry)
	root.Groups = append(root.Groups, group)
	return root
}

// NewUUID returns a new randomly generated UUID
func NewUUID() UUID {
	var id UUID
	rand.Read(id[:])
	return id
}

// NewGroup returns a new group with time data and uuid set
func NewGroup() Group {
	return Group{
		EnableAutoType:  w.BoolWrapper(true),
		EnableSearching: w.BoolWrapper(true),
		Times:           NewTimeData(),
		UUID:            NewUUID(),
	}
}

// NewTimeData returns a TimeData struct with good defaults (no expire time, all times set to now)
func NewTimeData() TimeData {
	now := w.Now()
	return TimeData{
		CreationTime:         &now,
		LastModificationTime: &now,
		LastAccessTime:       &now,
		LocationChanged:      &now,
		Expires:              false,
		UsageCount:           0,
	}
}

// NewEntry return a new entry with time data and uuid set
func NewEntry() Entry {
	e := Entry{}
	e.Times = NewTimeData()
	e.UUID = NewUUID()
	return e
}

// MarshalText is a marshaler method to encode uuid content as base 64 and return it
func (u UUID) MarshalText() ([]byte, error) {
	text := make([]byte, 24)
	base64.StdEncoding.Encode(text, u[:])
	return text, nil
}

// UnmarshalText unmarshals a byte slice into a UUID by decoding the given data from base64
func (u *UUID) UnmarshalText(text []byte) error {
	id := make([]byte, base64.StdEncoding.DecodedLen(len(text)))
	length, err := base64.StdEncoding.Decode(id, text)
	if err != nil {
		return err
	}
	if length != 16 {
		return ErrInvalidUUIDLength
	}
	copy((*u)[:], id[:16])
	return nil
}

// Compare allowes to check whether two instance of UUID are equal in value.
// This is used for searching a uuid
func (u UUID) Compare(c UUID) bool {
	for i, v := range c {
		if u[i] != v {
			return false
		}
	}
	return true
}

// Returns true if the e's password has in-memory protection
func (e *Entry) protected() bool {
	for _, v := range e.Values {
		if v.Key == "Password" && bool(v.Value.Protected) {
			return true
		}
	}
	return false
}

// Get returns the value in e corresponding with key k, or an empty string otherwise
func (e *Entry) Get(key string) *ValueData {
	for i := range e.Values {
		if e.Values[i].Key == key {
			return &e.Values[i]
		}
	}
	return nil
}

// GetContent returns the content of the value belonging to the given key in string form
func (e *Entry) GetContent(key string) string {
	val := e.Get(key)
	if val == nil {
		return ""
	}
	return val.Value.Content
}

// GetIndex returns the index of the Value belonging to the given key, or -1 if none is found
func (e *Entry) GetIndex(key string) int {
	for i := range e.Values {
		if e.Values[i].Key == key {
			return i
		}
	}
	return -1
}

// GetPassword returns the password of an entry
func (e *Entry) GetPassword() string {
	return e.GetContent("Password")
}

// GetPasswordIndex returns the index in the values slice belonging to the password
func (e *Entry) GetPasswordIndex() int {
	return e.GetIndex("Password")
}

// GetTitle returns the title of an entry
func (e *Entry) GetTitle() string {
	return e.GetContent("Title")
}

// ErrInvalidUUIDLength is an error which is returned during unmarshaling if the UUID does not have 16 bytes length
var ErrInvalidUUIDLength = errors.New("gokeepasslib: length of decoded UUID was not 16")
<commit_msg>Removed unused e.protected() func<commit_after>package gokeepasslib

import (
	"crypto/rand"
	"encoding/base64"
	"encoding/xml"
	"errors"

	w "github.com/tobischo/gokeepasslib/wrappers"
)

// MetaData is the structure for the metadata headers at the top of kdbx files,
// it contains things like the name of the database
type MetaData struct {
	Generator                  string         `xml:"Generator"`
	HeaderHash                 string         `xml:"HeaderHash"`
	DatabaseName               string         `xml:"DatabaseName"`
	DatabaseNameChanged        *w.TimeWrapper `xml:"DatabaseNameChanged"`
	DatabaseDescription        string         `xml:"DatabaseDescription"`
	DatabaseDescriptionChanged *w.TimeWrapper `xml:"DatabaseDescriptionChanged"`
	DefaultUserName            string         `xml:"DefaultUserName"`
	DefaultUserNameChanged     *w.TimeWrapper `xml:"DefaultUserNameChanged"`
	MaintenanceHistoryDays     int64          `xml:"MaintenanceHistoryDays"`
	Color                      string         `xml:"Color"`
	MasterKeyChanged           *w.TimeWrapper `xml:"MasterKeyChanged"`
	MasterKeyChangeRec         int64          `xml:"MasterKeyChangeRec"`
	MasterKeyChangeForce       int64          `xml:"MasterKeyChangeForce"`
	MemoryProtection           MemProtection  `xml:"MemoryProtection"`
	RecycleBinEnabled          w.BoolWrapper  `xml:"RecycleBinEnabled"`
	RecycleBinUUID             UUID           `xml:"RecycleBinUUID"`
	RecycleBinChanged          *w.TimeWrapper `xml:"RecycleBinChanged"`
	EntryTemplatesGroup        string         `xml:"EntryTemplatesGroup"`
	EntryTemplatesGroupChanged *w.TimeWrapper `xml:"EntryTemplatesGroupChanged"`
	HistoryMaxItems            int64          `xml:"HistoryMaxItems"`
	HistoryMaxSize             int64          `xml:"HistoryMaxSize"`
	LastSelectedGroup          string         `xml:"LastSelectedGroup"`
	LastTopVisibleGroup        string         `xml:"LastTopVisibleGroup"`
	Binaries                   Binaries       `xml:"Binaries>Binary"`
	CustomData                 []CustomData   `xml:"CustomData>Item"`
}

// MemProtection is a structure containing settings for MemoryProtection
type MemProtection struct {
	ProtectTitle    w.BoolWrapper `xml:"ProtectTitle"`
	ProtectUserName w.BoolWrapper `xml:"ProtectUserName"`
	ProtectPassword w.BoolWrapper `xml:"ProtectPassword"`
	ProtectURL      w.BoolWrapper `xml:"ProtectURL"`
	ProtectNotes    w.BoolWrapper `xml:"ProtectNotes"`
}

// RootData stores the actual content of a database (all enteries sorted into groups and the recycle bin)
type RootData struct {
	Groups         []Group             `xml:"Group"`
	DeletedObjects []DeletedObjectData `xml:"DeletedObjects>DeletedObject"`
}

// Group is a structure to store entries in their named groups for organization
type Group struct {
	UUID                    UUID          `xml:"UUID"`
	Name                    string        `xml:"Name"`
	Notes                   string        `xml:"Notes"`
	IconID                  int64         `xml:"IconID"`
	Times                   TimeData      `xml:"Times"`
	IsExpanded              w.BoolWrapper `xml:"IsExpanded"`
	DefaultAutoTypeSequence string        `xml:"DefaultAutoTypeSequence"`
	EnableAutoType          w.BoolWrapper `xml:"EnableAutoType"`
	EnableSearching         w.BoolWrapper `xml:"EnableSearching"`
	LastTopVisibleEntry     string        `xml:"LastTopVisibleEntry"`
	Entries                 []Entry       `xml:"Entry,omitempty"`
	Groups                  []Group       `xml:"Group,omitempty"`
}

// UUID stores a universal identifier for each group+entry
type UUID [16]byte

// TimeData contains all metadata related to times for groups and entries
// e.g. the last modification time or the creation time
type TimeData struct {
	CreationTime         *w.TimeWrapper `xml:"CreationTime"`
	LastModificationTime *w.TimeWrapper `xml:"LastModificationTime"`
	LastAccessTime       *w.TimeWrapper `xml:"LastAccessTime"`
	ExpiryTime           *w.TimeWrapper `xml:"ExpiryTime"`
	Expires              w.BoolWrapper  `xml:"Expires"`
	UsageCount           int64          `xml:"UsageCount"`
	LocationChanged      *w.TimeWrapper `xml:"LocationChanged"`
}

// Entry is the structure which holds information about a parsed entry in a keepass database
type Entry struct {
	UUID            UUID              `xml:"UUID"`
	IconID          int64             `xml:"IconID"`
	ForegroundColor string            `xml:"ForegroundColor"`
	BackgroundColor string            `xml:"BackgroundColor"`
	OverrideURL     string            `xml:"OverrideURL"`
	Tags            string            `xml:"Tags"`
	Times           TimeData          `xml:"Times"`
	Values          []ValueData       `xml:"String,omitempty"`
	AutoType        AutoTypeData      `xml:"AutoType"`
	Histories       []History         `xml:"History"`
	Binaries        []BinaryReference `xml:"Binary,omitempty"`
}

// History stores information about changes made to an entry,
// in the form of a list of previous versions of that entry
type History struct {
	Entries []Entry `xml:"Entry"`
}

// ValueData is a structure containing key value pairs of information stored in an entry
type ValueData struct {
	Key   string `xml:"Key"`
	Value V      `xml:"Value"`
}

// V is a wrapper for the content of a value, so that it can store whether it is protected
type V struct {
	Content   string        `xml:",chardata"`
	Protected w.BoolWrapper `xml:"Protected,attr,omitempty"`
}

// AutoTypeData is a structure containing auto type settings of an entry
type AutoTypeData struct {
	Enabled                 w.BoolWrapper        `xml:"Enabled"`
	DataTransferObfuscation int64                `xml:"DataTransferObfuscation"`
	Association             *AutoTypeAssociation `xml:"Association,omitempty"`
}

// AutoTypeAssociation is a structure that store the keystroke sequence of a window for AutoTypeData
type AutoTypeAssociation struct {
	Window            string `xml:"Window"`
	KeystrokeSequence string `xml:"KeystrokeSequence"`
}

// DeletedObjectData is the structure for a deleted object
type DeletedObjectData struct {
	XMLName      xml.Name       `xml:"DeletedObject"`
	UUID         UUID           `xml:"UUID"`
	DeletionTime *w.TimeWrapper `xml:"DeletionTime"`
}

// CustomData is the structure for plugins custom data
type CustomData struct {
	XMLName xml.Name `xml:"Item"`
	Key     string   `xml:"Key"`
	Value   string   `xml:"Value"`
}

// NewMetaData creates a MetaData struct with some defaults set
func NewMetaData() *MetaData {
	now := w.Now()

	return &MetaData{
		MasterKeyChanged:       &now,
		MasterKeyChangeRec:     -1,
		MasterKeyChangeForce:   -1,
		HistoryMaxItems:        10,
		HistoryMaxSize:         6291456, // 6 MB
		MaintenanceHistoryDays: 365,
	}
}

// NewRootData returns a RootData struct with good defaults
func NewRootData() *RootData {
	root := new(RootData)
	group := NewGroup()
	group.Name = "NewDatabase"
	entry := NewEntry()
	entry.Values = append(entry.Values, ValueData{Key: "Title", Value: V{Content: "Sample Entry"}})
	group.Entries = append(group.Entries, entry)
	root.Groups = append(root.Groups, group)
	return root
}

// NewUUID returns a new randomly generated UUID
func NewUUID() UUID {
	var id UUID
	rand.Read(id[:])
	return id
}

// NewGroup returns a new group with time data and uuid set
func NewGroup() Group {
	return Group{
		EnableAutoType:  w.BoolWrapper(true),
		EnableSearching: w.BoolWrapper(true),
		Times:           NewTimeData(),
		UUID:            NewUUID(),
	}
}

// NewTimeData returns a TimeData struct with good defaults (no expire time, all times set to now)
func NewTimeData() TimeData {
	now := w.Now()
	return TimeData{
		CreationTime:         &now,
		LastModificationTime: &now,
		LastAccessTime:       &now,
		LocationChanged:      &now,
		Expires:              false,
		UsageCount:           0,
	}
}

// NewEntry return a new entry with time data and uuid set
func NewEntry() Entry {
	e := Entry{}
	e.Times = NewTimeData()
	e.UUID = NewUUID()
	return e
}

// MarshalText is a marshaler method to encode uuid content as base 64 and return it
func (u UUID) MarshalText() ([]byte, error) {
	text := make([]byte, 24)
	base64.StdEncoding.Encode(text, u[:])
	return text, nil
}

// UnmarshalText unmarshals a byte slice into a UUID by decoding the given data from base64
func (u *UUID) UnmarshalText(text []byte) error {
	id := make([]byte, base64.StdEncoding.DecodedLen(len(text)))
	length, err := base64.StdEncoding.Decode(id, text)
	if err != nil {
		return err
	}
	if length != 16 {
		return ErrInvalidUUIDLength
	}
	copy((*u)[:], id[:16])
	return nil
}

// Compare allowes to check whether two instance of UUID are equal in value.
// This is used for searching a uuid
func (u UUID) Compare(c UUID) bool {
	for i, v := range c {
		if u[i] != v {
			return false
		}
	}
	return true
}

// Get returns the value in e corresponding with key k, or an empty string otherwise
func (e *Entry) Get(key string) *ValueData {
	for i := range e.Values {
		if e.Values[i].Key == key {
			return &e.Values[i]
		}
	}
	return nil
}

// GetContent returns the content of the value belonging to the given key in string form
func (e *Entry) GetContent(key string) string {
	val := e.Get(key)
	if val == nil {
		return ""
	}
	return val.Value.Content
}

// GetIndex returns the index of the Value belonging to the given key, or -1 if none is found
func (e *Entry) GetIndex(key string) int {
	for i := range e.Values {
		if e.Values[i].Key == key {
			return i
		}
	}
	return -1
}

// GetPassword returns the password of an entry
func (e *Entry) GetPassword() string {
	return e.GetContent("Password")
}

// GetPasswordIndex returns the index in the values slice belonging to the password
func (e *Entry) GetPasswordIndex() int {
	return e.GetIndex("Password")
}

// GetTitle returns the title of an entry
func (e *Entry) GetTitle() string {
	return e.GetContent("Title")
}

// ErrInvalidUUIDLength is an error which is returned during unmarshaling if the UUID does not have 16 bytes length
var ErrInvalidUUIDLength = errors.New("gokeepasslib: length of decoded UUID was not 16")
<|endoftext|>


<commit_before>package zim

import (
	"bytes"
	"encoding/binary"
	"errors"
	"os"
)

const (
	ZIM = 72173914
)

type ZimReader struct {
	f             *os.File
	ArticleCount  uint32
	clusterCount  uint32
	urlPtrPos     uint64
	titlePtrPos   uint64
	clusterPtrPos uint64
	mimeListPos   uint64
	mainPage      uint32
	layoutPage    uint32
}

func NewReader(path string) (*ZimReader, error) {
	f, err := os.Open(path)
	z := ZimReader{f: f, mainPage: 0xffffffff, layoutPage: 0xffffffff}
	if err != nil {
		return nil, err
	}

	err = z.readFileHeaders()
	return &z, err
}

func (z *ZimReader) readFileHeaders() error {
	// get size
	z.f.Stat()
	_, err := z.f.Seek(0, 0)
	if err != nil {
		panic(err)
	}

	b := make([]byte, 80)

	_, err = z.f.Read(b)
	if err != nil {
		panic(err)
	}

	// checking for file type
	v, err := readInt32(b[0:4])
	if err != nil {
		panic(err)
	}
	if v != ZIM {
		return errors.New("Not a ZIM file")
	}

	// checking for version
	v, err = readInt32(b[4:9])
	if err != nil {
		panic(err)
	}
	if v != 5 {
		return errors.New("Unsupported version 5 only")
	}

	// checking for articles count
	v, err = readInt32(b[24:29])
	if err != nil {
		panic(err)
	}
	z.ArticleCount = v

	// checking for cluster count
	v, err = readInt32(b[28:33])
	if err != nil {
		panic(err)
	}
	z.clusterCount = v

	// checking for urlPtrPos
	vb, err := readInt64(b[32:41])
	if err != nil {
		panic(err)
	}
	z.urlPtrPos = vb

	// checking for titlePtrPos
	vb, err = readInt64(b[40:49])
	if err != nil {
		panic(err)
	}
	z.titlePtrPos = vb

	// checking for clusterPtrPos
	vb, err = readInt64(b[48:57])
	if err != nil {
		panic(err)
	}
	z.clusterPtrPos = vb

	// checking for mimeListPos
	vb, err = readInt64(b[56:65])
	if err != nil {
		panic(err)
	}
	z.mimeListPos = vb

	// checking for mainPage
	v, err = readInt32(b[64:69])
	if err != nil {
		panic(err)
	}
	z.mainPage = v

	// checking for layoutPage
	v, err = readInt32(b[68:73])
	if err != nil {
		panic(err)
	}
	z.layoutPage = v

	return err
}

func readInt32(b []byte) (v uint32, err error) {
	buf := bytes.NewBuffer(b)
	err = binary.Read(buf, binary.LittleEndian, &v)
	if err != nil {
		return v, err
	}
	return v, nil
}

func readInt64(b []byte) (v uint64, err error) {
	buf := bytes.NewBuffer(b)
	err = binary.Read(buf, binary.LittleEndian, &v)
	if err != nil {
		return v, err
	}
	return v, nil
}

func (z *ZimReader) FindByName(string) error {
	return nil
}
<commit_msg>further reading data<commit_after>package zim

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"os"
	"unicode/utf8"
)

const (
	ZIM = 72173914
)

type ZimReader struct {
	f             *os.File
	ArticleCount  uint32
	clusterCount  uint32
	urlPtrPos     uint64
	titlePtrPos   uint64
	clusterPtrPos uint64
	mimeListPos   uint64
	mainPage      uint32
	layoutPage    uint32
}

func NewReader(path string) (*ZimReader, error) {
	f, err := os.Open(path)
	z := ZimReader{f: f, mainPage: 0xffffffff, layoutPage: 0xffffffff}
	if err != nil {
		return nil, err
	}

	err = z.readFileHeaders()
	return &z, err
}

func (z *ZimReader) readFileHeaders() error {
	// get size
	z.f.Stat()
	_, err := z.f.Seek(0, 0)
	if err != nil {
		panic(err)
	}

	b := make([]byte, 1024)

	_, err = z.f.Read(b)
	if err != nil {
		panic(err)
	}

	// checking for file type
	v, err := readInt32(b[0:4])
	if err != nil {
		panic(err)
	}
	if v != ZIM {
		return errors.New("Not a ZIM file")
	}

	// checking for version
	v, err = readInt32(b[4:9])
	if err != nil {
		panic(err)
	}
	if v != 5 {
		return errors.New("Unsupported version 5 only")
	}

	// checking for articles count
	v, err = readInt32(b[24:29])
	if err != nil {
		panic(err)
	}
	z.ArticleCount = v

	// checking for cluster count
	v, err = readInt32(b[28:33])
	if err != nil {
		panic(err)
	}
	z.clusterCount = v

	// checking for urlPtrPos
	vb, err := readInt64(b[32:41])
	if err != nil {
		panic(err)
	}
	z.urlPtrPos = vb

	// checking for titlePtrPos
	vb, err = readInt64(b[40:49])
	if err != nil {
		panic(err)
	}
	z.titlePtrPos = vb

	// checking for clusterPtrPos
	vb, err = readInt64(b[48:57])
	if err != nil {
		panic(err)
	}
	z.clusterPtrPos = vb

	// checking for mimeListPos
	vb, err = readInt64(b[56:65])
	if err != nil {
		panic(err)
	}
	z.mimeListPos = vb

	// checking for mainPage
	v, err = readInt32(b[64:69])
	if err != nil {
		panic(err)
	}
	z.mainPage = v

	// checking for layoutPage
	v, err = readInt32(b[68:73])
	if err != nil {
		panic(err)
	}
	z.layoutPage = v

	// Mime type list
	z.f.Seek(int64(z.mimeListPos), 0)

	eos := make([]byte, 1)
	utf8.EncodeRune(eos, '\x00')
	for {
		// read a chunk
		n, err := z.f.Read(b)
		if err != nil && err != io.EOF {
			panic(err)
		}
		if n == 0 {
			break
		}

		a := bytes.Split(b, eos)
		fmt.Println(len(a))
		for m := range a {
			fmt.Println(string(m))
		}
		break

	}

	return err
}

func readInt32(b []byte) (v uint32, err error) {
	buf := bytes.NewBuffer(b)
	err = binary.Read(buf, binary.LittleEndian, &v)
	if err != nil {
		return v, err
	}
	return v, nil
}

func readInt64(b []byte) (v uint64, err error) {
	buf := bytes.NewBuffer(b)
	err = binary.Read(buf, binary.LittleEndian, &v)
	if err != nil {
		return v, err
	}
	return v, nil
}

func (z *ZimReader) FindByName(string) error {
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/hmac"
	"crypto/md4"
	"crypto/md5"
	"crypto/ripemd160"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/sha512"
	"flag"
	"fmt"
	"hash"
	"hash/adler32"
	"hash/crc32"
	"hash/crc64"
	"hash/fnv"
	"io"
	"log"
	"os"
	"sort"
	"strings"
)

const (
	version = "1.0"
	chunkSize = 32768 // Read files in 32KB chunks
)

var (
	alg *string = flag.String("a", "md5", "algorithm")
	str *string = flag.String("s", "", "string to hash instead of file")
	key *string = flag.String("k", "", "key (for hashes that use a key, e.g. HMAC)")
	salt *string = flag.String("salt", "", "salt")
)

var (
	algDescs = map[string]string{
		"adler32": "Adler-32 checksum (RFC 1950)",
		"crc32": "32-bit cyclic redundancy check (CRC-32) checksum (defaults to IEEE polynomial)",
		"crc32ieee": "CRC-32 using the IEEE polynomial (0xedb88320)",
		"crc32castagnoli": "CRC-32 using the Castagnoli polynomial (0x82f63b78)",
		"crc32koopman": "CRC-32 using the Koopman polynomial (0xeb31d82e)",
		"crc64": "64-bit cyclic redundancy check (CRC-64) checksum (defaults to ISO polynomial)",
		"crc64iso": "CRC-64 using the ISO polynomial (0xD800000000000000)",
		"crc64ecma": "CRC-64 using the ECMA polynomial (0xC96C5795D7870F42)",
		"fnv": "FNV-1 non-cryptographic hash (defaults to fnv32)",
		"fnv32": "32-bit FNV-1",
		"fnv32a": "32-bit FNV-1a",
		"fnv64": "64-bit FNV-1",
		"fnv64a": "64-bit FNV-1a",
		"hmac": "Keyed-Hash Message Authentication Code (HMAC) (requires -k <key>) (defaults to SHA-256)",
		"hmacmd5": "HMAC using MD5 (requires -k <key>)",
		"hmacsha1": "HMAC using SHA-1 (requires -k <key>)",
		"hmacsha256": "HMAC using SHA-256 (requires -k <key>)",
		"hmacsha512": "HMAC using SHA-512 (requires -k <key>)",
		"md4": "MD4 hash (RFC 1320)",
		"md5": "MD5 hash (RFC 1321)",
		"ripemd160": "RIPEMD-160 hash",
		"sha1": "SHA-1 hash (RFC 3174)",
		"sha224": "SHA-224 hash (FIPS 180-2)",
		"sha256": "SHA-256 hash (FIPS 180-2)",
		"sha384": "SHA-384 hash (FIPS 180-2)",
		"sha512": "SHA-512 hash (FIPS 180-2)",
	}
)

func GetGenerator(a string) (hash.Hash, os.Error) {
	var g hash.Hash
	switch a {
	default:
		return md5.New(), os.NewError("Invalid algorithm")
	case "adler32":
		g = adler32.New()
	case "crc32", "crc32ieee":
		g = crc32.New(crc32.MakeTable(crc32.IEEE))
	case "crc32castagnoli":
		g = crc32.New(crc32.MakeTable(crc32.Castagnoli))
	case "crc32koopman":
		g = crc32.New(crc32.MakeTable(crc32.Koopman))
	case "crc64", "crc64iso":
		g = crc64.New(crc64.MakeTable(crc64.ISO))
	case "crc64ecma":
		g = crc64.New(crc64.MakeTable(crc64.ECMA))
	case "fnv", "fnv32":
		g = fnv.New32()
	case "fnv32a":
		g = fnv.New32a()
	case "fnv64":
		g = fnv.New64()
	case "fnv64a":
		g = fnv.New64a()
	case "hmac", "hmacsha256":
		g = hmac.NewSHA256([]byte(*key))
	case "hmacmd5":
		g = hmac.NewMD5([]byte(*key))
	case "hmacsha1":
		g = hmac.NewSHA256([]byte(*key))
	case "hmacsha512":
		g = hmac.New(sha512.New, []byte(*key))
	case "md4":
		g = md4.New()
	case "md5":
		g = md5.New()
	case "ripemd160":
		g = ripemd160.New()
	case "sha1":
		g = sha1.New()
	case "sha224":
		g = sha256.New224()
	case "sha256":
		g = sha256.New()
	case "sha384":
		g = sha512.New384()
	case "sha512":
		g = sha512.New()
	}
	return g, nil
}

func HashString(gen hash.Hash, s string) string {
	gen.Write([]byte(s))
	return fmt.Sprintf("%x", gen.Sum())
}

func HashFile(gen hash.Hash, f io.Reader) (string, os.Error) {
	buf := make([]byte, chunkSize)
	gen.Write([]byte(*salt))
	for {
		bytesRead, err := f.Read(buf)
		if err != nil {
			if bytesRead == 0 && err == os.EOF { // Empty file
				gen.Write([]byte(""))
			} else {
				return "", err
			}
		} else {
			gen.Write(buf[:bytesRead])
		}
		if bytesRead < chunkSize { // EOF
			break
		}
	}
	return fmt.Sprintf("%x", gen.Sum()), nil
}

func Usage() {
	fmt.Println("Usage:", os.Args[0], "[-a <algorithm>] [-salt <salt>] -s <string to hash>/-f <file to hash>\n")
	fmt.Println("Examples:")
	fmt.Println(" ", os.Args[0], "-a md5 document.txt               ", "Generate MD5 hash of a file")
	fmt.Println(" ", os.Args[0], "-a md5 *                          ", "Generate MD5 hash of all files in folder")
	fmt.Println(" ", os.Args[0], "-a sha1 -s hello world            ", "Generate SHA-1 hash of a string")
	fmt.Println(" ", os.Args[0], "-a sha1 -salt s4lt -s hello world ", "Generate salted SHA-1 hash of a string")
	fmt.Println("")
	fmt.Println("Available algorithms (default is MD5):")
	mk := make([]string, len(algDescs))
	i := 0
	for k, _ := range algDescs {
		mk[i] = k
		i++
	}
	sort.Strings(mk)
	for _, v := range mk {
		fmt.Printf("  %-15s %s\n", v, algDescs[v])
	}
}

func main() {
	flag.Parse()
	if flag.NArg() == 0 && *str == "" {
		Usage()
		return
	}
	*alg = strings.ToLower(*alg)
	gen, err := GetGenerator(*alg)
	if err != nil {
		log.Fatalln(err)
	}
	if *str == "" { // Hash file(s)
		for _, path := range flag.Args() {
			var res string
			f, err := os.Open(path)
			defer f.Close()
			if err != nil {
				res = err.String()
			} else {
				h, err := HashFile(gen, f)
				if err != nil {
					res = err.String()
				} else {
					res = h
				}
			}
			fmt.Println(res, "", path)
			gen.Reset()
		}
	} else { // Hash string
		fmt.Println(HashString(gen, *salt + *str))
	}
}
<commit_msg>gofmt<commit_after>package main

import (
	"crypto/hmac"
	"crypto/md4"
	"crypto/md5"
	"crypto/ripemd160"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/sha512"
	"flag"
	"fmt"
	"hash"
	"hash/adler32"
	"hash/crc32"
	"hash/crc64"
	"hash/fnv"
	"io"
	"log"
	"os"
	"sort"
	"strings"
)

const (
	version   = "1.0"
	chunkSize = 32768 // Read files in 32KB chunks
)

var (
	alg  *string = flag.String("a", "md5", "algorithm")
	str  *string = flag.String("s", "", "string to hash instead of file")
	key  *string = flag.String("k", "", "key (for hashes that use a key, e.g. HMAC)")
	salt *string = flag.String("salt", "", "salt")
)

var (
	algDescs = map[string]string{
		"adler32":         "Adler-32 checksum (RFC 1950)",
		"crc32":           "32-bit cyclic redundancy check (CRC-32) checksum (defaults to IEEE polynomial)",
		"crc32ieee":       "CRC-32 using the IEEE polynomial (0xedb88320)",
		"crc32castagnoli": "CRC-32 using the Castagnoli polynomial (0x82f63b78)",
		"crc32koopman":    "CRC-32 using the Koopman polynomial (0xeb31d82e)",
		"crc64":           "64-bit cyclic redundancy check (CRC-64) checksum (defaults to ISO polynomial)",
		"crc64iso":        "CRC-64 using the ISO polynomial (0xD800000000000000)",
		"crc64ecma":       "CRC-64 using the ECMA polynomial (0xC96C5795D7870F42)",
		"fnv":             "FNV-1 non-cryptographic hash (defaults to fnv32)",
		"fnv32":           "32-bit FNV-1",
		"fnv32a":          "32-bit FNV-1a",
		"fnv64":           "64-bit FNV-1",
		"fnv64a":          "64-bit FNV-1a",
		"hmac":            "Keyed-Hash Message Authentication Code (HMAC) (requires -k <key>) (defaults to SHA-256)",
		"hmacmd5":         "HMAC using MD5 (requires -k <key>)",
		"hmacsha1":        "HMAC using SHA-1 (requires -k <key>)",
		"hmacsha256":      "HMAC using SHA-256 (requires -k <key>)",
		"hmacsha512":      "HMAC using SHA-512 (requires -k <key>)",
		"md4":             "MD4 hash (RFC 1320)",
		"md5":             "MD5 hash (RFC 1321)",
		"ripemd160":       "RIPEMD-160 hash",
		"sha1":            "SHA-1 hash (RFC 3174)",
		"sha224":          "SHA-224 hash (FIPS 180-2)",
		"sha256":          "SHA-256 hash (FIPS 180-2)",
		"sha384":          "SHA-384 hash (FIPS 180-2)",
		"sha512":          "SHA-512 hash (FIPS 180-2)",
	}
)

func GetGenerator(a string) (hash.Hash, os.Error) {
	var g hash.Hash
	switch a {
	default:
		return md5.New(), os.NewError("Invalid algorithm")
	case "adler32":
		g = adler32.New()
	case "crc32", "crc32ieee":
		g = crc32.New(crc32.MakeTable(crc32.IEEE))
	case "crc32castagnoli":
		g = crc32.New(crc32.MakeTable(crc32.Castagnoli))
	case "crc32koopman":
		g = crc32.New(crc32.MakeTable(crc32.Koopman))
	case "crc64", "crc64iso":
		g = crc64.New(crc64.MakeTable(crc64.ISO))
	case "crc64ecma":
		g = crc64.New(crc64.MakeTable(crc64.ECMA))
	case "fnv", "fnv32":
		g = fnv.New32()
	case "fnv32a":
		g = fnv.New32a()
	case "fnv64":
		g = fnv.New64()
	case "fnv64a":
		g = fnv.New64a()
	case "hmac", "hmacsha256":
		g = hmac.NewSHA256([]byte(*key))
	case "hmacmd5":
		g = hmac.NewMD5([]byte(*key))
	case "hmacsha1":
		g = hmac.NewSHA256([]byte(*key))
	case "hmacsha512":
		g = hmac.New(sha512.New, []byte(*key))
	case "md4":
		g = md4.New()
	case "md5":
		g = md5.New()
	case "ripemd160":
		g = ripemd160.New()
	case "sha1":
		g = sha1.New()
	case "sha224":
		g = sha256.New224()
	case "sha256":
		g = sha256.New()
	case "sha384":
		g = sha512.New384()
	case "sha512":
		g = sha512.New()
	}
	return g, nil
}

func HashString(gen hash.Hash, s string) string {
	gen.Write([]byte(s))
	return fmt.Sprintf("%x", gen.Sum())
}

func HashFile(gen hash.Hash, f io.Reader) (string, os.Error) {
	buf := make([]byte, chunkSize)
	gen.Write([]byte(*salt))
	for {
		bytesRead, err := f.Read(buf)
		if err != nil {
			if err == os.EOF && bytesRead == 0 { // Empty file
				gen.Write([]byte(""))
			} else {
				return "", err
			}
		} else {
			gen.Write(buf[:bytesRead])
		}
		if bytesRead < chunkSize { // EOF
			break
		}
	}
	return fmt.Sprintf("%x", gen.Sum()), nil
}

func Usage() {
	fmt.Println("Usage:", os.Args[0], "[-a <algorithm>] [-salt <salt>] -s <string to hash>/-f <file to hash>\n")
	fmt.Println("Examples:")
	fmt.Println(" ", os.Args[0], "-a md5 document.txt               ", "Generate MD5 hash of a file")
	fmt.Println(" ", os.Args[0], "-a md5 *                          ", "Generate MD5 hash of all files in folder")
	fmt.Println(" ", os.Args[0], "-a sha1 -s hello world            ", "Generate SHA-1 hash of a string")
	fmt.Println(" ", os.Args[0], "-a sha1 -salt s4lt -s hello world ", "Generate salted SHA-1 hash of a string")
	fmt.Println("")
	fmt.Println("Available algorithms (default is MD5):")
	mk := make([]string, len(algDescs))
	i := 0
	for k, _ := range algDescs {
		mk[i] = k
		i++
	}
	sort.Strings(mk)
	for _, v := range mk {
		fmt.Printf("  %-15s %s\n", v, algDescs[v])
	}
}

func main() {
	flag.Parse()
	if flag.NArg() == 0 && *str == "" {
		Usage()
		return
	}
	*alg = strings.ToLower(*alg)
	gen, err := GetGenerator(*alg)
	if err != nil {
		log.Fatalln(err)
	}
	if *str == "" { // Hash file(s)
		for _, path := range flag.Args() {
			var res string
			f, err := os.Open(path)
			defer f.Close()
			if err != nil {
				res = err.String()
			} else {
				h, err := HashFile(gen, f)
				if err != nil {
					res = err.String()
				} else {
					res = h
				}
			}
			fmt.Println(res, "", path)
			gen.Reset()
		}
	} else { // Hash string
		fmt.Println(HashString(gen, *salt+*str))
	}
}
<|endoftext|>


<commit_before>/*
 * This plugin expose syncthing to the admin user
 */
package plg_handler_syncthing

import (
	"encoding/base64"
	"fmt"
	"github.com/gorilla/mux"
	. "github.com/mickael-kerjean/filestash/server/common"
	"golang.org/x/crypto/bcrypt"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"strings"
	"time"
)

const SYNCTHING_URI = "/admin/syncthing"
var syncthing_enable func() bool = func() bool {
	return Config.Get("features.syncthing.enable").Schema(func(f *FormElement) *FormElement {
		if f == nil {
			f = &FormElement{}
		}
		f.Name = "enable"
		f.Type = "enable"
		f.Target = []string{"syncthing_server_url"}
		f.Description = "Enable/Disable the office suite to manage word, excel and powerpoint documents. This setting requires a restart to comes into effect"
		f.Default = false
		if u := os.Getenv("SYNCTHING_URL"); u != "" {
			f.Default = true
		}
		return f
	}).Bool()
}
func init() {
	syncthing_enable()
	Config.Get("features.syncthing.server_url").Schema(func(f *FormElement) *FormElement {
		if f == nil {
			f = &FormElement{}
		}
		f.Id = "syncthing_server_url"
		f.Name = "server_url"
		f.Type = "text"
		f.Description = "Location of your Syncthing server"
		f.Default = ""
		f.Placeholder = "Eg: http://127.0.0.1:8080"
		if u := os.Getenv("SYNCTHING_URL"); u != "" {
			f.Default = u
			f.Placeholder = fmt.Sprintf("Default: '%s'", u)
		}
		return f
	})

	Hooks.Register.HttpEndpoint(func(r *mux.Router, _ *App) error {
		r.HandleFunc(SYNCTHING_URI, func (res http.ResponseWriter, req *http.Request) {
			http.Redirect(res, req, SYNCTHING_URI + "/", http.StatusTemporaryRedirect)
		})
		r.Handle(SYNCTHING_URI + "/", AuthBasic(
			func() (string, string) { return "admin", Config.Get("auth.admin").String() },
			http.HandlerFunc(SyncthingProxyHandler),
		))

		r.PathPrefix(SYNCTHING_URI + "/").HandlerFunc(SyncthingProxyHandler)
		return nil
	})
}

func AuthBasic(credentials func() (string, string), fn http.Handler) http.HandlerFunc {
	var notAuthorised = func(res http.ResponseWriter, req *http.Request) {
		time.Sleep(1 * time.Second)
		res.Header().Set("WWW-Authenticate", `Basic realm="User protect", charset="UTF-8"`)
		res.WriteHeader(http.StatusUnauthorized)
		res.Write([]byte("Not Authorised"))
		return
	}

	return func(res http.ResponseWriter, req *http.Request) {
		if syncthing_enable() == false {
			http.NotFoundHandler().ServeHTTP(res, req)
			return
		}

		auth := req.Header.Get("Authorization")
		if strings.HasPrefix(auth, "Basic ") == false {
			notAuthorised(res, req)
			return
		}
		auth = strings.TrimPrefix(auth, "Basic ")
		decoded, err := base64.StdEncoding.DecodeString(auth)
		if err != nil {
			notAuthorised(res, req)
			return
		}
		auth = string(decoded)
		stuffs := strings.Split(auth, ":")
		if len(stuffs) < 2 {
			notAuthorised(res, req)
			return
		}
		username := stuffs[0]
		password := strings.Join(stuffs[1:], ":")
		refUsername, refPassword := credentials()
		if refUsername != username {
			notAuthorised(res, req)
			return
		} else if err = bcrypt.CompareHashAndPassword([]byte(refPassword), []byte(password)); err != nil {
			notAuthorised(res, req)
			return
		}
		fn.ServeHTTP(res, req)
		return
	}
}

func SyncthingProxyHandler(res http.ResponseWriter, req *http.Request) {
	req.URL.Path = strings.TrimPrefix(req.URL.Path, SYNCTHING_URI)
	req.Header.Set("X-Forwarded-Host", req.Host + SYNCTHING_URI)
	req.Header.Set("X-Forwarded-Proto", func() string {
		if scheme := req.Header.Get("X-Forwarded-Proto"); scheme != "" {
			return scheme
		} else if req.TLS != nil {
			return "https"
		}
		return "http"
	}())
	u, err := url.Parse(Config.Get("features.syncthing.server_url").String())
	if err != nil {
		SendErrorResult(res, err)
		return
	}

	reverseProxy := &httputil.ReverseProxy{
		Director: func(rq *http.Request) {
			rq.URL.Scheme = "http"
			rq.URL.Host = u.Host
			rq.URL.Path = func(a, b string) string {
				aslash := strings.HasSuffix(a, "/")
				bslash := strings.HasPrefix(b, "/")
				switch {
				case aslash && bslash:
					return a + b[1:]
				case !aslash && !bslash:
					return a + "/" + b
				}
				return a + b
			}(u.Path, rq.URL.Path)
			if u.RawQuery == "" || rq.URL.RawQuery == "" {
				rq.URL.RawQuery = u.RawQuery + rq.URL.RawQuery
			} else {
				rq.URL.RawQuery = u.RawQuery + "&" + rq.URL.RawQuery
			}
		},
	}
	reverseProxy.ErrorHandler = func(rw http.ResponseWriter, rq *http.Request, err error) {
		Log.Warning("[syncthing] %s", err.Error())
		SendErrorResult(rw, NewError(err.Error(), http.StatusBadGateway))
	}
	reverseProxy.ServeHTTP(res, req)
}
<commit_msg>fix (syncthing): wrong description - #224<commit_after>/*
 * This plugin expose syncthing to the admin user
 */
package plg_handler_syncthing

import (
	"encoding/base64"
	"fmt"
	"github.com/gorilla/mux"
	. "github.com/mickael-kerjean/filestash/server/common"
	"golang.org/x/crypto/bcrypt"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"strings"
	"time"
)

const SYNCTHING_URI = "/admin/syncthing"
func init() {
	plugin_enable := Config.Get("features.syncthing.enable").Schema(func(f *FormElement) *FormElement {
		if f == nil {
			f = &FormElement{}
		}
		f.Name = "enable"
		f.Type = "enable"
		f.Target = []string{"syncthing_server_url"}
		f.Description = "Enable/Disable integration with the syncthing server. This will make your syncthing server available at `/admin/syncthing`"
		f.Default = false
		if u := os.Getenv("SYNCTHING_URL"); u != "" {
			f.Default = true
		}
		return f
	}).Bool()
	Config.Get("features.syncthing.server_url").Schema(func(f *FormElement) *FormElement {
		if f == nil {
			f = &FormElement{}
		}
		f.Id = "syncthing_server_url"
		f.Name = "server_url"
		f.Type = "text"
		f.Description = "Location of your Syncthing server"
		f.Default = ""
		f.Placeholder = "Eg: http://127.0.0.1:8080"
		if u := os.Getenv("SYNCTHING_URL"); u != "" {
			f.Default = u
			f.Placeholder = fmt.Sprintf("Default: '%s'", u)
		}
		return f
	})

	Hooks.Register.HttpEndpoint(func(r *mux.Router, _ *App) error {
		if plugin_enable == false {
			return nil
		}
		r.HandleFunc(SYNCTHING_URI, func (res http.ResponseWriter, req *http.Request) {
			http.Redirect(res, req, SYNCTHING_URI + "/", http.StatusTemporaryRedirect)
		})
		r.Handle(SYNCTHING_URI + "/", AuthBasic(
			func() (string, string) { return "admin", Config.Get("auth.admin").String() },
			http.HandlerFunc(SyncthingProxyHandler),
		))

		r.PathPrefix(SYNCTHING_URI + "/").HandlerFunc(SyncthingProxyHandler)
		return nil
	})
}

func AuthBasic(credentials func() (string, string), fn http.Handler) http.HandlerFunc {
	var notAuthorised = func(res http.ResponseWriter, req *http.Request) {
		time.Sleep(1 * time.Second)
		res.Header().Set("WWW-Authenticate", `Basic realm="User protect", charset="UTF-8"`)
		res.WriteHeader(http.StatusUnauthorized)
		res.Write([]byte("Not Authorised"))
		return
	}

	return func(res http.ResponseWriter, req *http.Request) {
		auth := req.Header.Get("Authorization")
		if strings.HasPrefix(auth, "Basic ") == false {
			notAuthorised(res, req)
			return
		}
		auth = strings.TrimPrefix(auth, "Basic ")
		decoded, err := base64.StdEncoding.DecodeString(auth)
		if err != nil {
			notAuthorised(res, req)
			return
		}
		auth = string(decoded)
		stuffs := strings.Split(auth, ":")
		if len(stuffs) < 2 {
			notAuthorised(res, req)
			return
		}
		username := stuffs[0]
		password := strings.Join(stuffs[1:], ":")
		refUsername, refPassword := credentials()
		if refUsername != username {
			notAuthorised(res, req)
			return
		} else if err = bcrypt.CompareHashAndPassword([]byte(refPassword), []byte(password)); err != nil {
			notAuthorised(res, req)
			return
		}
		fn.ServeHTTP(res, req)
		return
	}
}

func SyncthingProxyHandler(res http.ResponseWriter, req *http.Request) {
	req.URL.Path = strings.TrimPrefix(req.URL.Path, SYNCTHING_URI)
	req.Header.Set("X-Forwarded-Host", req.Host + SYNCTHING_URI)
	req.Header.Set("X-Forwarded-Proto", func() string {
		if scheme := req.Header.Get("X-Forwarded-Proto"); scheme != "" {
			return scheme
		} else if req.TLS != nil {
			return "https"
		}
		return "http"
	}())
	u, err := url.Parse(Config.Get("features.syncthing.server_url").String())
	if err != nil {
		SendErrorResult(res, err)
		return
	}

	reverseProxy := &httputil.ReverseProxy{
		Director: func(rq *http.Request) {
			rq.URL.Scheme = "http"
			rq.URL.Host = u.Host
			rq.URL.Path = func(a, b string) string {
				aslash := strings.HasSuffix(a, "/")
				bslash := strings.HasPrefix(b, "/")
				switch {
				case aslash && bslash:
					return a + b[1:]
				case !aslash && !bslash:
					return a + "/" + b
				}
				return a + b
			}(u.Path, rq.URL.Path)
			if u.RawQuery == "" || rq.URL.RawQuery == "" {
				rq.URL.RawQuery = u.RawQuery + rq.URL.RawQuery
			} else {
				rq.URL.RawQuery = u.RawQuery + "&" + rq.URL.RawQuery
			}
		},
	}
	reverseProxy.ErrorHandler = func(rw http.ResponseWriter, rq *http.Request, err error) {
		Log.Warning("[syncthing] %s", err.Error())
		SendErrorResult(rw, NewError(err.Error(), http.StatusBadGateway))
	}
	reverseProxy.ServeHTTP(res, req)
}
<|endoftext|>


<commit_before>//
// Copyright (c) 2017 Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//

package export

// Protocols
const (
	ProtoHTTP  = "HTTP"
	ProtoTCP   = "TCP"
	ProtoMAC   = "MAC"
	ProtoZMQ   = "ZMQ"
	ProtoOther = "OTHER"
)

// Methods
const (
	MethodGet    = "GET"
	MethodPost   = "POST"
	MethodPut    = "PUT"
	MethodPatch  = "PATCH"
	MethodDelete = "DELETE"
)

// Addressable - address for reaching the service
type Addressable struct {
	Name      string
	Method    string
	Protocol  string
	Address   string
	Port      int
	Path      string
	Publisher string
	User      string
	Password  string
	Topic     string
}
<commit_msg>Removed method not used<commit_after>//
// Copyright (c) 2017 Mainflux
//
// SPDX-License-Identifier: Apache-2.0
//

package export

// Protocols
const (
	ProtoHTTP  = "HTTP"
	ProtoTCP   = "TCP"
	ProtoMAC   = "MAC"
	ProtoZMQ   = "ZMQ"
	ProtoOther = "OTHER"
)

// Methods
const (
	MethodGet  = "GET"
	MethodPost = "POST"
)

// Addressable - address for reaching the service
type Addressable struct {
	Name      string
	Method    string
	Protocol  string
	Address   string
	Port      int
	Path      string
	Publisher string
	User      string
	Password  string
	Topic     string
}
<|endoftext|>


<commit_before>package admin

import (
	"log"
	"net/http"
	"path"
	"reflect"
	"regexp"
	"strings"
	"time"

	"github.com/qor/qor"
	"github.com/qor/qor/roles"
)

type (
	handle func(c *Context)

	handler struct {
		Path   *regexp.Regexp
		Handle handle
	}
)

type Router struct {
	Prefix      string
	routers     map[string][]handler
	middlewares []*Middleware
}

func newRouter() *Router {
	return &Router{routers: map[string][]handler{
		"GET":    []handler{},
		"PUT":    []handler{},
		"POST":   []handler{},
		"DELETE": []handler{},
	}}
}

type Middleware struct {
	Handler func(*Context, *Middleware)
	next    *Middleware
}

func (middleware Middleware) Next(context *Context) {
	if next := middleware.next; next != nil {
		next.Handler(context, next)
	}
}

func (r *Router) Use(handler func(*Context, *Middleware)) {
	r.middlewares = append(r.middlewares, &Middleware{Handler: handler})
}

func (r *Router) Get(path string, handle handle) {
	r.routers["GET"] = append(r.routers["GET"], handler{Path: regexp.MustCompile(path), Handle: handle})
}

func (r *Router) Post(path string, handle handle) {
	r.routers["POST"] = append(r.routers["POST"], handler{Path: regexp.MustCompile(path), Handle: handle})
}

func (r *Router) Put(path string, handle handle) {
	r.routers["PUT"] = append(r.routers["PUT"], handler{Path: regexp.MustCompile(path), Handle: handle})
}

func (r *Router) Delete(path string, handle handle) {
	r.routers["DELETE"] = append(r.routers["DELETE"], handler{Path: regexp.MustCompile(path), Handle: handle})
}

func (admin *Admin) MountTo(prefix string, mux *http.ServeMux) {
	prefix = "/" + strings.Trim(prefix, "/")
	router := admin.router
	router.Prefix = prefix

	admin.compile()

	controller := &controller{admin}
	router.Get("^/?$", controller.Dashboard)
	router.Get("^/[^/]+/new$", controller.New)
	router.Post("^/[^/]+$", controller.Create)
	router.Post("^/[^/]+/action/[^/]+(\\?.*)?$", controller.Action)
	router.Get("^/[^/]+/.*$", controller.Show)
	router.Put("^/[^/]+/.*$", controller.Update)
	router.Post("^/[^/]+/.*$", controller.Update)
	router.Delete("^/[^/]+/.*$", controller.Delete)
	router.Get("^/[^/]+$", controller.Index)

	mux.Handle(prefix, admin)     // /:prefix
	mux.Handle(prefix+"/", admin) // /:prefix/:xxx
}

type ConfigureInjector interface {
	ConfigureQorResource(*Resource)
}

func (res *Resource) compile() {
	admin := res.GetAdmin()
	for _, field := range admin.Config.DB.NewScope(res.Value).Fields() {
		if field.IsNormal {
			if injector, ok := reflect.New(field.Struct.Type).Interface().(ConfigureInjector); ok {
				injector.ConfigureQorResource(res)
			}
		}
	}

	if injector, ok := res.Value.(ConfigureInjector); ok {
		injector.ConfigureQorResource(res)
	}
}

func (admin *Admin) compile() {
	admin.generateMenuLinks()

	router := admin.GetRouter()

	for _, res := range admin.resources {
		res.compile()
	}

	router.Use(func(context *Context, middleware *Middleware) {
		w := context.Writer
		req := context.Request

		// 128 MB
		req.ParseMultipartForm(32 << 22)
		if len(req.Form["_method"]) > 0 {
			req.Method = strings.ToUpper(req.Form["_method"][0])
		}

		var pathMatch = regexp.MustCompile(path.Join(router.Prefix, `(\w+)(?:/(\w+))?[^/]*`))
		var matches = pathMatch.FindStringSubmatch(req.URL.Path)
		if len(matches) > 1 {
			context.setResource(admin.GetResource(matches[1]))
			if len(matches) > 2 {
				context.ResourceID = matches[2]
			}
		}

		handlers := router.routers[strings.ToUpper(req.Method)]
		relativePath := strings.TrimPrefix(req.URL.Path, router.Prefix)
		for _, handler := range handlers {
			if handler.Path.MatchString(relativePath) {
				handler.Handle(context)
				return
			}
		}
		http.NotFound(w, req)
	})

	for index, middleware := range router.middlewares {
		var next *Middleware
		if len(router.middlewares) > index+1 {
			next = router.middlewares[index+1]
		}
		middleware.next = next
	}
}

func (admin *Admin) NewContext(w http.ResponseWriter, r *http.Request) *Context {
	context := Context{Context: &qor.Context{Config: admin.Config, Request: r, Writer: w}, Admin: admin}

	return &context
}

func (admin *Admin) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	var relativePath = strings.TrimPrefix(req.URL.Path, admin.router.Prefix)
	var context = admin.NewContext(w, req)

	if regexp.MustCompile("^/assets/.*$").MatchString(relativePath) {
		(&controller{admin}).Asset(context)
		return
	}

	defer func() func() {
		begin := time.Now()
		log.Printf("Start [%s] %s\n", req.Method, req.RequestURI)

		return func() {
			log.Printf("Finish [%s] %s Took %.2fms\n", req.Method, req.RequestURI, time.Now().Sub(begin).Seconds()*1000)
		}
	}()()

	var currentUser qor.CurrentUser
	if admin.auth != nil {
		if currentUser = admin.auth.GetCurrentUser(context); currentUser == nil {
			http.Redirect(w, req, admin.auth.LoginURL(context), http.StatusSeeOther)
			return
		} else {
			context.CurrentUser = currentUser
			context.SetDB(context.GetDB().Set("qor:current_user", context.CurrentUser))
		}
	}
	context.Roles = roles.MatchedRoles(req, currentUser)

	firstStack := admin.router.middlewares[0]
	firstStack.Handler(context, firstStack)
}
<commit_msg>Fixes ConfigureQorResource<commit_after>package admin

import (
	"log"
	"net/http"
	"path"
	"reflect"
	"regexp"
	"strings"
	"time"

	"github.com/qor/qor"
	"github.com/qor/qor/roles"
)

type (
	handle func(c *Context)

	handler struct {
		Path   *regexp.Regexp
		Handle handle
	}
)

type Router struct {
	Prefix      string
	routers     map[string][]handler
	middlewares []*Middleware
}

func newRouter() *Router {
	return &Router{routers: map[string][]handler{
		"GET":    []handler{},
		"PUT":    []handler{},
		"POST":   []handler{},
		"DELETE": []handler{},
	}}
}

type Middleware struct {
	Handler func(*Context, *Middleware)
	next    *Middleware
}

func (middleware Middleware) Next(context *Context) {
	if next := middleware.next; next != nil {
		next.Handler(context, next)
	}
}

func (r *Router) Use(handler func(*Context, *Middleware)) {
	r.middlewares = append(r.middlewares, &Middleware{Handler: handler})
}

func (r *Router) Get(path string, handle handle) {
	r.routers["GET"] = append(r.routers["GET"], handler{Path: regexp.MustCompile(path), Handle: handle})
}

func (r *Router) Post(path string, handle handle) {
	r.routers["POST"] = append(r.routers["POST"], handler{Path: regexp.MustCompile(path), Handle: handle})
}

func (r *Router) Put(path string, handle handle) {
	r.routers["PUT"] = append(r.routers["PUT"], handler{Path: regexp.MustCompile(path), Handle: handle})
}

func (r *Router) Delete(path string, handle handle) {
	r.routers["DELETE"] = append(r.routers["DELETE"], handler{Path: regexp.MustCompile(path), Handle: handle})
}

func (admin *Admin) MountTo(prefix string, mux *http.ServeMux) {
	prefix = "/" + strings.Trim(prefix, "/")
	router := admin.router
	router.Prefix = prefix

	admin.compile()

	controller := &controller{admin}
	router.Get("^/?$", controller.Dashboard)
	router.Get("^/[^/]+/new$", controller.New)
	router.Post("^/[^/]+$", controller.Create)
	router.Post("^/[^/]+/action/[^/]+(\\?.*)?$", controller.Action)
	router.Get("^/[^/]+/.*$", controller.Show)
	router.Put("^/[^/]+/.*$", controller.Update)
	router.Post("^/[^/]+/.*$", controller.Update)
	router.Delete("^/[^/]+/.*$", controller.Delete)
	router.Get("^/[^/]+$", controller.Index)

	mux.Handle(prefix, admin)     // /:prefix
	mux.Handle(prefix+"/", admin) // /:prefix/:xxx
}

type ConfigureInjector interface {
	ConfigureQorResource(*Resource)
}

func (res *Resource) compile() {
	admin := res.GetAdmin()
	scope := admin.Config.DB.NewScope(res.Value)
	modelType := scope.GetModelStruct().ModelType
	for i := 0; i < modelType.NumField(); i++ {
		fieldStruct := modelType.Field(i)
		if field, ok := scope.FieldByName(fieldStruct.Name); !ok || field.Relationship == nil {
			if injector, ok := reflect.New(fieldStruct.Type).Interface().(ConfigureInjector); ok {
				injector.ConfigureQorResource(res)
			}
		}
	}

	if injector, ok := res.Value.(ConfigureInjector); ok {
		injector.ConfigureQorResource(res)
	}
}

func (admin *Admin) compile() {
	admin.generateMenuLinks()

	router := admin.GetRouter()

	for _, res := range admin.resources {
		res.compile()
	}

	router.Use(func(context *Context, middleware *Middleware) {
		w := context.Writer
		req := context.Request

		// 128 MB
		req.ParseMultipartForm(32 << 22)
		if len(req.Form["_method"]) > 0 {
			req.Method = strings.ToUpper(req.Form["_method"][0])
		}

		var pathMatch = regexp.MustCompile(path.Join(router.Prefix, `(\w+)(?:/(\w+))?[^/]*`))
		var matches = pathMatch.FindStringSubmatch(req.URL.Path)
		if len(matches) > 1 {
			context.setResource(admin.GetResource(matches[1]))
			if len(matches) > 2 {
				context.ResourceID = matches[2]
			}
		}

		handlers := router.routers[strings.ToUpper(req.Method)]
		relativePath := strings.TrimPrefix(req.URL.Path, router.Prefix)
		for _, handler := range handlers {
			if handler.Path.MatchString(relativePath) {
				handler.Handle(context)
				return
			}
		}
		http.NotFound(w, req)
	})

	for index, middleware := range router.middlewares {
		var next *Middleware
		if len(router.middlewares) > index+1 {
			next = router.middlewares[index+1]
		}
		middleware.next = next
	}
}

func (admin *Admin) NewContext(w http.ResponseWriter, r *http.Request) *Context {
	context := Context{Context: &qor.Context{Config: admin.Config, Request: r, Writer: w}, Admin: admin}

	return &context
}

func (admin *Admin) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	var relativePath = strings.TrimPrefix(req.URL.Path, admin.router.Prefix)
	var context = admin.NewContext(w, req)

	if regexp.MustCompile("^/assets/.*$").MatchString(relativePath) {
		(&controller{admin}).Asset(context)
		return
	}

	defer func() func() {
		begin := time.Now()
		log.Printf("Start [%s] %s\n", req.Method, req.RequestURI)

		return func() {
			log.Printf("Finish [%s] %s Took %.2fms\n", req.Method, req.RequestURI, time.Now().Sub(begin).Seconds()*1000)
		}
	}()()

	var currentUser qor.CurrentUser
	if admin.auth != nil {
		if currentUser = admin.auth.GetCurrentUser(context); currentUser == nil {
			http.Redirect(w, req, admin.auth.LoginURL(context), http.StatusSeeOther)
			return
		} else {
			context.CurrentUser = currentUser
			context.SetDB(context.GetDB().Set("qor:current_user", context.CurrentUser))
		}
	}
	context.Roles = roles.MatchedRoles(req, currentUser)

	firstStack := admin.router.middlewares[0]
	firstStack.Handler(context, firstStack)
}
<|endoftext|>


<commit_before>// Package ae provides tools to synchronize state between local and remote consul servers.
package ae

import (
	"fmt"
	"log"
	"math"
	"sync"
	"time"

	"github.com/hashicorp/consul/lib"
)

// scaleThreshold is the number of nodes after which regular sync runs are
// spread out farther apart. The value should be a power of 2 since the
// scale function uses log2.
//
// When set to 128 nodes the delay between regular runs is doubled when the
// cluster is larger than 128 nodes. It doubles again when it passes 256
// nodes, and again at 512 nodes and so forth. At 8192 nodes, the delay
// factor is 8.
//
// If you update this, you may need to adjust the tuning of
// CoordinateUpdatePeriod and CoordinateUpdateMaxBatchSize.
const scaleThreshold = 128

// scaleFactor returns a factor by which the next sync run should be delayed to
// avoid saturation of the cluster. The larger the cluster grows the farther
// the sync runs should be spread apart.
//
// The current implementation uses a log2 scale which doubles the delay between
// runs every time the cluster doubles in size.
func scaleFactor(nodes int) int {
	if nodes <= scaleThreshold {
		return 1.0
	}
	return int(math.Ceil(math.Log2(float64(nodes))-math.Log2(float64(scaleThreshold))) + 1.0)
}

type SyncState interface {
	SyncChanges() error
	SyncFull() error
}

// StateSyncer manages background synchronization of the given state.
//
// The state is synchronized on a regular basis or on demand when either
// the state has changed or a new Consul server has joined the cluster.
//
// The regular state sychronization provides a self-healing mechanism
// for the cluster which is also called anti-entropy.
type StateSyncer struct {
	// State contains the data that needs to be synchronized.
	State SyncState

	// Interval is the time between two full sync runs.
	Interval time.Duration

	// ShutdownCh is closed when the application is shutting down.
	ShutdownCh chan struct{}

	// Logger is the logger.
	Logger *log.Logger

	// ClusterSize returns the number of members in the cluster to
	// allow staggering the sync runs based on cluster size.
	// This needs to be set before Run() is called.
	ClusterSize func() int

	// SyncFull allows triggering an immediate but staggered full sync
	// in a non-blocking way.
	SyncFull *Trigger

	// SyncChanges allows triggering an immediate partial sync
	// in a non-blocking way.
	SyncChanges *Trigger

	// paused stores whether sync runs are temporarily disabled.
	pauseLock sync.Mutex
	paused    int

	// serverUpInterval is the max time after which a full sync is
	// performed when a server has been added to the cluster.
	serverUpInterval time.Duration

	// retryFailInterval is the time after which a failed full sync is retried.
	retryFailInterval time.Duration

	// stagger randomly picks a duration between 0s and the given duration.
	stagger func(time.Duration) time.Duration

	// retrySyncFullEvent generates an event based on multiple conditions
	// when the state machine is trying to retry a full state sync.
	retrySyncFullEvent func() event

	// syncChangesEvent generates an event based on multiple conditions
	// when the state machine is performing partial state syncs.
	syncChangesEvent func() event
}

const (
	// serverUpIntv is the max time to wait before a sync is triggered
	// when a consul server has been added to the cluster.
	serverUpIntv = 3 * time.Second

	// retryFailIntv is the min time to wait before a failed sync is retried.
	retryFailIntv = 15 * time.Second
)

func NewStateSyncer(state SyncState, intv time.Duration, shutdownCh chan struct{}, logger *log.Logger) *StateSyncer {
	s := &StateSyncer{
		State:             state,
		Interval:          intv,
		ShutdownCh:        shutdownCh,
		Logger:            logger,
		SyncFull:          NewTrigger(),
		SyncChanges:       NewTrigger(),
		serverUpInterval:  serverUpIntv,
		retryFailInterval: retryFailIntv,
	}

	// retain these methods as member variables so that
	// we can mock them for testing.
	s.retrySyncFullEvent = s.retrySyncFullEventFn
	s.syncChangesEvent = s.syncChangesEventFn
	s.stagger = s.staggerFn

	return s
}

// fsmState defines states for the state machine.
type fsmState string

const (
	doneState          fsmState = "done"
	fullSyncState      fsmState = "fullSync"
	partialSyncState   fsmState = "partialSync"
	retryFullSyncState fsmState = "retryFullSync"
)

// Run is the long running method to perform state synchronization
// between local and remote servers.
func (s *StateSyncer) Run() {
	if s.ClusterSize == nil {
		panic("ClusterSize not set")
	}
	s.runFSM(fullSyncState, s.nextFSMState)
}

// runFSM runs the state machine.
func (s *StateSyncer) runFSM(fs fsmState, next func(fsmState) fsmState) {
	for {
		if fs = next(fs); fs == doneState {
			return
		}
	}
}

// nextFSMState determines the next state based on the current state.
func (s *StateSyncer) nextFSMState(fs fsmState) fsmState {
	switch fs {
	case fullSyncState:
		if s.Paused() {
			return retryFullSyncState
		}

		err := s.State.SyncFull()
		if err != nil {
			s.Logger.Printf("[ERR] agent: failed to sync remote state: %v", err)
			return retryFullSyncState
		}

		return partialSyncState

	case retryFullSyncState:
		e := s.retrySyncFullEvent()
		switch e {
		case syncFullNotifEvent, syncFullTimerEvent:
			return fullSyncState
		case shutdownEvent:
			return doneState
		default:
			panic(fmt.Sprintf("invalid event: %s", e))
		}

	case partialSyncState:
		e := s.syncChangesEvent()
		switch e {
		case syncFullNotifEvent, syncFullTimerEvent:
			return fullSyncState

		case syncChangesNotifEvent:
			if s.Paused() {
				return partialSyncState
			}

			err := s.State.SyncChanges()
			if err != nil {
				s.Logger.Printf("[ERR] agent: failed to sync changes: %v", err)
			}
			return partialSyncState

		case shutdownEvent:
			return doneState

		default:
			panic(fmt.Sprintf("invalid event: %s", e))
		}

	default:
		panic(fmt.Sprintf("invalid state: %s", fs))
	}
}

// event defines a timing or notification event from multiple timers and
// channels.
type event string

const (
	shutdownEvent         event = "shutdown"
	syncFullNotifEvent    event = "syncFullNotif"
	syncFullTimerEvent    event = "syncFullTimer"
	syncChangesNotifEvent event = "syncChangesNotif"
)

// retrySyncFullEventFn waits for an event which triggers a retry
// of a full sync or a termination signal. This function should not be
// called directly but through s.retryFullSyncState to allow mocking for
// testing.
func (s *StateSyncer) retrySyncFullEventFn() event {
	select {
	// trigger a full sync immediately.
	// this is usually called when a consul server was added to the cluster.
	// stagger the delay to avoid a thundering herd.
	case <-s.SyncFull.Notif():
		select {
		case <-time.After(s.stagger(s.serverUpInterval)):
			return syncFullNotifEvent
		case <-s.ShutdownCh:
			return shutdownEvent
		}

	// retry full sync after some time
	// todo(fs): why don't we use s.Interval here?
	case <-time.After(s.retryFailInterval + s.stagger(s.retryFailInterval)):
		return syncFullTimerEvent

	case <-s.ShutdownCh:
		return shutdownEvent
	}
}

// syncChangesEventFn waits for a event which either triggers a full
// or a partial sync or a termination signal. This function should not
// be called directly but through s.syncChangesEvent to allow mocking
// for testing.
func (s *StateSyncer) syncChangesEventFn() event {
	select {
	// trigger a full sync immediately
	// this is usually called when a consul server was added to the cluster.
	// stagger the delay to avoid a thundering herd.
	case <-s.SyncFull.Notif():
		select {
		case <-time.After(s.stagger(s.serverUpInterval)):
			return syncFullNotifEvent
		case <-s.ShutdownCh:
			return shutdownEvent
		}

	// time for a full sync again
	case <-time.After(s.Interval + s.stagger(s.Interval)):
		return syncFullTimerEvent

	// do partial syncs on demand
	case <-s.SyncChanges.Notif():
		return syncChangesNotifEvent

	case <-s.ShutdownCh:
		return shutdownEvent
	}
}

// stubbed out for testing
var libRandomStagger = lib.RandomStagger

// staggerFn returns a random duration which depends on the cluster size
// and a random factor which should provide some timely distribution of
// cluster wide events. This function should not be called directly
// but through s.stagger to allow mocking for testing.
func (s *StateSyncer) staggerFn(d time.Duration) time.Duration {
	f := scaleFactor(s.ClusterSize())
	return libRandomStagger(time.Duration(f) * d)
}

// Pause temporarily disables sync runs.
func (s *StateSyncer) Pause() {
	s.pauseLock.Lock()
	s.paused++
	s.pauseLock.Unlock()
}

// Paused returns whether sync runs are temporarily disabled.
func (s *StateSyncer) Paused() bool {
	s.pauseLock.Lock()
	defer s.pauseLock.Unlock()
	return s.paused != 0
}

// Resume re-enables sync runs.
func (s *StateSyncer) Resume() {
	s.pauseLock.Lock()
	s.paused--
	if s.paused < 0 {
		panic("unbalanced pause/resume")
	}
	if s.paused == 0 {
		s.SyncChanges.Trigger()
	}
	s.pauseLock.Unlock()
}
<commit_msg>ae: do not trigger on Resume while holding the lock<commit_after>// Package ae provides tools to synchronize state between local and remote consul servers.
package ae

import (
	"fmt"
	"log"
	"math"
	"sync"
	"time"

	"github.com/hashicorp/consul/lib"
)

// scaleThreshold is the number of nodes after which regular sync runs are
// spread out farther apart. The value should be a power of 2 since the
// scale function uses log2.
//
// When set to 128 nodes the delay between regular runs is doubled when the
// cluster is larger than 128 nodes. It doubles again when it passes 256
// nodes, and again at 512 nodes and so forth. At 8192 nodes, the delay
// factor is 8.
//
// If you update this, you may need to adjust the tuning of
// CoordinateUpdatePeriod and CoordinateUpdateMaxBatchSize.
const scaleThreshold = 128

// scaleFactor returns a factor by which the next sync run should be delayed to
// avoid saturation of the cluster. The larger the cluster grows the farther
// the sync runs should be spread apart.
//
// The current implementation uses a log2 scale which doubles the delay between
// runs every time the cluster doubles in size.
func scaleFactor(nodes int) int {
	if nodes <= scaleThreshold {
		return 1.0
	}
	return int(math.Ceil(math.Log2(float64(nodes))-math.Log2(float64(scaleThreshold))) + 1.0)
}

type SyncState interface {
	SyncChanges() error
	SyncFull() error
}

// StateSyncer manages background synchronization of the given state.
//
// The state is synchronized on a regular basis or on demand when either
// the state has changed or a new Consul server has joined the cluster.
//
// The regular state sychronization provides a self-healing mechanism
// for the cluster which is also called anti-entropy.
type StateSyncer struct {
	// State contains the data that needs to be synchronized.
	State SyncState

	// Interval is the time between two full sync runs.
	Interval time.Duration

	// ShutdownCh is closed when the application is shutting down.
	ShutdownCh chan struct{}

	// Logger is the logger.
	Logger *log.Logger

	// ClusterSize returns the number of members in the cluster to
	// allow staggering the sync runs based on cluster size.
	// This needs to be set before Run() is called.
	ClusterSize func() int

	// SyncFull allows triggering an immediate but staggered full sync
	// in a non-blocking way.
	SyncFull *Trigger

	// SyncChanges allows triggering an immediate partial sync
	// in a non-blocking way.
	SyncChanges *Trigger

	// paused stores whether sync runs are temporarily disabled.
	pauseLock sync.Mutex
	paused    int

	// serverUpInterval is the max time after which a full sync is
	// performed when a server has been added to the cluster.
	serverUpInterval time.Duration

	// retryFailInterval is the time after which a failed full sync is retried.
	retryFailInterval time.Duration

	// stagger randomly picks a duration between 0s and the given duration.
	stagger func(time.Duration) time.Duration

	// retrySyncFullEvent generates an event based on multiple conditions
	// when the state machine is trying to retry a full state sync.
	retrySyncFullEvent func() event

	// syncChangesEvent generates an event based on multiple conditions
	// when the state machine is performing partial state syncs.
	syncChangesEvent func() event
}

const (
	// serverUpIntv is the max time to wait before a sync is triggered
	// when a consul server has been added to the cluster.
	serverUpIntv = 3 * time.Second

	// retryFailIntv is the min time to wait before a failed sync is retried.
	retryFailIntv = 15 * time.Second
)

func NewStateSyncer(state SyncState, intv time.Duration, shutdownCh chan struct{}, logger *log.Logger) *StateSyncer {
	s := &StateSyncer{
		State:             state,
		Interval:          intv,
		ShutdownCh:        shutdownCh,
		Logger:            logger,
		SyncFull:          NewTrigger(),
		SyncChanges:       NewTrigger(),
		serverUpInterval:  serverUpIntv,
		retryFailInterval: retryFailIntv,
	}

	// retain these methods as member variables so that
	// we can mock them for testing.
	s.retrySyncFullEvent = s.retrySyncFullEventFn
	s.syncChangesEvent = s.syncChangesEventFn
	s.stagger = s.staggerFn

	return s
}

// fsmState defines states for the state machine.
type fsmState string

const (
	doneState          fsmState = "done"
	fullSyncState      fsmState = "fullSync"
	partialSyncState   fsmState = "partialSync"
	retryFullSyncState fsmState = "retryFullSync"
)

// Run is the long running method to perform state synchronization
// between local and remote servers.
func (s *StateSyncer) Run() {
	if s.ClusterSize == nil {
		panic("ClusterSize not set")
	}
	s.runFSM(fullSyncState, s.nextFSMState)
}

// runFSM runs the state machine.
func (s *StateSyncer) runFSM(fs fsmState, next func(fsmState) fsmState) {
	for {
		if fs = next(fs); fs == doneState {
			return
		}
	}
}

// nextFSMState determines the next state based on the current state.
func (s *StateSyncer) nextFSMState(fs fsmState) fsmState {
	switch fs {
	case fullSyncState:
		if s.Paused() {
			return retryFullSyncState
		}

		err := s.State.SyncFull()
		if err != nil {
			s.Logger.Printf("[ERR] agent: failed to sync remote state: %v", err)
			return retryFullSyncState
		}

		return partialSyncState

	case retryFullSyncState:
		e := s.retrySyncFullEvent()
		switch e {
		case syncFullNotifEvent, syncFullTimerEvent:
			return fullSyncState
		case shutdownEvent:
			return doneState
		default:
			panic(fmt.Sprintf("invalid event: %s", e))
		}

	case partialSyncState:
		e := s.syncChangesEvent()
		switch e {
		case syncFullNotifEvent, syncFullTimerEvent:
			return fullSyncState

		case syncChangesNotifEvent:
			if s.Paused() {
				return partialSyncState
			}

			err := s.State.SyncChanges()
			if err != nil {
				s.Logger.Printf("[ERR] agent: failed to sync changes: %v", err)
			}
			return partialSyncState

		case shutdownEvent:
			return doneState

		default:
			panic(fmt.Sprintf("invalid event: %s", e))
		}

	default:
		panic(fmt.Sprintf("invalid state: %s", fs))
	}
}

// event defines a timing or notification event from multiple timers and
// channels.
type event string

const (
	shutdownEvent         event = "shutdown"
	syncFullNotifEvent    event = "syncFullNotif"
	syncFullTimerEvent    event = "syncFullTimer"
	syncChangesNotifEvent event = "syncChangesNotif"
)

// retrySyncFullEventFn waits for an event which triggers a retry
// of a full sync or a termination signal. This function should not be
// called directly but through s.retryFullSyncState to allow mocking for
// testing.
func (s *StateSyncer) retrySyncFullEventFn() event {
	select {
	// trigger a full sync immediately.
	// this is usually called when a consul server was added to the cluster.
	// stagger the delay to avoid a thundering herd.
	case <-s.SyncFull.Notif():
		select {
		case <-time.After(s.stagger(s.serverUpInterval)):
			return syncFullNotifEvent
		case <-s.ShutdownCh:
			return shutdownEvent
		}

	// retry full sync after some time
	// todo(fs): why don't we use s.Interval here?
	case <-time.After(s.retryFailInterval + s.stagger(s.retryFailInterval)):
		return syncFullTimerEvent

	case <-s.ShutdownCh:
		return shutdownEvent
	}
}

// syncChangesEventFn waits for a event which either triggers a full
// or a partial sync or a termination signal. This function should not
// be called directly but through s.syncChangesEvent to allow mocking
// for testing.
func (s *StateSyncer) syncChangesEventFn() event {
	select {
	// trigger a full sync immediately
	// this is usually called when a consul server was added to the cluster.
	// stagger the delay to avoid a thundering herd.
	case <-s.SyncFull.Notif():
		select {
		case <-time.After(s.stagger(s.serverUpInterval)):
			return syncFullNotifEvent
		case <-s.ShutdownCh:
			return shutdownEvent
		}

	// time for a full sync again
	case <-time.After(s.Interval + s.stagger(s.Interval)):
		return syncFullTimerEvent

	// do partial syncs on demand
	case <-s.SyncChanges.Notif():
		return syncChangesNotifEvent

	case <-s.ShutdownCh:
		return shutdownEvent
	}
}

// stubbed out for testing
var libRandomStagger = lib.RandomStagger

// staggerFn returns a random duration which depends on the cluster size
// and a random factor which should provide some timely distribution of
// cluster wide events. This function should not be called directly
// but through s.stagger to allow mocking for testing.
func (s *StateSyncer) staggerFn(d time.Duration) time.Duration {
	f := scaleFactor(s.ClusterSize())
	return libRandomStagger(time.Duration(f) * d)
}

// Pause temporarily disables sync runs.
func (s *StateSyncer) Pause() {
	s.pauseLock.Lock()
	s.paused++
	s.pauseLock.Unlock()
}

// Paused returns whether sync runs are temporarily disabled.
func (s *StateSyncer) Paused() bool {
	s.pauseLock.Lock()
	defer s.pauseLock.Unlock()
	return s.paused != 0
}

// Resume re-enables sync runs.
func (s *StateSyncer) Resume() {
	s.pauseLock.Lock()
	s.paused--
	if s.paused < 0 {
		panic("unbalanced pause/resume")
	}
	trigger := s.paused == 0
	s.pauseLock.Unlock()
	if trigger {
		s.SyncChanges.Trigger()
	}
}
<|endoftext|>


<commit_before>package agent

import (
	"bytes"
	"crypto/rsa"
	"crypto/x509"
	"encoding/json"
	"encoding/pem"
	"errors"
	"fmt"
	"os"
	"strconv"

	"github.com/lestrrat/go-jwx/jwk"
)

var StorePrefix = "aaa-data"

/*
prefix: {{letsencrypt-base}}/aaa-agent/

Per Store instance:
{{email}}/info
	- {{email}}.json -- the registration info
	- {{email}}.jwk  -- the account private key in JWK

{{email}}/domain/{{domain}}/
    - authz.json    -- the authorization result
	- privkey.jwk   -- the private key in JWK
	- fullchain.pem -- the cert + intermediates
	- cert.pem      -- the cert only
	- chain.pem     -- the intermediate only
*/

type Store struct {
	email  string
	filer  Filer
	prefix string
}

func NewStore(email string, filer Filer) (*Store, error) {
	if email == "" {
		return nil, errors.New("aaa: email must not be empty")
	}

	if debug, _ := strconv.ParseBool(os.Getenv("AAA_DEBUG")); debug {
		filer = new(OSFiler)
	}

	s := &Store{
		email:  email,
		filer:  filer,
		prefix: StorePrefix,
	}

	return s, nil
}

// LoadKey returns RSA private key in JWK.
func (s *Store) LoadPrivateKey() (jwk.Key, error) {
	blob, err := s.filer.ReadFile(s.joinPrefix("info", s.email+".jwk"))
	if err != nil {
		return nil, err
	}

	keyset, err := jwk.Parse(blob)
	if err != nil {
		return nil, err
	}
	if len(keyset.Keys) == 0 {
		return nil, errors.New("aaa: no key found")
	}

	return keyset.Keys[0], nil
}

func (s *Store) LoadPublicKey() (jwk.Key, error) {
	// LE currently supports RS256 only
	privkey, err := s.LoadPrivateKey()
	if err != nil {
		return nil, err
	}

	key, err := privkey.Materialize()
	if err != nil {
		return nil, err
	}

	rsaPrivKey, ok := key.(*rsa.PrivateKey)
	if !ok {
		return nil, fmt.Errorf("aaa: key is not *rsa.PrivateKey but %v", privkey)
	}

	// restore its public key from the private key
	return jwk.NewRsaPublicKey(&rsaPrivKey.PublicKey)
}

func (s *Store) SaveKey(privateKey jwk.Key) error {
	blob, err := json.Marshal(privateKey)
	if err != nil {
		return err
	}

	return s.filer.WriteFile(s.joinPrefix("info", s.email+".jwk"), blob)
}

func (s *Store) SaveCertKey(domain string, privateKey jwk.Key) error {
	key, err := privateKey.Materialize()
	if err != nil {
		return err
	}

	rsaPrivKey, ok := key.(*rsa.PrivateKey)
	if !ok {
		return fmt.Errorf("aaa: key is not *rsa.PrivateKey but %v", privateKey)
	}

	buf := new(bytes.Buffer)
	if err := pem.Encode(buf, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rsaPrivKey)}); err != nil {
		return err
	}
	return s.filer.WriteFile(s.joinPrefix("domain", domain, "privkey.pem"), buf.Bytes())
}

func (s *Store) LoadCertKey(domain string) (jwk.Key, error) {
	blob, err := s.filer.ReadFile(s.joinPrefix("domain", domain, "privkey.jwk"))
	if err != nil {
		return nil, err
	}

	keyset, err := jwk.Parse(blob)
	if err != nil {
		return nil, err
	}
	if len(keyset.Keys) == 0 {
		return nil, errors.New("aaa: no key found")
	}

	return keyset.Keys[0], nil
}

func (s *Store) LoadCert(domain string) (*x509.Certificate, error) {
	blob, err := s.filer.ReadFile(s.joinPrefix("domain", domain, "cert.pem"))
	if err != nil {
		return nil, err
	}

	block, _ := pem.Decode(blob)
	return x509.ParseCertificate(block.Bytes)
}

func (s *Store) SaveCert(domain string, issuerCert, myCert *x509.Certificate) error {
	buf := new(bytes.Buffer)

	for _, f := range []struct {
		Name  string
		Certs []*x509.Certificate
	}{
		{
			Name:  "fullchain.pem",
			Certs: []*x509.Certificate{issuerCert, myCert},
		},
		{
			Name:  "cert.pem",
			Certs: []*x509.Certificate{myCert},
		},
		{
			Name:  "chain.pem",
			Certs: []*x509.Certificate{issuerCert},
		},
	} {
		for _, cert := range f.Certs {
			if err := pem.Encode(buf, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}); err != nil {
				return err
			}
		}

		if err := s.filer.WriteFile(s.joinPrefix("domain", domain, f.Name), buf.Bytes()); err != nil {
			return err
		}

		buf.Reset()
	}

	return nil
}

func (s *Store) LoadAuthorization(domain string) (*Authorization, error) {
	blob, err := s.filer.ReadFile(s.joinPrefix("domain", domain, "authz.json"))
	if err != nil {
		return nil, err
	}

	authz := &Authorization{}
	if err := json.Unmarshal(blob, authz); err != nil {
		return nil, err
	}

	return authz, nil
}

func (s *Store) SaveAuthorization(authz *Authorization) error {
	blob, err := json.Marshal(authz)
	if err != nil {
		return err
	}

	domain := authz.Identifier.Value
	return s.filer.WriteFile(s.joinPrefix("domain", domain, "authz.json"), blob)
}

func (s *Store) ListDomains() ([]string, error) {
	dirs, err := s.filer.ListDir(s.joinPrefix("domain"))
	if err != nil {
		return nil, err
	}

	domains := make([]string, 0, len(dirs))
	for _, dir := range dirs {
		elem := s.filer.Split(dir)

		// domain is in 4th element
		if len(elem) > 3 {
			domains = append(domains, elem[3])
		}
	}
	return domains, nil
}

func (s *Store) SaveAccount(account *Account) error {
	blob, err := json.Marshal(account)
	if err != nil {
		return err
	}

	return s.filer.WriteFile(s.joinPrefix("info", s.email+".json"), blob)
}

func (s *Store) LoadAccount() (*Account, error) {
	blob, err := s.filer.ReadFile(s.joinPrefix("info", s.email+".json"))
	if err != nil {
		return nil, err
	}

	account := &Account{}
	if err := json.Unmarshal(blob, account); err != nil {
		return nil, err
	}

	return account, nil
}

func (s *Store) joinPrefix(fns ...string) string {
	return s.filer.Join(append([]string{s.prefix, s.email}, fns...)...)
}
<commit_msg>fullchain must be in correct order [myCert, issuerCert]<commit_after>package agent

import (
	"bytes"
	"crypto/rsa"
	"crypto/x509"
	"encoding/json"
	"encoding/pem"
	"errors"
	"fmt"
	"os"
	"strconv"

	"github.com/lestrrat/go-jwx/jwk"
)

var StorePrefix = "aaa-data"

/*
prefix: {{letsencrypt-base}}/aaa-agent/

Per Store instance:
{{email}}/info
	- {{email}}.json -- the registration info
	- {{email}}.jwk  -- the account private key in JWK

{{email}}/domain/{{domain}}/
    - authz.json    -- the authorization result
	- privkey.jwk   -- the private key in JWK
	- fullchain.pem -- the cert + intermediates
	- cert.pem      -- the cert only
	- chain.pem     -- the intermediate only
*/

type Store struct {
	email  string
	filer  Filer
	prefix string
}

func NewStore(email string, filer Filer) (*Store, error) {
	if email == "" {
		return nil, errors.New("aaa: email must not be empty")
	}

	if debug, _ := strconv.ParseBool(os.Getenv("AAA_DEBUG")); debug {
		filer = new(OSFiler)
	}

	s := &Store{
		email:  email,
		filer:  filer,
		prefix: StorePrefix,
	}

	return s, nil
}

// LoadKey returns RSA private key in JWK.
func (s *Store) LoadPrivateKey() (jwk.Key, error) {
	blob, err := s.filer.ReadFile(s.joinPrefix("info", s.email+".jwk"))
	if err != nil {
		return nil, err
	}

	keyset, err := jwk.Parse(blob)
	if err != nil {
		return nil, err
	}
	if len(keyset.Keys) == 0 {
		return nil, errors.New("aaa: no key found")
	}

	return keyset.Keys[0], nil
}

func (s *Store) LoadPublicKey() (jwk.Key, error) {
	// LE currently supports RS256 only
	privkey, err := s.LoadPrivateKey()
	if err != nil {
		return nil, err
	}

	key, err := privkey.Materialize()
	if err != nil {
		return nil, err
	}

	rsaPrivKey, ok := key.(*rsa.PrivateKey)
	if !ok {
		return nil, fmt.Errorf("aaa: key is not *rsa.PrivateKey but %v", privkey)
	}

	// restore its public key from the private key
	return jwk.NewRsaPublicKey(&rsaPrivKey.PublicKey)
}

func (s *Store) SaveKey(privateKey jwk.Key) error {
	blob, err := json.Marshal(privateKey)
	if err != nil {
		return err
	}

	return s.filer.WriteFile(s.joinPrefix("info", s.email+".jwk"), blob)
}

func (s *Store) SaveCertKey(domain string, privateKey jwk.Key) error {
	key, err := privateKey.Materialize()
	if err != nil {
		return err
	}

	rsaPrivKey, ok := key.(*rsa.PrivateKey)
	if !ok {
		return fmt.Errorf("aaa: key is not *rsa.PrivateKey but %v", privateKey)
	}

	buf := new(bytes.Buffer)
	if err := pem.Encode(buf, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rsaPrivKey)}); err != nil {
		return err
	}
	return s.filer.WriteFile(s.joinPrefix("domain", domain, "privkey.pem"), buf.Bytes())
}

func (s *Store) LoadCertKey(domain string) (jwk.Key, error) {
	blob, err := s.filer.ReadFile(s.joinPrefix("domain", domain, "privkey.jwk"))
	if err != nil {
		return nil, err
	}

	keyset, err := jwk.Parse(blob)
	if err != nil {
		return nil, err
	}
	if len(keyset.Keys) == 0 {
		return nil, errors.New("aaa: no key found")
	}

	return keyset.Keys[0], nil
}

func (s *Store) LoadCert(domain string) (*x509.Certificate, error) {
	blob, err := s.filer.ReadFile(s.joinPrefix("domain", domain, "cert.pem"))
	if err != nil {
		return nil, err
	}

	block, _ := pem.Decode(blob)
	return x509.ParseCertificate(block.Bytes)
}

func (s *Store) SaveCert(domain string, issuerCert, myCert *x509.Certificate) error {
	buf := new(bytes.Buffer)

	for _, f := range []struct {
		Name  string
		Certs []*x509.Certificate
	}{
		{
			Name:  "fullchain.pem",
			Certs: []*x509.Certificate{myCert, issuerCert},
		},
		{
			Name:  "cert.pem",
			Certs: []*x509.Certificate{myCert},
		},
		{
			Name:  "chain.pem",
			Certs: []*x509.Certificate{issuerCert},
		},
	} {
		for _, cert := range f.Certs {
			if err := pem.Encode(buf, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}); err != nil {
				return err
			}
		}

		if err := s.filer.WriteFile(s.joinPrefix("domain", domain, f.Name), buf.Bytes()); err != nil {
			return err
		}

		buf.Reset()
	}

	return nil
}

func (s *Store) LoadAuthorization(domain string) (*Authorization, error) {
	blob, err := s.filer.ReadFile(s.joinPrefix("domain", domain, "authz.json"))
	if err != nil {
		return nil, err
	}

	authz := &Authorization{}
	if err := json.Unmarshal(blob, authz); err != nil {
		return nil, err
	}

	return authz, nil
}

func (s *Store) SaveAuthorization(authz *Authorization) error {
	blob, err := json.Marshal(authz)
	if err != nil {
		return err
	}

	domain := authz.Identifier.Value
	return s.filer.WriteFile(s.joinPrefix("domain", domain, "authz.json"), blob)
}

func (s *Store) ListDomains() ([]string, error) {
	dirs, err := s.filer.ListDir(s.joinPrefix("domain"))
	if err != nil {
		return nil, err
	}

	domains := make([]string, 0, len(dirs))
	for _, dir := range dirs {
		elem := s.filer.Split(dir)

		// domain is in 4th element
		if len(elem) > 3 {
			domains = append(domains, elem[3])
		}
	}
	return domains, nil
}

func (s *Store) SaveAccount(account *Account) error {
	blob, err := json.Marshal(account)
	if err != nil {
		return err
	}

	return s.filer.WriteFile(s.joinPrefix("info", s.email+".json"), blob)
}

func (s *Store) LoadAccount() (*Account, error) {
	blob, err := s.filer.ReadFile(s.joinPrefix("info", s.email+".json"))
	if err != nil {
		return nil, err
	}

	account := &Account{}
	if err := json.Unmarshal(blob, account); err != nil {
		return nil, err
	}

	return account, nil
}

func (s *Store) joinPrefix(fns ...string) string {
	return s.filer.Join(append([]string{s.prefix, s.email}, fns...)...)
}
<|endoftext|>