1//Command line tool capable of steganography encoding and decoding any file within given images as carriers
2package main
3
4import (
5	"flag"
6	"fmt"
7	"github.com/DimitarPetrov/stegify/steg"
8	"os"
9	"strings"
10)
11
12const encode = "encode"
13const decode = "decode"
14
15type sliceFlag []string
16
17func (sf *sliceFlag) String() string {
18	return strings.Join(*sf, " ")
19}
20
21func (sf *sliceFlag) Set(value string) error {
22	*sf = append(*sf, value)
23	return nil
24}
25
26var carrierFilesSlice sliceFlag
27var carrierFiles = flag.String("carriers", "", "carrier files in which the data is encoded (separated by space)")
28var dataFile = flag.String("data", "", "data file which is being encoded in the carrier")
29var resultFilesSlice sliceFlag
30var resultFiles = flag.String("results", "", "names of the result files (separated by space)")
31
32func init() {
33	flag.StringVar(carrierFiles, "c", "", "carrier files in which the data is encoded (separated by space, shorthand for --carriers)")
34	flag.Var(&carrierFilesSlice, "carrier", "carrier file in which the data is encoded (could be used multiple times for multiple carriers)")
35	flag.StringVar(dataFile, "d", "", "data file which is being encoded in the carrier (shorthand for --data)")
36	flag.Var(&resultFilesSlice, "result", "name of the result file (could be used multiple times for multiple result file names)")
37	flag.StringVar(resultFiles, "r", "", "names of the result files (separated by space, shorthand for --results)")
38
39	flag.Usage = func() {
40		fmt.Fprintln(os.Stdout, "Usage: stegify [encode/decode] [flags...]")
41		flag.PrintDefaults()
42		fmt.Fprintln(os.Stdout, `NOTE: When multiple carriers are provided with different kinds of flags, the names provided through "carrier" flag are taken first and with "carriers"/"c" flags second. Same goes for the "result"/"results" flags.`)
43		fmt.Fprintln(os.Stdout, `NOTE: When no results are provided a default values will be used for the names of the results.`)
44	}
45}
46
47func main() {
48	operation := parseOperation()
49	flag.Parse()
50	carriers := parseCarriers()
51	results := parseResults()
52
53	switch operation {
54	case encode:
55		if len(results) == 0 { // if no results provided use defaults
56			for i := range carriers {
57				results = append(results, fmt.Sprintf("result%d", i))
58			}
59		}
60		if len(results) != len(carriers) {
61			fmt.Fprintln(os.Stderr, "Carrier and result files count must be equal when encoding.")
62			os.Exit(1)
63		}
64		if dataFile == nil || *dataFile == "" {
65			fmt.Fprintln(os.Stderr, "Data file must be specified. Use stegify --help for more information.")
66			os.Exit(1)
67		}
68
69		err := steg.MultiCarrierEncodeByFileNames(carriers, *dataFile, results)
70		if err != nil {
71			fmt.Fprintln(os.Stderr, err)
72			os.Exit(1)
73		}
74	case decode:
75		if len(results) == 0 { // if no result provided use default
76			results = append(results, "result")
77		}
78		if len(results) != 1 {
79			fmt.Fprintln(os.Stderr, "Only one result file expected.")
80			os.Exit(1)
81		}
82		err := steg.MultiCarrierDecodeByFileNames(carriers, results[0])
83		if err != nil {
84			fmt.Fprintln(os.Stderr, err)
85			os.Exit(1)
86		}
87	}
88}
89
90func parseOperation() string {
91	if len(os.Args) < 2 {
92		fmt.Fprintln(os.Stderr, "Operation must be specified [encode/decode]. Use stegify --help for more information.")
93		os.Exit(1)
94	}
95	operation := os.Args[1]
96	if operation != encode && operation != decode {
97		helpFlags := map[string]bool{
98			"--help": true,
99			"-help":  true,
100			"--h":    true,
101			"-h":     true,
102		}
103		if helpFlags[operation] {
104			flag.Parse()
105			os.Exit(0)
106		}
107		fmt.Fprintf(os.Stderr, "Unsupported operation: %s. Only [encode/decode] operations are supported.\n Use stegify --help for more information.", operation)
108		os.Exit(1)
109	}
110
111	os.Args = append(os.Args[:1], os.Args[2:]...) // needed because go flags implementation stop parsing after first non-flag argument
112	return operation
113}
114
115func parseCarriers() []string {
116	carriers := make([]string, 0)
117	if len(carrierFilesSlice) != 0 {
118		carriers = append(carriers, carrierFilesSlice...)
119	}
120
121	if len(*carrierFiles) != 0 {
122		carriers = append(carriers, strings.Split(*carrierFiles, " ")...)
123	}
124
125	if len(carriers) == 0 {
126		fmt.Fprintln(os.Stderr, "Carrier file must be specified. Use stegify --help for more information.")
127		os.Exit(1)
128	}
129
130	return carriers
131}
132
133func parseResults() []string {
134	results := make([]string, 0)
135	if len(resultFilesSlice) != 0 {
136		results = append(results, resultFilesSlice...)
137	}
138
139	if len(*resultFiles) != 0 {
140		results = append(results, strings.Split(*resultFiles, " ")...)
141	}
142
143	return results
144}
145