Implement config files

This commit is contained in:
Elara 2020-12-30 23:54:18 -08:00
parent 029b16a1a3
commit 9bd7b30222
8 changed files with 390 additions and 288 deletions

290
config.go
View File

@ -1,278 +1,66 @@
package main package main
import ( import (
"archive/tar" "errors"
"encoding/json" "github.com/pelletier/go-toml"
"github.com/pkg/browser"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"io"
"io/ioutil" "io/ioutil"
"net/url"
"os" "os"
"path/filepath"
"strconv"
"strings"
) )
// Create config type to store action type and data
type Config struct { type Config struct {
ActionType string Receiver ReceiverConfig
ActionData string Sender SenderConfig
Targets map[string]map[string]string
} }
// Instantiate and return a new Config struct type ReceiverConfig struct {
func NewConfig(actionType string, actionData string) *Config { DestDir string `toml:"destinationDirectory"`
return &Config{ActionType: actionType, ActionData: actionData} SkipZeroconf bool
WorkDir string `toml:"workingDirectory"`
} }
func (config *Config) Validate() { type SenderConfig struct {
if config.ActionType == "url" { WorkDir string `toml:"workingDirectory"`
// Parse URL in config
urlParser, err := url.Parse(config.ActionData)
// If there was an error parsing
if err != nil {
// Alert user of invalid url
log.Fatal().Err(err).Msg("Invalid URL")
// If scheme is not detected
} else if urlParser.Scheme == "" {
// Alert user of invalid scheme
log.Fatal().Msg("Invalid URL scheme")
// If host is not detected
} else if urlParser.Host == "" {
// Alert user of invalid host
log.Fatal().Msg("Invalid URL host")
}
}
} }
// Create config file func GetConfigPath() string {
func (config *Config) CreateFile(dir string) {
// Use ConsoleWriter logger // Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{}) log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
// Create config file at given directory configLocations := []string{"~/.config/opensend.toml", "/etc/opensend.toml"}
configFile, err := os.Create(dir + "/config.json") for _, configLocation := range configLocations {
if err != nil { expandedPath := ExpandPath(configLocation)
log.Fatal().Err(err).Msg("Error creating config file") if _, err := os.Stat(expandedPath); errors.Is(err, os.ErrNotExist) {
}
// Close config file at the end of this function
defer configFile.Close()
// Marshal given Config struct into a []byte
jsonData, err := json.Marshal(config)
if err != nil {
log.Fatal().Err(err).Msg("Error encoding JSON")
}
// Write []byte to previously created config file
bytesWritten, err := configFile.Write(jsonData)
if err != nil {
log.Fatal().Err(err).Msg("Error writing JSON to file")
}
// Log bytes written
log.Info().Str("file", "config.json").Msg("Wrote " + strconv.Itoa(bytesWritten) + " bytes")
}
// Collect all required files into given directory
func (config *Config) CollectFiles(dir string) {
// Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
// If action type is file
if config.ActionType == "file" {
// Open file path in config.ActionData
src, err := os.Open(config.ActionData)
if err != nil {
log.Fatal().Err(err).Msg("Error opening file from config")
}
// Close source file at the end of this function
defer src.Close()
// Create new file with the same name at given directory
dst, err := os.Create(dir + "/" + filepath.Base(config.ActionData))
if err != nil {
log.Fatal().Err(err).Msg("Error creating file")
}
// Close new file at the end of this function
defer dst.Close()
// Copy data from source file to destination file
_, err = io.Copy(dst, src)
if err != nil {
log.Fatal().Err(err).Msg("Error copying data to file")
}
// Replace file path in config.ActionData with file name
config.ActionData = filepath.Base(config.ActionData)
} else if config.ActionType == "dir" {
// Create tar archive
tarFile, err := os.Create(dir + "/" + filepath.Base(config.ActionData) + ".tar")
if err != nil {
log.Fatal().Err(err).Msg("Error creating file")
}
// Close tar file at the end of this function
defer tarFile.Close()
// Create writer for tar archive
tarArchiver := tar.NewWriter(tarFile)
// Close archiver at the end of this function
defer tarArchiver.Close()
// Walk given directory
err = filepath.Walk(config.ActionData, func(path string, info os.FileInfo, err error) error {
// Return if error walking
if err != nil {
return err
}
// Skip if file is not normal mode
if !info.Mode().IsRegular() {
return nil
}
// Create tar header for file
header, err := tar.FileInfoHeader(info, info.Name())
if err != nil {
return err
}
// Change header name to reflect decompressed filepath
header.Name = strings.TrimPrefix(strings.ReplaceAll(path, config.ActionData, ""), string(filepath.Separator))
// Write header to archive
if err := tarArchiver.WriteHeader(header); err != nil {
return err
}
// Open source file
src, err := os.Open(path)
if err != nil {
return err
}
// Close source file at the end of this function
defer src.Close()
// Copy source bytes to tar archive
if _, err := io.Copy(tarArchiver, src); err != nil {
return err
}
// Return at the end of the function
return nil
})
if err != nil {
log.Fatal().Err(err).Msg("Error creating tar archive")
}
// Set config data to base path for receiver
config.ActionData = filepath.Base(config.ActionData)
}
}
// Read config file at given file path
func (config *Config) ReadFile(filePath string) {
// Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
// Read file at filePath
fileData, err := ioutil.ReadFile(filePath)
if err != nil {
log.Fatal().Err(err).Msg("Error reading config file")
}
// Unmarshal data from JSON into config struct
err = json.Unmarshal(fileData, config)
if err != nil {
log.Fatal().Err(err).Msg("Error decoding JSON")
}
}
// Execute action specified in config
func (config *Config) ExecuteAction(srcDir string, destDir string) {
// Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
// If action is file
if config.ActionType == "file" {
// Open file from config at given directory
src, err := os.Open(srcDir + "/" + config.ActionData)
if err != nil {
log.Fatal().Err(err).Msg("Error reading file from config")
}
// Close source file at the end of this function
defer src.Close()
// Create file in user's Downloads directory
dst, err := os.Create(filepath.Clean(destDir) + "/" + config.ActionData)
if err != nil {
log.Fatal().Err(err).Msg("Error creating file")
}
// Close destination file at the end of this function
defer dst.Close()
// Copy data from source file to destination file
_, err = io.Copy(dst, src)
if err != nil {
log.Fatal().Err(err).Msg("Error copying data to file")
}
// If action is url
} else if config.ActionType == "url" {
// Parse received URL
urlParser, err := url.Parse(config.ActionData)
// If there was an error parsing
if err != nil {
// Alert user of invalid url
log.Fatal().Err(err).Msg("Invalid URL")
// If scheme is not detected
} else if urlParser.Scheme == "" {
// Alert user of invalid scheme
log.Fatal().Msg("Invalid URL scheme")
// If host is not detected
} else if urlParser.Host == "" {
// Alert user of invalid host
log.Fatal().Msg("Invalid URL host")
}
// Attempt to open URL in browser
err = browser.OpenURL(config.ActionData)
if err != nil {
log.Fatal().Err(err).Msg("Error opening browser")
}
// If action is dir
} else if config.ActionType == "dir" {
// Set destination directory to ~/Downloads/{dir name}
dstDir := filepath.Clean(destDir) + "/" + config.ActionData
// Try to create destination directory
err := os.MkdirAll(dstDir, 0755)
if err != nil {
log.Fatal().Err(err).Msg("Error creating directory")
}
// Try to open tar archive file
tarFile, err := os.Open(srcDir + "/" + config.ActionData + ".tar")
if err != nil {
log.Fatal().Err(err).Msg("Error opening tar archive")
}
// Close tar archive file at the end of this function
defer tarFile.Close()
// Create tar reader to unarchive tar archive
tarUnarchiver := tar.NewReader(tarFile)
// Loop to recursively unarchive tar file
unarchiveLoop:
for {
// Jump to next header in tar archive
header, err := tarUnarchiver.Next()
switch {
// If EOF
case err == io.EOF:
// break loop
break unarchiveLoop
case err != nil:
log.Fatal().Err(err).Msg("Error unarchiving tar archive")
// If nil header
case header == nil:
// Skip
continue continue
} }
// Set target path to header name in destination dir return expandedPath
targetPath := filepath.Join(dstDir, header.Name) }
switch header.Typeflag { return ""
// If regular file }
case tar.TypeReg:
// Try to create containing folder ignoring errors func NewConfig(path string) *Config {
_ = os.MkdirAll(filepath.Dir(targetPath), 0755) // Use ConsoleWriter logger
// Create file with mode contained in header at target path log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
dstFile, err := os.OpenFile(targetPath, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) newConfig := &Config{}
newConfig.SetDefaults()
if path != "" {
confData, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Error creating file during unarchiving") log.Fatal().Err(err).Msg("Error reading config")
} }
// Copy data from tar archive into file err = toml.Unmarshal(confData, newConfig)
_, err = io.Copy(dstFile, tarUnarchiver)
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Error copying data to file") log.Fatal().Err(err).Msg("Error unmarshalling toml")
} }
} }
return newConfig
} }
// Catchall
} else { func (config *Config) SetDefaults() {
// Log unknown action type config.Receiver.DestDir = ExpandPath("~/Downloads")
log.Fatal().Msg("Unknown action type " + config.ActionType) config.Receiver.WorkDir = ExpandPath("~/.opensend")
} config.Receiver.SkipZeroconf = false
config.Sender.WorkDir = ExpandPath("~/.opensend")
config.Targets = map[string]map[string]string{}
} }

