Skip to content

Commit 5fcd441

Browse files
committed
Fix CI: Add missing source files and update .gitignore
- Add cli.go, config.go, handlers.go, web_stub.go to repository - Add web.go and test files - Fix .gitignore to only ignore binary in root, not cmd/file-encryptor directory - This resolves undefined function errors in GitHub Actions CI
1 parent ea61264 commit 5fcd441

File tree

9 files changed

+2582
-1
lines changed

9 files changed

+2582
-1
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*.dll
55
*.so
66
*.dylib
7-
file-encryptor
7+
/file-encryptor
88
bin/
99
dist/
1010

cmd/file-encryptor/cli.go

Lines changed: 366 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,366 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
"strings"
9+
"time"
10+
)
11+
12+
// stringSliceFlag implements flag.Value for handling multiple file arguments
13+
type stringSliceFlag []string
14+
15+
func (s *stringSliceFlag) String() string {
16+
return strings.Join(*s, ",")
17+
}
18+
19+
func (s *stringSliceFlag) Set(value string) error {
20+
*s = append(*s, value)
21+
return nil
22+
}
23+
24+
// CLIArgs holds parsed command line arguments
25+
type CLIArgs struct {
26+
// Operations
27+
Encrypt bool
28+
Decrypt bool
29+
GenerateKeys bool
30+
31+
// Files and keys
32+
Files []string
33+
Key string
34+
Password string
35+
KeyBaseName string
36+
37+
// Configuration
38+
ConfigFile string
39+
Timeout time.Duration
40+
41+
// Output options
42+
Verbose bool
43+
Quiet bool
44+
45+
// Web UI options
46+
WebUI bool
47+
WebPort int
48+
WebHost string
49+
WebTLS bool
50+
CertFile string
51+
KeyFile string
52+
53+
// Additional options
54+
ShowConfig bool
55+
SaveConfig string
56+
ShowVersion bool
57+
ShowHelp bool
58+
}
59+
60+
// ParseCLI parses command line arguments and returns CLIArgs
61+
func ParseCLI() (*CLIArgs, error) {
62+
args := &CLIArgs{}
63+
var files stringSliceFlag
64+
65+
// Define flags
66+
flag.BoolVar(&args.Encrypt, "e", false, "Encrypt the file(s)")
67+
flag.BoolVar(&args.Encrypt, "encrypt", false, "Encrypt the file(s)")
68+
69+
flag.BoolVar(&args.Decrypt, "d", false, "Decrypt the file(s)")
70+
flag.BoolVar(&args.Decrypt, "decrypt", false, "Decrypt the file(s)")
71+
72+
flag.Var(&files, "file", "Files to encrypt or decrypt (can be specified multiple times)")
73+
flag.Var(&files, "f", "Files to encrypt or decrypt (shorthand)")
74+
75+
flag.StringVar(&args.Key, "key", "", "Path to the key file")
76+
flag.StringVar(&args.Key, "k", "", "Path to the key file (shorthand)")
77+
78+
flag.StringVar(&args.Password, "password", "", "Password for encryption/decryption")
79+
flag.StringVar(&args.Password, "p", "", "Password for encryption/decryption (shorthand)")
80+
81+
flag.BoolVar(&args.GenerateKeys, "generate-keys", false, "Generate a new RSA key pair")
82+
flag.StringVar(&args.KeyBaseName, "key-name", "key", "Base name for the generated key files")
83+
84+
flag.StringVar(&args.ConfigFile, "config", "", "Path to configuration file")
85+
flag.StringVar(&args.ConfigFile, "c", "", "Path to configuration file (shorthand)")
86+
87+
flag.DurationVar(&args.Timeout, "timeout", 0, "Timeout for the entire operation")
88+
flag.DurationVar(&args.Timeout, "t", 0, "Timeout for the entire operation (shorthand)")
89+
90+
flag.BoolVar(&args.Verbose, "verbose", false, "Enable verbose output")
91+
flag.BoolVar(&args.Verbose, "v", false, "Enable verbose output (shorthand)")
92+
93+
flag.BoolVar(&args.Quiet, "quiet", false, "Suppress non-error output")
94+
flag.BoolVar(&args.Quiet, "q", false, "Suppress non-error output (shorthand)")
95+
96+
flag.BoolVar(&args.ShowConfig, "show-config", false, "Show current configuration and exit")
97+
flag.StringVar(&args.SaveConfig, "save-config", "", "Save current configuration to file")
98+
99+
flag.BoolVar(&args.WebUI, "web", false, "Start web UI server")
100+
flag.IntVar(&args.WebPort, "web-port", 8080, "Port for web UI server")
101+
flag.StringVar(&args.WebHost, "web-host", "localhost", "Host for web UI server")
102+
flag.BoolVar(&args.WebTLS, "web-tls", false, "Enable TLS for web UI")
103+
flag.StringVar(&args.CertFile, "cert-file", "", "TLS certificate file")
104+
flag.StringVar(&args.KeyFile, "key-file", "", "TLS private key file")
105+
106+
flag.BoolVar(&args.ShowVersion, "version", false, "Show version information")
107+
flag.BoolVar(&args.ShowHelp, "help", false, "Show help information")
108+
flag.BoolVar(&args.ShowHelp, "h", false, "Show help information (shorthand)")
109+
110+
// Custom usage function
111+
flag.Usage = func() {
112+
fmt.Fprintf(os.Stderr, "File Encryptor - Secure file encryption tool\n\n")
113+
fmt.Fprintf(os.Stderr, "Usage: %s [options] [files...]\n\n", os.Args[0])
114+
fmt.Fprintf(os.Stderr, "Operations:\n")
115+
fmt.Fprintf(os.Stderr, " -e, --encrypt Encrypt the specified files\n")
116+
fmt.Fprintf(os.Stderr, " -d, --decrypt Decrypt the specified files\n")
117+
fmt.Fprintf(os.Stderr, " --generate-keys Generate a new RSA key pair\n")
118+
fmt.Fprintf(os.Stderr, "\nFiles and Keys:\n")
119+
fmt.Fprintf(os.Stderr, " -f, --file FILE Files to process (can be used multiple times)\n")
120+
fmt.Fprintf(os.Stderr, " -k, --key FILE Path to key file (public for encrypt, private for decrypt)\n")
121+
fmt.Fprintf(os.Stderr, " -p, --password PASS Password for encryption/decryption\n")
122+
fmt.Fprintf(os.Stderr, " --key-name NAME Base name for generated key files (default: key)\n")
123+
fmt.Fprintf(os.Stderr, "\nConfiguration:\n")
124+
fmt.Fprintf(os.Stderr, " -c, --config FILE Path to configuration file\n")
125+
fmt.Fprintf(os.Stderr, " -t, --timeout DURATION Timeout for operation (e.g., 30m, 1h)\n")
126+
fmt.Fprintf(os.Stderr, " --show-config Show current configuration\n")
127+
fmt.Fprintf(os.Stderr, " --save-config FILE Save current configuration to file\n")
128+
fmt.Fprintf(os.Stderr, "\nOutput:\n")
129+
fmt.Fprintf(os.Stderr, " -v, --verbose Enable verbose output\n")
130+
fmt.Fprintf(os.Stderr, " -q, --quiet Suppress non-error output\n")
131+
fmt.Fprintf(os.Stderr, " --version Show version information\n")
132+
fmt.Fprintf(os.Stderr, " -h, --help Show this help message\n")
133+
fmt.Fprintf(os.Stderr, "\nExamples:\n")
134+
fmt.Fprintf(os.Stderr, " # Encrypt files with password\n")
135+
fmt.Fprintf(os.Stderr, " %s -e -p mypassword file1.txt file2.pdf\n", os.Args[0])
136+
fmt.Fprintf(os.Stderr, "\n # Encrypt with RSA public key\n")
137+
fmt.Fprintf(os.Stderr, " %s -e -k public.key -f document.docx\n", os.Args[0])
138+
fmt.Fprintf(os.Stderr, "\n # Decrypt with private key\n")
139+
fmt.Fprintf(os.Stderr, " %s -d -k private.key document.docx.enc\n", os.Args[0])
140+
fmt.Fprintf(os.Stderr, "\n # Generate keys and encrypt\n")
141+
fmt.Fprintf(os.Stderr, " %s --generate-keys -e -f secret.txt\n", os.Args[0])
142+
fmt.Fprintf(os.Stderr, "\nEnvironment Variables:\n")
143+
fmt.Fprintf(os.Stderr, " FILE_ENCRYPTOR_LOG_LEVEL Set log level (debug, info, warn, error)\n")
144+
fmt.Fprintf(os.Stderr, " FILE_ENCRYPTOR_MAX_WORKERS Set maximum worker threads\n")
145+
fmt.Fprintf(os.Stderr, " FILE_ENCRYPTOR_TIMEOUT Set default timeout\n")
146+
fmt.Fprintf(os.Stderr, " FILE_ENCRYPTOR_DEBUG Enable debug mode (true/false)\n")
147+
}
148+
149+
// Parse flags
150+
flag.Parse()
151+
152+
// Add remaining arguments as files (if they don't start with -)
153+
remainingArgs := flag.Args()
154+
for _, arg := range remainingArgs {
155+
if !strings.HasPrefix(arg, "-") {
156+
files = append(files, arg)
157+
}
158+
}
159+
160+
args.Files = []string(files)
161+
162+
return args, nil
163+
}
164+
165+
// ValidateArgs validates the parsed CLI arguments
166+
func ValidateArgs(args *CLIArgs, config *Config) error {
167+
// Handle special cases first
168+
if args.ShowHelp {
169+
flag.Usage()
170+
os.Exit(0)
171+
}
172+
173+
if args.ShowVersion {
174+
if Version != "dev" {
175+
fmt.Printf("File Encryptor %s\n", Version)
176+
fmt.Printf("Git Commit: %s\n", GitCommit)
177+
fmt.Printf("Build Time: %s\n", BuildTime)
178+
} else {
179+
fmt.Printf("File Encryptor %s (development build)\n", AppVersion)
180+
}
181+
fmt.Printf("Built with Go %s\n", "1.23+")
182+
os.Exit(0)
183+
}
184+
185+
if args.ShowConfig {
186+
return nil // Will be handled in main
187+
}
188+
189+
if args.SaveConfig != "" {
190+
return nil // Will be handled in main
191+
}
192+
193+
// Validate conflicting options
194+
if args.Verbose && args.Quiet {
195+
return fmt.Errorf("cannot specify both --verbose and --quiet")
196+
}
197+
198+
// Handle key generation special case
199+
if args.GenerateKeys && !args.Encrypt && len(args.Files) == 0 {
200+
return nil // Just generating keys
201+
}
202+
203+
if args.GenerateKeys {
204+
if args.Decrypt || args.Key != "" || args.Password != "" {
205+
return fmt.Errorf("--generate-keys cannot be combined with decrypt, key, or password options")
206+
}
207+
}
208+
209+
// Validate operation selection
210+
if (args.Encrypt && args.Decrypt) || (!args.Encrypt && !args.Decrypt && !args.GenerateKeys) {
211+
return fmt.Errorf("please specify either -e/--encrypt or -d/--decrypt")
212+
}
213+
214+
// Validate files
215+
if len(args.Files) == 0 && !args.GenerateKeys {
216+
return fmt.Errorf("please provide at least one file using --file/-f or as arguments")
217+
}
218+
219+
// Validate authentication method
220+
if args.Key == "" && args.Password == "" && !args.GenerateKeys {
221+
return fmt.Errorf("please provide either --key/-k or --password/-p")
222+
}
223+
224+
if args.Key != "" && args.Password != "" {
225+
return fmt.Errorf("please provide either --key/-k or --password/-p, not both")
226+
}
227+
228+
// Validate file existence and permissions
229+
if err := validateFiles(args.Files, args.Encrypt); err != nil {
230+
return err
231+
}
232+
233+
// Validate key file if specified
234+
if args.Key != "" {
235+
if err := validateKeyFile(args.Key); err != nil {
236+
return fmt.Errorf("key file validation failed: %w", err)
237+
}
238+
}
239+
240+
return nil
241+
}
242+
243+
// validateFiles checks if the specified files exist and are accessible
244+
func validateFiles(files []string, isEncryption bool) error {
245+
for _, file := range files {
246+
if err := validateSingleFile(file, isEncryption); err != nil {
247+
return fmt.Errorf("file '%s': %w", file, err)
248+
}
249+
}
250+
return nil
251+
}
252+
253+
// validateSingleFile validates a single file
254+
func validateSingleFile(file string, isEncryption bool) error {
255+
info, err := os.Stat(file)
256+
if err != nil {
257+
if os.IsNotExist(err) {
258+
return fmt.Errorf("file does not exist")
259+
}
260+
return fmt.Errorf("cannot access file: %w", err)
261+
}
262+
263+
if info.IsDir() {
264+
return fmt.Errorf("is a directory, not a file")
265+
}
266+
267+
// Check file permissions
268+
if isEncryption {
269+
// For encryption, we need read access
270+
if err := checkReadPermission(file); err != nil {
271+
return fmt.Errorf("cannot read file: %w", err)
272+
}
273+
} else {
274+
// For decryption, check if it looks like an encrypted file
275+
if !strings.HasSuffix(file, ".enc") {
276+
return fmt.Errorf("file does not appear to be encrypted (missing .enc extension)")
277+
}
278+
if err := checkReadPermission(file); err != nil {
279+
return fmt.Errorf("cannot read encrypted file: %w", err)
280+
}
281+
}
282+
283+
// Check if file is too large (optional warning)
284+
if info.Size() > 10<<30 { // 10GB
285+
fmt.Fprintf(os.Stderr, "Warning: File '%s' is very large (%s). This may take a long time.\n",
286+
file, formatBytes(info.Size()))
287+
}
288+
289+
return nil
290+
}
291+
292+
// validateKeyFile checks if the key file exists and is accessible
293+
func validateKeyFile(keyFile string) error {
294+
info, err := os.Stat(keyFile)
295+
if err != nil {
296+
if os.IsNotExist(err) {
297+
return fmt.Errorf("key file does not exist")
298+
}
299+
return fmt.Errorf("cannot access key file: %w", err)
300+
}
301+
302+
if info.IsDir() {
303+
return fmt.Errorf("key path is a directory, not a file")
304+
}
305+
306+
if err := checkReadPermission(keyFile); err != nil {
307+
return fmt.Errorf("cannot read key file: %w", err)
308+
}
309+
310+
return nil
311+
}
312+
313+
// checkReadPermission checks if we can read the file
314+
func checkReadPermission(file string) error {
315+
f, err := os.Open(file)
316+
if err != nil {
317+
return err
318+
}
319+
f.Close()
320+
return nil
321+
}
322+
323+
// formatBytes formats byte count as human readable string
324+
func formatBytes(bytes int64) string {
325+
const unit = 1024
326+
if bytes < unit {
327+
return fmt.Sprintf("%d B", bytes)
328+
}
329+
div, exp := int64(unit), 0
330+
for n := bytes / unit; n >= unit; n /= unit {
331+
div *= unit
332+
exp++
333+
}
334+
return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
335+
}
336+
337+
// GetConfigPath returns the configuration file path, checking multiple locations
338+
func GetConfigPath(specified string) string {
339+
if specified != "" {
340+
return specified
341+
}
342+
343+
// Check common configuration locations
344+
locations := []string{
345+
"./file-encryptor.yaml",
346+
"./file-encryptor.yml",
347+
"~/.config/file-encryptor/config.yaml",
348+
"~/.file-encryptor.yaml",
349+
}
350+
351+
for _, location := range locations {
352+
// Expand home directory
353+
if strings.HasPrefix(location, "~/") {
354+
home, err := os.UserHomeDir()
355+
if err == nil {
356+
location = filepath.Join(home, location[2:])
357+
}
358+
}
359+
360+
if _, err := os.Stat(location); err == nil {
361+
return location
362+
}
363+
}
364+
365+
return "" // No config file found
366+
}

0 commit comments

Comments
 (0)