1package commands
2
3import (
4	"errors"
5	"fmt"
6	"io"
7	"io/ioutil"
8	"os"
9	"path/filepath"
10	"sort"
11	"strings"
12
13	"github.com/jesseduffield/horcrux/pkg/multiplexing"
14	"github.com/jesseduffield/horcrux/pkg/shamir"
15)
16
17func GetHorcruxPathsInDir(dir string) ([]string, error) {
18	files, err := ioutil.ReadDir(dir)
19	if err != nil {
20		return nil, err
21	}
22
23	paths := []string{}
24	for _, file := range files {
25		if filepath.Ext(file.Name()) == ".horcrux" {
26			paths = append(paths, file.Name())
27		}
28	}
29
30	return paths, nil
31}
32
33type byIndex []Horcrux
34
35func (h byIndex) Len() int {
36	return len(h)
37}
38
39func (h byIndex) Swap(i, j int) {
40	h[i], h[j] = h[j], h[i]
41}
42
43func (h byIndex) Less(i, j int) bool {
44	return h[i].GetHeader().Index < h[j].GetHeader().Index
45}
46
47func GetHorcruxes(paths []string) ([]Horcrux, error) {
48	horcruxes := []Horcrux{}
49
50	for _, path := range paths {
51		currentHorcrux, err := NewHorcrux(path)
52		if err != nil {
53			return nil, err
54		}
55		for _, horcrux := range horcruxes {
56			if horcrux.GetHeader().Index == currentHorcrux.GetHeader().Index && horcrux.GetHeader().OriginalFilename == currentHorcrux.GetHeader().OriginalFilename {
57				// we've already obtained this horcrux so we'll skip this instance
58				continue
59			}
60		}
61
62		horcruxes = append(horcruxes, *currentHorcrux)
63	}
64
65	sort.Sort(byIndex(horcruxes))
66
67	return horcruxes, nil
68}
69
70func ValidateHorcruxes(horcruxes []Horcrux) error {
71	if len(horcruxes) == 0 {
72		return errors.New("No horcruxes supplied")
73	}
74
75	if len(horcruxes) < horcruxes[0].GetHeader().Threshold {
76		return fmt.Errorf(
77			"You do not have all the required horcruxes. There are %d required to resurrect the original file. You only have %d",
78			horcruxes[0].GetHeader().Threshold,
79			len(horcruxes),
80		)
81	}
82
83	for _, horcrux := range horcruxes {
84		if !strings.HasSuffix(horcrux.GetPath(), ".horcrux") {
85			return fmt.Errorf("%s is not a horcrux file (requires .horcrux extension)", horcrux.GetPath())
86		}
87		if horcrux.GetHeader().OriginalFilename != horcruxes[0].GetHeader().OriginalFilename || horcrux.GetHeader().Timestamp != horcruxes[0].GetHeader().Timestamp {
88			return errors.New("All horcruxes in the given directory must have the same original filename and timestamp.")
89		}
90	}
91
92	return nil
93}
94
95func Bind(paths []string, dstPath string, overwrite bool) error {
96	horcruxes, err := GetHorcruxes(paths)
97	if err != nil {
98		return err
99	}
100
101	if err := ValidateHorcruxes(horcruxes); err != nil {
102		return err
103	}
104
105	firstHorcrux := horcruxes[0]
106
107	// if dstPath is empty we use the original filename
108	if dstPath == "" {
109		cwd, err := os.Getwd()
110		if err != nil {
111			return err
112		}
113		dstPath = filepath.Join(cwd, firstHorcrux.GetHeader().OriginalFilename)
114	}
115
116	if fileExists(dstPath) && !overwrite {
117		return os.ErrExist
118	}
119
120	keyFragments := make([][]byte, len(horcruxes))
121	for i := range keyFragments {
122		keyFragments[i] = horcruxes[i].GetHeader().KeyFragment
123	}
124
125	key, err := shamir.Combine(keyFragments)
126	if err != nil {
127		return err
128	}
129
130	var fileReader io.Reader
131	if firstHorcrux.GetHeader().Total == firstHorcrux.GetHeader().Threshold {
132		horcruxFiles := make([]*os.File, len(horcruxes))
133		for i, horcrux := range horcruxes {
134			horcruxFiles[i] = horcrux.GetFile()
135		}
136
137		fileReader = &multiplexing.Multiplexer{Readers: horcruxFiles}
138	} else {
139		fileReader = firstHorcrux.GetFile() // arbitrarily read from the first horcrux: they all contain the same contents
140	}
141
142	reader := cryptoReader(fileReader, key)
143
144	_ = os.Truncate(dstPath, 0)
145
146	newFile, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE, 0644)
147	if err != nil {
148		return err
149	}
150	defer newFile.Close()
151
152	_, err = io.Copy(newFile, reader)
153	if err != nil {
154		return err
155	}
156
157	return err
158}
159