24
extra.go Normal file
View File

@ -0,0 +1,24 @@
package main
import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"os"
"path/filepath"
"strings"
)
func ExpandPath(s string) string {
// Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
homeDir, err := os.UserHomeDir()
if err != nil {
log.Fatal().Err(err).Msg("Error getting home directory")
}
expandedString := os.ExpandEnv(s)
if strings.HasPrefix(expandedString, "~") {
expandedString = strings.Replace(expandedString, "~", homeDir, 1)
}
expandedString = filepath.Clean(expandedString)
return expandedString
}

View File

@ -223,7 +223,7 @@ func RecvFiles(connection net.Conn) {
// Otherwise // Otherwise
} else { } else {
// Create new file at index filepath // Create new file at index filepath
newFile, err := os.Create(opensendDir + "/" + file) newFile, err := os.Create(*workDir + "/" + file)
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Error creating file") log.Fatal().Err(err).Msg("Error creating file")
} }

1
go.mod
View File

@ -5,6 +5,7 @@ go 1.15
require ( require (
github.com/grandcat/zeroconf v1.0.0 github.com/grandcat/zeroconf v1.0.0
github.com/klauspost/compress v1.11.3 github.com/klauspost/compress v1.11.3
github.com/pelletier/go-toml v1.8.1
github.com/pkg/browser v0.0.0-20201112035734-206646e67786 github.com/pkg/browser v0.0.0-20201112035734-206646e67786
github.com/rs/zerolog v1.20.0 github.com/rs/zerolog v1.20.0
) )

4
go.sum
View File

@ -1,12 +1,16 @@
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE= github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE=
github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs= github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs=
github.com/klauspost/compress v1.11.3 h1:dB4Bn0tN3wdCzQxnS8r06kV74qN/TAfaIS0bVE8h3jc= github.com/klauspost/compress v1.11.3 h1:dB4Bn0tN3wdCzQxnS8r06kV74qN/TAfaIS0bVE8h3jc=
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM= github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM=
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/pkg/browser v0.0.0-20201112035734-206646e67786 h1:4Gk0Dsp90g2YwfsxDOjvkEIgKGh+2R9FlvormRycveA= github.com/pkg/browser v0.0.0-20201112035734-206646e67786 h1:4Gk0Dsp90g2YwfsxDOjvkEIgKGh+2R9FlvormRycveA=
github.com/pkg/browser v0.0.0-20201112035734-206646e67786/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= github.com/pkg/browser v0.0.0-20201112035734-206646e67786/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

View File

@ -15,7 +15,7 @@ func (hook FatalHook) Run(_ *zerolog.Event, level zerolog.Level, _ string) {
// If log event is fatal // If log event is fatal
if level == zerolog.FatalLevel { if level == zerolog.FatalLevel {
// Attempt removal of opensend directory // Attempt removal of opensend directory
_ = os.RemoveAll(opensendDir) _ = os.RemoveAll(*workDir)
} }
} }
@ -33,7 +33,7 @@ func (hook TCPFatalHook) Run(_ *zerolog.Event, level zerolog.Level, _ string) {
// Close connection // Close connection
_ = hook.conn.Close() _ = hook.conn.Close()
// Attempt removal of opensend directory // Attempt removal of opensend directory
_ = os.RemoveAll(opensendDir) _ = os.RemoveAll(*workDir)
} }
} }

83
main.go
View File

@ -17,19 +17,43 @@ import (
"time" "time"
) )
var opensendDir string var workDir *string
func main() { func main() {
// Use ConsoleWriter logger // Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{}) log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
// Get user's home directory confPath := GetConfigPath()
homeDir, err := os.UserHomeDir() config := NewConfig(confPath)
if err != nil {
log.Fatal().Err(err).Msg("Error getting home directory") // Create --send-to flag to send to a specific IP
sendTo := flag.String("send-to", "", "Use IP address of receiver instead of mDNS")
// Create --dest-dir flag to save to a specified folder
destDir := flag.String("dest-dir", "", "Destination directory for files or dirs sent over opensend")
workDir = flag.String("work-dir", "", "Working directory for opensend")
givenCfgPath := flag.String("config", "", "Opensend config to use")
// Create --skip-mdns to skip service registration
skipMdns := flag.Bool("skip-mdns", config.Receiver.SkipZeroconf, "Skip zeroconf service registration (use if mdns fails)")
// Create -t flag for type
actionType := flag.String("t", "", "Type of data being sent")
// Create -d flag for data
actionData := flag.String("d", "", "Data to send")
// Create -s flag for sending
sendFlag := flag.Bool("s", false, "Send data")
// Create -r flag for receiving
recvFlag := flag.Bool("r", false, "Receive data")
// Parse flags
flag.Parse()
if *givenCfgPath != "" {
config = NewConfig(*givenCfgPath)
}
if *workDir == "" {
if *sendFlag {
*workDir = ExpandPath(config.Sender.WorkDir)
} else {
*workDir = ExpandPath(config.Receiver.WorkDir)
}
} }
// Define opensend directory as ~/.opensend
opensendDir = homeDir + "/.opensend"
// Create channel for signals // Create channel for signals
sig := make(chan os.Signal, 1) sig := make(chan os.Signal, 1)
@ -43,31 +67,14 @@ func main() {
// Warn user that a signal has been received and that opensend is shutting down // Warn user that a signal has been received and that opensend is shutting down
log.Warn().Msg("Signal received. Shutting down.") log.Warn().Msg("Signal received. Shutting down.")
// Remove opensend directory to avoid future conflicts // Remove opensend directory to avoid future conflicts
_ = os.RemoveAll(opensendDir) _ = os.RemoveAll(*workDir)
// Exit with code 0 // Exit with code 0
os.Exit(0) os.Exit(0)
} }
}() }()
// Create --send-to flag to send to a specific IP
sendTo := flag.String("send-to", "", "Use IP address of receiver instead of mDNS")
// Create --dest-dir flag to save to a specified folder
destDir := flag.String("dest-dir", homeDir+"/Downloads", "Destination directory for files or dirs sent over opensend")
// Create --skip-mdns to skip service registration
skipMdns := flag.Bool("skip-mdns", false, "Skip zeroconf service registration (use if mdns fails)")
// Create -t flag for type
actionType := flag.String("t", "", "Type of data being sent")
// Create -d flag for data
actionData := flag.String("d", "", "Data to send")
// Create -s flag for sending
sendFlag := flag.Bool("s", false, "Send data")
// Create -r flag for receiving
recvFlag := flag.Bool("r", false, "Receive data")
// Parse flags
flag.Parse()
// Create opensend dir ignoring errors // Create opensend dir ignoring errors
_ = os.Mkdir(opensendDir, 0755) _ = os.Mkdir(*workDir, 0755)
// If -s given // If -s given
if *sendFlag { if *sendFlag {
if *actionType == "" || *actionData == "" { if *actionType == "" || *actionData == "" {
@ -119,13 +126,13 @@ func main() {
choiceIP = discoveredIPs[choiceIndex] choiceIP = discoveredIPs[choiceIndex]
} }
// Instantiate Config object // Instantiate Config object
config := NewConfig(*actionType, *actionData) parameters := NewParameters(*actionType, *actionData)
// Validate data in config struct // Validate data in config struct
config.Validate() parameters.Validate()
// Collect any files that may be required for transaction into opensend directory // Collect any files that may be required for transaction into opensend directory
config.CollectFiles(opensendDir) parameters.CollectFiles(*workDir)
// Create config file in opensend directory // Create config file in opensend directory
config.CreateFile(opensendDir) parameters.CreateFile(*workDir)
// Notify user of key exchange // Notify user of key exchange
log.Info().Msg("Performing key exchange") log.Info().Msg("Performing key exchange")
// Exchange RSA keys with receiver // Exchange RSA keys with receiver
@ -135,15 +142,15 @@ func main() {
// Encrypt shared key using RSA public key // Encrypt shared key using RSA public key
key := EncryptKey(sharedKey, rawKey) key := EncryptKey(sharedKey, rawKey)
// Save encrypted key in opensend directory as key.aes // Save encrypted key in opensend directory as key.aes
SaveEncryptedKey(key, opensendDir+"/key.aes") SaveEncryptedKey(key, *workDir+"/key.aes")
// Notify user file encryption is beginning // Notify user file encryption is beginning
log.Info().Msg("Encrypting files") log.Info().Msg("Encrypting files")
// Encrypt all files in opensend directory using shared key // Encrypt all files in opensend directory using shared key
EncryptFiles(opensendDir, sharedKey) EncryptFiles(*workDir, sharedKey)
// Notify user server has started // Notify user server has started
log.Info().Msg("Server started on port 9898") log.Info().Msg("Server started on port 9898")
// Send all files in opensend directory using an HTTP server on port 9898 // Send all files in opensend directory using an HTTP server on port 9898
SendFiles(opensendDir) SendFiles(*workDir)
// If -r given // If -r given
} else if *recvFlag { } else if *recvFlag {
// If --skip-mdns is not given // If --skip-mdns is not given
@ -178,21 +185,21 @@ func main() {
// Notify user file decryption is beginning // Notify user file decryption is beginning
log.Info().Msg("Decrypting files") log.Info().Msg("Decrypting files")
// Decrypt all files in opensend directory using shared key // Decrypt all files in opensend directory using shared key
DecryptFiles(opensendDir, sharedKey) DecryptFiles(*workDir, sharedKey)
// Instantiate Config // Instantiate Config
config := &Config{} parameters := &Parameters{}
// Read config file in opensend directory // Read config file in opensend directory
config.ReadFile(opensendDir + "/config.json") parameters.ReadFile(*workDir + "/parameters.json")
// Notify user that action is being executed // Notify user that action is being executed
log.Info().Msg("Executing JSON action") log.Info().Msg("Executing JSON action")
// Execute JSON action using files within opensend directory // Execute JSON action using files within opensend directory
config.ExecuteAction(opensendDir, *destDir) parameters.ExecuteAction(*workDir, *destDir)
} else { } else {
flag.Usage() flag.Usage()
log.Fatal().Msg("You must choose sender or receiver mode using -s or -r") log.Fatal().Msg("You must choose sender or receiver mode using -s or -r")
} }
// Remove opensend directory // Remove opensend directory
err = os.RemoveAll(opensendDir) err := os.RemoveAll(*workDir)
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Error removing opensend directory") log.Fatal().Err(err).Msg("Error removing opensend directory")
} }

278
parameterSerialization.go Normal file
View File

@ -0,0 +1,278 @@
package main
import (
"archive/tar"
"encoding/json"
"github.com/pkg/browser"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"io"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
)
// Create config type to store action type and data
type Parameters struct {
ActionType string
ActionData string
}
// Instantiate and return a new Config struct
func NewParameters(actionType string, actionData string) *Parameters {
return &Parameters{ActionType: actionType, ActionData: actionData}
}
func (parameters *Parameters) Validate() {
if parameters.ActionType == "url" {
// Parse URL in parameters
urlParser, err := url.Parse(parameters.ActionData)
// If there was an error parsing
if err != nil {
// Alert user of invalid url
log.Fatal().Err(err).Msg("Invalid URL")
// If scheme is not detected
} else if urlParser.Scheme == "" {
// Alert user of invalid scheme
log.Fatal().Msg("Invalid URL scheme")
// If host is not detected
} else if urlParser.Host == "" {
// Alert user of invalid host
log.Fatal().Msg("Invalid URL host")
}
}
}
// Create config file
func (parameters *Parameters) CreateFile(dir string) {
// Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
// Create parameters file at given directory
configFile, err := os.Create(dir + "/parameters.json")
if err != nil {
log.Fatal().Err(err).Msg("Error creating parameters file")
}
// Close parameters file at the end of this function
defer configFile.Close()
// Marshal given Parameters struct into a []byte
jsonData, err := json.Marshal(parameters)
if err != nil {
log.Fatal().Err(err).Msg("Error encoding JSON")
}
// Write []byte to previously created parameters file
bytesWritten, err := configFile.Write(jsonData)
if err != nil {
log.Fatal().Err(err).Msg("Error writing JSON to file")
}
// Log bytes written
log.Info().Str("file", "parameters.json").Msg("Wrote " + strconv.Itoa(bytesWritten) + " bytes")
}
// Collect all required files into given directory
func (parameters *Parameters) CollectFiles(dir string) {
// Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
// If action type is file
if parameters.ActionType == "file" {
// Open file path in parameters.ActionData
src, err := os.Open(parameters.ActionData)
if err != nil {
log.Fatal().Err(err).Msg("Error opening file from parameters")
}
// Close source file at the end of this function
defer src.Close()
// Create new file with the same name at given directory
dst, err := os.Create(dir + "/" + filepath.Base(parameters.ActionData))
if err != nil {
log.Fatal().Err(err).Msg("Error creating file")
}
// Close new file at the end of this function
defer dst.Close()
// Copy data from source file to destination file
_, err = io.Copy(dst, src)
if err != nil {
log.Fatal().Err(err).Msg("Error copying data to file")
}
// Replace file path in parameters.ActionData with file name
parameters.ActionData = filepath.Base(parameters.ActionData)
} else if parameters.ActionType == "dir" {
// Create tar archive
tarFile, err := os.Create(dir + "/" + filepath.Base(parameters.ActionData) + ".tar")
if err != nil {
log.Fatal().Err(err).Msg("Error creating file")
}
// Close tar file at the end of this function
defer tarFile.Close()
// Create writer for tar archive
tarArchiver := tar.NewWriter(tarFile)
// Close archiver at the end of this function
defer tarArchiver.Close()
// Walk given directory
err = filepath.Walk(parameters.ActionData, func(path string, info os.FileInfo, err error) error {
// Return if error walking
if err != nil {
return err
}
// Skip if file is not normal mode
if !info.Mode().IsRegular() {
return nil
}
// Create tar header for file
header, err := tar.FileInfoHeader(info, info.Name())
if err != nil {
return err
}
// Change header name to reflect decompressed filepath
header.Name = strings.TrimPrefix(strings.ReplaceAll(path, parameters.ActionData, ""), string(filepath.Separator))
// Write header to archive
if err := tarArchiver.WriteHeader(header); err != nil {
return err
}
// Open source file
src, err := os.Open(path)
if err != nil {
return err
}
// Close source file at the end of this function
defer src.Close()
// Copy source bytes to tar archive
if _, err := io.Copy(tarArchiver, src); err != nil {
return err
}
// Return at the end of the function
return nil
})
if err != nil {
log.Fatal().Err(err).Msg("Error creating tar archive")
}
// Set parameters data to base path for receiver
parameters.ActionData = filepath.Base(parameters.ActionData)
}
}
// Read config file at given file path
func (parameters *Parameters) ReadFile(filePath string) {
// Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
// Read file at filePath
fileData, err := ioutil.ReadFile(filePath)
if err != nil {
log.Fatal().Err(err).Msg("Error reading parameters file")
}
// Unmarshal data from JSON into parameters struct
err = json.Unmarshal(fileData, parameters)
if err != nil {
log.Fatal().Err(err).Msg("Error decoding JSON")
}
}
// Execute action specified in config
func (parameters *Parameters) ExecuteAction(srcDir string, destDir string) {
// Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
// If action is file
if parameters.ActionType == "file" {
// Open file from parameters at given directory
src, err := os.Open(srcDir + "/" + parameters.ActionData)
if err != nil {
log.Fatal().Err(err).Msg("Error reading file from parameters")
}
// Close source file at the end of this function
defer src.Close()
// Create file in user's Downloads directory
dst, err := os.Create(filepath.Clean(destDir) + "/" + parameters.ActionData)
if err != nil {
log.Fatal().Err(err).Msg("Error creating file")
}
// Close destination file at the end of this function
defer dst.Close()
// Copy data from source file to destination file
_, err = io.Copy(dst, src)
if err != nil {
log.Fatal().Err(err).Msg("Error copying data to file")
}
// If action is url
} else if parameters.ActionType == "url" {
// Parse received URL
urlParser, err := url.Parse(parameters.ActionData)
// If there was an error parsing
if err != nil {
// Alert user of invalid url
log.Fatal().Err(err).Msg("Invalid URL")
// If scheme is not detected
} else if urlParser.Scheme == "" {
// Alert user of invalid scheme
log.Fatal().Msg("Invalid URL scheme")
// If host is not detected
} else if urlParser.Host == "" {
// Alert user of invalid host
log.Fatal().Msg("Invalid URL host")
}
// Attempt to open URL in browser
err = browser.OpenURL(parameters.ActionData)
if err != nil {
log.Fatal().Err(err).Msg("Error opening browser")
}
// If action is dir
} else if parameters.ActionType == "dir" {
// Set destination directory to ~/Downloads/{dir name}
dstDir := filepath.Clean(destDir) + "/" + parameters.ActionData
// Try to create destination directory
err := os.MkdirAll(dstDir, 0755)
if err != nil {
log.Fatal().Err(err).Msg("Error creating directory")
}
// Try to open tar archive file
tarFile, err := os.Open(srcDir + "/" + parameters.ActionData + ".tar")
if err != nil {
log.Fatal().Err(err).Msg("Error opening tar archive")
}
// Close tar archive file at the end of this function
defer tarFile.Close()
// Create tar reader to unarchive tar archive
tarUnarchiver := tar.NewReader(tarFile)
// Loop to recursively unarchive tar file
unarchiveLoop:
for {
// Jump to next header in tar archive
header, err := tarUnarchiver.Next()
switch {
// If EOF
case err == io.EOF:
// break loop
break unarchiveLoop
case err != nil:
log.Fatal().Err(err).Msg("Error unarchiving tar archive")
// If nil header
case header == nil:
// Skip
continue
}
// Set target path to header name in destination dir
targetPath := filepath.Join(dstDir, header.Name)
switch header.Typeflag {
// If regular file
case tar.TypeReg:
// Try to create containing folder ignoring errors
_ = os.MkdirAll(filepath.Dir(targetPath), 0755)
// Create file with mode contained in header at target path
dstFile, err := os.OpenFile(targetPath, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
if err != nil {
log.Fatal().Err(err).Msg("Error creating file during unarchiving")
}
// Copy data from tar archive into file
_, err = io.Copy(dstFile, tarUnarchiver)
if err != nil {
log.Fatal().Err(err).Msg("Error copying data to file")
}
}
}
// Catchall
} else {
// Log unknown action type
log.Fatal().Msg("Unknown action type " + parameters.ActionType)
}
}